-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(node): Update scope transactionName
in http and express instrumentations
#11434
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
const { loggingTransport } = require('@sentry-internal/node-integration-tests'); | ||
const Sentry = require('@sentry/node'); | ||
|
||
Sentry.init({ | ||
dsn: 'https://[email protected]/1337', | ||
release: '1.0', | ||
// disable attaching headers to /test/* endpoints | ||
tracePropagationTargets: [/^(?!.*test).*$/], | ||
tracesSampleRate: 1.0, | ||
transport: loggingTransport, | ||
}); | ||
|
||
// express must be required after Sentry is initialized | ||
const express = require('express'); | ||
const cors = require('cors'); | ||
const { startExpressServerAndSendPortToRunner } = require('@sentry-internal/node-integration-tests'); | ||
|
||
const app = express(); | ||
|
||
app.use(cors()); | ||
|
||
app.get('/test/:id1/:id2', (_req, res) => { | ||
Sentry.captureMessage(new Error('error_1')); | ||
res.send('Success'); | ||
}); | ||
|
||
Sentry.setupExpressErrorHandler(app); | ||
|
||
startExpressServerAndSendPortToRunner(app); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; | ||
|
||
describe('express tracing experimental', () => { | ||
afterAll(() => { | ||
cleanupChildProcesses(); | ||
}); | ||
|
||
describe('CJS', () => { | ||
test('should apply the scope transactionName to error events', done => { | ||
createRunner(__dirname, 'server.js') | ||
.ignore('session', 'sessions', 'transaction') | ||
.expect({ | ||
event: { | ||
exception: { | ||
values: [ | ||
{ | ||
value: 'error_1', | ||
}, | ||
], | ||
}, | ||
transaction: 'GET /test/:id1/:id2', | ||
}, | ||
}) | ||
.start(done) | ||
.makeRequest('get', '/test/123/abc?q=1'); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,10 +4,11 @@ import { SpanKind } from '@opentelemetry/api'; | |
import { registerInstrumentations } from '@opentelemetry/instrumentation'; | ||
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'; | ||
|
||
import { addBreadcrumb, defineIntegration, getIsolationScope, isSentryRequestUrl } from '@sentry/core'; | ||
import { addBreadcrumb, defineIntegration, getIsolationScope, isSentryRequestUrl, spanToJSON } from '@sentry/core'; | ||
import { _INTERNAL, getClient, getSpanKind } from '@sentry/opentelemetry'; | ||
import type { IntegrationFn } from '@sentry/types'; | ||
|
||
import { stripUrlQueryAndFragment } from '@sentry/utils'; | ||
import type { NodeClient } from '../sdk/client'; | ||
import { setIsolationScope } from '../sdk/scope'; | ||
import type { HTTPModuleRequestIncomingMessage } from '../transports/http-module'; | ||
|
@@ -81,19 +82,35 @@ const _httpIntegration = ((options: HttpOptions = {}) => { | |
requireParentforOutgoingSpans: true, | ||
requireParentforIncomingSpans: false, | ||
requestHook: (span, req) => { | ||
_updateSpan(span); | ||
addOriginToSpan(span, 'auto.http.otel.http'); | ||
|
||
if (getSpanKind(span) !== SpanKind.SERVER) { | ||
return; | ||
} | ||
|
||
// Update the isolation scope, isolate this request | ||
if (getSpanKind(span) === SpanKind.SERVER) { | ||
const isolationScope = getIsolationScope().clone(); | ||
isolationScope.setSDKProcessingMetadata({ request: req }); | ||
|
||
const client = getClient<NodeClient>(); | ||
if (client && client.getOptions().autoSessionTracking) { | ||
isolationScope.setRequestSession({ status: 'ok' }); | ||
} | ||
setIsolationScope(isolationScope); | ||
const isolationScope = getIsolationScope().clone(); | ||
isolationScope.setSDKProcessingMetadata({ request: req }); | ||
|
||
const client = getClient<NodeClient>(); | ||
if (client && client.getOptions().autoSessionTracking) { | ||
isolationScope.setRequestSession({ status: 'ok' }); | ||
} | ||
setIsolationScope(isolationScope); | ||
|
||
// attempt to update the scope's `transactionName` based on the request URL | ||
// Ideally, framework instrumentations coming after the HttpInstrumentation | ||
// update the transactionName once we get a parameterized route. | ||
const attributes = spanToJSON(span).data; | ||
if (!attributes) { | ||
return; | ||
} | ||
|
||
const httpMethod = String(attributes['http.method']).toUpperCase() || 'GET'; | ||
const httpTarget = stripUrlQueryAndFragment(String(attributes['http.target'])) || '/'; | ||
const bestEffortTransactionName = `${httpMethod} ${httpTarget}`; | ||
|
||
isolationScope.setTransactionName(bestEffortTransactionName); | ||
}, | ||
responseHook: (span, res) => { | ||
if (_breadcrumbs) { | ||
|
@@ -123,11 +140,6 @@ const _httpIntegration = ((options: HttpOptions = {}) => { | |
*/ | ||
export const httpIntegration = defineIntegration(_httpIntegration); | ||
|
||
/** Update the span with data we need. */ | ||
function _updateSpan(span: Span): void { | ||
addOriginToSpan(span, 'auto.http.otel.http'); | ||
} | ||
Comment on lines
-126
to
-129
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. slight refactor (can also revert if reviewers prefer): This function is just a one-liner, being called once, so I inlined it. |
||
|
||
/** Add a breadcrumb for outgoing requests. */ | ||
function _addRequestBreadcrumb(span: Span, response: HTTPModuleRequestIncomingMessage | ServerResponse): void { | ||
if (getSpanKind(span) !== SpanKind.CLIENT) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,15 @@ const _expressIntegration = (() => { | |
requestHook(span) { | ||
addOriginToSpan(span, 'auto.http.otel.express'); | ||
}, | ||
spanNameHook(info, defaultName) { | ||
if (info.layerType === 'request_handler') { | ||
// type cast b/c Otel unfortunately types info.request as any :( | ||
const req = info.request as { method?: string }; | ||
const method = req.method ? req.method.toUpperCase() : 'GET'; | ||
getIsolationScope().setTransactionName(`${method} ${info.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. I guess there's also a case to be made that this should be set on the current scope instead. |
||
} | ||
return defaultName; | ||
}, | ||
}), | ||
], | ||
}); | ||
|
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.
Side note: This is not the only integration test that covers
event.transaction
values in Express. There are a bunch more but they didn't change because changes 1 and 3 (see description) cancel each other out. Which IMO is good news :D