Skip to content

Commit 0d67dbc

Browse files
author
cod1k
committed
Expand setupOpenTelemetryTracer tests and enhance SentryCloudflareTracer error handling for optional tracer usage.
1 parent 7d7d3b9 commit 0d67dbc

File tree

2 files changed

+79
-11
lines changed

2 files changed

+79
-11
lines changed

packages/cloudflare/src/opentelemetry/tracer.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,29 @@
1-
import type { Context, Span, SpanOptions, Tracer, TracerProvider } from '@opentelemetry/api';
2-
import { ProxyTracerProvider, trace } from '@opentelemetry/api';
1+
import type { Context, ProxyTracerProvider, Span, SpanOptions, Tracer, TracerProvider } from '@opentelemetry/api';
2+
import { trace } from '@opentelemetry/api';
33
import { startInactiveSpan, startSpanManual } from '@sentry/core';
44

55
/**
66
* Set up a mock OTEL tracer to allow inter-op with OpenTelemetry emitted spans.
77
* This is not perfect but handles easy/common use cases.
88
*/
99
export function setupOpenTelemetryTracer(): void {
10-
const current = trace.getTracerProvider();
11-
const delegate = current instanceof ProxyTracerProvider ? current.getDelegate() : current;
12-
trace.setGlobalTracerProvider(new SentryCloudflareTraceProvider(delegate));
10+
const result = trace.setGlobalTracerProvider(new SentryCloudflareTraceProvider());
11+
if (result) {
12+
return;
13+
}
14+
const current = trace.getTracerProvider() as ProxyTracerProvider;
15+
current.setDelegate(new SentryCloudflareTraceProvider(current.getDelegate()));
1316
}
1417

1518
class SentryCloudflareTraceProvider implements TracerProvider {
1619
private readonly _tracers: Map<string, Tracer> = new Map();
1720

18-
public constructor(private readonly _provider: TracerProvider) {}
21+
public constructor(private readonly _provider?: TracerProvider) {}
1922

2023
public getTracer(name: string, version?: string, options?: { schemaUrl?: string }): Tracer {
2124
const key = `${name}@${version || ''}:${options?.schemaUrl || ''}`;
2225
if (!this._tracers.has(key)) {
23-
const tracer = this._provider.getTracer(key, version, options);
26+
const tracer = this._provider?.getTracer?.(key, version, options);
2427
this._tracers.set(key, new SentryCloudflareTracer(tracer));
2528
}
2629

@@ -30,9 +33,9 @@ class SentryCloudflareTraceProvider implements TracerProvider {
3033
}
3134

3235
class SentryCloudflareTracer implements Tracer {
33-
public constructor(private readonly _tracer: Tracer) {}
36+
public constructor(private readonly _tracer?: Tracer) {}
3437
public startSpan(name: string, options?: SpanOptions): Span {
35-
const topSpan = this._tracer.startSpan(name, options);
38+
const topSpan = this._tracer?.startSpan?.(name, options);
3639
const sentrySpan = startInactiveSpan({
3740
name,
3841
...options,
@@ -41,7 +44,18 @@ class SentryCloudflareTracer implements Tracer {
4144
'sentry.cloudflare_tracer': true,
4245
},
4346
});
47+
if (!topSpan) {
48+
return sentrySpan;
49+
}
4450
return new Proxy(sentrySpan, {
51+
set: (target, p, newValue, receiver) => {
52+
try {
53+
Reflect.set(topSpan, p, newValue, receiver);
54+
} catch {
55+
//
56+
}
57+
return Reflect.set(target, p, newValue);
58+
},
4559
get: (target, p) => {
4660
const propertyValue = Reflect.get(target, p);
4761
if (typeof propertyValue !== 'function') {
@@ -55,7 +69,7 @@ class SentryCloudflareTracer implements Tracer {
5569
apply: (target, thisArg, argArray) => {
5670
try {
5771
Reflect.apply(proxyTo, topSpan, argArray);
58-
} catch (e) {
72+
} catch {
5973
//
6074
}
6175
return Reflect.apply(target, thisArg, argArray);

packages/cloudflare/test/opentelemetry.test.ts

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { trace } from '@opentelemetry/api';
22
import type { TransactionEvent } from '@sentry/core';
33
import { startSpan } from '@sentry/core';
4-
import { beforeEach, describe, expect, test } from 'vitest';
4+
import { beforeEach, describe, expect, test, vi } from 'vitest';
55
import { init } from '../src/sdk';
66
import { resetSdk } from './testUtils';
77

@@ -132,6 +132,60 @@ describe('opentelemetry compatibility', () => {
132132
'sentry.source': 'custom',
133133
});
134134

135+
expect(transactionEvent?.spans).toEqual([
136+
expect.objectContaining({
137+
description: 'otel span',
138+
data: {
139+
'sentry.cloudflare_tracer': true,
140+
'sentry.origin': 'manual',
141+
},
142+
}),
143+
]);
144+
});
145+
test('Ensure that sentry spans works over other opentelemetry implementations', async () => {
146+
const transactionEvents: TransactionEvent[] = [];
147+
const end = vi.fn();
148+
const _startSpan = vi.fn().mockImplementation(() => ({ end }));
149+
150+
const getTracer = vi.fn().mockImplementation(() => ({
151+
startSpan: _startSpan,
152+
}));
153+
trace.setGlobalTracerProvider({
154+
getTracer,
155+
});
156+
157+
const client = init({
158+
dsn: 'https://username@domain/123',
159+
tracesSampleRate: 1,
160+
beforeSendTransaction: event => {
161+
transactionEvents.push(event);
162+
return null;
163+
},
164+
});
165+
166+
const tracer = trace.getTracer('test');
167+
168+
expect(getTracer).toBeCalledWith('test@:', undefined, undefined);
169+
startSpan({ name: 'sentry span' }, () => {
170+
const span = tracer.startSpan('otel span');
171+
span.end();
172+
});
173+
expect(_startSpan).toBeCalledWith('otel span', undefined);
174+
expect(end).toBeCalled();
175+
176+
await client!.flush();
177+
178+
expect(transactionEvents).toHaveLength(1);
179+
const [transactionEvent] = transactionEvents;
180+
181+
expect(transactionEvent?.spans?.length).toBe(1);
182+
expect(transactionEvent?.transaction).toBe('sentry span');
183+
expect(transactionEvent?.contexts?.trace?.data).toEqual({
184+
'sentry.origin': 'manual',
185+
'sentry.sample_rate': 1,
186+
'sentry.source': 'custom',
187+
});
188+
135189
expect(transactionEvent?.spans).toEqual([
136190
expect.objectContaining({
137191
description: 'otel span',

0 commit comments

Comments
 (0)