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

feat(nextjs): Attach server component spans to pageload request span #12182

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
feat(nextjs): Attach server component spans to pageload request span
  • Loading branch information
lforst committed May 23, 2024
commit 8116f516537e0ea42e6c97a285b74305c253b462
1 change: 1 addition & 0 deletions packages/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
},
"dependencies": {
"@opentelemetry/instrumentation-http": "0.51.1",
"@opentelemetry/api": "^1.8.0",
"@rollup/plugin-commonjs": "24.0.0",
"@sentry/core": "8.2.1",
"@sentry/node": "8.2.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,39 @@ import {
} from '@sentry/core';
import { WINDOW, startBrowserTracingNavigationSpan, startBrowserTracingPageLoadSpan } from '@sentry/react';
import type { Client } from '@sentry/types';
import { addFetchInstrumentationHandler, browserPerformanceTimeOrigin } from '@sentry/utils';
import { addFetchInstrumentationHandler, browserPerformanceTimeOrigin, parseBaggageHeader } from '@sentry/utils';

/** Instruments the Next.js app router for pageloads. */
export function appRouterInstrumentPageLoad(client: Client): void {
// We use an event processor to override the automatically collected Request Browser metric span ID with the span ID
// hint from the server so that the SSR spans are properly attached to the request span.
client.addEventProcessor(event => {
if (event.type !== 'transaction' || event.contexts?.trace?.op !== 'pageload') {
return event;
}

const baggage = WINDOW.document.querySelector('meta[name=baggage]')?.getAttribute('content');
if (baggage) {
const parsedBaggage = parseBaggageHeader(baggage);
if (parsedBaggage && parsedBaggage['sentry-request-span-id-suggestion']) {
const spanIdSuggestion = parsedBaggage['sentry-request-span-id-suggestion'];
event.spans?.forEach(span => {
if (span.description === 'request' && span.op === 'browser') {
// Replace request span ID
span.span_id = spanIdSuggestion;

if (event.contexts?.trace) {
// Unset the parent span of the pageload transaction - it is now the root of the trace
event.contexts.trace.parent_span_id = undefined;
}
}
});
}
}

return event;
});

startBrowserTracingPageLoadSpan(client, {
name: WINDOW.location.pathname,
// pageload should always start at timeOrigin (and needs to be in s, not ms)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ export function wrapGenerationFunctionWithSentry<F extends (...args: any[]) => a
: {
traceId: requestTraceId || uuid4(),
spanId: uuid4().substring(16),
parentSpanId: otelContext
.active()
.getValue(EXPERIMENTAL_SENTRY_REQUEST_SPAN_ID_SUGGESTION_CONTEXT_KEY) as string | undefined,
},
);

Expand Down
3 changes: 3 additions & 0 deletions packages/nextjs/src/common/wrapServerComponentWithSentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ export function wrapServerComponentWithSentry<F extends (...args: any[]) => any>
: {
traceId: requestTraceId || uuid4(),
spanId: uuid4().substring(16),
parentSpanId: otelContext
.active()
.getValue(EXPERIMENTAL_SENTRY_REQUEST_SPAN_ID_SUGGESTION_CONTEXT_KEY) as string | undefined,
},
);

Expand Down
13 changes: 12 additions & 1 deletion packages/nextjs/src/server/httpIntegration.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { context } from '@opentelemetry/api';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
import { httpIntegration as originalHttpIntegration } from '@sentry/node';
import { EXPERIMENTAL_SENTRY_REQUEST_SPAN_ID_SUGGESTION_CONTEXT_KEY } from '@sentry/opentelemetry';
import type { IntegrationFn } from '@sentry/types';
import { uuid4 } from '@sentry/utils';

/**
* Next.js handles incoming requests itself,
Expand All @@ -16,7 +19,15 @@ class CustomNextjsHttpIntegration extends HttpInstrumentation {
original: (event: string, ...args: unknown[]) => boolean,
): ((this: unknown, event: string, ...args: unknown[]) => boolean) => {
return function incomingRequest(this: unknown, event: string, ...args: unknown[]): boolean {
return original.apply(this, [event, ...args]);
const requestSpanIdSuggestion = uuid4().substring(16);
return context.with(
context
.active()
.setValue(EXPERIMENTAL_SENTRY_REQUEST_SPAN_ID_SUGGESTION_CONTEXT_KEY, requestSpanIdSuggestion),
() => {
return original.apply(this, [event, ...args]);
},
);
};
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ describe('appRouterInstrumentPageLoad', () => {
const emit = jest.fn();
const client = {
emit,
addEventProcessor: jest.fn(),
} as unknown as Client;

appRouterInstrumentPageLoad(client);
Expand Down
4 changes: 4 additions & 0 deletions packages/opentelemetry/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ export const SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY = createContextKey('sentry_
export const SENTRY_FORK_SET_SCOPE_CONTEXT_KEY = createContextKey('sentry_fork_set_scope');

export const SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY = createContextKey('sentry_fork_set_isolation_scope');

export const EXPERIMENTAL_SENTRY_REQUEST_SPAN_ID_SUGGESTION_CONTEXT_KEY = createContextKey(
'sentry_request_span_id_suggestion',
);
2 changes: 2 additions & 0 deletions packages/opentelemetry/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,7 @@ export { openTelemetrySetupCheck } from './utils/setupCheck';

export { addOpenTelemetryInstrumentation } from './instrumentation';

export { EXPERIMENTAL_SENTRY_REQUEST_SPAN_ID_SUGGESTION_CONTEXT_KEY } from './constants';

// Legacy
export { getClient } from '@sentry/core';
10 changes: 10 additions & 0 deletions packages/opentelemetry/src/propagator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from '@sentry/utils';

import {
EXPERIMENTAL_SENTRY_REQUEST_SPAN_ID_SUGGESTION_CONTEXT_KEY,
SENTRY_BAGGAGE_HEADER,
SENTRY_TRACE_HEADER,
SENTRY_TRACE_STATE_DSC,
Expand Down Expand Up @@ -131,6 +132,15 @@ export class SentryPropagator extends W3CBaggagePropagator {
setter.set(carrier, SENTRY_TRACE_HEADER, generateSentryTraceHeader(traceId, spanId, sampled));
}

const requestSpanIdSuggestion = context.getValue(EXPERIMENTAL_SENTRY_REQUEST_SPAN_ID_SUGGESTION_CONTEXT_KEY) as
| string
| undefined;
if (requestSpanIdSuggestion) {
baggage = baggage.setEntry(`${SENTRY_BAGGAGE_KEY_PREFIX}request-span-id-suggestion`, {
value: requestSpanIdSuggestion,
});
}

super.inject(propagation.setBaggage(context, baggage), carrier, setter);
}

Expand Down
Loading