Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 1.8k
feat(nextjs): Extract tracing logic from server component wrapper templates#18408
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
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
45f14abfbb8f372a2613d960dcd140f32cc805e1af076c4df6b15148c6751b38f07b99e30ea5c4fec975ecf4dab1b8b43901cbe44ba33529File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import { PropsWithChildren } from 'react'; | ||
| export const dynamic = 'force-dynamic'; | ||
| export default function Layout({ children }: PropsWithChildren<{}>) { | ||
| return ( | ||
| <div> | ||
| <p>DynamicLayout</p> | ||
| {children} | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| export const dynamic = 'force-dynamic'; | ||
| export default async function Page() { | ||
| return ( | ||
| <div> | ||
| <p>Dynamic Page</p> | ||
| </div> | ||
| ); | ||
| } | ||
| export async function generateMetadata() { | ||
| return { | ||
| title: 'I am dynamic page generated metadata', | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| const packageJson = require('../package.json'); | ||
| const nextjsVersion = packageJson.dependencies.next; | ||
| const nextjsMajor = Number(nextjsVersion.split('.')[0]); | ||
| export const isNext13 = !isNaN(nextjsMajor) && nextjsMajor === 13; | ||
| export const nextjsMajorVersion = nextjsMajor; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| export const ATTR_NEXT_SPAN_TYPE = 'next.span_type'; | ||
| export const ATTR_NEXT_SPAN_NAME = 'next.span_name'; | ||
| export const ATTR_NEXT_ROUTE = 'next.route'; | ||
| export const ATTR_NEXT_SPAN_DESCRIPTION = 'next.span_description'; | ||
| export const ATTR_NEXT_SEGMENT = 'next.segment'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,23 @@ | ||
| import type { PropagationContext } from '@sentry/core'; | ||
| import { debug, getActiveSpan, getRootSpan, GLOBAL_OBJ, Scope, spanToJSON, startNewTrace } from '@sentry/core'; | ||
| import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions'; | ||
| import type { PropagationContext, Span, SpanAttributes } from '@sentry/core'; | ||
| import { | ||
| debug, | ||
| getActiveSpan, | ||
| getRootSpan, | ||
| GLOBAL_OBJ, | ||
| Scope, | ||
| SEMANTIC_ATTRIBUTE_SENTRY_OP, | ||
| spanToJSON, | ||
| startNewTrace, | ||
| } from '@sentry/core'; | ||
| import { DEBUG_BUILD } from '../debug-build'; | ||
| import { ATTR_NEXT_SEGMENT, ATTR_NEXT_SPAN_NAME, ATTR_NEXT_SPAN_TYPE } from '../nextSpanAttributes'; | ||
| import { TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION } from '../span-attributes-with-logic-attached'; | ||
| const commonPropagationContextMap = new WeakMap<object, PropagationContext>(); | ||
| const PAGE_SEGMENT = '__PAGE__'; | ||
| /** | ||
| * Takes a shared (garbage collectable) object between resources, e.g. a headers object shared between Next.js server components and returns a common propagation context. | ||
| * | ||
| @@ -108,3 +121,61 @@ export function dropNextjsRootContext(): void { | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * Checks if the span is a resolve segment span. | ||
| * @param spanAttributes The attributes of the span to check. | ||
| * @returns True if the span is a resolve segment span, false otherwise. | ||
| */ | ||
| export function isResolveSegmentSpan(spanAttributes: SpanAttributes): boolean { | ||
| return ( | ||
| spanAttributes[ATTR_NEXT_SPAN_TYPE] === 'NextNodeServer.getLayoutOrPageModule' && | ||
| spanAttributes[ATTR_NEXT_SPAN_NAME] === 'resolve segment modules' && | ||
| typeof spanAttributes[ATTR_NEXT_SEGMENT] === 'string' | ||
| ); | ||
| } | ||
| /** | ||
| * Returns the enhanced name for a resolve segment span. | ||
| * @param segment The segment of the resolve segment span. | ||
| * @param route The route of the resolve segment span. | ||
| * @returns The enhanced name for the resolve segment span. | ||
| */ | ||
| export function getEnhancedResolveSegmentSpanName({ segment, route }: { segment: string; route: string }): string { | ||
| if (segment === PAGE_SEGMENT) { | ||
| return `resolve page server component "${route}"`; | ||
| } | ||
| if (segment === '') { | ||
| return 'resolve root layout server component'; | ||
| } | ||
| return `resolve layout server component "${segment}"`; | ||
| } | ||
| /** | ||
| * Maybe enhances the span name for a resolve segment span. | ||
| * If the span is not a resolve segment span, this function does nothing. | ||
| * @param activeSpan The active span. | ||
| * @param spanAttributes The attributes of the span to check. | ||
| * @param rootSpanAttributes The attributes of the according root span. | ||
| */ | ||
| export function maybeEnhanceServerComponentSpanName( | ||
| activeSpan: Span, | ||
| spanAttributes: SpanAttributes, | ||
| rootSpanAttributes: SpanAttributes, | ||
| ): void { | ||
| if (!isResolveSegmentSpan(spanAttributes)) { | ||
| return; | ||
| } | ||
| const segment = spanAttributes[ATTR_NEXT_SEGMENT] as string; | ||
| const route = rootSpanAttributes[ATTR_HTTP_ROUTE]; | ||
| const enhancedName = getEnhancedResolveSegmentSpanName({ segment, route: typeof route === 'string' ? route : '' }); | ||
| activeSpan.updateName(enhancedName); | ||
| activeSpan.setAttributes({ | ||
| 'sentry.nextjs.ssr.function.type': segment === PAGE_SEGMENT ? 'Page' : 'Layout', | ||
| 'sentry.nextjs.ssr.function.route': route, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Inconsistent type handling for route span attributeThe | ||
| }); | ||
| activeSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'function.nextjs'); | ||
| } | ||
chargome marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Debug console.log left in test code
A
console.log(transactionEvent?.transaction)statement appears to have been left in the test code, likely from debugging. While this is in test code, it will produce unnecessary output during test runs.