Skip to content

feat(observability): per-request Server-Timing — auth/db/hooks/serialize spans (perf-tuning mode)#3107

Merged
os-zhuang merged 1 commit into
mainfrom
claude/github-issue-2408-m2r3wo
Jul 17, 2026
Merged

feat(observability): per-request Server-Timing — auth/db/hooks/serialize spans (perf-tuning mode)#3107
os-zhuang merged 1 commit into
mainfrom
claude/github-issue-2408-m2r3wo

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Completes #2408.

What

Turns the opt-in Server-Timing header (perf-tuning mode) into a real per-request breakdown, so an operator can open DevTools → Network → Timing and see exactly where a request's server time went — no external tracing backend required.

Before, only total / parse / handler were emitted. A data request now also carries:

span recorded by meaning
auth dispatcher identity / session resolution (the prime suspect for unexplained data-API overhead)
db SQL driver total SQL time; desc is the query count (db;dur=210;desc="6 queries")
hooks ObjectQL engine total business-hook time with a hook count
serialize HTTP adapter response JSON encoding

Example:

Server-Timing: parse;dur=0.4, auth;dur=42, db;dur=210;desc="6 queries", hooks;dur=18;desc="3 hooks", serialize;dur=7, handler;dur=280, total;dur=355

How

  • CollectorPerfTiming.count() / countServerTiming(name, dur, unit) fold high-frequency events into one aggregate member (name;dur=<sum>;desc="<count> <unit>"), so per-query / per-hook timing doesn't flood the header.
  • dbSqlDriver wires knex query / query-response / query-error (keyed by __knexQueryUid) and attributes each query to the originating request via AsyncLocalStorage — correct under concurrency, never cross-attributing. SQL text is never emitted, only durations + a count.
  • auth — the dispatcher wraps identity/session resolution (both the REST and GraphQL paths).
  • hooks — fed through the engine's existing HookMetricsRecorder seam, wired from the runtime (AppPlugin) so @objectstack/objectql's lean core tier (ADR-0076) stays observability-free.
  • serialize — the HTTP adapter times c.json().

Bug fix (surfaced by the end-to-end test)

The ambient AsyncLocalStorage store was a plain module-level const. Because @objectstack/observability ships both an ESM and a CJS build (and may be inlined by a bundler), consumers could load separate copies, each with its own store — so a request scope opened by the HTTP server was invisible to the SQL driver / engine and the cross-layer spans silently never recorded. The store is now pinned to a Symbol.for(...) global-registry key so every copy shares one. A real-socket integration test now guards this.

Gating / overhead

  • Off by default; enabled via serverTiming: true on HonoServerPlugin or OS_SERVER_TIMING=true (satisfies the AC's "or env flag").
  • Every span is a no-op when off (a single AsyncLocalStorage lookup), so there is zero measurable overhead on the normal path.
  • Only durations and counts are exposed; SQL text is never surfaced, so the header is safe for non-admins. The optional admin-only richer JSON / X-OS-Debug-Timing per-request path from the issue is intentionally out of scope — the env-flag gate already meets the acceptance criteria.

Acceptance criteria

  • Data response carries Server-Timing decomposing total into ≥ auth, db (with query count), hooks, serialize
  • Per-query SQL timing attributed to the originating request (ALS, concurrency-safe)
  • Zero measurable overhead when off
  • Gated (env flag / option); SQL text never exposed to non-admins
  • Unit test (collector) + integration test (header on a real find, reflecting query count)

Tests

  • observability: 58 pass (collector aggregate, ambient scope, global-store invariant)
  • driver-sql: 279 pass (incl. new db-span concurrency/attribution test)
  • plugin-hono-server: 72 pass (incl. real serialize span + aggregate-through-header)
  • runtime: app-plugin + dispatcher suites (284) pass; new real-HTTP server-timing.integration.test.ts proves db;desc="N queries" end-to-end over a socket
  • full workspace pnpm build: 71/71 green

🤖 Generated with Claude Code

https://claude.ai/code/session_01VoJNgMJRhgqm78xF7MxFhX


Generated by Claude Code

…lize spans (perf-tuning mode)

Completes #2408. The opt-in Server-Timing header now breaks a request's server
time into the phases that actually explain it, so an operator can read the
breakdown in DevTools -> Network -> Timing without standing up an external
tracing backend.

- collector: PerfTiming.count() / countServerTiming() fold high-frequency
  events (per query, per hook) into one aggregate member
  (name;dur=<sum>;desc="<count> <unit>").
- db: SqlDriver wires knex query / query-response events (keyed by
  __knexQueryUid), attributing per-query time to the originating request via
  AsyncLocalStorage (correct under concurrency; SQL text is never emitted).
- auth: the dispatcher times identity/session resolution.
- hooks: fed through the engine's existing HookMetricsRecorder seam, wired from
  the runtime so objectql's lean core tier stays observability-free.
- serialize: the HTTP adapter times response JSON encoding.

Fix a latent dual-instance bug found by a real end-to-end HTTP test: the ambient
AsyncLocalStorage store was a plain module-level const, so the ESM and CJS builds
(or an inlined bundle) each got their own store. A request scope opened by the
HTTP server was then invisible to the SQL driver / engine, silently dropping the
cross-layer spans. The store is now pinned to a global-registry symbol so every
copy shares one.

Every span is a no-op when perf-tuning is off (serverTiming / OS_SERVER_TIMING),
so there is zero measurable overhead on the normal path. Gating is unchanged
(env flag / option); only durations and counts are exposed, never SQL text.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VoJNgMJRhgqm78xF7MxFhX
@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 17, 2026 9:30am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file tests tooling size/l labels Jul 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/observability, @objectstack/driver-sql, @objectstack/plugin-hono-server, @objectstack/runtime.

19 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/api/index.mdx (via @objectstack/runtime)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/runtime)
  • content/docs/concepts/north-star.mdx (via packages/runtime)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/driver-sql, @objectstack/runtime)
  • content/docs/deployment/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/deployment/vercel.mdx (via @objectstack/runtime)
  • content/docs/getting-started/glossary.mdx (via @objectstack/driver-sql)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/plugin-hono-server, @objectstack/runtime)
  • content/docs/permissions/authentication.mdx (via @objectstack/plugin-hono-server, @objectstack/runtime)
  • content/docs/plugins/anatomy.mdx (via @objectstack/driver-sql)
  • content/docs/plugins/index.mdx (via @objectstack/plugin-hono-server)
  • content/docs/plugins/packages.mdx (via @objectstack/driver-sql, @objectstack/plugin-hono-server, @objectstack/runtime)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/driver-sql, @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/driver-sql, @objectstack/runtime)
  • content/docs/releases/implementation-status.mdx (via @objectstack/observability, @objectstack/driver-sql, @objectstack/plugin-hono-server, @objectstack/runtime)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review July 17, 2026 12:26
@os-zhuang
os-zhuang merged commit ce468c8 into main Jul 17, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/github-issue-2408-m2r3wo branch July 17, 2026 12:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants