Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/deno/src/opentelemetry/tracer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import {
* This is not perfect but handles easy/common use cases.
*/
export function setupOpenTelemetryTracer(): void {
// Clear any pre-existing OTel global registration (e.g. from Supabase Edge Runtime
// or Deno's built-in OTel) so Sentry's TracerProvider gets registered successfully.
trace.disable();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m l: I'm wondering if this backfires for people using Sentry with a custom OTel setup or deliberately with Deno's native tracing (OTLP exporter). The good news is that we don't document this setup for Deno, so I think we can just ignore it for the moment and walk back on this change if anyone complains.

Update: I just saw that we gate this function call with skipOpenTelemetrySetup, so users can opt out of it. That's good. So I guess the worst consequence here is that anyone using native tracing with Sentry might need to set this flag now. Which we can classify as a fix because that's how we intended the SDK to work anyway. Downgraded from logaf M to L

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which we can classify as a fix because that's how we intended the SDK to work anyway.

I think it's actually even more of a fix than that. If someone is using native Deno OTel tracing, then this will register Sentry as the global trace provider. If you're using Sentry in Deno, that's almost certainly what you want to see happen (and what would happen, if nothing else was getting in before our initialization).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds reasonable. Let's roll with it!

trace.setGlobalTracerProvider(new SentryDenoTraceProvider());
}

Expand Down
64 changes: 30 additions & 34 deletions packages/deno/test/opentelemetry.test.ts
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment, no direct action required: I'm fine with these unit tests for now but to be clear these don't prove that the fix works as intended in an actual app. Long-term I'd like us to at least add one e2e app for Deno (or an integration tests setup like for Node) to more reliably verify this. This goes back to my main review comment that I don't think we fully grasped the scope of the current behavior yet. If we had such a test, we could more reliably say that at least some spans are sent.

Original file line number Diff line number Diff line change
Expand Up @@ -144,38 +144,39 @@ Deno.test('opentelemetry spans should interop with Sentry spans', async () => {
assertEquals(otelSpan?.data?.['sentry.origin'], 'manual');
});

Deno.test('should be compatible with native Deno OpenTelemetry', async () => {
Deno.test('should override pre-existing OTel provider with Sentry provider', async () => {
resetSdk();

const providerBefore = trace.getTracerProvider();
// Simulate a pre-existing OTel registration (e.g. from Supabase Edge Runtime)
const fakeProvider = { getTracer: () => ({}) };
trace.setGlobalTracerProvider(fakeProvider as any);

const transactionEvents: any[] = [];

const client = init({
dsn: 'https://username@domain/123',
tracesSampleRate: 1,
beforeSendTransaction: () => null,
beforeSendTransaction: event => {
transactionEvents.push(event);
return null;
},
}) as DenoClient;

// Sentry should have overridden the pre-existing provider via trace.disable()
const providerAfter = trace.getTracerProvider();
assertEquals(providerBefore, providerAfter);
assertNotEquals(providerAfter, fakeProvider);

// Verify Sentry's tracer actually captures spans
const tracer = trace.getTracer('compat-test');
const span = tracer.startSpan('test-span');
span.setAttributes({ 'test.compatibility': true });
span.end();

tracer.startActiveSpan('active-span', activeSpan => {
activeSpan.end();
});

const otelSpan = tracer.startSpan('post-init-span');
otelSpan.end();

startSpan({ name: 'sentry-span' }, () => {
const nestedOtelSpan = tracer.startSpan('nested-otel-span');
nestedOtelSpan.end();
});

await client.flush();

assertEquals(transactionEvents.length, 1);
assertEquals(transactionEvents[0]?.transaction, 'test-span');
assertEquals(transactionEvents[0]?.contexts?.trace?.data?.['sentry.deno_tracer'], true);
});

// Test that name parameter takes precedence over options.name for both startSpan and startActiveSpan
Expand Down Expand Up @@ -238,42 +239,37 @@ Deno.test('name parameter should take precedence over options.name in startActiv
assertEquals(transactionEvent?.transaction, 'prisma:client:operation');
});

Deno.test('should verify native Deno OpenTelemetry works when enabled', async () => {
Deno.test('should override native Deno OpenTelemetry when enabled', async () => {
resetSdk();

// Set environment variable to enable native OTel
const originalValue = Deno.env.get('OTEL_DENO');
Deno.env.set('OTEL_DENO', 'true');

try {
const transactionEvents: any[] = [];

const client = init({
dsn: 'https://username@domain/123',
tracesSampleRate: 1,
beforeSendTransaction: () => null,
beforeSendTransaction: event => {
transactionEvents.push(event);
return null;
},
}) as DenoClient;

const provider = trace.getTracerProvider();
// Sentry's trace.disable() + setGlobalTracerProvider should have overridden
// any native Deno OTel provider, so spans go through Sentry's tracer.
const tracer = trace.getTracer('native-verification');
const span = tracer.startSpan('verification-span');

if (provider.constructor.name === 'Function') {
// Native OTel is active
assertNotEquals(span.constructor.name, 'NonRecordingSpan');

let contextWorks = false;
tracer.startActiveSpan('parent-span', parentSpan => {
if (trace.getActiveSpan() === parentSpan) {
contextWorks = true;
}
parentSpan.end();
});
assertEquals(contextWorks, true);
}

span.setAttributes({ 'test.native_otel': true });
span.end();

await client.flush();

assertEquals(transactionEvents.length, 1);
assertEquals(transactionEvents[0]?.transaction, 'verification-span');
assertEquals(transactionEvents[0]?.contexts?.trace?.data?.['sentry.deno_tracer'], true);
} finally {
// Restore original environment
if (originalValue === undefined) {
Expand Down
Loading