Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
node tests and jsdoc
  • Loading branch information
Lms24 committed Dec 13, 2024
commit 3028f4588b39bc32e1ca23315a6a51d088f2090e
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
Expand Up @@ -169,4 +169,10 @@ describe('httpIntegration', () => {
runner.makeRequest('get', '/testRequest');
});
});

describe('does not override the span name set by the user', () => {
test('via `span.updateName`', done => {
createRunner(__dirname, 'server-updateName.js').start(done);
});
});
});
8 changes: 8 additions & 0 deletions packages/core/src/envelope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ export function createEventEnvelope(
// have temporarily added, etc. Even if we don't happen to be using it at some point in the future, let's not get rid
// of this `delete`, lest we miss putting it back in the next time the property is in use.)
delete event.sdkProcessingMetadata;
try {
// @ts-expect-error - for bundle size we try/catch the access to this property
delete event.contexts.trace.data._sentry_span_name_set_by_user;
// @ts-expect-error - for bundle size we try/catch the access to this property
event.spans.forEach(span => delete span.data._sentry_span_name_set_by_user);
} catch {
// Do nothing
}

const eventItem: EventItem = [{ type: eventType }, event];
return createEnvelope<EventEnvelope>(envelopeHeaders, [eventItem]);
Expand Down
16 changes: 16 additions & 0 deletions packages/core/src/types-hoist/span.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,22 @@ export interface Span {

/**
* Update the name of the span.
*
* **Important:** You most likely want to use `Sentry.updateSpanName(span, name)` instead!
*
* This method will update the current span name but cannot guarantee that the new name will be
* the final name of the span. Instrumentation might still overwrite the name with an automatically
* computed name, for example in `http.server` or `db` spans.
*
* You can ensure that your name is kept and not overwritten by
* - either calling `Sentry.updateSpanName(span, name)`
* - or by calling `span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'custom')`
* in addition to `span.updateName`.
*
* If you want to update a span name in a browser-only app, `span.updateName` and `Sentry.updateSpanName`
* are identical: In both cases, the name will not be overwritten by the Sentry SDK.
*
* @param name the new name of the span
*/
updateName(name: string): this;

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/utils/spanUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,4 +334,5 @@ export function showSpanDropWarning(): void {
export function updateSpanName(span: Span, name: string): void {
span.updateName(name);
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'custom');
span.setAttribute('_sentry_span_name_set_by_user', name);
}
32 changes: 22 additions & 10 deletions packages/opentelemetry/src/utils/parseSpanDescription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m: Instead of repeating getOriginalName, can we just do this at the very top of this method and then use this processed name everywhere below?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to do this initially but then we'd potentially lose the inferred op, no?

}

// eslint-disable-next-line deprecation/deprecation
Expand All @@ -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';
Expand All @@ -65,7 +65,7 @@ export function inferSpanData(originalName: string, attributes: SpanAttributes,
if (rpcService) {
return {
op: 'rpc',
description: originalName,
description: getOriginalName(spanName, attributes),
source: customSourceOrRoute,
};
}
Expand All @@ -76,7 +76,7 @@ export function inferSpanData(originalName: string, attributes: SpanAttributes,
if (messagingSystem) {
return {
op: 'message',
description: originalName,
description: getOriginalName(spanName, attributes),
source: customSourceOrRoute,
};
}
Expand All @@ -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' };
}

/**
Expand All @@ -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.
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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,
};
Expand Down Expand Up @@ -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' &&
attributes['_sentry_span_name_set_by_user'] &&
typeof attributes['_sentry_span_name_set_by_user'] === 'string'
? attributes['_sentry_span_name_set_by_user']
: name;
}