{{ message }}
feat(otel): opt-in OTel trace export, off by default (plan 39) - #2243
Merged
Conversation
Member
Author
This was referenced Jul 4, 2026
Lays the foundation for plans/39-otel-trace-export.md: a memoized BasicTracerProvider + BatchSpanProcessor + OTLPTraceExporter singleton gated on CHM_OTEL_EXPORTER_URL, plus a withSpan(name, attrs, fn) helper that is a true no-op (zero OTel API calls) when the URL is unset or invalid. AsyncLocalStorageContextManager is registered only on the enabled path so nested spans (wired in a follow-up commit) nest under their parent without needing manual context threading. withClickHouseQuerySpan wraps a FetchDataResult-shaped call and only attaches query_id/read_bytes/host attributes when the result actually populates them. Co-Authored-By: duyetbot <bot@duyet.net>
fetchData/fetchJsonEachRowAsNormalizedJson now parse the read_bytes field out of ClickHouse's X-ClickHouse-Summary response header into metadata.readBytes, only when the header is present and parses cleanly. Needed by the OTel clickhouse-query span attribute (plans/39-otel-trace-export.md) but has no OTel dependency itself — this package stays otel-agnostic. Co-Authored-By: duyetbot <bot@duyet.net>
…quest path Root span: otelMiddleware in start.ts wraps every request in a dashboard-request span (positioned after Sentry so it covers security-headers/auth/the route handler), warming the memoized OTel singleton from the Worker env and flushing inline before the response returns (no waitUntil available here, same constraint sentryMiddleware already documents). Child span: query-executor.ts's three ClickHouse execution call sites (executeTableConfig, executeChartQuery, executeMultiChartQuery) each wrap their fetchData/fetchJsonEachRowAsNormalizedJson call in a clickhouse-query span via withClickHouseQuerySpan. This is the actual load-bearing @chm/clickhouse-client seam (8 route handlers funnel through it) — not clickhouse-helpers.ts's fetchDataWithHost, which has no production callers. AsyncLocalStorageContextManager (registered only when export is enabled, see exporter.ts) propagates the parent/child relationship across the async chain without manual context threading. There is no distinguishable "system-table-read" seam beneath clickhouse-query (fetchData makes one HTTP call per invocation), so the span tree is two levels (dashboard-request -> clickhouse-query) rather than three, per the plan's own documented fallback. Co-Authored-By: duyetbot <bot@duyet.net>
Adds the env var to .env.example and the environment-variables
reference (self-host docs), with a Jaeger OTLP/HTTP example and the
workerd (HTTP-only, no gRPC) constraint. Declares
@opentelemetry/{api,context-async-hooks,exporter-trace-otlp-http,
resources,sdk-trace-base,semantic-conventions} in apps/dashboard's
package.json — app-side only, not packages/clickhouse-client.
NOTE: bun.lock is NOT updated by this commit (bun install could not be
run in this worktree — see task constraints). The packages are already
present in the shared node_modules and all otel tests pass against
them, but a real `bun install` is needed before a frozen-lockfile CI
job (cloudflare.yml, test.yml, a11y.yml, etc.) will pass. Flagged in
the handoff report.
Co-Authored-By: duyetbot <bot@duyet.net>
start.ts: wrap next() in an async arrow (not a bare () => next()) so withSpan's fn parameter, typed Promise<T>, actually type-checks — TanStack's next() return type isn't structurally a Promise. Caught by bun run type-check (bun test alone never runs tsc). Adds span-nesting.test.ts: registers a real AsyncLocalStorageContextManager and proves a clickhouse-query span started after an await inside dashboard-request's callback still comes out as its child (parentSpanContext matches), and that two concurrent requests' spans don't cross-contaminate. This verifies the withSpan/ALS composition nests correctly; the residual (that TanStack's own next() chain preserves the same ALS store end-to-end) is only confirmed against a real collector, not by this unit test. Co-Authored-By: duyetbot <bot@duyet.net>
apps/dashboard/bun.lock was missing the OTel deps added by this PR, causing bun install --frozen-lockfile to fail in CI. Rebased onto current main (clean, no conflicts) and regenerated the lockfile. Co-Authored-By: duyetbot <bot@duyet.net>
duyet
force-pushed
the
advisor/39-otel-trace-export
branch
from
July 4, 2026 05:13
4c05ed2 to
c4faf5f
Compare
duyet
enabled auto-merge (squash)
July 4, 2026 05:13
Contributor
☁️ Cloudflare Preview Deployment
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What (plan 39 — OTel trace export)
Opt-in trace export gated on
CHM_OTEL_EXPORTER_URL— a true no-op (zero OTel API touched) when unset. Rootdashboard-requestspan (middleware after Sentry) →clickhouse-querychild span at the realquery-executor.tsseam (corrected from the plan'sfetchDataWithHost, which has no production callers). Parsesread_bytesfromX-ClickHouse-Summary.bun run check/depcruiseclean; fixed a real type bug in the middleware.🔶 Held — hard merge blocker
Adds 6
@opentelemetry/*deps toapps/dashboard/package.jsonbutbun.lockis not regenerated (agents can'tbun install). Every--frozen-lockfileCI job will fail until someone runsbun install. Also based a few commits behindmain(stale worktree base — verifystart.ts/package.jsonmerge cleanly). Do not merge until the lockfile is regenerated.Co-Authored-By: duyetbot bot@duyet.net