Skip to content

feat(node-experimental): Implement new performance APIs #8911

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 1, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions packages/node-experimental/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export { init } from './sdk/init';
export { INTEGRATIONS as Integrations };
export { getAutoPerformanceIntegrations } from './integrations/getAutoPerformanceIntegrations';
export * as Handlers from './sdk/handlers';
export * from './sdk/trace';

export {
makeNodeTransport,
Expand Down
119 changes: 119 additions & 0 deletions packages/node-experimental/src/sdk/trace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import type { Span as OtelSpan, Tracer } from '@opentelemetry/api';
import { trace } from '@opentelemetry/api';
import { getCurrentHub, hasTracingEnabled, Transaction } from '@sentry/core';
import { _INTERNAL_getSentrySpan } from '@sentry/opentelemetry-node';
import type { Span, TransactionContext } from '@sentry/types';
import { isThenable } from '@sentry/utils';

import type { NodeExperimentalClient } from './client';

/**
* Wraps a function with a transaction/span and finishes the span after the function is done.
* The created span is the active span and will be used as parent by other spans created inside the function
* and can be accessed via `Sentry.getSpan()`, as long as the function is executed while the scope is active.
*
* If you want to create a span that is not set as active, use {@link startSpan}.
*
* Note that if you have not enabled tracing extensions via `addTracingExtensions`
* or you didn't set `tracesSampleRate`, this function will not generate spans
* and the `span` returned from the callback will be undefined.
*/
export function startActiveSpan<T>(context: TransactionContext, callback: (span: Span | undefined) => T): T {
const tracer = getTracer();
if (!tracer) {
return callback(undefined);
}

const name = context.description || context.op || '<unknown>';

return tracer.startActiveSpan(name, (span: OtelSpan): T => {
const otelSpanId = span.spanContext().spanId;

const sentrySpan = _INTERNAL_getSentrySpan(otelSpanId);

if (sentrySpan && isTransaction(sentrySpan) && context.metadata) {
sentrySpan.setMetadata(context.metadata);
}

function finishSpan(): void {
span.end();
}

let maybePromiseResult: T;
try {
maybePromiseResult = callback(sentrySpan);
} catch (e) {
sentrySpan && sentrySpan.setStatus('internal_error');
finishSpan();
throw e;
}

if (isThenable(maybePromiseResult)) {
Promise.resolve(maybePromiseResult).then(
() => {
finishSpan();
},
() => {
sentrySpan && sentrySpan.setStatus('internal_error');
finishSpan();
},
);
} else {
finishSpan();
}

return maybePromiseResult;
});
}

/**
* Creates a span. This span is not set as active, so will not get automatic instrumentation spans
* as children or be able to be accessed via `Sentry.getSpan()`.
*
* If you want to create a span that is set as active, use {@link startActiveSpan}.
*
* Note that if you have not enabled tracing extensions via `addTracingExtensions`
* or you didn't set `tracesSampleRate` or `tracesSampler`, this function will not generate spans
* and the `span` returned from the callback will be undefined.
*/
export function startSpan(context: TransactionContext): Span | undefined {
const tracer = getTracer();
if (!tracer) {
return undefined;
}

const name = context.description || context.op || '<unknown>';
const otelSpan = tracer.startSpan(name);

const otelSpanId = otelSpan.spanContext().spanId;

const sentrySpan = _INTERNAL_getSentrySpan(otelSpanId);

if (sentrySpan && isTransaction(sentrySpan) && context.metadata) {
sentrySpan.setMetadata(context.metadata);
}

return sentrySpan;
}

/**
* Returns the currently active span.
*/
export function getActiveSpan(): Span | undefined {
const otelSpan = trace.getActiveSpan();
const spanId = otelSpan && otelSpan.spanContext().spanId;
return spanId ? _INTERNAL_getSentrySpan(spanId) : undefined;
}

function getTracer(): Tracer | undefined {
if (!hasTracingEnabled()) {
return undefined;
}

const client = getCurrentHub().getClient<NodeExperimentalClient>();
return client && client.tracer;
}

function isTransaction(span: Span): span is Transaction {
return span instanceof Transaction;
}
2 changes: 1 addition & 1 deletion packages/opentelemetry-node/src/spanprocessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export class SentrySpanProcessor implements OtelSpanProcessor {
*/
public onEnd(otelSpan: OtelSpan): void {
const otelSpanId = otelSpan.spanContext().spanId;
const sentrySpan = SENTRY_SPAN_PROCESSOR_MAP.get(otelSpanId);
const sentrySpan = getSentrySpan(otelSpanId);

if (!sentrySpan) {
__DEBUG_BUILD__ &&
Expand Down