mirror of
https://github.com/simonw/datasette.git
synced 2026-09-02 14:44:07 +02:00
The span and attribute names were string literals spread across four call sites in database.py and one in app.py, with a hand-written reference page that would have been true only on the day it was written. That drift is not hypothetical: an earlier iteration of this work carried a README asserting parameter values were never recorded for two branches after that had stopped being true. datasette/telemetry_registry.py now holds each name once, with its documentation. Attribute and SpanName subclass str, so a registry entry *is* the string OpenTelemetry wants - no wrapper API over the OTel calls, no parallel structure to keep in step, and a typo becomes an ImportError rather than a silently misnamed attribute. docs/internals.rst renders the span reference from it via cog, and `cog --check docs/*.rst` already runs in CI, so the reference cannot drift from the definitions. Nothing changes on the wire: the emitted span names and attribute keys are byte-identical before and after, verified by diffing a dump of both. tests/test_telemetry_registry.py exercises a real workload and compares it against the registry in both directions - emitted-but-unregistered catches instrumentation added without documentation, registered-but-never-emitted catches documentation that has outlived its code. Because the call sites now take their names from the registry, neither direction can catch a rename: move DB_NAMESPACE to "db.namespace2" and code and registry still agree while every dashboard breaks. So the literal names are also written out in the test and asserted against the registry and against the wire separately. That pair is the only comparison in the file not derived from the registry itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
37 lines
1.3 KiB
Python
37 lines
1.3 KiB
Python
"""
|
|
Render the span reference in ``internals.rst`` from
|
|
``datasette/telemetry_registry.py``.
|
|
|
|
Driven by cog, and ``cog --check docs/*.rst`` runs in CI - so adding a span
|
|
without documenting it, or documenting one that no longer exists, is a build
|
|
failure rather than something a reader discovers later.
|
|
"""
|
|
|
|
|
|
def _attribute_lines(cog, attributes):
|
|
if not attributes:
|
|
cog.out(" No attributes.\n\n")
|
|
return
|
|
cog.out(" Attributes:\n\n")
|
|
for attribute in attributes:
|
|
suffix = " *(optional)*" if attribute.optional else ""
|
|
cog.out(f" - ``{attribute}``{suffix} - {attribute.description}\n")
|
|
cog.out("\n")
|
|
|
|
|
|
def spans(cog):
|
|
from opentelemetry.trace import SpanKind
|
|
|
|
from datasette.telemetry_registry import SPANS
|
|
|
|
cog.out("\n")
|
|
for span in SPANS:
|
|
title = f"{span}*" if span.prefix else str(span)
|
|
cog.out(f"``{title}``\n")
|
|
cog.out(f" {span.description}\n\n")
|
|
# INTERNAL is the default and the overwhelming majority of spans -
|
|
# printing it on every one would be noise. Only the exceptional case,
|
|
# a real database call, is worth calling out.
|
|
if span.kind != SpanKind.INTERNAL:
|
|
cog.out(f" Kind: ``{span.kind.name}``.\n\n")
|
|
_attribute_lines(cog, span.attributes)
|