Skip to content
Closed
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
3 changes: 2 additions & 1 deletion dev-packages/rollup-utils/npmHelpers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export function makeBaseNPMConfig(options = {}) {
packageSpecificConfig = {},
addPolyfills = true,
sucrase = {},
bundledBuiltins = [],
} = options;

const nodeResolvePlugin = makeNodeResolvePlugin();
Expand Down Expand Up @@ -113,7 +114,7 @@ export function makeBaseNPMConfig(options = {}) {

// don't include imported modules from outside the package in the final output
external: [
...builtinModules,
...builtinModules.filter(m => !bundledBuiltins.includes(m)),
...Object.keys(packageDotJSON.dependencies || {}),
...Object.keys(packageDotJSON.peerDependencies || {}),
...Object.keys(packageDotJSON.optionalDependencies || {}),
Expand Down
108 changes: 53 additions & 55 deletions packages/nextjs/src/common/utils/edgeWrapperUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { winterCGRequestToRequestData } from '@sentry/utils';

import type { EdgeRouteHandler } from '../../edge/types';
import { flushSafelyWithTimeout } from './responseEnd';
import { commonObjectToIsolationScope, escapeNextjsTracing } from './tracingUtils';
import { commonObjectToIsolationScope } from './tracingUtils';
import { vercelWaitUntil } from './vercelWaitUntil';

/**
Expand All @@ -24,68 +24,66 @@ export function withEdgeWrapping<H extends EdgeRouteHandler>(
options: { spanDescription: string; spanOp: string; mechanismFunctionName: string },
): (...params: Parameters<H>) => Promise<ReturnType<H>> {
return async function (this: unknown, ...args) {
return escapeNextjsTracing(() => {
const req: unknown = args[0];
return withIsolationScope(commonObjectToIsolationScope(req), isolationScope => {
let sentryTrace;
let baggage;
const req: unknown = args[0];
return withIsolationScope(commonObjectToIsolationScope(req), isolationScope => {
let sentryTrace;
let baggage;

if (req instanceof Request) {
sentryTrace = req.headers.get('sentry-trace') || '';
baggage = req.headers.get('baggage');
if (req instanceof Request) {
sentryTrace = req.headers.get('sentry-trace') || '';
baggage = req.headers.get('baggage');

isolationScope.setSDKProcessingMetadata({
request: winterCGRequestToRequestData(req),
});
}
isolationScope.setSDKProcessingMetadata({
request: winterCGRequestToRequestData(req),
});
}

isolationScope.setTransactionName(options.spanDescription);
isolationScope.setTransactionName(options.spanDescription);

return continueTrace(
{
sentryTrace,
baggage,
},
() => {
return startSpan(
{
name: options.spanDescription,
op: options.spanOp,
forceTransaction: true,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs.withEdgeWrapping',
},
return continueTrace(
{
sentryTrace,
baggage,
},
() => {
return startSpan(
{
name: options.spanDescription,
op: options.spanOp,
forceTransaction: true,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs.withEdgeWrapping',
},
async span => {
const handlerResult = await handleCallbackErrors(
() => handler.apply(this, args),
error => {
captureException(error, {
mechanism: {
type: 'instrument',
handled: false,
data: {
function: options.mechanismFunctionName,
},
},
async span => {
const handlerResult = await handleCallbackErrors(
() => handler.apply(this, args),
error => {
captureException(error, {
mechanism: {
type: 'instrument',
handled: false,
data: {
function: options.mechanismFunctionName,
},
});
},
);
},
});
},
);

if (handlerResult instanceof Response) {
setHttpStatus(span, handlerResult.status);
} else {
span.setStatus({ code: SPAN_STATUS_OK });
}
if (handlerResult instanceof Response) {
setHttpStatus(span, handlerResult.status);
} else {
span.setStatus({ code: SPAN_STATUS_OK });
}

return handlerResult;
},
);
},
).finally(() => {
vercelWaitUntil(flushSafelyWithTimeout());
});
return handlerResult;
},
);
},
).finally(() => {
vercelWaitUntil(flushSafelyWithTimeout());
});
});
};
Expand Down
106 changes: 17 additions & 89 deletions packages/nextjs/src/common/utils/wrapperUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,14 @@ import {
SPAN_STATUS_ERROR,
SPAN_STATUS_OK,
captureException,
continueTrace,
getTraceData,
startInactiveSpan,
startSpan,
startSpanManual,
withActiveSpan,
withIsolationScope,
} from '@sentry/core';
import type { Span } from '@sentry/types';
import { isString } from '@sentry/utils';

import { autoEndSpanOnResponseEnd, flushSafelyWithTimeout } from './responseEnd';
import { commonObjectToIsolationScope, escapeNextjsTracing } from './tracingUtils';
import { flushSafelyWithTimeout } from './responseEnd';
import { commonObjectToIsolationScope } from './tracingUtils';
import { vercelWaitUntil } from './vercelWaitUntil';

declare module 'http' {
Expand All @@ -26,21 +21,6 @@ declare module 'http' {
}
}

/**
* Grabs a span off a Next.js datafetcher request object, if it was previously put there via
* `setSpanOnRequest`.
*
* @param req The Next.js datafetcher request object
* @returns the span on the request object if there is one, or `undefined` if the request object didn't have one.
*/
export function getSpanFromRequest(req: IncomingMessage): Span | undefined {
return req._sentrySpan;
}

function setSpanOnRequest(span: Span, req: IncomingMessage): void {
req._sentrySpan = span;
}

/**
* Wraps a function that potentially throws. If it does, the error is passed to `captureException` and rethrown.
*
Expand Down Expand Up @@ -80,7 +60,10 @@ export function withErrorInstrumentation<F extends (...args: any[]) => any>(
export function withTracedServerSideDataFetcher<F extends (...args: any[]) => Promise<any> | any>(
origDataFetcher: F,
req: IncomingMessage,
// TODO(v9): Remove these unused arguments
// eslint-disable-next-line @typescript-eslint/no-unused-vars
res: ServerResponse,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
options: {
/** Parameterized route of the request - will be used for naming the transaction. */
requestedRouteName: string;
Expand All @@ -94,80 +77,25 @@ export function withTracedServerSideDataFetcher<F extends (...args: any[]) => Pr
this: unknown,
...args: Parameters<F>
): Promise<{ data: ReturnType<F>; sentryTrace?: string; baggage?: string }> {
return escapeNextjsTracing(() => {
const isolationScope = commonObjectToIsolationScope(req);
return withIsolationScope(isolationScope, () => {
isolationScope.setTransactionName(`${options.dataFetchingMethodName} (${options.dataFetcherRouteName})`);
isolationScope.setSDKProcessingMetadata({
request: req,
});

const sentryTrace =
req.headers && isString(req.headers['sentry-trace']) ? req.headers['sentry-trace'] : undefined;
const baggage = req.headers?.baggage;

return continueTrace({ sentryTrace, baggage }, () => {
const requestSpan = getOrStartRequestSpan(req, res, options.requestedRouteName);
return withActiveSpan(requestSpan, () => {
return startSpanManual(
{
op: 'function.nextjs',
name: `${options.dataFetchingMethodName} (${options.dataFetcherRouteName})`,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs',
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
},
},
async dataFetcherSpan => {
dataFetcherSpan.setStatus({ code: SPAN_STATUS_OK });
const { 'sentry-trace': sentryTrace, baggage } = getTraceData();
try {
return {
sentryTrace: sentryTrace,
baggage: baggage,
data: await origDataFetcher.apply(this, args),
};
} catch (e) {
dataFetcherSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
requestSpan?.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
throw e;
} finally {
dataFetcherSpan.end();
}
},
);
});
});
const isolationScope = commonObjectToIsolationScope(req);
// TODO(lforst): Get rid of this with
return withIsolationScope(isolationScope, async () => {
isolationScope.setSDKProcessingMetadata({
request: req,
});

const { 'sentry-trace': sentryTrace, baggage } = getTraceData();
return {
sentryTrace: sentryTrace,
baggage: baggage,
data: await origDataFetcher.apply(this, args),
};
}).finally(() => {
vercelWaitUntil(flushSafelyWithTimeout());
});
};
}

function getOrStartRequestSpan(req: IncomingMessage, res: ServerResponse, name: string): Span {
const existingSpan = getSpanFromRequest(req);
if (existingSpan) {
return existingSpan;
}

const requestSpan = startInactiveSpan({
name,
forceTransaction: true,
op: 'http.server',
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs',
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
},
});

requestSpan.setStatus({ code: SPAN_STATUS_OK });
setSpanOnRequest(requestSpan, req);
autoEndSpanOnResponseEnd(requestSpan, res);

return requestSpan;
}

/**
* Call a data fetcher and trace it. Only traces the function if there is an active transaction on the scope.
*
Expand Down
80 changes: 20 additions & 60 deletions packages/nextjs/src/common/wrapPageComponentWithSentry.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { captureException, getCurrentScope, withIsolationScope } from '@sentry/core';
import { extractTraceparentData } from '@sentry/utils';
import { escapeNextjsTracing } from './utils/tracingUtils';
import { captureException } from '@sentry/core';

interface FunctionComponent {
(...args: unknown[]): unknown;
Expand All @@ -25,69 +23,31 @@ export function wrapPageComponentWithSentry(pageComponent: FunctionComponent | C
if (isReactClassComponent(pageComponent)) {
return class SentryWrappedPageComponent extends pageComponent {
public render(...args: unknown[]): unknown {
return escapeNextjsTracing(() => {
return withIsolationScope(() => {
const scope = getCurrentScope();
// We extract the sentry trace data that is put in the component props by datafetcher wrappers
const sentryTraceData =
typeof this.props === 'object' &&
this.props !== null &&
'_sentryTraceData' in this.props &&
typeof this.props._sentryTraceData === 'string'
? this.props._sentryTraceData
: undefined;

if (sentryTraceData) {
const traceparentData = extractTraceparentData(sentryTraceData);
scope.setContext('trace', {
span_id: traceparentData?.parentSpanId,
trace_id: traceparentData?.traceId,
});
}

try {
return super.render(...args);
} catch (e) {
captureException(e, {
mechanism: {
handled: false,
},
});
throw e;
}
try {
return super.render(...args);
} catch (e) {
captureException(e, {
mechanism: {
handled: false,
},
});
});
throw e;
}
}
};
} else if (typeof pageComponent === 'function') {
return new Proxy(pageComponent, {
apply(target, thisArg, argArray: [{ _sentryTraceData?: string } | undefined]) {
return escapeNextjsTracing(() => {
return withIsolationScope(() => {
const scope = getCurrentScope();
// We extract the sentry trace data that is put in the component props by datafetcher wrappers
const sentryTraceData = argArray?.[0]?._sentryTraceData;

if (sentryTraceData) {
const traceparentData = extractTraceparentData(sentryTraceData);
scope.setContext('trace', {
span_id: traceparentData?.parentSpanId,
trace_id: traceparentData?.traceId,
});
}

try {
return target.apply(thisArg, argArray);
} catch (e) {
captureException(e, {
mechanism: {
handled: false,
},
});
throw e;
}
apply(target, thisArg, argArray) {
try {
return target.apply(thisArg, argArray);
} catch (e) {
captureException(e, {
mechanism: {
handled: false,
},
});
});
throw e;
}
},
});
} else {
Expand Down
Loading
Loading