autotel 2.26.3 → 3.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/README.md +50 -23
  2. package/dist/attribute-redacting-processor.cjs +8 -8
  3. package/dist/attribute-redacting-processor.d.cts +10 -1
  4. package/dist/attribute-redacting-processor.d.ts +10 -1
  5. package/dist/attribute-redacting-processor.js +1 -1
  6. package/dist/attributes.cjs +21 -21
  7. package/dist/attributes.d.cts +3 -3
  8. package/dist/attributes.d.ts +3 -3
  9. package/dist/attributes.js +2 -2
  10. package/dist/auto.cjs +3 -3
  11. package/dist/auto.js +2 -2
  12. package/dist/business-baggage.d.cts +1 -1
  13. package/dist/business-baggage.d.ts +1 -1
  14. package/dist/chunk-4P6ZOARG.cjs +33 -0
  15. package/dist/chunk-4P6ZOARG.cjs.map +1 -0
  16. package/dist/{chunk-U54FTVFH.js → chunk-52PUSFC2.js} +3 -3
  17. package/dist/{chunk-U54FTVFH.js.map → chunk-52PUSFC2.js.map} +1 -1
  18. package/dist/{chunk-YEVCD6DR.cjs → chunk-7SMNC4LS.cjs} +7 -7
  19. package/dist/{chunk-YEVCD6DR.cjs.map → chunk-7SMNC4LS.cjs.map} +1 -1
  20. package/dist/{chunk-563EL6O6.cjs → chunk-BPO2PQ3T.cjs} +12 -8
  21. package/dist/chunk-BPO2PQ3T.cjs.map +1 -0
  22. package/dist/{chunk-WZOKY3PW.cjs → chunk-DAZ7EGR4.cjs} +19 -19
  23. package/dist/{chunk-WZOKY3PW.cjs.map → chunk-DAZ7EGR4.cjs.map} +1 -1
  24. package/dist/{chunk-ER43K7ES.js → chunk-DDXIUZEG.js} +3 -3
  25. package/dist/{chunk-ER43K7ES.js.map → chunk-DDXIUZEG.js.map} +1 -1
  26. package/dist/{chunk-JKIMEPI2.cjs → chunk-DQ2SUROF.cjs} +4 -4
  27. package/dist/{chunk-JKIMEPI2.cjs.map → chunk-DQ2SUROF.cjs.map} +1 -1
  28. package/dist/{chunk-B3ZHLLMP.js → chunk-DSMSIVTG.js} +2 -2
  29. package/dist/chunk-DSMSIVTG.js.map +1 -0
  30. package/dist/{chunk-OBWXM4NN.cjs → chunk-HKZHUGGN.cjs} +15 -14
  31. package/dist/chunk-HKZHUGGN.cjs.map +1 -0
  32. package/dist/{chunk-TDNKIHKT.js → chunk-JVWJDHDB.js} +13 -4
  33. package/dist/chunk-JVWJDHDB.js.map +1 -0
  34. package/dist/{chunk-YN7USLHW.js → chunk-K7HSRLP5.js} +11 -10
  35. package/dist/chunk-K7HSRLP5.js.map +1 -0
  36. package/dist/chunk-KIL5CUN6.js +31 -0
  37. package/dist/chunk-KIL5CUN6.js.map +1 -0
  38. package/dist/chunk-KKGM42RQ.cjs +1207 -0
  39. package/dist/chunk-KKGM42RQ.cjs.map +1 -0
  40. package/dist/{chunk-6YGUN7IY.cjs → chunk-MOO75VE4.cjs} +18 -17
  41. package/dist/chunk-MOO75VE4.cjs.map +1 -0
  42. package/dist/{chunk-GML3FBOT.cjs → chunk-NCSMD3TK.cjs} +2 -2
  43. package/dist/chunk-NCSMD3TK.cjs.map +1 -0
  44. package/dist/{chunk-CMNGGTQL.cjs → chunk-NXLRY2CE.cjs} +13 -4
  45. package/dist/chunk-NXLRY2CE.cjs.map +1 -0
  46. package/dist/{chunk-BJ2XPN77.js → chunk-OM4OSBOP.js} +5 -5
  47. package/dist/{chunk-BJ2XPN77.js.map → chunk-OM4OSBOP.js.map} +1 -1
  48. package/dist/{chunk-HPUGKUMZ.js → chunk-PMRWMRXY.js} +13 -640
  49. package/dist/chunk-PMRWMRXY.js.map +1 -0
  50. package/dist/{chunk-UTZR7P7E.cjs → chunk-QPH5ZKP5.cjs} +43 -673
  51. package/dist/chunk-QPH5ZKP5.cjs.map +1 -0
  52. package/dist/chunk-SEO6NAQT.js +14 -0
  53. package/dist/chunk-SEO6NAQT.js.map +1 -0
  54. package/dist/{chunk-QC5MNKVF.js → chunk-TFRZOUTV.js} +13 -12
  55. package/dist/chunk-TFRZOUTV.js.map +1 -0
  56. package/dist/chunk-VQTCQKHQ.cjs +17 -0
  57. package/dist/chunk-VQTCQKHQ.cjs.map +1 -0
  58. package/dist/chunk-Z7VAOK5X.js +1183 -0
  59. package/dist/chunk-Z7VAOK5X.js.map +1 -0
  60. package/dist/{chunk-W35FVJBC.js → chunk-ZDPIWKWD.js} +9 -5
  61. package/dist/chunk-ZDPIWKWD.js.map +1 -0
  62. package/dist/correlation-id.cjs +22 -10
  63. package/dist/correlation-id.js +14 -2
  64. package/dist/decorators.cjs +7 -8
  65. package/dist/decorators.cjs.map +1 -1
  66. package/dist/decorators.d.cts +1 -1
  67. package/dist/decorators.d.ts +1 -1
  68. package/dist/decorators.js +6 -7
  69. package/dist/decorators.js.map +1 -1
  70. package/dist/event.cjs +8 -9
  71. package/dist/event.js +5 -6
  72. package/dist/functional.cjs +13 -14
  73. package/dist/functional.d.cts +1 -1
  74. package/dist/functional.d.ts +1 -1
  75. package/dist/functional.js +6 -7
  76. package/dist/http.cjs +13 -2
  77. package/dist/http.cjs.map +1 -1
  78. package/dist/http.js +12 -1
  79. package/dist/http.js.map +1 -1
  80. package/dist/index.cjs +305 -280
  81. package/dist/index.cjs.map +1 -1
  82. package/dist/index.d.cts +89 -10
  83. package/dist/index.d.ts +89 -10
  84. package/dist/index.js +180 -181
  85. package/dist/index.js.map +1 -1
  86. package/dist/instrumentation.cjs +9 -9
  87. package/dist/instrumentation.js +2 -2
  88. package/dist/messaging-adapters.d.cts +1 -1
  89. package/dist/messaging-adapters.d.ts +1 -1
  90. package/dist/messaging-testing.d.cts +1 -1
  91. package/dist/messaging-testing.d.ts +1 -1
  92. package/dist/messaging.cjs +11 -11
  93. package/dist/messaging.d.cts +1 -1
  94. package/dist/messaging.d.ts +1 -1
  95. package/dist/messaging.js +8 -8
  96. package/dist/semantic-helpers.cjs +11 -12
  97. package/dist/semantic-helpers.d.cts +1 -1
  98. package/dist/semantic-helpers.d.ts +1 -1
  99. package/dist/semantic-helpers.js +7 -8
  100. package/dist/{trace-context-t5X1AP-e.d.cts → trace-context-DbGKd1Rn.d.cts} +18 -5
  101. package/dist/{trace-context-t5X1AP-e.d.ts → trace-context-DbGKd1Rn.d.ts} +18 -5
  102. package/dist/trace-helpers.cjs +13 -13
  103. package/dist/trace-helpers.d.cts +2 -2
  104. package/dist/trace-helpers.d.ts +2 -2
  105. package/dist/trace-helpers.js +1 -1
  106. package/dist/{utils-CbUkl8r1.d.cts → utils-BahBCFtJ.d.cts} +1 -1
  107. package/dist/{utils-Buel3cj0.d.ts → utils-CLKwaUlG.d.ts} +1 -1
  108. package/dist/webhook.cjs +21 -12
  109. package/dist/webhook.cjs.map +1 -1
  110. package/dist/webhook.d.cts +1 -1
  111. package/dist/webhook.d.ts +1 -1
  112. package/dist/webhook.js +20 -11
  113. package/dist/webhook.js.map +1 -1
  114. package/dist/workflow-distributed.cjs +25 -21
  115. package/dist/workflow-distributed.cjs.map +1 -1
  116. package/dist/workflow-distributed.d.cts +1 -1
  117. package/dist/workflow-distributed.d.ts +1 -1
  118. package/dist/workflow-distributed.js +23 -19
  119. package/dist/workflow-distributed.js.map +1 -1
  120. package/dist/workflow.cjs +12 -12
  121. package/dist/workflow.d.cts +1 -1
  122. package/dist/workflow.d.ts +1 -1
  123. package/dist/workflow.js +8 -8
  124. package/package.json +43 -45
  125. package/skills/analyze-traces/SKILL.md +178 -0
  126. package/skills/autotel-core/SKILL.md +2 -7
  127. package/skills/autotel-events/SKILL.md +2 -6
  128. package/skills/autotel-frameworks/SKILL.md +2 -9
  129. package/skills/autotel-instrumentation/SKILL.md +2 -7
  130. package/skills/autotel-request-logging/SKILL.md +2 -8
  131. package/skills/autotel-structured-errors/SKILL.md +2 -7
  132. package/skills/build-audit-trails/SKILL.md +302 -0
  133. package/skills/debug-missing-spans/SKILL.md +248 -0
  134. package/skills/migrate-to-autotel/SKILL.md +268 -0
  135. package/skills/review-otel-patterns/SKILL.md +488 -0
  136. package/skills/review-otel-patterns/references/code-review.md +75 -0
  137. package/skills/review-otel-patterns/references/processor-pipeline.md +205 -0
  138. package/skills/review-otel-patterns/references/structured-errors.md +102 -0
  139. package/skills/review-otel-patterns/references/wide-spans.md +85 -0
  140. package/skills/tune-sampling/SKILL.md +210 -0
  141. package/src/attribute-redacting-processor.test.ts +6 -4
  142. package/src/attribute-redacting-processor.ts +11 -2
  143. package/src/correlated-events.test.ts +151 -0
  144. package/src/correlated-events.ts +47 -0
  145. package/src/drain-toolkit.test.ts +113 -0
  146. package/src/drain-toolkit.ts +129 -0
  147. package/src/enricher-toolkit.test.ts +67 -0
  148. package/src/enricher-toolkit.ts +79 -0
  149. package/src/functional.ts +2 -0
  150. package/src/gen-ai-events.ts +14 -5
  151. package/src/index.ts +39 -4
  152. package/src/messaging.ts +10 -9
  153. package/src/redact-values.test.ts +24 -10
  154. package/src/redact-values.ts +9 -2
  155. package/src/request-logger.test.ts +91 -0
  156. package/src/request-logger.ts +40 -5
  157. package/src/structured-error.test.ts +86 -1
  158. package/src/structured-error.ts +9 -2
  159. package/src/trace-context.ts +39 -11
  160. package/src/trace-helpers.ts +2 -2
  161. package/src/trace-hybrid.test.ts +42 -0
  162. package/src/trace-hybrid.ts +37 -0
  163. package/src/webhook.ts +16 -7
  164. package/src/workflow-distributed.ts +18 -13
  165. package/src/workflow.ts +7 -6
  166. package/bin/intent.js +0 -6
  167. package/dist/chunk-563EL6O6.cjs.map +0 -1
  168. package/dist/chunk-6YGUN7IY.cjs.map +0 -1
  169. package/dist/chunk-B3ZHLLMP.js.map +0 -1
  170. package/dist/chunk-BBBWDIYQ.js +0 -211
  171. package/dist/chunk-BBBWDIYQ.js.map +0 -1
  172. package/dist/chunk-CMNGGTQL.cjs.map +0 -1
  173. package/dist/chunk-D5LMF53P.cjs +0 -150
  174. package/dist/chunk-D5LMF53P.cjs.map +0 -1
  175. package/dist/chunk-GML3FBOT.cjs.map +0 -1
  176. package/dist/chunk-HPUGKUMZ.js.map +0 -1
  177. package/dist/chunk-HZ3FYBJG.cjs +0 -217
  178. package/dist/chunk-HZ3FYBJG.cjs.map +0 -1
  179. package/dist/chunk-JSNUWSBH.cjs +0 -62
  180. package/dist/chunk-JSNUWSBH.cjs.map +0 -1
  181. package/dist/chunk-OBWXM4NN.cjs.map +0 -1
  182. package/dist/chunk-QC5MNKVF.js.map +0 -1
  183. package/dist/chunk-S4OFEXLA.js +0 -53
  184. package/dist/chunk-S4OFEXLA.js.map +0 -1
  185. package/dist/chunk-TDNKIHKT.js.map +0 -1
  186. package/dist/chunk-UTZR7P7E.cjs.map +0 -1
  187. package/dist/chunk-W35FVJBC.js.map +0 -1
  188. package/dist/chunk-WD4RP6IV.js +0 -146
  189. package/dist/chunk-WD4RP6IV.js.map +0 -1
  190. package/dist/chunk-YN7USLHW.js.map +0 -1
  191. package/src/package-manifest.test.ts +0 -24
@@ -0,0 +1,488 @@
1
+ ---
2
+ name: review-otel-patterns
3
+ description: >
4
+ Review TypeScript/JavaScript code for OpenTelemetry instrumentation patterns and guide adoption of autotel.
5
+ Covers Next.js, Nuxt, Nitro, TanStack Start, SvelteKit, NestJS, Express, Hono, Fastify, Elysia,
6
+ Cloudflare Workers, AWS Lambda, edge runtimes, and standalone Node. Detects unstructured tracing,
7
+ missing span attributes, manual exporter setup, broken context propagation, exposed PII, and ad-hoc
8
+ error handling. Covers spans, metrics, logs, structured errors, the autotel processor pipeline
9
+ (tail-sampling, attribute redaction, span-name normalisation, filtering, baggage),
10
+ `defineWorkerFetch` for Cloudflare async drains, multi-vendor OTLP backends (Honeycomb, Datadog,
11
+ Grafana Cloud, Sentry, Axiom, HyperDX), `composeSpanProcessors` / `composeSubscribers` /
12
+ `composePostProcessors` for pipelines, AI SDK observability with gen-ai semantic conventions, and
13
+ end-to-end OTLP testing.
14
+ license: MIT
15
+ ---
16
+
17
+ # Review OpenTelemetry patterns
18
+
19
+ Review and improve OpenTelemetry instrumentation in TypeScript/JavaScript codebases using autotel. Replace ad-hoc tracing with idiomatic OTel-native spans, metrics and structured logs that work across every major framework and edge runtime — without vendor lock-in.
20
+
21
+ ## When to use
22
+
23
+ - Setting up autotel in a new or existing project (any supported framework)
24
+ - Reviewing code for OpenTelemetry best practices
25
+ - Converting `console.log` / ad-hoc tracing to spans + structured events
26
+ - Improving error handling with structured errors and span status
27
+ - Configuring sampling, redaction, processors, or backends
28
+ - Migrating between observability vendors
29
+
30
+ ## Quick reference
31
+
32
+ | Working on… | Resource |
33
+ | -------------------------------- | -------------------------------------------------------------------- |
34
+ | Span design + wide events | [references/wide-spans.md](references/wide-spans.md) |
35
+ | Structured errors | [references/structured-errors.md](references/structured-errors.md) |
36
+ | Code review checklist | [references/code-review.md](references/code-review.md) |
37
+ | Processor pipeline + composition | [references/processor-pipeline.md](references/processor-pipeline.md) |
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ npm install autotel
43
+ ```
44
+
45
+ For framework-specific helpers, install the relevant package alongside core:
46
+
47
+ ```bash
48
+ npm install autotel-cloudflare # Workers + Durable Objects + Workflows
49
+ npm install autotel-edge # Vendor-agnostic edge runtime base
50
+ npm install autotel-hono # Hono middleware
51
+ npm install autotel-tanstack # TanStack Start
52
+ npm install autotel-adapters # Next.js / Nitro / Hono / Cloudflare adapter toolkit
53
+ npm install autotel-vitest # Span assertions in tests
54
+ npm install autotel-playwright # Browser → server trace propagation
55
+ npm install autotel-drizzle # Drizzle ORM auto-instrumentation
56
+ npm install autotel-mongoose # Mongoose auto-instrumentation
57
+ npm install autotel-sentry # Sentry exporter via OTLP
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Framework setup
63
+
64
+ ### Next.js (App Router)
65
+
66
+ Step 1 — Initialise once in `instrumentation.ts`:
67
+
68
+ ```typescript
69
+ // instrumentation.ts
70
+ import { init } from 'autotel';
71
+
72
+ export function register() {
73
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
74
+ init({
75
+ service: 'my-app',
76
+ exporter: { url: process.env.OTLP_ENDPOINT! },
77
+ sampling: { rates: { server: 25, client: 5 } },
78
+ attributeRedactor: 'default',
79
+ });
80
+ }
81
+ }
82
+ ```
83
+
84
+ Step 2 — Wrap route handlers:
85
+
86
+ ```typescript
87
+ // app/api/checkout/route.ts
88
+ import { withAutotel } from 'autotel-adapters';
89
+
90
+ export const POST = withAutotel(async (request: Request) => {
91
+ const log = useLogger();
92
+ log.set({ user: { id: 'usr_123', plan: 'enterprise' } });
93
+ log.set({ cart: { items: 3, total: 14_999 } });
94
+ return Response.json({ ok: true });
95
+ });
96
+ ```
97
+
98
+ Step 3 — Tag Server Actions the same way (`withAutotel`).
99
+
100
+ Step 4 — Browser → server trace propagation: drop in `<TraceProvider />` from `autotel-web` so the W3C `traceparent` header is forwarded automatically.
101
+
102
+ ### Nuxt + Nitro v3
103
+
104
+ ```typescript
105
+ // nitro.config.ts
106
+ import { defineConfig } from 'nitro';
107
+ import autotel from 'autotel-adapters/nitro';
108
+
109
+ export default defineConfig({
110
+ modules: [
111
+ autotel({ service: 'my-api', sampling: { rates: { server: 25 } } }),
112
+ ],
113
+ });
114
+ ```
115
+
116
+ ```typescript
117
+ // routes/api/checkout.post.ts
118
+ import { useLogger } from 'autotel-adapters/nitro';
119
+
120
+ export default defineEventHandler(async (event) => {
121
+ const log = useLogger(event);
122
+ log.set({ action: 'checkout', user: { id: event.context.user?.id } });
123
+ return { ok: true };
124
+ });
125
+ ```
126
+
127
+ ### TanStack Start
128
+
129
+ ```typescript
130
+ // nitro.config.ts
131
+ import autotel from 'autotel-adapters/nitro';
132
+ export default defineConfig({
133
+ modules: [autotel({ service: 'my-app' })],
134
+ });
135
+ ```
136
+
137
+ ```typescript
138
+ // src/routes/__root.tsx
139
+ import { createMiddleware } from '@tanstack/react-start';
140
+ import { autotelMiddleware } from 'autotel-tanstack';
141
+
142
+ export const Route = createRootRoute({
143
+ server: { middleware: [createMiddleware().server(autotelMiddleware())] },
144
+ });
145
+ ```
146
+
147
+ ### Hono
148
+
149
+ ```typescript
150
+ import { Hono } from 'hono';
151
+ import { honoToolkit } from 'autotel-adapters';
152
+
153
+ const { useLogger, withAutotel } = honoToolkit;
154
+ const app = new Hono();
155
+ app.use('*', withAutotel({ service: 'my-api' }));
156
+
157
+ app.get('/api/users', (c) => {
158
+ const log = useLogger(c);
159
+ log.set({ users: { count: 42 } });
160
+ return c.json({ users: [] });
161
+ });
162
+ ```
163
+
164
+ ### Express / Fastify / Elysia / NestJS
165
+
166
+ The `autotel-adapters` toolkit ships a uniform shape for these: `withAutotel` middleware + `useLogger()` from anywhere in the call stack. See per-framework docs in `packages/autotel-adapters/skills/autotel-adapters/SKILL.md`.
167
+
168
+ ### Cloudflare Workers (with auto `waitUntil`)
169
+
170
+ `defineWorkerFetch` instruments the handler **and** wires `ctx.waitUntil` for span exports — without it, async exports silently drop:
171
+
172
+ ```typescript
173
+ import { defineWorkerFetch } from 'autotel-cloudflare';
174
+
175
+ export default defineWorkerFetch(
176
+ { service: { name: 'edge-api' } },
177
+ async (request, env, ctx, log) => {
178
+ log.set({ route: '/health', user: { id: env.userId } });
179
+ return new Response('ok');
180
+ },
181
+ );
182
+ ```
183
+
184
+ For broader use cases (`scheduled`, `queue`, `email` handlers, Durable Objects, Workflows), use `wrapModule` / `wrapDurableObject` / `instrumentWorkflow` — same auto-`waitUntil` semantics.
185
+
186
+ ### AWS Lambda
187
+
188
+ ```typescript
189
+ import { withLambda } from 'autotel-aws';
190
+
191
+ export const handler = withLambda(async (event) => {
192
+ const log = useLogger();
193
+ log.set({ event: { source: event.source } });
194
+ return { statusCode: 200 };
195
+ });
196
+ ```
197
+
198
+ ### Standalone Node / scripts
199
+
200
+ ```typescript
201
+ import { init, trace } from 'autotel';
202
+
203
+ init({ service: 'my-worker', exporter: { url: process.env.OTLP_ENDPOINT! } });
204
+
205
+ const processJob = trace(async (job: Job) => {
206
+ // span auto-named after the function
207
+ const log = useLogger();
208
+ log.set({ job: { id: job.id, source: job.source } });
209
+ });
210
+ ```
211
+
212
+ ---
213
+
214
+ ## Configuration options
215
+
216
+ All options work with `init()`, framework adapters, and `wrapModule` / `defineWorkerFetch`:
217
+
218
+ | Option | Type | Default | Description |
219
+ | --------------------------------------- | --------------------------------------------------------------- | ----------------- | --------------------------------------------------------------- |
220
+ | `service` / `service.name` | `string` | `'app'` | Service name in `service.name` resource attribute |
221
+ | `exporter` | `{ url, headers?, protocol? }` | — | OTLP HTTP/JSON or HTTP/protobuf endpoint |
222
+ | `spanProcessors` | `SpanProcessor[]` | — | Use **instead of** `exporter` for full control |
223
+ | `sampling.rates` | `{ server?: number, client?: number, internal?: number }` | `100%` | Head sampling per span kind (0–100%) |
224
+ | `sampling.tail` | `TailSampleFn` | — | Keep traces matching predicate (e.g. errors, slow) |
225
+ | `attributeRedactor` | `'default' \| 'strict' \| 'pci-dss' \| AttributeRedactorConfig` | — | PII redaction; on by default in production |
226
+ | `instrumentation.disabled` | `boolean` | `false` | Hard-off switch (ideal for local dev) |
227
+ | `instrumentation.instrumentGlobalFetch` | `boolean` | `true` | Patch `globalThis.fetch` for outbound HTTP spans |
228
+ | `subscribers` | `EdgeSubscriber[]` | — | In-process side effects (metrics, audit, AI cost) |
229
+ | `postProcessor` | `PostProcessorFn` | — | Mutate spans before export (redact, drop, tag) |
230
+ | `propagator` | `TextMapPropagator` | W3C trace-context | Override propagation format |
231
+ | `dataSafety` | `DataSafetyConfig` | — | Per-attribute safety (`captureDbStatement: 'obfuscated'`, etc.) |
232
+
233
+ ---
234
+
235
+ ## Backends (multi-vendor OTLP)
236
+
237
+ Switch backends with **no code changes** — autotel speaks OTLP HTTP/JSON and HTTP/protobuf out of the box.
238
+
239
+ | Backend | Endpoint | Headers |
240
+ | -------------------------------- | ---------------------------------------------------------- | ------------------------------------- |
241
+ | Honeycomb | `https://api.honeycomb.io/v1/traces` | `{ 'x-honeycomb-team': '<key>' }` |
242
+ | Grafana Cloud | `https://otlp-gateway-<region>.grafana.net/otlp/v1/traces` | `{ authorization: 'Basic <b64>' }` |
243
+ | Datadog (OTLP intake) | `https://trace.agent.datadoghq.com/api/v0.4/traces` | `{ 'dd-api-key': '<key>' }` |
244
+ | Sentry | `<dsn>/api/<id>/envelope/` (use `autotel-sentry`) | `{ 'x-sentry-auth': '…' }` |
245
+ | Axiom | `https://api.axiom.co/v1/traces` | `{ authorization: 'Bearer <token>' }` |
246
+ | HyperDX | `https://in-otel.hyperdx.io/v1/traces` | `{ authorization: '<key>' }` |
247
+ | New Relic | `https://otlp.nr-data.net/v1/traces` | `{ 'api-key': '<key>' }` |
248
+ | Local Jaeger / Tempo / Collector | `http://localhost:4318/v1/traces` | — |
249
+
250
+ Use `init({ exporter: { url, headers } })`. Multiple destinations? Use `composeSpanProcessors([batchA, batchB])` (see Composition below).
251
+
252
+ ---
253
+
254
+ ## Auto-redaction (PII protection)
255
+
256
+ Built-in masking scrubs sensitive data from span attributes **before** export. **On by default in production**, off in development. Smart partial masking preserves debug signal:
257
+
258
+ | Pattern | Example input | Masked output |
259
+ | ------------ | --------------------- | -------------------------------------------- |
260
+ | `creditCard` | `4111-1111-1111-1111` | `****1111` (PCI-DSS compliant) |
261
+ | `email` | `alice@example.com` | `a***@***.com` |
262
+ | `ipv4` | `192.168.1.100` | `***.***.***.100` |
263
+ | `phone` | `+33 6 12 34 56 78` | `+33******78` (requires `+cc` or `(parens)`) |
264
+ | `jwt` | `eyJhbGciOi…` | `eyJ***.***` |
265
+ | `bearer` | `Bearer sk_live_abc…` | `Bearer ***` |
266
+ | `iban` | `FR76 3000 6000 …189` | `FR76****189` |
267
+
268
+ Three presets ship out of the box: `'default'` (PII-grade), `'strict'` (adds JWT / Bearer / IBAN, redacts more keys), `'pci-dss'` (cards only, PCI focus).
269
+
270
+ ```typescript
271
+ init({
272
+ service: 'my-app',
273
+ attributeRedactor: 'default',
274
+ });
275
+ ```
276
+
277
+ Custom config:
278
+
279
+ ```typescript
280
+ import { builtinPatterns, type AttributeRedactorConfig } from 'autotel';
281
+
282
+ const config: AttributeRedactorConfig = {
283
+ keyPatterns: [/password/i, /^x-internal-/i],
284
+ builtins: ['email', 'creditCard', 'jwt'], // pick specific masks
285
+ valuePatterns: [
286
+ { name: 'customerId', pattern: /CUST-\d{8}/g, replacement: 'CUST-***' },
287
+ ],
288
+ };
289
+ init({ attributeRedactor: config });
290
+ ```
291
+
292
+ For free-text fields outside the span pipeline (logs, error messages, frontend payloads), use `createStringRedactor('default')` — same masks, returns a `(s: string) => string`.
293
+
294
+ ---
295
+
296
+ ## Composition (build pipelines from small parts)
297
+
298
+ ```typescript
299
+ import {
300
+ composeSpanProcessors,
301
+ composeSubscribers,
302
+ composePostProcessors,
303
+ defineConfig,
304
+ } from 'autotel-edge';
305
+
306
+ export const otelConfig = defineConfig({
307
+ service: { name: 'checkout' },
308
+ spanProcessors: composeSpanProcessors([
309
+ new BatchSpanProcessor(honeycombExporter),
310
+ new BatchSpanProcessor(datadogExporter),
311
+ new TailSamplingProcessor({ keep: { errors: true, slow: 500 } }),
312
+ ]),
313
+ subscribers: [
314
+ composeSubscribers([metricsSubscriber, auditSubscriber, aiCostSubscriber]),
315
+ ],
316
+ postProcessor: composePostProcessors([
317
+ redactPiiInStackTraces,
318
+ dropHealthChecks,
319
+ ]),
320
+ });
321
+ ```
322
+
323
+ Errors are isolated per item — one bad processor cannot break the others. See [references/processor-pipeline.md](references/processor-pipeline.md) for the full pipeline cookbook.
324
+
325
+ ---
326
+
327
+ ## Processors (autotel-specific superpowers)
328
+
329
+ | Processor | Purpose |
330
+ | ------------------------------ | ----------------------------------------------------------- |
331
+ | `AttributeRedactingProcessor` | PII masking with smart partial output |
332
+ | `TailSamplingProcessor` | Keep errors + slow + sampled-by-rate |
333
+ | `FilteringSpanProcessor` | Drop spans matching predicate (health checks, etc.) |
334
+ | `SpanNameNormalizingProcessor` | Normalise `/users/123` → `/users/{id}` to bound cardinality |
335
+ | `BaggageSpanProcessor` | Lift baggage entries onto every span |
336
+ | `PrettyConsoleExporter` | Hierarchical colourised output for local dev |
337
+
338
+ Compose them at build time with `composeSpanProcessors([...])` — no boilerplate.
339
+
340
+ ---
341
+
342
+ ## AI SDK integration (gen-ai semantic conventions)
343
+
344
+ autotel implements the **OTel gen-ai semantic conventions** out of the box. Token usage, tool calls, model info, latency, cost — captured as standard attributes (`gen_ai.usage.input_tokens`, `gen_ai.tool.name`, `gen_ai.response.finish_reason`, …) so any backend that understands OTel can render LLM telemetry without custom mapping.
345
+
346
+ ```typescript
347
+ import { trace } from 'autotel';
348
+ import { withAiTelemetry } from 'autotel-edge';
349
+ import { streamText } from 'ai';
350
+
351
+ const handler = trace(async (req) => {
352
+ const result = await streamText({
353
+ model: withAiTelemetry('anthropic/claude-sonnet-4.6'),
354
+ messages: req.messages,
355
+ experimental_telemetry: { isEnabled: true },
356
+ });
357
+ return result.toResponse();
358
+ });
359
+ ```
360
+
361
+ Captured attributes per call: `gen_ai.system`, `gen_ai.request.model`, `gen_ai.usage.{input,output,reasoning,cache_read}_tokens`, `gen_ai.response.finish_reason`, `gen_ai.response.id`, plus per-tool spans with `gen_ai.tool.name`, `gen_ai.tool.duration`. Cost estimation comes for free if you pass a pricing map to `withAiTelemetry`.
362
+
363
+ Anti-patterns to detect:
364
+
365
+ | Anti-pattern | Fix |
366
+ | ------------------------------------- | --------------------------------------------------------- |
367
+ | Manual `result.usage` printing | `withAiTelemetry()` — captures via middleware |
368
+ | Custom `ai.tokens` attribute names | Use OTel gen-ai conventions (`gen_ai.usage.input_tokens`) |
369
+ | Tool calls as plain log lines | Each tool call gets a child span automatically |
370
+ | No retry / partial-failure visibility | `experimental_telemetry: { isEnabled: true }` flips it on |
371
+
372
+ ---
373
+
374
+ ## Structured errors
375
+
376
+ Throw rich errors that carry status, audience, and remediation hints — and consume them at HTTP boundaries:
377
+
378
+ ```typescript
379
+ import { createStructuredError, parseError } from 'autotel';
380
+
381
+ throw createStructuredError({
382
+ message: 'Payment declined',
383
+ status: 402,
384
+ why: 'Card declined by issuer — insufficient funds',
385
+ fix: 'Use a different payment method or contact your bank',
386
+ link: 'https://docs.example.com/payments/declined',
387
+ internal: { correlationId: 'req_abc', resourceId: 'cust_123' },
388
+ });
389
+
390
+ // at the HTTP boundary
391
+ app.onError((error, c) => {
392
+ const parsed = parseError(error);
393
+ // `internal` is stripped from `parsed` — never returned to clients
394
+ return c.json(parsed, parsed.status);
395
+ });
396
+ ```
397
+
398
+ `createStructuredError` records the error onto the active span automatically (`exception.type`, `exception.message`, `exception.stacktrace`) and sets `span.status = ERROR`.
399
+
400
+ See [references/structured-errors.md](references/structured-errors.md) for templates.
401
+
402
+ ---
403
+
404
+ ## Testing your instrumentation
405
+
406
+ ### Unit tests (in-memory exporter)
407
+
408
+ ```typescript
409
+ import { InMemorySpanExporter } from 'autotel/exporters';
410
+ import { SimpleSpanProcessor } from 'autotel/processors';
411
+ import { init, trace } from 'autotel';
412
+
413
+ const exporter = new InMemorySpanExporter();
414
+ init({ service: 'test', spanProcessors: [new SimpleSpanProcessor(exporter)] });
415
+
416
+ await trace(async () => {
417
+ // … code under test
418
+ })();
419
+
420
+ const spans = exporter.getFinishedSpans();
421
+ expect(spans).toContainSpan({
422
+ name: 'processOrder',
423
+ attributes: { 'order.id': '123' },
424
+ });
425
+ ```
426
+
427
+ `autotel-vitest` ships a custom matcher (`toContainSpan`) and a `withSpans()` helper.
428
+
429
+ ### End-to-end (real OTLP backend)
430
+
431
+ `packages/autotel/test/e2e/` ships a working OTLP HTTP/JSON smoke test that tags every span with `e2e_run_id` / `e2e_correlation_id` for cleanup, skips gracefully when env vars are missing, and is wired up to a daily GitHub Actions cron. Copy it to test against your own backend (Honeycomb, Grafana Cloud, Datadog, …):
432
+
433
+ ```bash
434
+ pnpm --filter autotel run test:e2e
435
+ ```
436
+
437
+ ### Bundle size guard
438
+
439
+ `scripts/check-bundle-size.mjs` measures every `packages/autotel*/dist` against `bundle-size-baseline.json` and fails CI on growth past 5 % / 2 KiB. Update the baseline only when the growth is intentional.
440
+
441
+ ---
442
+
443
+ ## Anti-patterns to detect
444
+
445
+ | Anti-pattern | Fix |
446
+ | --------------------------------------------------- | ------------------------------------------------------------------- |
447
+ | `console.log` in handlers | Use `useLogger()` — fields land on the active span |
448
+ | Manual `tracer.startSpan` boilerplate | `trace(fn)` — auto-named, auto-ended, auto-status |
449
+ | `try { … } catch (e) { console.error(e); throw e }` | Replace with `createStructuredError({ … })` |
450
+ | `throw new Error('something went wrong')` | `createStructuredError({ message, status, why, fix })` |
451
+ | Ad-hoc `span.setAttribute('user_id', id)` | Use `useLogger().set({ user: { id } })` — flattens with stable keys |
452
+ | Multiple exporters wired in parallel by hand | `composeSpanProcessors([…])` |
453
+ | PII in attributes | `attributeRedactor: 'default'` (on in prod by default) |
454
+ | Cloudflare Workers without `waitUntil` | Use `defineWorkerFetch` / `wrapModule` |
455
+ | High-cardinality span names (`/users/123`) | `SpanNameNormalizingProcessor` |
456
+ | AI SDK token logs | `withAiTelemetry()` + gen-ai semantic conventions |
457
+ | Health checks blowing up trace volume | `FilteringSpanProcessor` |
458
+ | No tests for instrumentation | `InMemorySpanExporter` + `autotel-vitest` matchers |
459
+ | Manual context propagation in fetch | `instrumentation.instrumentGlobalFetch: true` (default) |
460
+
461
+ See [references/code-review.md](references/code-review.md) for the full checklist.
462
+
463
+ ---
464
+
465
+ ## Why autotel beats manually-wired OTel
466
+
467
+ | Concern | Plain `@opentelemetry/sdk-node` | autotel |
468
+ | --------------------- | ---------------------------------------------------- | ----------------------------------------------------- |
469
+ | Setup | Multi-page checklist, vendor-specific resource attrs | One `init()` call, sane defaults |
470
+ | Cloudflare Workers | DIY `waitUntil` (logs/spans drop silently if missed) | `defineWorkerFetch` auto-wires it |
471
+ | PII redaction | DIY span processor | `attributeRedactor: 'default'` (smart masks built in) |
472
+ | High-cardinality URLs | DIY span name munging | `SpanNameNormalizingProcessor` |
473
+ | Multi-backend | Hand-write a tee processor | `composeSpanProcessors([…])` |
474
+ | Local debugging | Manual `ConsoleSpanExporter` plumbing | `init({ debug: 'pretty' })` |
475
+ | AI SDK | Custom attributes, vendor-specific dashboards | OTel gen-ai semconv out of the box |
476
+ | Bundle size | Unbounded | CI guard with `bundle-size-baseline.json` |
477
+ | Real-backend tests | DIY | `pnpm test:e2e` ships a working OTLP smoke test |
478
+
479
+ ---
480
+
481
+ ## Loading reference files
482
+
483
+ Load based on what you're working on — **do not load all at once**:
484
+
485
+ - Designing spans → [references/wide-spans.md](references/wide-spans.md)
486
+ - Improving errors → [references/structured-errors.md](references/structured-errors.md)
487
+ - Full code review → [references/code-review.md](references/code-review.md)
488
+ - Pipeline / processors → [references/processor-pipeline.md](references/processor-pipeline.md)
@@ -0,0 +1,75 @@
1
+ # OpenTelemetry code review checklist
2
+
3
+ Run through this list when adding observability to a new service or auditing an existing one.
4
+
5
+ ## Setup
6
+
7
+ - [ ] **Service name set.** `service.name` resource attribute is unique per deployable unit (not just `"app"`).
8
+ - [ ] **OTLP endpoint configured** via `OTLP_ENDPOINT` env var. Don't hard-code per-vendor URLs.
9
+ - [ ] **No global SDK init in handlers.** Init runs once at module load (Next.js `instrumentation.ts`, Workers top of module, Node `--require`).
10
+ - [ ] **Production redaction on.** `attributeRedactor: 'default'` (or `'strict'` / `'pci-dss'`) — autotel turns it on automatically when `NODE_ENV === 'production'`.
11
+ - [ ] **Sampling configured.** Don't ship 100% in production unless volume is genuinely tiny. `sampling.rates: { server: 25 }` is a reasonable starting point.
12
+
13
+ ## Spans
14
+
15
+ - [ ] **One wide span per logical unit of work.** Avoid hundreds of trivial spans.
16
+ - [ ] **Span names are low-cardinality.** `/users/:id`, not `/users/123`. Use `SpanNameNormalizingProcessor` if necessary.
17
+ - [ ] **No raw bodies in attributes.** Pick fields explicitly: `{ user: { id, plan } }` not `{ user: requestBody }`.
18
+ - [ ] **Status is set on errors.** `createStructuredError` does it; manual throws should set `span.status = ERROR`.
19
+ - [ ] **Context propagation works end-to-end.** W3C `traceparent` headers on outbound requests (autotel's global fetch instrumentation handles this).
20
+ - [ ] **Cross-service propagation works.** Browser → server, server → service, server → queue worker.
21
+
22
+ ## Cloudflare Workers
23
+
24
+ - [ ] **`defineWorkerFetch` (or `wrapModule`).** Async drains drop silently if `ctx.waitUntil` isn't wired.
25
+ - [ ] **No SDK calls inside `addEventListener`.** Use the module worker style.
26
+ - [ ] **Bindings instrumented.** `instrumentBindings: true` to get spans for KV / R2 / D1 / Service Bindings.
27
+ - [ ] **Durable Objects wrapped.** `wrapDurableObject` for transactional state.
28
+
29
+ ## Logs (structured events)
30
+
31
+ - [ ] **`useLogger().set({ … })` instead of `console.log`.** Logger fields land on the active span automatically.
32
+ - [ ] **No PII in attributes** (or rely on redactor). Don't log raw email / cards / phones / JWTs.
33
+ - [ ] **Group fields with objects.** `{ user: { id, plan } }`, not flat `userId` / `userPlan`.
34
+ - [ ] **Decisions captured.** Which branch, which fallback, which feature flag — not just inputs.
35
+ - [ ] **Background work uses `log.fork()`** — gets its own span and `_parentCorrelationId` for correlation.
36
+
37
+ ## Errors
38
+
39
+ - [ ] **`createStructuredError({ message, status, why, fix })`.** No bare `new Error('…')`.
40
+ - [ ] **`parseError()` at HTTP boundaries.** `internal` is stripped; clients only see safe fields.
41
+ - [ ] **Validation errors include `details`.** Per-field problems for the client to render.
42
+ - [ ] **No double-logging.** Don't `console.error(e); throw e`; the span will pick it up.
43
+
44
+ ## Metrics
45
+
46
+ - [ ] **Use OTel meter API**, not service-specific clients.
47
+ - [ ] **Counters for events, histograms for durations, gauges for snapshots** — match the right type.
48
+ - [ ] **Bounded label cardinality.** Don't put `userId` on a metric label.
49
+
50
+ ## AI / LLM
51
+
52
+ - [ ] **`withAiTelemetry()` from `autotel-edge`.** Captures `gen_ai.*` semantic attributes automatically.
53
+ - [ ] **No bespoke `ai.tokens` attributes.** Use `gen_ai.usage.input_tokens`, etc.
54
+ - [ ] **Cost tracking via the `cost` option** — outputs `gen_ai.cost.usd` on the span.
55
+ - [ ] **Tool-call spans enabled.** `experimental_telemetry: { isEnabled: true }`.
56
+
57
+ ## Testing
58
+
59
+ - [ ] **`InMemorySpanExporter` in unit tests.** Assert spans + attributes.
60
+ - [ ] **`autotel-vitest` matchers** (`toContainSpan`, `withSpans()`).
61
+ - [ ] **One e2e smoke test against a real OTLP backend.** `pnpm test:e2e` template ships in `packages/autotel/test/e2e/`.
62
+ - [ ] **Bundle-size baseline.** `pnpm bundle-size` runs on PRs; commit the baseline only on intentional growth.
63
+
64
+ ## Pipeline / processors
65
+
66
+ - [ ] **One source of composition.** `composeSpanProcessors` / `composeSubscribers` / `composePostProcessors` over hand-rolled tee logic.
67
+ - [ ] **Tail sampling for noisy services.** Keep errors + slow + a sampled %.
68
+ - [ ] **Filter health checks.** `FilteringSpanProcessor({ exclude: ['/healthz', '/ready'] })`.
69
+ - [ ] **Baggage for cross-cutting context.** `BaggageSpanProcessor` lifts `feature_flags`, `tenant`, etc. onto every span automatically.
70
+
71
+ ## Secrets
72
+
73
+ - [ ] **No exporter tokens in client bundles.** Server-only env vars.
74
+ - [ ] **No span attributes containing secrets.** Even with redaction on, prefer not to capture them at all.
75
+ - [ ] **CI runs e2e with secret-protected workflow** (label `e2e`, never on fork PRs).