diff --git a/dev-packages/e2e-tests/test-applications/deno/tests/ai.test.ts b/dev-packages/e2e-tests/test-applications/deno/tests/ai.test.ts index d0b824bf9b2f..9d849a33224b 100644 --- a/dev-packages/e2e-tests/test-applications/deno/tests/ai.test.ts +++ b/dev-packages/e2e-tests/test-applications/deno/tests/ai.test.ts @@ -28,13 +28,27 @@ test('should create AI pipeline spans with Vercel AI SDK', async ({ baseURL }) = // Due to the AI SDK monkey-patching limitation (https://github.com/vercel/ai/pull/6716), // only explicitly opted-in calls produce telemetry spans. // The explicitly enabled call (experimental_telemetry: { isEnabled: true }) should produce spans. - const aiSpans = spans.filter( - (span: any) => + const aiSpans = spans.filter((span: any) => { + if ( span.op === 'gen_ai.invoke_agent' || span.op === 'gen_ai.generate_content' || - span.op === 'otel.span' || - span.description?.includes('ai.generateText'), - ); + span.op === 'gen_ai.execute_tool' + ) { + return true; + } + // Processed Vercel AI spans (incl. cases where OTel kind no longer maps to a generic `op`) + if (span.origin === 'auto.vercelai.otel') { + return true; + } + // Raw Vercel AI OTel span names / attributes before or without full Sentry mapping + if (typeof span.description === 'string' && span.description.startsWith('ai.')) { + return true; + } + if (span.data?.['ai.operationId'] != null || span.data?.['ai.pipeline.name'] != null) { + return true; + } + return false; + }); // We expect at least one AI-related span from the explicitly enabled call expect(aiSpans.length).toBeGreaterThanOrEqual(1); diff --git a/dev-packages/e2e-tests/test-applications/deno/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/deno/tests/transactions.test.ts index 3cd0892cebdc..19077bb76b75 100644 --- a/dev-packages/e2e-tests/test-applications/deno/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/deno/tests/transactions.test.ts @@ -33,11 +33,15 @@ test('Sends transaction with OTel tracer.startSpan despite pre-existing provider expect.arrayContaining([ expect.objectContaining({ description: 'test-otel-span', - op: 'otel.span', origin: 'manual', }), ]), ); + + const otelSpan = transaction.spans!.find((s: any) => s.description === 'test-otel-span'); + expect(otelSpan).toBeDefined(); + // INTERNAL (and other unmapped) kinds must not get a synthetic `otel.span` op + expect(otelSpan!.op).toBeUndefined(); }); test('Sends transaction with OTel tracer.startActiveSpan', async ({ baseURL }) => { @@ -53,11 +57,14 @@ test('Sends transaction with OTel tracer.startActiveSpan', async ({ baseURL }) = expect.arrayContaining([ expect.objectContaining({ description: 'test-otel-active-span', - op: 'otel.span', origin: 'manual', }), ]), ); + + const otelSpan = transaction.spans!.find((s: any) => s.description === 'test-otel-active-span'); + expect(otelSpan).toBeDefined(); + expect(otelSpan!.op).toBeUndefined(); }); 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 }) }), expect.objectContaining({ description: 'otel-child', - op: 'otel.span', origin: 'manual', }), ]), @@ -87,4 +93,5 @@ test('OTel span appears as child of Sentry span (interop)', async ({ baseURL }) const sentrySpan = transaction.spans!.find((s: any) => s.description === 'sentry-parent'); const otelSpan = transaction.spans!.find((s: any) => s.description === 'otel-child'); expect(otelSpan!.parent_span_id).toBe(sentrySpan!.span_id); + expect(otelSpan!.op).toBeUndefined(); }); diff --git a/packages/deno/src/opentelemetry/tracer.ts b/packages/deno/src/opentelemetry/tracer.ts index 7bc704446d37..bdd86bde6a8d 100644 --- a/packages/deno/src/opentelemetry/tracer.ts +++ b/packages/deno/src/opentelemetry/tracer.ts @@ -43,7 +43,7 @@ class SentryDenoTracer implements Tracer { attributes: { ...options?.attributes, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'manual', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: op, + ...(op ? { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: op } : {}), 'sentry.deno_tracer': true, }, }); @@ -77,7 +77,7 @@ class SentryDenoTracer implements Tracer { attributes: { ...opts.attributes, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'manual', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: op, + ...(op ? { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: op } : {}), 'sentry.deno_tracer': true, }, }; @@ -96,7 +96,7 @@ class SentryDenoTracer implements Tracer { return startSpanManual(spanOpts, callback) as ReturnType; } - private _mapSpanKindToOp(kind?: SpanKind): string { + private _mapSpanKindToOp(kind?: SpanKind): string | undefined { switch (kind) { case SpanKind.CLIENT: return 'http.client'; @@ -107,7 +107,7 @@ class SentryDenoTracer implements Tracer { case SpanKind.CONSUMER: return 'message.consume'; default: - return 'otel.span'; + return undefined; } } }