diff --git a/README.md b/README.md
index 00eaa5f90..a517bd380 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,25 @@
# Pydantic Logfire β Uncomplicated Observability
-[![CI](https://github.com/pydantic/logfire/actions/workflows/main.yml/badge.svg?event=push)](https://github.com/pydantic/logfire/actions?query=event%3Apush+branch%3Amain+workflow%3ACI)
-[![codecov](https://codecov.io/gh/pydantic/logfire/graph/badge.svg?token=735CNGCGFD)](https://codecov.io/gh/pydantic/logfire)
-[![pypi](https://img.shields.io/pypi/v/logfire.svg)](https://pypi.python.org/pypi/logfire)
-[![license](https://img.shields.io/github/license/pydantic/logfire.svg)](https://github.com/pydantic/logfire/blob/main/LICENSE)
-[![versions](https://img.shields.io/pypi/pyversions/logfire.svg)](https://github.com/pydantic/logfire)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From the team behind Pydantic, **Logfire** is an observability platform built on the same belief as our
open source library β that the most powerful tools can be easy to use.
diff --git a/docs/extra/tweaks.css b/docs/extra/tweaks.css
index daed915d7..513cc09ed 100644
--- a/docs/extra/tweaks.css
+++ b/docs/extra/tweaks.css
@@ -79,3 +79,25 @@ li.md-nav__item>a[href^="#logfire.configure("] {
.md-search__output em {
color: var(--md-primary-fg-color);
}
+
+.md-search__input::-webkit-search-decoration,
+.md-search__input::-webkit-search-cancel-button,
+.md-search__input::-webkit-search-results-button,
+.md-search__input::-webkit-search-results-decoration {
+ -webkit-appearance:none;
+}
+
+.md-search-result__article {
+ padding-bottom: .55em;
+}
+
+.ais-SearchBox-form {
+ display: flex;
+ flex-direction: row;
+ gap: 10px;
+}
+
+.md-search-result mark.ais-Highlight-highlighted,
+.md-search-result mark.ais-Snippet-highlighted {
+ color: var(--md-primary-fg-color);
+}
diff --git a/docs/index.md b/docs/index.md
index 3dc8387bd..fc14c9cca 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,12 +1,14 @@
-# Pydantic Logfire
+# Getting Started
+
+## About Logfire
From the team behind **Pydantic**, **Logfire** is a new type of observability platform built on
the same belief as our open source library β that the most powerful tools can be easy to use.
-**Logfire** is built on OpenTelemetry, and supports monitoring your application from any language,
+**Logfire** is built on OpenTelemetry, and supports monitoring your application from **any language**,
with particularly great support for Python! [Read more](why.md).
-## Getting Started
+## Overview
This page is a quick walk-through for setting up a Python app:
diff --git a/docs/javascripts/algolia-search.js b/docs/javascripts/algolia-search.js
new file mode 100644
index 000000000..be9e2fe76
--- /dev/null
+++ b/docs/javascripts/algolia-search.js
@@ -0,0 +1,107 @@
+const ALGOLIA_APP_ID = 'KPPUDTIAVX';
+const ALGOLIA_API_KEY = '1fc841595212a2c3afe8c24dd4cb8790';
+const ALGOLIA_INDEX_NAME = 'alt-logfire-docs';
+
+const { liteClient: algoliasearch } = window['algoliasearch/lite'];
+const searchClient = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_API_KEY);
+
+const search = instantsearch({
+ indexName: ALGOLIA_INDEX_NAME,
+ searchClient,
+ searchFunction(helper) {
+ const query = helper.state.query
+
+ if (query && query.length > 1) {
+ document.querySelector('#hits').hidden = false
+ document.querySelector('#type-to-start-searching').hidden = true
+ helper.search();
+ } else {
+ document.querySelector('#hits').hidden = true
+ document.querySelector('#type-to-start-searching').hidden = false
+ }
+ },
+});
+
+// create custom widget, to integrate with MkDocs built-in markup
+const customSearchBox = instantsearch.connectors.connectSearchBox((renderOptions, isFirstRender) => {
+ const { query, refine, clear } = renderOptions;
+
+ if (isFirstRender) {
+ document.querySelector('#searchbox').addEventListener('input', event => {
+ refine(event.target.value);
+ });
+
+ document.querySelector('#searchbox').addEventListener('focus', () => {
+ document.querySelector('#__search').checked = true;
+ });
+
+ document.querySelector('#searchbox-clear').addEventListener('click', () => {
+ clear();
+ });
+
+ document.querySelector('#searchbox').addEventListener('keydown', (event) => {
+ // on down arrow, find the first search result and focus it
+ if (event.key === 'ArrowDown') {
+ document.querySelector('.md-search-result__link').focus();
+ event.preventDefault();
+ }
+ });
+
+ // for Hits, add keyboard navigation
+ document.querySelector('#hits').addEventListener('keydown', (event) => {
+ if (event.key === 'ArrowDown') {
+ const next = event.target.parentElement.nextElementSibling;
+ if (next) {
+ next.querySelector('.md-search-result__link').focus();
+ event.preventDefault();
+ }
+ } else if (event.key === 'ArrowUp') {
+ const prev = event.target.parentElement.previousElementSibling;
+ if (prev) {
+ prev.querySelector('.md-search-result__link').focus();
+ } else {
+ document.querySelector('#searchbox').focus();
+ }
+ event.preventDefault();
+ }
+ })
+
+ document.addEventListener('keydown', (event) => {
+ // if forward slash is pressed, focus the search box
+ if (event.key === '/' && event.target.tagName !== 'INPUT') {
+ document.querySelector('#searchbox').focus();
+ event.preventDefault();
+ }
+ })
+ }
+
+
+ document.querySelector('#type-to-start-searching').hidden = query.length > 1;
+ document.querySelector('#searchbox').value = query;
+});
+
+search.addWidgets([
+ customSearchBox({}),
+
+ instantsearch.widgets.hits({
+ container: '#hits',
+ cssClasses: {
+ 'list': 'md-search-result__list',
+ 'item': 'md-search-result__item'
+ },
+ templates: {
+ item: (hit, { html, components }) => {
+ return html`
+
+
+
+
${components.Highlight({ attribute: 'title', hit })}
+
${components.Snippet({ attribute: 'content', hit })}
+
+ `
+ },
+ },
+ })
+]);
+
+search.start();
diff --git a/docs/javascripts/search-worker.js b/docs/javascripts/search-worker.js
deleted file mode 100644
index 8273c5281..000000000
--- a/docs/javascripts/search-worker.js
+++ /dev/null
@@ -1,69 +0,0 @@
-importScripts('https://cdn.jsdelivr.net/npm/algoliasearch@5.18.0/dist/algoliasearch.umd.min.js')
-
-const SETUP = 0
-const READY = 1
-const QUERY = 2
-const RESULT = 3
-
-
-const appID = 'KPPUDTIAVX';
-const apiKey = '1fc841595212a2c3afe8c24dd4cb8790';
-const indexName = 'logfire-docs';
-
-const client = algoliasearch.algoliasearch(appID, apiKey);
-
-self.onmessage = async (event) => {
- if (event.data.type === SETUP) {
- self.postMessage({ type: READY });
- } else if (event.data.type === QUERY) {
-
- const query = event.data.data
-
- if (query === '') {
- self.postMessage({
- type: RESULT, data: {
- items: []
- }
- });
- return
- }
-
- const { results } = await client.search({
- requests: [
- {
- indexName,
- query,
- },
- ],
- });
-
- const hits = results[0].hits
-
- // make navigation work with preview deployments
- const stripDocsPathName = !(new URL(self.location.href).pathname.startsWith('/docs'));
-
- const mappedGroupedResults = hits.reduce((acc, hit) => {
- if (!acc[hit.pageID]) {
- acc[hit.pageID] = []
- }
- acc[hit.pageID].push({
- score: 1,
- terms: {},
- location: stripDocsPathName ? hit.abs_url.replace('/docs', '') : hit.abs_url,
- title: hit.title,
- text: hit._highlightResult.content.value,
-
- })
- return acc
- }, {})
-
-
-
-
- self.postMessage({
- type: RESULT, data: {
- items: Object.values(mappedGroupedResults)
- }
- });
- }
-};
diff --git a/docs/overrides/main.html b/docs/overrides/main.html
index 70fb5770e..9c9d4f1a1 100644
--- a/docs/overrides/main.html
+++ b/docs/overrides/main.html
@@ -1,50 +1,5 @@
{% extends "base.html" %}
-{% block config %}
- {%- set app = {
- "base": base_url,
- "features": features,
- "translations": {},
- "search": base_url + "/javascripts/search-worker.js" | url
- } -%}
-
-
- {%- if config.extra.version -%}
- {%- set mike = config.plugins.get("mike") -%}
- {%- if not mike or mike.config.version_selector -%}
- {%- set _ = app.update({ "version": config.extra.version }) -%}
- {%- endif -%}
- {%- endif -%}
-
-
- {%- if config.extra.tags -%}
- {%- set _ = app.update({ "tags": config.extra.tags }) -%}
- {%- endif -%}
-
-
- {%- set translations = app.translations -%}
- {%- for key in [
- "clipboard.copy",
- "clipboard.copied",
- "search.result.placeholder",
- "search.result.none",
- "search.result.one",
- "search.result.other",
- "search.result.more.one",
- "search.result.more.other",
- "search.result.term.missing",
- "select.version"
- ] -%}
- {%- set _ = translations.update({ key: lang.t(key) }) -%}
- {%- endfor -%}
-
-
-
-
-{% endblock %}
-
{% block content %}
{{ super() }}
diff --git a/docs/overrides/partials/search.html b/docs/overrides/partials/search.html
new file mode 100644
index 000000000..e7dd8394b
--- /dev/null
+++ b/docs/overrides/partials/search.html
@@ -0,0 +1,32 @@
+
diff --git a/docs/plugins/algolia.py b/docs/plugins/algolia.py
index 223be70fc..9e5b7a618 100644
--- a/docs/plugins/algolia.py
+++ b/docs/plugins/algolia.py
@@ -1,21 +1,34 @@
from __future__ import annotations as _annotations
import os
-from typing import Any, cast
+from typing import cast
from algoliasearch.search_client import SearchClient
from bs4 import BeautifulSoup
from mkdocs.config import Config
from mkdocs.structure.files import Files
from mkdocs.structure.pages import Page
+from typing_extensions import TypedDict
-records: list[dict[str, Any]] = []
-ALGOLIA_INDEX_NAME = 'logfire-docs'
+
+class AlgoliaRecord(TypedDict):
+ content: str
+ pageID: str
+ abs_url: str
+ title: str
+ objectID: str
+ rank: int
+
+
+records: list[AlgoliaRecord] = []
+ALGOLIA_INDEX_NAME = 'alt-logfire-docs'
ALGOLIA_APP_ID = 'KPPUDTIAVX'
ALGOLIA_WRITE_API_KEY = os.environ.get('ALGOLIA_WRITE_API_KEY')
# Algolia accepts 100k, leaaving some room for other fields
MAX_CONTENT_SIZE = 90_000
+HEADING_TAG_NAMES = ['h1', 'h2', 'h3']
+
def on_page_content(html: str, page: Page, config: Config, files: Files) -> str:
if not ALGOLIA_WRITE_API_KEY:
@@ -26,6 +39,13 @@ def on_page_content(html: str, page: Page, config: Config, files: Files) -> str:
soup = BeautifulSoup(html, 'html.parser')
+ # If the page does not start with a heading, add the h1 with the title
+ # Some examples don't have a heading. or start with h2
+ first_element = soup.find()
+
+ if not first_element or not first_element.name or first_element.name not in ['h1', 'h2', 'h3']:
+ soup.insert(0, BeautifulSoup(f'{title}
', 'html.parser'))
+
# Clean up presentational and UI elements
for element in soup.find_all(['autoref']):
element.decompose()
@@ -50,8 +70,10 @@ def on_page_content(html: str, page: Page, config: Config, files: Files) -> str:
for extra in soup.find_all('table', attrs={'class': 'highlighttable'}):
extra.replace_with(BeautifulSoup(f'{extra.find("code").get_text()}
', 'html.parser'))
- # Find all h1 and h2 headings
- headings = soup.find_all(['h1', 'h2'])
+ headings = soup.find_all(HEADING_TAG_NAMES)
+
+ # Use the rank to put the sections in the beginning higher in the search results
+ rank = 100
# Process each section
for i in range(len(headings)):
@@ -62,26 +84,39 @@ def on_page_content(html: str, page: Page, config: Config, files: Files) -> str:
# Get content until next heading
content: list[str] = []
sibling = current_heading.find_next_sibling()
- while sibling and sibling.name not in ['h1', 'h2']:
+ while sibling and sibling.name not in HEADING_TAG_NAMES:
content.append(str(sibling))
sibling = sibling.find_next_sibling()
- section_html = ''.join(content)
+ section_soup = BeautifulSoup(''.join(content), 'html.parser')
+ section_plain_text = section_soup.get_text(' ', strip=True)
# Create anchor URL
- anchor_url = f'{page.abs_url}#{heading_id}' if heading_id else page.abs_url
+ anchor_url = f'{page.abs_url}#{heading_id}' if heading_id else page.abs_url or ''
+
+ record_title = title
+
+ if current_heading.name == 'h2':
+ record_title = f'{title} - {section_title}'
+ elif current_heading.name == 'h3':
+ previous_heading = current_heading.find_previous(['h1', 'h2'])
+ parent_title = previous_heading.get_text().replace('ΒΆ', '').strip()
+ record_title = f'{title} - {parent_title} - {section_title}'
# Create record for this section
records.append(
- {
- 'content': section_html,
- 'pageID': title,
- 'abs_url': anchor_url,
- 'title': f'{title} - {section_title}',
- 'objectID': anchor_url,
- }
+ AlgoliaRecord(
+ content=section_plain_text,
+ pageID=title,
+ abs_url=anchor_url,
+ title=record_title,
+ objectID=anchor_url,
+ rank=rank,
+ )
)
+ rank -= 5
+
return html
@@ -92,6 +127,15 @@ def on_post_build(config: Config) -> None:
client = SearchClient.create(ALGOLIA_APP_ID, ALGOLIA_WRITE_API_KEY)
index = client.init_index(ALGOLIA_INDEX_NAME)
+ index.set_settings( # type: ignore[reportUnknownMemberType]
+ settings={
+ 'searchableAttributes': ['title', 'content'],
+ 'attributesToSnippet': ['content:40'],
+ 'customRanking': [
+ 'desc(rank)',
+ ],
+ }
+ )
for large_record in list(filter(lambda record: len(record['content']) >= MAX_CONTENT_SIZE, records)):
print(f'Content for {large_record["abs_url"]} is too large to be indexed. Skipping...')
print(f'Content : {large_record["content"]} characters')
diff --git a/docs/plugins/main.py b/docs/plugins/main.py
index 6fbde3aee..3a4ecaf9c 100644
--- a/docs/plugins/main.py
+++ b/docs/plugins/main.py
@@ -92,7 +92,6 @@ def install_logfire(markdown: str, page: Page) -> str:
# Split them and strip quotes for each one separately.
extras = [arg.strip('\'"') for arg in arguments[1].strip('[]').split(',')] if len(arguments) > 1 else []
package = 'logfire' if not extras else f"'logfire[{','.join(extras)}]'"
- extras_arg = ' '.join(f'-E {extra}' for extra in extras)
instructions = [
'=== "pip"',
' ```bash',
@@ -102,10 +101,6 @@ def install_logfire(markdown: str, page: Page) -> str:
' ```bash',
f' uv add {package}',
' ```',
- '=== "rye"',
- ' ```bash',
- f' rye add logfire {extras_arg}',
- ' ```',
'=== "poetry"',
' ```bash',
f' poetry add {package}',
diff --git a/mkdocs.yml b/mkdocs.yml
index 768d3610b..69fabdc94 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -62,12 +62,16 @@ extra_css:
# used for analytics
extra_javascript:
- "/flarelytics/client.js"
+ - "https://cdn.jsdelivr.net/npm/algoliasearch@5.20.0/dist/lite/builds/browser.umd.js"
+ - "https://cdn.jsdelivr.net/npm/instantsearch.js@4.77.3/dist/instantsearch.production.min.js"
+ - "javascripts/algolia-search.js"
nav:
- - Logfire:
+ - Getting Started:
- Logfire: index.md
- Why Logfire?: why.md
- Concepts: concepts.md
+ - Join Slack: help.md
- Onboarding Checklist:
- Onboarding Checklist: guides/onboarding-checklist/index.md
- Integrate Logfire: guides/onboarding-checklist/integrate.md
@@ -148,7 +152,6 @@ nav:
- Propagate: reference/api/propagate.md
- Exceptions: reference/api/exceptions.md
- Pydantic: reference/api/pydantic.md
- - Help: help.md
- Roadmap: roadmap.md
- Release Notes: release-notes.md