-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(core): Add updateSpanName helper function
#14291
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 1 commit
1be63e2
7886728
3028f45
1cfb1c2
ff6001f
914ce83
dcde038
4416722
9534810
5e69e6a
99dc49a
64437ed
1772a77
24e8320
983a48b
f4a3e89
f8b614c
87a4d93
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| 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 bodyParser = require('body-parser'); | ||
| const { startExpressServerAndSendPortToRunner } = require('@sentry-internal/node-integration-tests'); | ||
|
|
||
| const app = express(); | ||
|
|
||
| app.use(cors()); | ||
| app.use(bodyParser.json()); | ||
| app.use(bodyParser.text()); | ||
| app.use(bodyParser.raw()); | ||
|
|
||
| app.get('/test/:id/span-updateName', (_req, res) => { | ||
| const span = Sentry.getActiveSpan(); | ||
| const rootSpan = Sentry.getRootSpan(span); | ||
| rootSpan.updateName('new-name'); | ||
| res.send({ response: 'response 1' }); | ||
| }); | ||
|
|
||
| app.get('/test/:id/span-updateName-source', (_req, res) => { | ||
| const span = Sentry.getActiveSpan(); | ||
| const rootSpan = Sentry.getRootSpan(span); | ||
| rootSpan.updateName('new-name'); | ||
| rootSpan.setAttribute(Sentry.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'custom'); | ||
| res.send({ response: 'response 2' }); | ||
| }); | ||
|
|
||
| app.get('/test/:id/updateSpanName', (_req, res) => { | ||
| const span = Sentry.getActiveSpan(); | ||
| const rootSpan = Sentry.getRootSpan(span); | ||
| Sentry.updateSpanName(rootSpan, 'new-name'); | ||
| res.send({ response: 'response 3' }); | ||
| }); | ||
|
|
||
| Sentry.setupExpressErrorHandler(app); | ||
|
|
||
| startExpressServerAndSendPortToRunner(app); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/node'; | ||
| import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; | ||
|
|
||
| describe('express tracing', () => { | ||
| afterAll(() => { | ||
| cleanupChildProcesses(); | ||
| }); | ||
|
|
||
| describe('CJS', () => { | ||
| // This test documents the unfortunate behaviour of using `span.updateName` on the server-side. | ||
| // For http.server root spans (which is the root span on the server 99% of the time), Otel's http instrumentation | ||
| // calls `span.updateName` and overwrites whatever the name was set to before (by us or by users). | ||
| test("calling just `span.updateName` doesn't update the final name in express (missing source)", done => { | ||
| createRunner(__dirname, 'server.js') | ||
| .expect({ | ||
| transaction: { | ||
| transaction: 'GET /test/:id/span-updateName', | ||
| transaction_info: { | ||
| source: 'route', | ||
| }, | ||
| }, | ||
| }) | ||
| .start(done) | ||
| .makeRequest('get', '/test/123/span-updateName'); | ||
| }); | ||
|
|
||
| // Also calling `updateName` AND setting a source doesn't change anything - Otel has no concept of source, this is sentry-internal. | ||
| // Therefore, only the source is updated but the name is still overwritten by Otel. | ||
| test("calling `span.updateName` and setting attribute source doesn't update the final name in express but it updates the source", done => { | ||
| createRunner(__dirname, 'server.js') | ||
| .expect({ | ||
| transaction: { | ||
| transaction: 'GET /test/:id/span-updateName-source', | ||
| transaction_info: { | ||
| source: 'custom', | ||
| }, | ||
| }, | ||
| }) | ||
| .start(done) | ||
| .makeRequest('get', '/test/123/span-updateName-source'); | ||
| }); | ||
|
|
||
| // This test documents the correct way to update the span name (and implicitly the source) in Node: | ||
| test('calling `Sentry.updateSpanName` updates the final name and source in express', done => { | ||
| createRunner(__dirname, 'server.js') | ||
| .expect({ | ||
| transaction: txnEvent => { | ||
| expect(txnEvent).toMatchObject({ | ||
| transaction: 'new-name', | ||
| transaction_info: { | ||
| source: 'custom', | ||
| }, | ||
| contexts: { | ||
| trace: { | ||
| data: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom' }, | ||
| }, | ||
| }, | ||
| }); | ||
| // ensure we delete the internal attribute once we're done with it | ||
| expect(txnEvent.contexts?.trace?.data?.['_sentry_span_name_set_by_user']).toBeUndefined(); | ||
| }, | ||
| }) | ||
| .start(done) | ||
| .makeRequest('get', '/test/123/updateSpanName'); | ||
| }); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,24 @@ | ||
| import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/node'; | ||
| import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; | ||
|
|
||
| afterAll(() => { | ||
| cleanupChildProcesses(); | ||
| }); | ||
|
|
||
| test('should send a manually started root span', done => { | ||
| test('sends a manually started root span with source custom', done => { | ||
| createRunner(__dirname, 'scenario.ts') | ||
| .expect({ transaction: { transaction: 'test_span' } }) | ||
| .expect({ | ||
| transaction: { | ||
| transaction: 'test_span', | ||
| transaction_info: { source: 'custom' }, | ||
| contexts: { | ||
| trace: { | ||
| span_id: expect.any(String), | ||
| trace_id: expect.any(String), | ||
| data: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom' }, | ||
| }, | ||
| }, | ||
| }, | ||
| }) | ||
| .start(done); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import { loggingTransport } from '@sentry-internal/node-integration-tests'; | ||
| import * as Sentry from '@sentry/node'; | ||
|
|
||
| Sentry.init({ | ||
| dsn: 'https://[email protected]/1337', | ||
| release: '1.0', | ||
| tracesSampleRate: 1.0, | ||
| transport: loggingTransport, | ||
| }); | ||
|
|
||
| Sentry.startSpan( | ||
| { name: 'test_span', attributes: { [Sentry.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url' } }, | ||
| (span: Sentry.Span) => { | ||
| span.updateName('new name'); | ||
| }, | ||
| ); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/node'; | ||
| import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; | ||
|
|
||
| afterAll(() => { | ||
| cleanupChildProcesses(); | ||
| }); | ||
|
|
||
| test('updates the span name when calling `span.updateName`', done => { | ||
| createRunner(__dirname, 'scenario.ts') | ||
| .expect({ | ||
| transaction: { | ||
| transaction: 'new name', | ||
| transaction_info: { source: 'url' }, | ||
| contexts: { | ||
| trace: { | ||
| span_id: expect.any(String), | ||
| trace_id: expect.any(String), | ||
| data: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url' }, | ||
| }, | ||
| }, | ||
| }, | ||
| }) | ||
| .start(done); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import { loggingTransport } from '@sentry-internal/node-integration-tests'; | ||
| import * as Sentry from '@sentry/node'; | ||
|
|
||
| Sentry.init({ | ||
| dsn: 'https://[email protected]/1337', | ||
| release: '1.0', | ||
| tracesSampleRate: 1.0, | ||
| transport: loggingTransport, | ||
| }); | ||
|
|
||
| Sentry.startSpan( | ||
| { name: 'test_span', attributes: { [Sentry.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url' } }, | ||
| (span: Sentry.Span) => { | ||
| Sentry.updateSpanName(span, 'new name'); | ||
| }, | ||
| ); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/node'; | ||
| import { cleanupChildProcesses, createRunner } from '../../../../utils/runner'; | ||
|
|
||
| afterAll(() => { | ||
| cleanupChildProcesses(); | ||
| }); | ||
|
|
||
| test('updates the span name and source when calling `updateSpanName`', done => { | ||
| createRunner(__dirname, 'scenario.ts') | ||
| .expect({ | ||
| transaction: { | ||
| transaction: 'new name', | ||
| transaction_info: { source: 'custom' }, | ||
| contexts: { | ||
| trace: { | ||
| span_id: expect.any(String), | ||
| trace_id: expect.any(String), | ||
| data: { [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom' }, | ||
| }, | ||
| }, | ||
| }, | ||
| }) | ||
| .start(done); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,12 +37,12 @@ interface SpanDescription { | |
| /** | ||
| * Infer the op & description for a set of name, attributes and kind of a span. | ||
| */ | ||
| export function inferSpanData(originalName: string, attributes: SpanAttributes, kind: SpanKind): SpanDescription { | ||
| export function inferSpanData(spanName: string, attributes: SpanAttributes, kind: SpanKind): SpanDescription { | ||
| // if http.method exists, this is an http request span | ||
| // eslint-disable-next-line deprecation/deprecation | ||
| const httpMethod = attributes[ATTR_HTTP_REQUEST_METHOD] || attributes[SEMATTRS_HTTP_METHOD]; | ||
| if (httpMethod) { | ||
| return descriptionForHttpMethod({ attributes, name: originalName, kind }, httpMethod); | ||
| return descriptionForHttpMethod({ attributes, name: getOriginalName(spanName, attributes), kind }, httpMethod); | ||
|
||
| } | ||
|
|
||
| // eslint-disable-next-line deprecation/deprecation | ||
|
|
@@ -54,7 +54,7 @@ export function inferSpanData(originalName: string, attributes: SpanAttributes, | |
| // If db.type exists then this is a database call span | ||
| // If the Redis DB is used as a cache, the span description should not be changed | ||
| if (dbSystem && !opIsCache) { | ||
| return descriptionForDbSystem({ attributes, name: originalName }); | ||
| return descriptionForDbSystem({ attributes, name: spanName }); | ||
| } | ||
|
|
||
| const customSourceOrRoute = attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] === 'custom' ? 'custom' : 'route'; | ||
|
|
@@ -65,7 +65,7 @@ export function inferSpanData(originalName: string, attributes: SpanAttributes, | |
| if (rpcService) { | ||
| return { | ||
| op: 'rpc', | ||
| description: originalName, | ||
| description: getOriginalName(spanName, attributes), | ||
| source: customSourceOrRoute, | ||
| }; | ||
| } | ||
|
|
@@ -76,7 +76,7 @@ export function inferSpanData(originalName: string, attributes: SpanAttributes, | |
| if (messagingSystem) { | ||
| return { | ||
| op: 'message', | ||
| description: originalName, | ||
| description: getOriginalName(spanName, attributes), | ||
| source: customSourceOrRoute, | ||
| }; | ||
| } | ||
|
|
@@ -85,10 +85,14 @@ export function inferSpanData(originalName: string, attributes: SpanAttributes, | |
| // eslint-disable-next-line deprecation/deprecation | ||
| const faasTrigger = attributes[SEMATTRS_FAAS_TRIGGER]; | ||
| if (faasTrigger) { | ||
| return { op: faasTrigger.toString(), description: originalName, source: customSourceOrRoute }; | ||
| return { | ||
| op: faasTrigger.toString(), | ||
| description: getOriginalName(spanName, attributes), | ||
| source: customSourceOrRoute, | ||
| }; | ||
| } | ||
|
|
||
| return { op: undefined, description: originalName, source: 'custom' }; | ||
| return { op: undefined, description: spanName, source: 'custom' }; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -111,7 +115,7 @@ export function parseSpanDescription(span: AbstractSpan): SpanDescription { | |
| function descriptionForDbSystem({ attributes, name }: { attributes: Attributes; name: string }): SpanDescription { | ||
| // if we already set the source to custom, we don't overwrite the span description but just adjust the op | ||
| if (attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] === 'custom') { | ||
| return { op: 'db', description: name, source: 'custom' }; | ||
| return { op: 'db', description: getOriginalName(name, attributes), source: 'custom' }; | ||
| } | ||
|
|
||
| // Use DB statement (Ex "SELECT * FROM table") if possible as description. | ||
|
|
@@ -147,7 +151,7 @@ export function descriptionForHttpMethod( | |
| const { urlPath, url, query, fragment, hasRoute } = getSanitizedUrl(attributes, kind); | ||
|
|
||
| if (!urlPath) { | ||
| return { op: opParts.join('.'), description: name, source: 'custom' }; | ||
| return { op: opParts.join('.'), description: getOriginalName(name, attributes), source: 'custom' }; | ||
| } | ||
|
|
||
| const graphqlOperationsAttribute = attributes[SEMANTIC_ATTRIBUTE_SENTRY_GRAPHQL_OPERATION]; | ||
|
|
@@ -193,7 +197,7 @@ export function descriptionForHttpMethod( | |
|
|
||
| return { | ||
| op: opParts.join('.'), | ||
| description: useInferredDescription ? description : name, | ||
| description: useInferredDescription ? description : getOriginalName(name, attributes), | ||
| source: useInferredDescription ? source : 'custom', | ||
| data, | ||
| }; | ||
|
|
@@ -259,3 +263,11 @@ export function getSanitizedUrl( | |
|
|
||
| return { urlPath: undefined, url, query, fragment, hasRoute: false }; | ||
| } | ||
|
|
||
| function getOriginalName(name: string, attributes: Attributes): string { | ||
| return attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] === 'custom' && | ||
Lms24 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| attributes['_sentry_span_name_set_by_user'] && | ||
| typeof attributes['_sentry_span_name_set_by_user'] === 'string' | ||
| ? attributes['_sentry_span_name_set_by_user'] | ||
| : name; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.