Skip to content
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

test(nextjs): Test pages router API routes incoming trace propagation #14818

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { NextApiRequest, NextApiResponse } from 'next';

type Data = {
name: string;
};

export default function handler(req: NextApiRequest, res: NextApiResponse<Data>) {
res.status(200).json({ name: 'John Doe' });
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';

test('Should create a transaction that has the same trace ID as the incoming request', async ({ request }) => {
const transactionPromise = waitForTransaction('nextjs-app-dir', async transactionEvent => {
return transactionEvent?.transaction === 'GET /api/trace-propagation';
});

await request.get('/api/trace-propagation', {
headers: {
'sentry-trace': '8ef4a40df2063cb023c93cbeb04d68c3-acf68e4724b58822-1',
},
});

expect((await transactionPromise).contexts?.trace).toBe(
expect.objectContaining({
trace_id: '8ef4a40df2063cb023c93cbeb04d68c3',
parent_span_id: 'acf68e4724b58822',
}),
);
});
1 change: 0 additions & 1 deletion packages/astro/src/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export declare function flush(timeout?: number | undefined): PromiseLike<boolean
// eslint-disable-next-line deprecation/deprecation
export declare const getCurrentHub: typeof clientSdk.getCurrentHub;
export declare const getClient: typeof clientSdk.getClient;
export declare const continueTrace: typeof clientSdk.continueTrace;

export declare const Span: clientSdk.Span;

Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/asyncContext/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Scope } from '../scope';
import type { getTraceData } from '../utils/traceData';
import type {
continueTrace,
startInactiveSpan,
startSpan,
startSpanManual,
Expand Down Expand Up @@ -68,4 +69,11 @@ export interface AsyncContextStrategy {

/** Get trace data as serialized string values for propagation via `sentry-trace` and `baggage`. */
getTraceData?: typeof getTraceData;

/**
* Continue a trace from `sentry-trace` and `baggage` values.
* These values can be obtained from incoming request headers, or in the browser from `<meta name="sentry-trace">`
* and `<meta name="baggage">` HTML tags.
*/
continueTrace?: typeof continueTrace;
}
13 changes: 9 additions & 4 deletions packages/core/src/tracing/trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,15 +192,20 @@ export function startInactiveSpan(options: StartSpanOptions): Span {
* be attached to the incoming trace.
*/
export const continueTrace = <V>(
{
sentryTrace,
baggage,
}: {
options: {
sentryTrace: Parameters<typeof propagationContextFromHeaders>[0];
baggage: Parameters<typeof propagationContextFromHeaders>[1];
},
callback: () => V,
): V => {
const carrier = getMainCarrier();
const acs = getAsyncContextStrategy(carrier);
if (acs.continueTrace) {
return acs.continueTrace(options, callback);
}

const { sentryTrace, baggage } = options;

return withScope(scope => {
const propagationContext = propagationContextFromHeaders(sentryTrace, baggage);
scope.setPropagationContext(propagationContext);
Expand Down
4 changes: 1 addition & 3 deletions packages/node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,6 @@ export type { NodeOptions } from './types';
export { addRequestDataToEvent, DEFAULT_USER_INCLUDES, extractRequestData } from '@sentry/core';

export {
// These are custom variants that need to be used instead of the core one
// As they have slightly different implementations
continueTrace,
// This needs exporting so the NodeClient can be used without calling init
setOpenTelemetryContextAsyncContextStrategy as setNodeAsyncContextStrategy,
} from '@sentry/opentelemetry';
Expand Down Expand Up @@ -105,6 +102,7 @@ export {
getIsolationScope,
getTraceData,
getTraceMetaTags,
continueTrace,
withScope,
withIsolationScope,
captureException,
Expand Down
1 change: 0 additions & 1 deletion packages/nuxt/src/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,3 @@ export declare const linkedErrorsIntegration: typeof clientSdk.linkedErrorsInteg
export declare const contextLinesIntegration: typeof clientSdk.contextLinesIntegration;
export declare const getDefaultIntegrations: (options: Options) => Integration[];
export declare const defaultStackParser: StackParser;
export declare const continueTrace: typeof clientSdk.continueTrace;
3 changes: 2 additions & 1 deletion packages/opentelemetry/src/asyncContextStrategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY,
SENTRY_FORK_SET_SCOPE_CONTEXT_KEY,
} from './constants';
import { startInactiveSpan, startSpan, startSpanManual, withActiveSpan } from './trace';
import { continueTrace, startInactiveSpan, startSpan, startSpanManual, withActiveSpan } from './trace';
import type { CurrentScopes } from './types';
import { getScopesFromContext } from './utils/contextData';
import { getActiveSpan } from './utils/getActiveSpan';
Expand Down Expand Up @@ -103,6 +103,7 @@ export function setOpenTelemetryContextAsyncContextStrategy(): void {
getActiveSpan,
suppressTracing,
getTraceData,
continueTrace,
// The types here don't fully align, because our own `Span` type is narrower
// than the OTEL one - but this is OK for here, as we now we'll only have OTEL spans passed around
withActiveSpan: withActiveSpan as typeof defaultWithActiveSpan,
Expand Down
17 changes: 14 additions & 3 deletions packages/opentelemetry/src/trace.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import type { Context, Span, SpanContext, SpanOptions, Tracer } from '@opentelemetry/api';
import { SpanStatusCode, TraceFlags, context, trace } from '@opentelemetry/api';
import { suppressTracing } from '@opentelemetry/core';
import type { Client, DynamicSamplingContext, Scope, Span as SentrySpan, TraceContext } from '@sentry/core';
import type {
Client,
DynamicSamplingContext,
Scope,
Span as SentrySpan,
TraceContext,
continueTrace as baseContinueTrace,
} from '@sentry/core';
import {
SDK_VERSION,
SEMANTIC_ATTRIBUTE_SENTRY_OP,
continueTrace as baseContinueTrace,
getClient,
getCurrentScope,
getDynamicSamplingContextFromScope,
getDynamicSamplingContextFromSpan,
getRootSpan,
getTraceContextFromScope,
handleCallbackErrors,
propagationContextFromHeaders,
spanToJSON,
spanToTraceContext,
withScope,
} from '@sentry/core';
import { continueTraceAsRemoteSpan } from './propagator';
import type { OpenTelemetryClient, OpenTelemetrySpanContext } from './types';
Expand Down Expand Up @@ -247,7 +255,10 @@ function getContextForScope(scope?: Scope): Context {
* It propagates the trace as a remote span, in addition to setting it on the propagation context.
*/
export function continueTrace<T>(options: Parameters<typeof baseContinueTrace>[0], callback: () => T): T {
return baseContinueTrace(options, () => {
return withScope(scope => {
const { sentryTrace, baggage } = options;
const propagationContext = propagationContextFromHeaders(sentryTrace, baggage);
scope.setPropagationContext(propagationContext);
return continueTraceAsRemoteSpan(context.active(), options, callback);
});
}
Expand Down
1 change: 0 additions & 1 deletion packages/remix/src/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ declare const runtime: 'client' | 'server';
// eslint-disable-next-line deprecation/deprecation
export declare const getCurrentHub: typeof clientSdk.getCurrentHub;
export declare const getClient: typeof clientSdk.getClient;
export declare const continueTrace: typeof clientSdk.continueTrace;

export const close = runtime === 'client' ? clientSdk.close : serverSdk.close;
export const flush = runtime === 'client' ? clientSdk.flush : serverSdk.flush;
Expand Down
2 changes: 1 addition & 1 deletion packages/remix/src/utils/instrumentServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
continueTrace,
fill,
getActiveSpan,
getClient,
Expand All @@ -19,7 +20,6 @@ import {
winterCGRequestToRequestData,
withIsolationScope,
} from '@sentry/core';
import { continueTrace } from '@sentry/opentelemetry';
import { DEBUG_BUILD } from './debug-build';
import { captureRemixServerException, errorHandleDataFunction, errorHandleDocumentRequestFunction } from './errors';
import { getFutureFlagsServer, getRemixVersionFromBuild } from './futureFlags';
Expand Down
2 changes: 0 additions & 2 deletions packages/solidstart/src/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,3 @@ export declare const getClient: typeof clientSdk.getClient;
export declare function close(timeout?: number | undefined): PromiseLike<boolean>;
export declare function flush(timeout?: number | undefined): PromiseLike<boolean>;
export declare function lastEventId(): string | undefined;

export declare const continueTrace: typeof clientSdk.continueTrace;
2 changes: 0 additions & 2 deletions packages/sveltekit/src/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,4 @@ export declare function close(timeout?: number | undefined): PromiseLike<boolean
export declare function flush(timeout?: number | undefined): PromiseLike<boolean>;
export declare function lastEventId(): string | undefined;

export declare const continueTrace: typeof clientSdk.continueTrace;

export declare function trackComponent(options: clientSdk.TrackingOptions): ReturnType<typeof clientSdk.trackComponent>;
2 changes: 1 addition & 1 deletion packages/sveltekit/src/server/handle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Span } from '@sentry/core';
import {
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
continueTrace,
getActiveSpan,
getCurrentScope,
getDefaultIsolationScope,
Expand All @@ -13,7 +14,6 @@ import {
winterCGRequestToRequestData,
withIsolationScope,
} from '@sentry/core';
import { continueTrace } from '@sentry/node';
import type { Handle, ResolveOptions } from '@sveltejs/kit';

import { DEBUG_BUILD } from '../common/debug-build';
Expand Down
Loading