Skip to content

Commit

Permalink
logic
Browse files Browse the repository at this point in the history
  • Loading branch information
lforst committed Jul 4, 2024
1 parent d4aa8f1 commit 84d9bd7
Show file tree
Hide file tree
Showing 6 changed files with 39 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default async function Page() {
}

export async function generateMetadata() {
(await fetch('http://example.com/')).text();
(await fetch('http://example.com/', { cache: 'no-store' })).text();

return {
title: 'my title',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';

test('all server component transactions should be attached to the pageload request span', async ({ page }) => {
test('App router transactions should be attached to the pageload request span', async ({ page }) => {
const serverTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
return transactionEvent?.transaction === 'GET /pageload-tracing';
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,6 @@ test('Creates a navigation transaction for app router routes', async ({ page })
});

const serverComponentTransactionPromise = waitForTransaction('nextjs-app-dir', async transactionEvent => {
console.log({
t: transactionEvent.transaction,
tid: transactionEvent.contexts?.trace?.trace_id,
ctid: (await clientNavigationTransactionPromise).contexts?.trace?.trace_id,
});
return (
transactionEvent?.transaction === 'GET /server-component/parameter/foo/bar/baz' &&
(await clientNavigationTransactionPromise).contexts?.trace?.trace_id ===
Expand Down
16 changes: 14 additions & 2 deletions packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ import {
withScope,
} from '@sentry/core';
import type { WebFetchHeaders } from '@sentry/types';
import { winterCGHeadersToDict } from '@sentry/utils';
import { propagationContextFromHeaders, uuid4, winterCGHeadersToDict } from '@sentry/utils';

import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
import type { GenerationFunctionContext } from '../common/types';
import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils';
import { commonObjectToIsolationScope } from './utils/tracingUtils';
import { commonObjectToIsolationScope, commonObjectToPropagationContext } from './utils/tracingUtils';

/**
* Wraps a generation function (e.g. generateMetadata) with Sentry error and performance instrumentation.
Expand All @@ -33,6 +33,7 @@ export function wrapGenerationFunctionWithSentry<F extends (...args: any[]) => a
const { requestAsyncStorage, componentRoute, componentType, generationFunctionIdentifier } = context;
return new Proxy(generationFunction, {
apply: (originalFunction, thisArg, args) => {
const requestTraceId = getActiveSpan()?.spanContext().traceId;
let headers: WebFetchHeaders | undefined = undefined;
// We try-catch here just in case anything goes wrong with the async storage here goes wrong since it is Next.js internal API
try {
Expand Down Expand Up @@ -74,6 +75,17 @@ export function wrapGenerationFunctionWithSentry<F extends (...args: any[]) => a
},
});

const propagationContext = commonObjectToPropagationContext(
headers,
headersDict?.['sentry-trace']
? propagationContextFromHeaders(headersDict['sentry-trace'], headersDict['baggage'])
: {
traceId: requestTraceId || uuid4(),
spanId: uuid4().substring(16),
},
);
scope.setPropagationContext(propagationContext);

scope.setExtra('route_data', data);

return startSpanManual(
Expand Down
20 changes: 18 additions & 2 deletions packages/nextjs/src/common/wrapServerComponentWithSentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ import {
withIsolationScope,
withScope,
} from '@sentry/core';
import { winterCGHeadersToDict } from '@sentry/utils';
import { propagationContextFromHeaders, uuid4, winterCGHeadersToDict } from '@sentry/utils';

import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
import { isNotFoundNavigationError, isRedirectNavigationError } from '../common/nextNavigationErrorUtils';
import type { ServerComponentContext } from '../common/types';
import { flushSafelyWithTimeout } from './utils/responseEnd';
import { commonObjectToIsolationScope } from './utils/tracingUtils';
import { commonObjectToIsolationScope, commonObjectToPropagationContext } from './utils/tracingUtils';
import { vercelWaitUntil } from './utils/vercelWaitUntil';

/**
Expand All @@ -36,6 +36,7 @@ export function wrapServerComponentWithSentry<F extends (...args: any[]) => any>
// hook. 🤯
return new Proxy(appDirComponent, {
apply: (originalFunction, thisArg, args) => {
const requestTraceId = getActiveSpan()?.spanContext().traceId;
const isolationScope = commonObjectToIsolationScope(context.headers);

const activeSpan = getActiveSpan();
Expand All @@ -59,6 +60,21 @@ export function wrapServerComponentWithSentry<F extends (...args: any[]) => any>
return withIsolationScope(isolationScope, () => {
return withScope(scope => {
scope.setTransactionName(`${componentType} Server Component (${componentRoute})`);

if (process.env.NEXT_RUNTIME === 'edge') {
const propagationContext = commonObjectToPropagationContext(
context.headers,
headersDict?.['sentry-trace']
? propagationContextFromHeaders(headersDict['sentry-trace'], headersDict['baggage'])
: {
traceId: requestTraceId || uuid4(),
spanId: uuid4().substring(16),
},
);

scope.setPropagationContext(propagationContext);
}

return startSpanManual(
{
op: 'function.nextjs',
Expand Down
19 changes: 5 additions & 14 deletions packages/nextjs/src/server/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { applySdkMetadata, getClient, getGlobalScope } from '@sentry/core';
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, applySdkMetadata, getClient, getGlobalScope } from '@sentry/core';
import { getDefaultIntegrations, init as nodeInit } from '@sentry/node';
import type { NodeClient, NodeOptions } from '@sentry/node';
import { GLOBAL_OBJ, logger } from '@sentry/utils';
Expand Down Expand Up @@ -86,14 +86,7 @@ export function init(options: NodeOptions): NodeClient | undefined {
return;
}

const customDefaultIntegrations = [
...getDefaultIntegrations(options).filter(
integration =>
// Next.js comes with its own Http instrumentation for OTel which would lead to double spans for route handler requests
integration.name !== 'Http',
),
httpIntegration(),
];
const customDefaultIntegrations = getDefaultIntegrations(options);

// Turn off Next.js' own fetch instrumentation
// https://github.com/lforst/nextjs-fork/blob/1994fd186defda77ad971c36dc3163db263c993f/packages/next/src/server/lib/patch-fetch.ts#L245
Expand Down Expand Up @@ -128,7 +121,6 @@ export function init(options: NodeOptions): NodeClient | undefined {
applySdkMetadata(opts, 'nextjs', ['nextjs', 'node']);

const client = nodeInit(opts);

// If we encounter a span emitted by Next.js, we do not want to sample it
// The reason for this is that the data quality of the spans varies, it is different per version of Next,
// and we need to keep our manual instrumentation around for the edge runtime anyhow.
Expand Down Expand Up @@ -160,11 +152,10 @@ export function init(options: NodeOptions): NodeClient | undefined {
return null;
}

// 'BaseServer.handleRequest' spans are the only Next.js spans we sample. However, we only want to actually keep these spans/transactions,
// when they server RSCs (app router routes). We mark these transactions with a "sentry.rsc" attribute in our RSC wrappers so we can
// filter them here.
// We only want to use our HTTP integration/instrumentation for app router requests, which are marked with the `sentry.rsc` attribute.
if (
event.contexts?.trace?.data?.['next.span_type'] === 'BaseServer.handleRequest' &&
(event.contexts?.trace?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] === 'auto.http.otel.http' ||
event.contexts?.trace?.data?.['next.span_type'] === 'BaseServer.handleRequest') &&
event.contexts?.trace?.data?.['sentry.rsc'] !== true
) {
return null;
Expand Down

0 comments on commit 84d9bd7

Please sign in to comment.