Skip to content

Commit 01a04ab

Browse files
authored
fix(deno): Avoid inferring invalid span op from Deno tracer (#20128)
fix(deno): Avoid inferring invalid span op from Deno tracer
1 parent 45d624f commit 01a04ab

File tree

3 files changed

+33
-12
lines changed

3 files changed

+33
-12
lines changed

dev-packages/e2e-tests/test-applications/deno/tests/ai.test.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,27 @@ test('should create AI pipeline spans with Vercel AI SDK', async ({ baseURL }) =
2828
// Due to the AI SDK monkey-patching limitation (https://github.com/vercel/ai/pull/6716),
2929
// only explicitly opted-in calls produce telemetry spans.
3030
// The explicitly enabled call (experimental_telemetry: { isEnabled: true }) should produce spans.
31-
const aiSpans = spans.filter(
32-
(span: any) =>
31+
const aiSpans = spans.filter((span: any) => {
32+
if (
3333
span.op === 'gen_ai.invoke_agent' ||
3434
span.op === 'gen_ai.generate_content' ||
35-
span.op === 'otel.span' ||
36-
span.description?.includes('ai.generateText'),
37-
);
35+
span.op === 'gen_ai.execute_tool'
36+
) {
37+
return true;
38+
}
39+
// Processed Vercel AI spans (incl. cases where OTel kind no longer maps to a generic `op`)
40+
if (span.origin === 'auto.vercelai.otel') {
41+
return true;
42+
}
43+
// Raw Vercel AI OTel span names / attributes before or without full Sentry mapping
44+
if (typeof span.description === 'string' && span.description.startsWith('ai.')) {
45+
return true;
46+
}
47+
if (span.data?.['ai.operationId'] != null || span.data?.['ai.pipeline.name'] != null) {
48+
return true;
49+
}
50+
return false;
51+
});
3852

3953
// We expect at least one AI-related span from the explicitly enabled call
4054
expect(aiSpans.length).toBeGreaterThanOrEqual(1);

dev-packages/e2e-tests/test-applications/deno/tests/transactions.test.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,15 @@ test('Sends transaction with OTel tracer.startSpan despite pre-existing provider
3333
expect.arrayContaining([
3434
expect.objectContaining({
3535
description: 'test-otel-span',
36-
op: 'otel.span',
3736
origin: 'manual',
3837
}),
3938
]),
4039
);
40+
41+
const otelSpan = transaction.spans!.find((s: any) => s.description === 'test-otel-span');
42+
expect(otelSpan).toBeDefined();
43+
// INTERNAL (and other unmapped) kinds must not get a synthetic `otel.span` op
44+
expect(otelSpan!.op).toBeUndefined();
4145
});
4246

4347
test('Sends transaction with OTel tracer.startActiveSpan', async ({ baseURL }) => {
@@ -53,11 +57,14 @@ test('Sends transaction with OTel tracer.startActiveSpan', async ({ baseURL }) =
5357
expect.arrayContaining([
5458
expect.objectContaining({
5559
description: 'test-otel-active-span',
56-
op: 'otel.span',
5760
origin: 'manual',
5861
}),
5962
]),
6063
);
64+
65+
const otelSpan = transaction.spans!.find((s: any) => s.description === 'test-otel-active-span');
66+
expect(otelSpan).toBeDefined();
67+
expect(otelSpan!.op).toBeUndefined();
6168
});
6269

6370
test('OTel span appears as child of Sentry span (interop)', async ({ baseURL }) => {
@@ -77,7 +84,6 @@ test('OTel span appears as child of Sentry span (interop)', async ({ baseURL })
7784
}),
7885
expect.objectContaining({
7986
description: 'otel-child',
80-
op: 'otel.span',
8187
origin: 'manual',
8288
}),
8389
]),
@@ -87,4 +93,5 @@ test('OTel span appears as child of Sentry span (interop)', async ({ baseURL })
8793
const sentrySpan = transaction.spans!.find((s: any) => s.description === 'sentry-parent');
8894
const otelSpan = transaction.spans!.find((s: any) => s.description === 'otel-child');
8995
expect(otelSpan!.parent_span_id).toBe(sentrySpan!.span_id);
96+
expect(otelSpan!.op).toBeUndefined();
9097
});

packages/deno/src/opentelemetry/tracer.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class SentryDenoTracer implements Tracer {
4343
attributes: {
4444
...options?.attributes,
4545
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'manual',
46-
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,
46+
...(op ? { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: op } : {}),
4747
'sentry.deno_tracer': true,
4848
},
4949
});
@@ -77,7 +77,7 @@ class SentryDenoTracer implements Tracer {
7777
attributes: {
7878
...opts.attributes,
7979
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'manual',
80-
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,
80+
...(op ? { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: op } : {}),
8181
'sentry.deno_tracer': true,
8282
},
8383
};
@@ -96,7 +96,7 @@ class SentryDenoTracer implements Tracer {
9696
return startSpanManual(spanOpts, callback) as ReturnType<F>;
9797
}
9898

99-
private _mapSpanKindToOp(kind?: SpanKind): string {
99+
private _mapSpanKindToOp(kind?: SpanKind): string | undefined {
100100
switch (kind) {
101101
case SpanKind.CLIENT:
102102
return 'http.client';
@@ -107,7 +107,7 @@ class SentryDenoTracer implements Tracer {
107107
case SpanKind.CONSUMER:
108108
return 'message.consume';
109109
default:
110-
return 'otel.span';
110+
return undefined;
111111
}
112112
}
113113
}

0 commit comments

Comments
 (0)