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: Add mechanism to promote spans to transactions #11332

Closed
wants to merge 1 commit into from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/nextjs/package.json
Expand Up @@ -35,12 +35,14 @@
"access": "public"
},
"dependencies": {
"@opentelemetry/api": "1.7.0",
"@rollup/plugin-commonjs": "24.0.0",
"@sentry/core": "8.0.0-alpha.7",
"@sentry/node": "8.0.0-alpha.7",
"@sentry/react": "8.0.0-alpha.7",
"@sentry/types": "8.0.0-alpha.7",
"@sentry/utils": "8.0.0-alpha.7",
"@sentry/opentelemetry": "8.0.0-alpha.7",
"@sentry/vercel-edge": "8.0.0-alpha.7",
"@sentry/webpack-plugin": "2.16.0",
"chalk": "3.0.0",
Expand Down
5 changes: 4 additions & 1 deletion packages/nextjs/src/server/index.ts
Expand Up @@ -12,6 +12,7 @@ import { onUncaughtExceptionIntegration } from './onUncaughtExceptionIntegration

export * from '@sentry/node';
import type { EventProcessor } from '@sentry/types';
import { promoteHttpSpansIntegration } from './promoteHttpSpansIntegration';

export { captureUnderscoreErrorException } from '../common/_error';
export { onUncaughtExceptionIntegration } from './onUncaughtExceptionIntegration';
Expand Down Expand Up @@ -76,8 +77,10 @@ export function init(options: NodeOptions): void {
integration =>
integration.name !== 'OnUncaughtException' &&
// Next.js comes with its own Node-Fetch instrumentation so we shouldn't add ours on-top
integration.name !== 'NodeFetch',
integration.name !== 'NodeFetch' &&
integration.name !== 'Http',
),
promoteHttpSpansIntegration(),
onUncaughtExceptionIntegration(),
];

Expand Down
17 changes: 17 additions & 0 deletions packages/nextjs/src/server/promoteHttpSpansIntegration.ts
@@ -0,0 +1,17 @@
import { SpanKind } from '@opentelemetry/api';
import { defineIntegration, spanToJSON } from '@sentry/core';
import { getSpanKind } from '@sentry/opentelemetry';

export const promoteHttpSpansIntegration = defineIntegration(() => ({
name: 'PromoteHttpSpansIntegration',
setup(client) {
client.on('spanStart', span => {
const spanJson = spanToJSON(span);

// The following check is a heuristic to determine whether the started span is a span that tracks an incoming HTTP request
if (getSpanKind(span) === SpanKind.SERVER && spanJson.data && 'http.method' in spanJson.data) {
span.setAttribute('sentry.promoteToTransaction', true);
}
});
},
}));
3 changes: 3 additions & 0 deletions packages/opentelemetry/src/semanticAttributes.ts
@@ -1,2 +1,5 @@
/** If this attribute is true, it means that the parent is a remote span. */
export const SEMANTIC_ATTRIBUTE_SENTRY_PARENT_IS_REMOTE = 'sentry.parentIsRemote';

/** If this attribute is true, it means that the span should be promoted to a transaction before being sent to sentry. */
export const SEMANTIC_ATTRIBUTE_SENTRY_PROMOTE_TO_TRANSACTION = 'sentry.promoteToTransaction';
8 changes: 6 additions & 2 deletions packages/opentelemetry/src/spanExporter.ts
Expand Up @@ -14,7 +14,10 @@ import type { SpanJSON, SpanOrigin, TraceContext, TransactionEvent, TransactionS
import { dropUndefinedKeys, logger } from '@sentry/utils';

import { DEBUG_BUILD } from './debug-build';
import { SEMANTIC_ATTRIBUTE_SENTRY_PARENT_IS_REMOTE } from './semanticAttributes';
import {
SEMANTIC_ATTRIBUTE_SENTRY_PARENT_IS_REMOTE,
SEMANTIC_ATTRIBUTE_SENTRY_PROMOTE_TO_TRANSACTION,
} from './semanticAttributes';
import { convertOtelTimeToSeconds } from './utils/convertOtelTimeToSeconds';
import { getDynamicSamplingContextFromSpan } from './utils/dynamicSamplingContext';
import { getRequestSpanData } from './utils/getRequestSpanData';
Expand Down Expand Up @@ -149,7 +152,7 @@ function maybeSend(spans: ReadableSpan[]): ReadableSpan[] {
}

function nodeIsCompletedRootNode(node: SpanNode): node is SpanNodeCompleted {
return !!node.span && !node.parentNode;
return (!!node.span && !node.parentNode) || !!node.span?.attributes[SEMANTIC_ATTRIBUTE_SENTRY_PROMOTE_TO_TRANSACTION];
}

function getCompletedRootNodes(nodes: SpanNode[]): SpanNodeCompleted[] {
Expand Down Expand Up @@ -319,6 +322,7 @@ function removeSentryAttributes(data: Record<string, unknown>): Record<string, u
/* eslint-disable @typescript-eslint/no-dynamic-delete */
delete cleanedData[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE];
delete cleanedData[SEMANTIC_ATTRIBUTE_SENTRY_PARENT_IS_REMOTE];
delete cleanedData[SEMANTIC_ATTRIBUTE_SENTRY_PROMOTE_TO_TRANSACTION];
/* eslint-enable @typescript-eslint/no-dynamic-delete */

return cleanedData;
Expand Down
53 changes: 46 additions & 7 deletions yarn.lock
Expand Up @@ -6728,8 +6728,17 @@
dependencies:
"@types/unist" "*"

"@types/history-4@npm:@types/history@4.7.8", "@types/history-5@npm:@types/history@4.7.8", "@types/history@*":
name "@types/history-4"
"@types/history-4@npm:@types/history@4.7.8":
version "4.7.8"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934"
integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==

"@types/history-5@npm:@types/history@4.7.8":
version "4.7.8"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934"
integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==

"@types/history@*":
version "4.7.8"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934"
integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==
Expand Down Expand Up @@ -7114,7 +7123,15 @@
"@types/history" "^3"
"@types/react" "*"

"@types/react-router-4@npm:@types/react-router@5.1.14", "@types/react-router-5@npm:@types/react-router@5.1.14":
"@types/react-router-4@npm:@types/react-router@5.1.14":
version "5.1.14"
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.14.tgz#e0442f4eb4c446541ad7435d44a97f8fe6df40da"
integrity sha512-LAJpqYUaCTMT2anZheoidiIymt8MuX286zoVFPM3DVb23aQBH0mAkFvzpd4LKqiolV8bBtZWT5Qp7hClCNDENw==
dependencies:
"@types/history" "*"
"@types/react" "*"

"@types/react-router-5@npm:@types/react-router@5.1.14":
version "5.1.14"
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.14.tgz#e0442f4eb4c446541ad7435d44a97f8fe6df40da"
integrity sha512-LAJpqYUaCTMT2anZheoidiIymt8MuX286zoVFPM3DVb23aQBH0mAkFvzpd4LKqiolV8bBtZWT5Qp7hClCNDENw==
Expand Down Expand Up @@ -25287,8 +25304,7 @@ react-is@^18.0.0:
dependencies:
"@remix-run/router" "1.0.2"

"react-router-6@npm:react-router@6.3.0", react-router@6.3.0:
name react-router-6
"react-router-6@npm:react-router@6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557"
integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==
Expand All @@ -25303,6 +25319,13 @@ react-router-dom@^6.2.2:
history "^5.2.0"
react-router "6.3.0"

react-router@6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557"
integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==
dependencies:
history "^5.2.0"

react@^18.0.0:
version "18.0.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.0.0.tgz#b468736d1f4a5891f38585ba8e8fb29f91c3cb96"
Expand Down Expand Up @@ -27777,7 +27800,7 @@ stringify-object@^3.2.1:
is-obj "^1.0.1"
is-regexp "^1.0.0"

"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
Expand Down Expand Up @@ -27805,6 +27828,13 @@ strip-ansi@^5.1.0, strip-ansi@^5.2.0:
dependencies:
ansi-regex "^4.1.0"

strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"

strip-ansi@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2"
Expand Down Expand Up @@ -30455,7 +30485,16 @@ workerpool@^6.4.0:
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.4.0.tgz#f8d5cfb45fde32fa3b7af72ad617c3369567a462"
integrity sha512-i3KR1mQMNwY2wx20ozq2EjISGtQWDIfV56We+yGJ5yDs8jTwQiLLaqHlkBHITlCuJnYlVRmXegxFxZg7gqI++A==

"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@7.0.0, wrap-ansi@^5.1.0, wrap-ansi@^6.2.0, wrap-ansi@^7.0.0, wrap-ansi@^8.1.0:
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"

wrap-ansi@7.0.0, wrap-ansi@^5.1.0, wrap-ansi@^6.2.0, wrap-ansi@^7.0.0, wrap-ansi@^8.1.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
Expand Down