Skip to content

Commit c5e2347

Browse files
committed
feat(node-experimental): Implement new performance APIs
1 parent 32befda commit c5e2347

File tree

4 files changed

+121
-2
lines changed

4 files changed

+121
-2
lines changed

packages/node-experimental/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export { init } from './sdk/init';
1111
export { INTEGRATIONS as Integrations };
1212
export { getAutoPerformanceIntegrations } from './integrations/getAutoPerformanceIntegrations';
1313
export * as Handlers from './sdk/handlers';
14+
export * from './sdk/trace';
1415

1516
export {
1617
makeNodeTransport,
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import type { Span as OtelSpan, Tracer } from '@opentelemetry/api';
2+
import { trace } from '@opentelemetry/api';
3+
import { getCurrentHub, hasTracingEnabled } from '@sentry/core';
4+
import { addOtelSpanData, getSentrySpan } from '@sentry/opentelemetry-node';
5+
import type { Span, TransactionContext } from '@sentry/types';
6+
import { isThenable } from '@sentry/utils';
7+
8+
import type { NodeExperimentalClient } from './client';
9+
10+
/**
11+
* Wraps a function with a transaction/span and finishes the span after the function is done.
12+
* The created span is the active span and will be used as parent by other spans created inside the function
13+
* and can be accessed via `Sentry.getSpan()`, as long as the function is executed while the scope is active.
14+
*
15+
* If you want to create a span that is not set as active, use {@link startSpan}.
16+
*
17+
* Note that if you have not enabled tracing extensions via `addTracingExtensions`
18+
* or you didn't set `tracesSampleRate`, this function will not generate spans
19+
* and the `span` returned from the callback will be undefined.
20+
*/
21+
export function startActiveSpan<T>(context: TransactionContext, callback: (span: Span | undefined) => T): T {
22+
const tracer = getTracer();
23+
if (!tracer) {
24+
return callback(undefined);
25+
}
26+
27+
const name = context.description || context.op || '<unknown>';
28+
const additionalData = {
29+
metadata: context.metadata,
30+
};
31+
32+
return tracer.startActiveSpan(name, (span: OtelSpan): T => {
33+
const otelSpanId = span.spanContext().spanId;
34+
addOtelSpanData(otelSpanId, additionalData);
35+
36+
const sentrySpan = getSentrySpan(otelSpanId);
37+
38+
function finishSpan(): void {
39+
span.end();
40+
}
41+
42+
let maybePromiseResult: T;
43+
try {
44+
maybePromiseResult = callback(sentrySpan);
45+
} catch (e) {
46+
sentrySpan && sentrySpan.setStatus('internal_error');
47+
finishSpan();
48+
throw e;
49+
}
50+
51+
if (isThenable(maybePromiseResult)) {
52+
Promise.resolve(maybePromiseResult).then(
53+
() => {
54+
finishSpan();
55+
},
56+
() => {
57+
sentrySpan && sentrySpan.setStatus('internal_error');
58+
finishSpan();
59+
},
60+
);
61+
} else {
62+
finishSpan();
63+
}
64+
65+
return maybePromiseResult;
66+
});
67+
}
68+
69+
/**
70+
* Creates a span. This span is not set as active, so will not get automatic instrumentation spans
71+
* as children or be able to be accessed via `Sentry.getSpan()`.
72+
*
73+
* If you want to create a span that is set as active, use {@link startActiveSpan}.
74+
*
75+
* Note that if you have not enabled tracing extensions via `addTracingExtensions`
76+
* or you didn't set `tracesSampleRate` or `tracesSampler`, this function will not generate spans
77+
* and the `span` returned from the callback will be undefined.
78+
*/
79+
export function startSpan(context: TransactionContext): Span | undefined {
80+
const tracer = getTracer();
81+
if (!tracer) {
82+
return undefined;
83+
}
84+
85+
const name = context.description || context.op || '<unknown>';
86+
const otelSpan = tracer.startSpan(name);
87+
88+
const otelSpanId = otelSpan.spanContext().spanId;
89+
const additionalData = {
90+
metadata: context.metadata,
91+
};
92+
addOtelSpanData(otelSpanId, additionalData);
93+
94+
return getSentrySpan(otelSpanId);
95+
}
96+
97+
/**
98+
* Returns the currently active span.
99+
*/
100+
export function getActiveSpan(): Span | undefined {
101+
const otelSpan = trace.getActiveSpan();
102+
const spanId = otelSpan && otelSpan.spanContext().spanId;
103+
return spanId ? getSentrySpan(spanId) : undefined;
104+
}
105+
106+
function getTracer(): Tracer | undefined {
107+
if (!hasTracingEnabled()) {
108+
return undefined;
109+
}
110+
111+
const client = getCurrentHub().getClient<NodeExperimentalClient>();
112+
return client && client.tracer;
113+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
export { SentrySpanProcessor } from './spanprocessor';
1+
export { SentrySpanProcessor, getSentrySpan } from './spanprocessor';
22
export { SentryPropagator } from './propagator';
33
export * from './utils/spanData';

packages/opentelemetry-node/src/spanprocessor.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ function clearSpan(otelSpanId: string): void {
2323
SENTRY_SPAN_PROCESSOR_MAP.delete(otelSpanId);
2424
}
2525

26+
/** Get a Sentry span for an otel span ID. */
27+
export function getSentrySpan(otelSpanId: string): SentrySpan | undefined {
28+
return SENTRY_SPAN_PROCESSOR_MAP.get(otelSpanId);
29+
}
30+
2631
/**
2732
* Converts OpenTelemetry Spans to Sentry Spans and sends them to Sentry via
2833
* the Sentry SDK.
@@ -92,7 +97,7 @@ export class SentrySpanProcessor implements OtelSpanProcessor {
9297
*/
9398
public onEnd(otelSpan: OtelSpan): void {
9499
const otelSpanId = otelSpan.spanContext().spanId;
95-
const sentrySpan = SENTRY_SPAN_PROCESSOR_MAP.get(otelSpanId);
100+
const sentrySpan = getSentrySpan(otelSpanId);
96101

97102
if (!sentrySpan) {
98103
__DEBUG_BUILD__ &&

0 commit comments

Comments
 (0)