autotel 5.0.0 → 6.0.0
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.
- package/dist/auto.cjs +1 -1
- package/dist/auto.js +1 -1
- package/dist/correlation-id.cjs +9 -9
- package/dist/correlation-id.js +1 -1
- package/dist/decorators.cjs +2 -2
- package/dist/decorators.cjs.map +1 -1
- package/dist/decorators.js +1 -1
- package/dist/event.cjs +4 -4
- package/dist/event.js +1 -1
- package/dist/{functional-Bit6noRu.js → functional-BnDUxnSl.js} +2 -4
- package/dist/{functional-Bit6noRu.js.map → functional-BnDUxnSl.js.map} +1 -1
- package/dist/{functional-Cj7PSItS.cjs → functional-DcLBuCPz.cjs} +12 -14
- package/dist/{functional-Cj7PSItS.cjs.map → functional-DcLBuCPz.cjs.map} +1 -1
- package/dist/functional.cjs +1 -1
- package/dist/functional.js +1 -1
- package/dist/http.cjs +2 -2
- package/dist/http.cjs.map +1 -1
- package/dist/http.js +1 -1
- package/dist/index.cjs +23 -164
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -145
- package/dist/index.js.map +1 -1
- package/dist/init-C3RApHKJ.cjs +3371 -0
- package/dist/init-C3RApHKJ.cjs.map +1 -0
- package/dist/{init-CTyqG9qR.d.ts → init-D6n2diBy.d.ts} +21 -2
- package/dist/init-D6n2diBy.d.ts.map +1 -0
- package/dist/init-R-gSEXkJ.js +3190 -0
- package/dist/init-R-gSEXkJ.js.map +1 -0
- package/dist/{init-a4yPDYqC.d.cts → init-XzXzrarP.d.cts} +21 -2
- package/dist/init-XzXzrarP.d.cts.map +1 -0
- package/dist/instrumentation.cjs +1 -1
- package/dist/instrumentation.js +1 -1
- package/dist/messaging.cjs +1 -1
- package/dist/messaging.js +1 -1
- package/dist/semantic-helpers.cjs +1 -1
- package/dist/semantic-helpers.js +1 -1
- package/dist/webhook.cjs +1 -1
- package/dist/webhook.js +1 -1
- package/dist/workflow-distributed.cjs +1 -1
- package/dist/workflow-distributed.js +1 -1
- package/dist/workflow.cjs +1 -1
- package/dist/workflow.js +1 -1
- package/dist/yaml-config.d.cts +1 -1
- package/dist/yaml-config.d.ts +1 -1
- package/package.json +2 -3
- package/dist/event-CwIpFdoY.cjs +0 -778
- package/dist/event-CwIpFdoY.cjs.map +0 -1
- package/dist/event-pLMF5b3d.js +0 -761
- package/dist/event-pLMF5b3d.js.map +0 -1
- package/dist/init-B2uvZReD.cjs +0 -1120
- package/dist/init-B2uvZReD.cjs.map +0 -1
- package/dist/init-CTyqG9qR.d.ts.map +0 -1
- package/dist/init-CdEWjhvl.js +0 -1043
- package/dist/init-CdEWjhvl.js.map +0 -1
- package/dist/init-a4yPDYqC.d.cts.map +0 -1
- package/dist/operation-context-CKBoA4Qy.js +0 -59
- package/dist/operation-context-CKBoA4Qy.js.map +0 -1
- package/dist/operation-context-hzKbTKnt.cjs +0 -72
- package/dist/operation-context-hzKbTKnt.cjs.map +0 -1
- package/dist/track-BBcxEJEC.js +0 -1110
- package/dist/track-BBcxEJEC.js.map +0 -1
- package/dist/track-BI6dmZ2X.cjs +0 -1207
- package/dist/track-BI6dmZ2X.cjs.map +0 -1
- package/skills/analyze-traces/SKILL.md +0 -209
- package/skills/autotel-core/SKILL.md +0 -166
- package/skills/autotel-events/SKILL.md +0 -84
- package/skills/autotel-frameworks/SKILL.md +0 -125
- package/skills/autotel-instrumentation/SKILL.md +0 -150
- package/skills/autotel-request-logging/SKILL.md +0 -99
- package/skills/autotel-structured-errors/SKILL.md +0 -125
- package/skills/build-audit-trails/SKILL.md +0 -460
- package/skills/build-audit-trails/references/audit-queries.md +0 -73
- package/skills/build-audit-trails/references/framework-wiring.md +0 -196
- package/skills/debug-missing-spans/SKILL.md +0 -250
- package/skills/migrate-to-autotel/SKILL.md +0 -269
- package/skills/review-otel-patterns/SKILL.md +0 -540
- package/skills/review-otel-patterns/references/code-review.md +0 -76
- package/skills/review-otel-patterns/references/processor-pipeline.md +0 -205
- package/skills/review-otel-patterns/references/structured-errors.md +0 -102
- package/skills/review-otel-patterns/references/wide-spans.md +0 -85
- package/skills/tune-sampling/SKILL.md +0 -290
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
# Framework wiring for audit trails
|
|
2
|
-
|
|
3
|
-
Each handler below records an authorization decision (allow and deny) and emits one audit span. They assume the `audit()` / `withAuthz()` helpers from Step 1–2 of the skill, or the `withAudit` helper from `autotel-audit`. Keep the audit call inside the traced request so it inherits the trace context.
|
|
4
|
-
|
|
5
|
-
The rule across every framework is the same: wrap the authorization decision so both branches reach the audit pipeline, and never put raw payloads on the span.
|
|
6
|
-
|
|
7
|
-
## Next.js (App Router)
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
// app/admin/users/[id]/route.ts
|
|
11
|
-
import { withAuthz } from '@/lib/audit';
|
|
12
|
-
|
|
13
|
-
export async function DELETE(
|
|
14
|
-
req: Request,
|
|
15
|
-
{ params }: { params: { id: string } },
|
|
16
|
-
) {
|
|
17
|
-
return withAuthz(
|
|
18
|
-
{
|
|
19
|
-
action: 'user.delete',
|
|
20
|
-
resource: { type: 'user', id: params.id },
|
|
21
|
-
actor: { id: req.headers.get('x-user-id')!, role: 'admin' },
|
|
22
|
-
},
|
|
23
|
-
async () => ({ allow: await canDelete(req, params.id) }),
|
|
24
|
-
async () => {
|
|
25
|
-
await db.user.delete({ where: { id: params.id } });
|
|
26
|
-
return Response.json({ ok: true });
|
|
27
|
-
},
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Nuxt / Nitro
|
|
33
|
-
|
|
34
|
-
```typescript
|
|
35
|
-
// server/api/secrets/[id].delete.ts
|
|
36
|
-
import { withAuthz } from '~/server/utils/audit';
|
|
37
|
-
|
|
38
|
-
export default defineEventHandler((event) =>
|
|
39
|
-
withAuthz(
|
|
40
|
-
{
|
|
41
|
-
action: 'secret.delete',
|
|
42
|
-
resource: { type: 'secret', id: getRouterParam(event, 'id')! },
|
|
43
|
-
actor: { id: event.context.user.id, role: event.context.user.role },
|
|
44
|
-
},
|
|
45
|
-
async () => ({ allow: await canManageSecret(event) }),
|
|
46
|
-
async () => {
|
|
47
|
-
await secrets.delete(getRouterParam(event, 'id')!);
|
|
48
|
-
return { ok: true };
|
|
49
|
-
},
|
|
50
|
-
),
|
|
51
|
-
);
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## NestJS
|
|
55
|
-
|
|
56
|
-
Wrap the audited work inside the service or an interceptor. The decision lives next to the business logic:
|
|
57
|
-
|
|
58
|
-
```typescript
|
|
59
|
-
@Injectable()
|
|
60
|
-
export class UsersService {
|
|
61
|
-
async remove(id: string, actor: Actor) {
|
|
62
|
-
return withAuthz(
|
|
63
|
-
{ action: 'user.delete', resource: { type: 'user', id }, actor },
|
|
64
|
-
async () => ({ allow: actor.role === 'admin' }),
|
|
65
|
-
async () => this.repo.delete(id),
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
## Express
|
|
72
|
-
|
|
73
|
-
```typescript
|
|
74
|
-
import { withAuthz } from './audit';
|
|
75
|
-
|
|
76
|
-
app.delete('/admin/users/:id', async (req, res, next) => {
|
|
77
|
-
try {
|
|
78
|
-
await withAuthz(
|
|
79
|
-
{
|
|
80
|
-
action: 'user.delete',
|
|
81
|
-
resource: { type: 'user', id: req.params.id },
|
|
82
|
-
actor: { id: req.user.id, role: req.user.role },
|
|
83
|
-
},
|
|
84
|
-
async () => ({ allow: req.user.role === 'admin' }),
|
|
85
|
-
async () => db.user.delete({ where: { id: req.params.id } }),
|
|
86
|
-
);
|
|
87
|
-
res.json({ ok: true });
|
|
88
|
-
} catch (err) {
|
|
89
|
-
next(err);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
## Fastify
|
|
95
|
-
|
|
96
|
-
```typescript
|
|
97
|
-
import { withAuthz } from './audit';
|
|
98
|
-
|
|
99
|
-
fastify.delete('/admin/users/:id', async (request, reply) => {
|
|
100
|
-
await withAuthz(
|
|
101
|
-
{
|
|
102
|
-
action: 'user.delete',
|
|
103
|
-
resource: { type: 'user', id: (request.params as { id: string }).id },
|
|
104
|
-
actor: { id: request.user.id, role: request.user.role },
|
|
105
|
-
},
|
|
106
|
-
async () => ({ allow: request.user.role === 'admin' }),
|
|
107
|
-
async () =>
|
|
108
|
-
db.user.delete({ where: { id: (request.params as { id: string }).id } }),
|
|
109
|
-
);
|
|
110
|
-
return { ok: true };
|
|
111
|
-
});
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
## Hono
|
|
115
|
-
|
|
116
|
-
```typescript
|
|
117
|
-
import { withAuthz } from './audit';
|
|
118
|
-
|
|
119
|
-
app.post('/secrets/:id/read', (c) =>
|
|
120
|
-
withAuthz(
|
|
121
|
-
{
|
|
122
|
-
action: 'secret.read',
|
|
123
|
-
resource: { type: 'secret', id: c.req.param('id') },
|
|
124
|
-
actor: { id: c.var.user.id, role: c.var.user.role },
|
|
125
|
-
},
|
|
126
|
-
() => requireScope(c, 'secrets:read'),
|
|
127
|
-
async () => c.json({ value: await secrets.read(c.req.param('id')) }),
|
|
128
|
-
),
|
|
129
|
-
);
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
## Cloudflare Workers
|
|
133
|
-
|
|
134
|
-
Audit from inside `defineWorkerFetch` so `ctx.waitUntil` exports the audit span before the response returns:
|
|
135
|
-
|
|
136
|
-
```typescript
|
|
137
|
-
import { defineWorkerFetch } from 'autotel-cloudflare';
|
|
138
|
-
import { withAuthz } from './audit';
|
|
139
|
-
|
|
140
|
-
export default defineWorkerFetch(
|
|
141
|
-
{ service: { name: 'admin-api' } },
|
|
142
|
-
async (request, env, ctx, log) =>
|
|
143
|
-
withAuthz(
|
|
144
|
-
{
|
|
145
|
-
action: 'data.export',
|
|
146
|
-
resource: { type: 'project', id: 'p_123' },
|
|
147
|
-
actor: { id: 'usr_42' },
|
|
148
|
-
},
|
|
149
|
-
async () => ({ allow: true }),
|
|
150
|
-
async () => Response.json({ ok: true }),
|
|
151
|
-
),
|
|
152
|
-
);
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
## AWS Lambda
|
|
156
|
-
|
|
157
|
-
Wrap the traced handler; the audit span is flushed when the handler settles:
|
|
158
|
-
|
|
159
|
-
```typescript
|
|
160
|
-
import { withAuthz } from './audit';
|
|
161
|
-
|
|
162
|
-
export const handler = async (event: APIGatewayProxyEventV2) =>
|
|
163
|
-
withAuthz(
|
|
164
|
-
{
|
|
165
|
-
action: 'invoice.void',
|
|
166
|
-
resource: { type: 'invoice', id: event.pathParameters!.id! },
|
|
167
|
-
actor: { id: event.requestContext.authorizer!.userId },
|
|
168
|
-
},
|
|
169
|
-
async () => ({ allow: await canVoid(event) }),
|
|
170
|
-
async () => {
|
|
171
|
-
await invoices.void(event.pathParameters!.id!);
|
|
172
|
-
return { statusCode: 200, body: JSON.stringify({ ok: true }) };
|
|
173
|
-
},
|
|
174
|
-
);
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
## Standalone scripts and cron jobs
|
|
178
|
-
|
|
179
|
-
Outside a request there is no ambient trace context. Wrap the job in `trace()` first so the audit span has a parent, and set the actor to the system principal:
|
|
180
|
-
|
|
181
|
-
```typescript
|
|
182
|
-
import { trace } from 'autotel';
|
|
183
|
-
import { withAudit } from 'autotel-audit';
|
|
184
|
-
|
|
185
|
-
export const nightlyPurge = trace(async function nightlyPurge() {
|
|
186
|
-
await withAudit(
|
|
187
|
-
{
|
|
188
|
-
action: 'data.purge',
|
|
189
|
-
resource: 'expired-sessions',
|
|
190
|
-
actorId: 'system:cron',
|
|
191
|
-
category: 'maintenance',
|
|
192
|
-
},
|
|
193
|
-
async () => sessions.purgeExpired(),
|
|
194
|
-
);
|
|
195
|
-
});
|
|
196
|
-
```
|
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: debug-missing-spans
|
|
3
|
-
description: >
|
|
4
|
-
Troubleshoot when expected OpenTelemetry spans don't reach the backend.
|
|
5
|
-
Walks the chain top-to-bottom — code → SDK init → processor → exporter →
|
|
6
|
-
network → backend ingest — with concrete tests at each step. Covers head
|
|
7
|
-
sampling, ctx.waitUntil drops on Cloudflare, init-order races, runtime
|
|
8
|
-
detection failures, propagation breaks, exporter auth errors, and
|
|
9
|
-
silent ratelimits.
|
|
10
|
-
license: MIT
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# Debug missing spans
|
|
14
|
-
|
|
15
|
-
When a span you expect isn't in the backend, the cause is somewhere in this chain:
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
code → SDK init → head sampler → processor → exporter → network → backend ingest → backend index
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
This skill walks each link in order with a quick check you can run. Don't skip steps. The cause is rarely where you'd guess.
|
|
22
|
-
|
|
23
|
-
## Step 0: Reproduce locally with the pretty exporter
|
|
24
|
-
|
|
25
|
-
Before chasing remote backends, confirm the span exists at all:
|
|
26
|
-
|
|
27
|
-
```typescript
|
|
28
|
-
init({
|
|
29
|
-
service: 'my-app',
|
|
30
|
-
debug: 'pretty', // hierarchical colourised output to stdout
|
|
31
|
-
});
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
If you see the span in stdout, the SDK + sampler are fine. Skip to "exporter / network". If you don't, keep reading.
|
|
35
|
-
|
|
36
|
-
## Step 1: Is the SDK actually initialised?
|
|
37
|
-
|
|
38
|
-
Common failure: `init()` runs after the first request because of import-order.
|
|
39
|
-
|
|
40
|
-
```typescript
|
|
41
|
-
import { trace } from '@opentelemetry/api';
|
|
42
|
-
|
|
43
|
-
const tracer = trace.getTracer('autotel-debug');
|
|
44
|
-
console.log(
|
|
45
|
-
'[autotel-debug] tracer is no-op:',
|
|
46
|
-
tracer.constructor.name === 'NoopTracer',
|
|
47
|
-
);
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
If `true`, `init()` ran too late. Move it to the very top of the entry file (or to `instrumentation.ts` for Next.js).
|
|
51
|
-
|
|
52
|
-
## Step 2: Head sampler
|
|
53
|
-
|
|
54
|
-
Print the effective head rate:
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
import { getActiveConfig } from 'autotel-edge';
|
|
58
|
-
console.log('[autotel-debug] sampling:', getActiveConfig()?.sampling);
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
Common gotchas:
|
|
62
|
-
|
|
63
|
-
- `sampling.rates: { server: 5 }`: 5 % means 95 % of spans never start.
|
|
64
|
-
- Inheriting `OTEL_TRACES_SAMPLER_ARG=0.01` from the environment via the OTel default sampler.
|
|
65
|
-
- Your test happens to hit the unsampled branch: instrument with `sampling: { rates: { server: 100 } }` while reproducing.
|
|
66
|
-
|
|
67
|
-
To force sampling for one request, send a `traceparent` with the sampled flag set:
|
|
68
|
-
|
|
69
|
-
```
|
|
70
|
-
traceparent: 00-<traceid>-<spanid>-01
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
(`-01` at the end = sampled.) autotel's parent-based sampler will respect it.
|
|
74
|
-
|
|
75
|
-
## Step 3: Cloudflare Workers: `ctx.waitUntil`
|
|
76
|
-
|
|
77
|
-
The single biggest cause of missing spans on the edge: **the response returned before the exporter flushed**.
|
|
78
|
-
|
|
79
|
-
If you're using `addEventListener('fetch', …)` or a hand-rolled `fetch` in a module worker without wiring `ctx.waitUntil(…)` to the export call, async drains drop silently.
|
|
80
|
-
|
|
81
|
-
Fix. Switch to `defineWorkerFetch` or `wrapModule`, both of which wire `waitUntil` automatically:
|
|
82
|
-
|
|
83
|
-
```typescript
|
|
84
|
-
import { defineWorkerFetch } from 'autotel-cloudflare';
|
|
85
|
-
|
|
86
|
-
export default defineWorkerFetch(
|
|
87
|
-
{ service: { name: 'edge' } },
|
|
88
|
-
async (request, env, ctx, log) => {
|
|
89
|
-
// log.set / spans here all flush via ctx.waitUntil before response returns
|
|
90
|
-
return new Response('ok');
|
|
91
|
-
},
|
|
92
|
-
);
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## Step 4: Processor pipeline
|
|
96
|
-
|
|
97
|
-
Print what's wired:
|
|
98
|
-
|
|
99
|
-
```typescript
|
|
100
|
-
import { trace } from '@opentelemetry/api';
|
|
101
|
-
const provider = trace.getTracerProvider();
|
|
102
|
-
console.log('[autotel-debug] provider:', provider.constructor.name);
|
|
103
|
-
console.log(
|
|
104
|
-
'[autotel-debug] processors:',
|
|
105
|
-
(provider as any)._registeredSpanProcessors?.map(
|
|
106
|
-
(p: any) => p.constructor.name,
|
|
107
|
-
),
|
|
108
|
-
);
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
Common issues:
|
|
112
|
-
|
|
113
|
-
- **A `FilteringSpanProcessor` excludes your span.** Check the `include` / `exclude` predicates.
|
|
114
|
-
- **A `TailSamplingProcessor` dropped the trace** (no error, no slow root, no debug header).
|
|
115
|
-
- **A `composePostProcessors` step returns `[]` for your span.**
|
|
116
|
-
|
|
117
|
-
To bisect, temporarily strip post-processors:
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
init({
|
|
121
|
-
service: 'my-app',
|
|
122
|
-
endpoint: process.env.OTLP_ENDPOINT!,
|
|
123
|
-
// no postProcessor, no tail sampler, no filter
|
|
124
|
-
});
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
If the span shows up now, add back the processors one at a time.
|
|
128
|
-
|
|
129
|
-
## Step 5: Exporter
|
|
130
|
-
|
|
131
|
-
Tail the SDK's diagnostic log:
|
|
132
|
-
|
|
133
|
-
```typescript
|
|
134
|
-
import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
|
|
135
|
-
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
Look for:
|
|
139
|
-
|
|
140
|
-
```
|
|
141
|
-
@opentelemetry/api: ... OTLPExporter: failed to send 4 traces, status: 401, error: ...
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
Common exporter errors:
|
|
145
|
-
|
|
146
|
-
| Status | Meaning | Fix |
|
|
147
|
-
| ------------- | ------------------------- | ----------------------------------------------------------- |
|
|
148
|
-
| `401` | Bad / missing auth header | Check `OTLP_HEADERS` / vendor token name |
|
|
149
|
-
| `403` | Token has no write scope | Issue a token with the right scope |
|
|
150
|
-
| `404` | Wrong endpoint URL | Check region (`api.honeycomb.io` vs `api.eu1.honeycomb.io`) |
|
|
151
|
-
| `413` | Batch too big | Lower `BatchSpanProcessor` `maxExportBatchSize` |
|
|
152
|
-
| `429` | Rate-limited | Reduce head/tail rates; honour `retry-after` |
|
|
153
|
-
| `502/503/504` | Upstream unhealthy | Often transient; add retries; check backend status |
|
|
154
|
-
| Network error | DNS / firewall | `curl -v <url>` from the same network |
|
|
155
|
-
|
|
156
|
-
## Step 6: Network / TLS
|
|
157
|
-
|
|
158
|
-
For self-hosted Collectors:
|
|
159
|
-
|
|
160
|
-
```bash
|
|
161
|
-
curl -v -X POST $OTLP_ENDPOINT \
|
|
162
|
-
-H 'content-type: application/json' \
|
|
163
|
-
-H "$AUTH_HEADER" \
|
|
164
|
-
-d '{"resourceSpans":[]}'
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
Should return `200`. If it doesn't, the problem is between you and the Collector. Not autotel.
|
|
168
|
-
|
|
169
|
-
For Cloudflare Workers, run `wrangler tail` and look for `OTLPExporter` errors.
|
|
170
|
-
|
|
171
|
-
## Step 7: Backend ingest: silent rejection
|
|
172
|
-
|
|
173
|
-
Some backends accept the request with a 200 but drop the events:
|
|
174
|
-
|
|
175
|
-
- **Honeycomb**: dataset must exist _and_ the API key must have write access to it. Mismatched key/dataset → silent drop.
|
|
176
|
-
- **Datadog**: check `service` is set (resource attribute `service.name`): they ignore spans without it.
|
|
177
|
-
- **Sentry**: SDK version mismatch on envelope → 200 but events disappear.
|
|
178
|
-
- **Grafana Cloud Tempo**: spans without `service.name` go to a fallback service called `unknown_service`.
|
|
179
|
-
|
|
180
|
-
For each backend, the dataset / index / project where you'd expect the span:
|
|
181
|
-
|
|
182
|
-
| Backend | Where the span lands |
|
|
183
|
-
| ------------- | --------------------------------------- |
|
|
184
|
-
| Honeycomb | dataset = `service.name` (auto-created) |
|
|
185
|
-
| Datadog | `service:<name>` filter |
|
|
186
|
-
| Grafana Tempo | search by `traceId` |
|
|
187
|
-
| Jaeger | service dropdown = `service.name` |
|
|
188
|
-
| Sentry | project linked to the DSN |
|
|
189
|
-
|
|
190
|
-
## Step 8: Backend index lag
|
|
191
|
-
|
|
192
|
-
After a 200, expect ingestion lag of:
|
|
193
|
-
|
|
194
|
-
| Backend | Typical lag |
|
|
195
|
-
| ------------------ | ----------- |
|
|
196
|
-
| Honeycomb | < 5 s |
|
|
197
|
-
| Datadog | 30–60 s |
|
|
198
|
-
| Grafana Tempo | 10–30 s |
|
|
199
|
-
| Sentry | 30–120 s |
|
|
200
|
-
| Self-hosted Jaeger | < 1 s |
|
|
201
|
-
|
|
202
|
-
Don't conclude the span is missing until you've waited > 2× the expected lag.
|
|
203
|
-
|
|
204
|
-
## Step-by-step checklist
|
|
205
|
-
|
|
206
|
-
```
|
|
207
|
-
[ ] Span shows in `debug: 'pretty'` stdout
|
|
208
|
-
[ ] `tracer.constructor.name !== 'NoopTracer'` (SDK initialised)
|
|
209
|
-
[ ] Head rate is high enough to allow the request
|
|
210
|
-
[ ] Workers handler uses defineWorkerFetch / wrapModule
|
|
211
|
-
[ ] No post-processor / tail sampler / filter strips it
|
|
212
|
-
[ ] Exporter logs no 4xx/5xx
|
|
213
|
-
[ ] Curl to OTLP endpoint returns 200
|
|
214
|
-
[ ] Backend has the right service.name / dataset / project
|
|
215
|
-
[ ] Waited 2× expected ingest lag
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
## When the trace partially shows up
|
|
219
|
-
|
|
220
|
-
Some spans land, some don't:
|
|
221
|
-
|
|
222
|
-
- **Trace context broken between services**: outbound HTTP calls aren't propagating `traceparent`. Confirm autotel's global fetch instrumentation is on (`instrumentation.instrumentGlobalFetch: true`, default).
|
|
223
|
-
- **Async boundary loses context**: a `setTimeout` / queue callback ran outside the AsyncLocalStorage scope. Wrap with `trace()` or use `context.with()`.
|
|
224
|
-
- **Cross-runtime call**: Node service → Workers → browser; verify `traceparent` arrives at each leg via response headers / network panel.
|
|
225
|
-
|
|
226
|
-
## When the SDK itself crashes
|
|
227
|
-
|
|
228
|
-
```
|
|
229
|
-
TypeError: Cannot read properties of undefined (reading 'startActiveSpan')
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
Usually means the API version (`@opentelemetry/api`) and SDK version (`@opentelemetry/sdk-trace-base`) drifted. Run:
|
|
233
|
-
|
|
234
|
-
```bash
|
|
235
|
-
pnpm why @opentelemetry/api
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
There should be exactly one resolved version. If there are two, dedup via `pnpm.overrides`.
|
|
239
|
-
|
|
240
|
-
## Anti-patterns to fix as you debug
|
|
241
|
-
|
|
242
|
-
| Anti-pattern | Why it loses spans |
|
|
243
|
-
| --------------------------------------------------------- | ------------------------------------------------------------------ |
|
|
244
|
-
| `init()` after the first import that uses tracing | Spans before `init()` are no-ops |
|
|
245
|
-
| `addEventListener('fetch', …)` on Workers | Pre-module-worker style; no `ctx.waitUntil` to wire |
|
|
246
|
-
| Single `OTLP_ENDPOINT` env var with `?` chars URL-encoded | Auth gets parsed as part of the path |
|
|
247
|
-
| Importing both `@sentry/tracing` and `autotel` | Double-instrumentation eats spans |
|
|
248
|
-
| `process.exit(0)` immediately after the work | The exporter never flushed; call `await provider.shutdown()` first |
|
|
249
|
-
|
|
250
|
-
If spans reach one backend but not another in a fan-out setup, bisect with `destinations`: keep one destination at a time, then re-add the others until the failing backend is isolated.
|