Skip to content

Commit d4fba89

Browse files
mydeaandreiborza
authored andcommitted
feat(cloudflare): Allow interop with OpenTelemetry emitted spans
1 parent 4d8581a commit d4fba89

File tree

3 files changed

+74
-1
lines changed

3 files changed

+74
-1
lines changed

packages/cloudflare/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@
4949
"access": "public"
5050
},
5151
"dependencies": {
52-
"@sentry/core": "9.31.0"
52+
"@sentry/core": "9.31.0",
53+
"@opentelemetry/api": "^1.9.0"
5354
},
5455
"peerDependencies": {
5556
"@cloudflare/workers-types": "^4.x"
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import type { Context, Span, SpanOptions, Tracer, TracerProvider } from '@opentelemetry/api';
2+
import { trace } from '@opentelemetry/api';
3+
import { startInactiveSpan, startSpan } from '@sentry/core';
4+
5+
/**
6+
* Set up a mock OTEL tracer to allow inter-op with OpenTelemetry emitted spans.
7+
* This is not perfect but handles easy/common use cases.
8+
*/
9+
export function setupOpenTelemetryTracer(): void {
10+
trace.setGlobalTracerProvider(new SentryCloudflareTraceProvider());
11+
}
12+
13+
class SentryCloudflareTraceProvider implements TracerProvider {
14+
private readonly _tracers: Map<string, Tracer> = new Map();
15+
16+
public getTracer(name: string, version?: string, options?: { schemaUrl?: string }): Tracer {
17+
const key = `${name}@${version || ''}:${options?.schemaUrl || ''}`;
18+
if (!this._tracers.has(key)) {
19+
this._tracers.set(key, new SentryCloudflareTracer());
20+
}
21+
22+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
23+
return this._tracers.get(key)!;
24+
}
25+
}
26+
27+
class SentryCloudflareTracer implements Tracer {
28+
public startSpan(name: string, options?: SpanOptions): Span {
29+
return startInactiveSpan({ name, ...options });
30+
}
31+
32+
/**
33+
* NOTE: This does not handle `context` being passed in. It will always put spans on the current scope.
34+
*/
35+
public startActiveSpan<F extends (span: Span) => unknown>(name: string, fn: F): ReturnType<F>;
36+
public startActiveSpan<F extends (span: Span) => unknown>(name: string, options: SpanOptions, fn: F): ReturnType<F>;
37+
public startActiveSpan<F extends (span: Span) => unknown>(
38+
name: string,
39+
options: SpanOptions,
40+
context: Context,
41+
fn: F,
42+
): ReturnType<F>;
43+
public startActiveSpan<F extends (span: Span) => unknown>(
44+
name: string,
45+
options: unknown,
46+
context?: unknown,
47+
fn?: F,
48+
): ReturnType<F> {
49+
const opts = typeof options === 'object' && options !== null ? options : {};
50+
51+
const spanOpts = {
52+
name,
53+
...opts,
54+
};
55+
56+
const callback = (
57+
typeof options === 'function'
58+
? options
59+
: typeof context === 'function'
60+
? context
61+
: typeof fn === 'function'
62+
? fn
63+
: // eslint-disable-next-line @typescript-eslint/no-empty-function
64+
() => {}
65+
) as F;
66+
67+
return startSpan(spanOpts, callback) as ReturnType<F>;
68+
}
69+
}

packages/cloudflare/src/sdk.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import type { CloudflareClientOptions, CloudflareOptions } from './client';
1414
import { CloudflareClient } from './client';
1515
import { fetchIntegration } from './integrations/fetch';
16+
import { setupOpenTelemetryTracer } from './opentelemetry/tracer';
1617
import { makeCloudflareTransport } from './transport';
1718
import { defaultStackParser } from './vendor/stacktrace';
1819

@@ -50,5 +51,7 @@ export function init(options: CloudflareOptions): CloudflareClient | undefined {
5051
transport: options.transport || makeCloudflareTransport,
5152
};
5253

54+
setupOpenTelemetryTracer();
55+
5356
return initAndBind(CloudflareClient, clientOptions) as CloudflareClient;
5457
}

0 commit comments

Comments
 (0)