fix(api): install import-in-the-middle hook for ESM auto-instrumentation#11
Conversation
OTel's auto-instrumentation patches modules at import/require time. With
Node 22 + ESM + --experimental-strip-types, `--import ./tracing.ts` runs
our SDK config before server.ts, but the import-in-the-middle loader
hook needs to be installed via Node's module hook registry — registering
it from inside an already-loaded module is too late for built-ins like
node:http.
Result of the gap: HTTP/postgres auto-instrumentation loaded but never
wrapped anything in prod, so Tempo received zero spans (even though
metrics + logs flowed fine). Trace IDs were missing from response
headers and from pino log records.
Fix: add apps/api/instrument.mjs that calls `register('import-in-the-middle/hook.mjs')`
and load it via a second --import flag before tracing.ts.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: efe50c5b28
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Usage: node --import ./instrument.mjs --import ./tracing.ts server.ts | ||
| import { register } from 'node:module'; | ||
|
|
||
| register('import-in-the-middle/hook.mjs', import.meta.url); |
There was a problem hiding this comment.
Declare import-in-the-middle as a direct dependency
instrument.mjs now imports import-in-the-middle/hook.mjs, but apps/api/package.json does not declare import-in-the-middle directly. This currently works only when the package manager hoists that transitive dependency; in non-hoisted or isolated installs (common in CI/deploy/container builds), startup will fail with ERR_MODULE_NOT_FOUND before the API boots. Please add import-in-the-middle to dependencies so the new boot path is deterministic across install environments.
Useful? React with 👍 / 👎.
What's broken
After deploying #5, metrics and logs flowed to Grafana fine, but Tempo received zero spans:
otelcol_receiver_accepted_spans_totalwas emptyx-trace-idresponse header was absent on every API responseservice_name=pagent-apibut notrace_id/span_idcorrelationRoot cause
@opentelemetry/auto-instrumentations-nodepatches modules at import time. With Node 22 + ESM +--experimental-strip-types,--import ./tracing.tsevaluates our SDK config beforeserver.ts, but theimport-in-the-middleloader hook has to be installed via Node's module hook registry — registering it from inside an already-loaded module is too late for built-ins likenode:http. SogetNodeAutoInstrumentationsloaded but silently never wrapped anything.The OTel team's standard solution is
node --import @opentelemetry/auto-instrumentations-node/register, but that bootstraps its ownNodeSDK(configured via env vars), which would conflict with our custom-configured SDK in tracing.ts (we wire metric reader + log processor + pino correlation).Fix
A four-line
apps/api/instrument.mjsthat registers the loader hook only — leaves SDK setup totracing.ts:Wired in via a second
--importflag (instrument.mjs runs before tracing.ts):Verification
npm test(162 tests pass)npm run typecheck(clean)otelcol_receiver_accepted_spans_total > 0in Grafana, andx-trace-idheader is present oncurl -i https://pagent.up.railway.app/health.