Skip to content

OpenTelemetry spans and metrics - #41

Draft
asg017 wants to merge 5 commits into
asg017/datasette-1a41from
asg017/otel
Draft

asg017 wants to merge 5 commits into
asg017/datasette-1a41from
asg017/otel

Conversation

@asg017

@asg017 asg017 commented Sep 28, 2026

Copy link
Copy Markdown
🤖 Claude-generated PR description

Adds OpenTelemetry spans and metrics to datasette-apps using Datasette 1.0a41's plugin telemetry kit (datasette.telemetry, datasette.telemetry_registry, datasette.telemetry_testing). This is stacked on #39 (Require Datasette 1.0a41); rebase onto main once that merges.

Scope datasette_apps, prefix apps.*, no schema_url. With no OpenTelemetry SDK installed, everything is a no-op. We add one span of our own (apps.query). Everywhere else we add attributes to core's request spans and to the current agent tool span, rather than re-wrapping work core already traces.

Commits (one per ticket)

  1. Telemetry scaffolding: registry, helpers, test fixtures. Adds telemetry_registry.py, which declares every span, metric and attribute, and telemetry.py, with helpers that never import the SDK. Also adds a tests/conftest.py wired to the kit's fixtures and static registry tests. opentelemetry-api becomes a runtime dependency; opentelemetry-sdk is dev-only.
  2. Trace app data queries.
    • app_query and debug_query now share one code path, wrapped in an apps.query span.
    • New apps.queries counter and apps.query.duration histogram, with outcome ok / not_allowed / denied / failed / invalid / not_found / error.
    • apps.app_id on the request spans of the nine app routes.
    • Internal datasette.client requests now carry W3C trace headers, so core's internal request span nests under apps.query. This works around datasette#2969; trace_headers() can go once the minimum Datasette version includes the fix.
  3. Count app changes and tag agent tool spans.
    • apps.changes counts create / edit / delete / pin / unpin, split by web / agent, and only after a successful write.
    • Agent tools set apps.tool.result on the current span, and apps.app_id only once the app is known to exist. Model-supplied IDs are never recorded unverified.
    • The instrumentation lives inside the tool bodies; tool functions aren't wrapped, so their signatures are unchanged.
  4. Telemetry for app_debug runs.
    • apps.debug.runs by outcome, plus run and script duration histograms, and apps.debug.jobs.expired, which counts only rows actually flipped.
    • Job and app IDs on the /frame and /query request spans.
    • app_debug executes twice per logical run (suspend, then resume). The outcome is recorded exactly once, on the terminal execution. Tests cover this with both a fake context and datasette-agent's real one.
  5. Telemetry conformance, privacy tests and README reference.
    • A workload that exercises every span, metric, attribute and enum value, checked with the kit's conform and covered assertions.
    • Sentinel privacy tests:
      • an unscoped pass over every span and metric;
      • a scoped pass for SQL text and stored query names, which core records by design.
    • A generated Telemetry reference in the README (scripts/telemetry-doc.py), with a pytest freshness test. CI needs no workflow changes.

Never recorded

App HTML, names and descriptions; SQL text or parameter values; stored query names; debug JS, results, logs or errors; CSP origins; actor or conversation IDs; frame, channel and bridge tokens; exception messages. apps.app_id, apps.debug.job_id and db.namespace go on spans only, never on metrics.

Notes

  • There's no HTTP response or tool return-value change anywhere in the branch.
  • A pre-existing 500 on JSON bodies that aren't objects is recorded as outcome error but not fixed here: App query endpoints return 500 on non-object JSON body #40.
  • apps.tool.result and apps.debug.runs are left unset when an exception escapes a tool. A datasette-agent version with telemetry records error.type on its own span. The released datasette-agent 0.4a0 has no telemetry, so our tool attributes land on whatever span is current.

Testing

  • pytest --ignore=tests/test_playwright.py: 284 passed, 1 skipped (skipped: an integration test gated on datasette_agent.telemetry). Also passes on Python 3.10.
  • pytest tests/test_playwright.py: 12 passed.
  • scripts/telemetry-doc.py --check: clean.

🤖 Generated with Claude Code

asg017 and others added 5 commits September 28, 2026 12:38
Declares every span, metric and attribute in telemetry_registry.py using
Datasette 1.0a41's plugin telemetry kit. No instrumentation yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds an apps.query span plus apps.queries / apps.query.duration metrics
around datasette.query() and datasette.storedQuery() calls, and tags
request spans on app routes with apps.app_id.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
apps.changes counts creates, edits, deletes and pins by channel (web or
agent). Agent tools set apps.app_id and apps.tool.result on the current
span, which is datasette-agent's execute_tool span when it has telemetry.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
apps.debug.runs counts terminal debug runs by outcome, with wall-clock
and browser-reported script durations. Lazily expired jobs are counted
separately, and the debug frame and query routes tag their request
spans with the job and app IDs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Checks that everything emitted is registered and everything registered
is emitted, plants sentinel values to prove app HTML, SQL parameters,
debug scripts and tokens never reach telemetry, and generates the
README's telemetry reference from the registry.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@asg017
asg017 added this pull request to stack #42 September 28, 2026 21:05

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant