Skip to content

Commit 92a7714

Browse files
committed
fix parent span/trace loss for ignored child spans in otel
1 parent 4c43129 commit 92a7714

File tree

4 files changed

+40
-15
lines changed

4 files changed

+40
-15
lines changed

dev-packages/node-integration-tests/suites/tracing/ignoreSpans-streamed/children/instrument.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Sentry.init({
77
tracesSampleRate: 1.0,
88
transport: loggingTransport,
99
traceLifecycle: 'stream',
10-
ignoreSpans: ['middleware - expressInit', 'custom-to-drop', { op: 'ignored-op' }],
10+
ignoreSpans: ['middleware - expressInit', /custom-to-drop/, { op: 'ignored-op' }],
1111
clientReportFlushInterval: 1_000,
12+
debug: true,
1213
});

dev-packages/node-integration-tests/suites/tracing/ignoreSpans-streamed/children/server.mjs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,24 @@ app.get('/test/express', (_req, res) => {
1919
name: 'custom',
2020
op: 'custom',
2121
},
22-
() => {},
22+
() => {
23+
Sentry.startSpan({ name: 'custom-grandchild', op: 'custom' }, () => {
24+
Sentry.startSpan({ name: 'custom-to-drop-grand-grandchild', op: 'custom' }, () => {
25+
Sentry.startSpan({ name: 'custom-grand-grand-grandchild', op: 'custom' }, () => {});
26+
});
27+
});
28+
Sentry.startSpan({ name: 'custom-grandchild-2', op: 'custom' }, () => {});
29+
},
2330
);
2431
},
25-
Sentry.startSpan({ name: 'name-passes-but-op-not-span-1', op: 'ignored-op' }, () => {}),
26-
Sentry.startSpan(
27-
// sentry.op attribute has precedence over top op argument
28-
{ name: 'name-passes-but-op-not-span-2', op: 'keep', attributes: { 'sentry.op': 'ignored-op' } },
29-
() => {},
30-
),
3132
);
33+
34+
Sentry.startSpan({ name: 'name-passes-but-op-not-span-1', op: 'ignored-op' }, () => {}),
35+
Sentry.startSpan(
36+
// sentry.op attribute has precedence over top op argument
37+
{ name: 'name-passes-but-op-not-span-2', /*op: 'keep',*/ attributes: { 'sentry.op': 'ignored-op' } },
38+
() => {},
39+
),
3240
res.send({ response: 'response 1' });
3341
});
3442

dev-packages/node-integration-tests/suites/tracing/ignoreSpans-streamed/children/test.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe('filtering child spans with ignoreSpans (streaming)', () => {
1616
discarded_events: [
1717
{
1818
category: 'span',
19-
quantity: 4,
19+
quantity: 5,
2020
reason: 'ignored',
2121
},
2222
],
@@ -26,7 +26,7 @@ describe('filtering child spans with ignoreSpans (streaming)', () => {
2626
span: container => {
2727
// 5 spans: 1 root, 2 middleware, 1 request handler, 1 custom
2828
// Would be 7 if we didn't ignore the 'middleware - expressInit' and 'custom-to-drop' spans
29-
expect(container.items).toHaveLength(5);
29+
expect(container.items).toHaveLength(8);
3030
const getSpan = (name: string, op: string) =>
3131
container.items.find(
3232
item => item.name === name && item.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_OP]?.value === op,
@@ -36,13 +36,22 @@ describe('filtering child spans with ignoreSpans (streaming)', () => {
3636
const requestHandlerSpan = getSpan('/test/express', 'request_handler.express');
3737
const httpServerSpan = getSpan('GET /test/express', 'http.server');
3838
const customSpan = getSpan('custom', 'custom');
39+
const customGrandchildSpan = getSpan('custom-grandchild', 'custom');
40+
const customGrandchild2Span = getSpan('custom-grandchild-2', 'custom');
41+
const customGrandGrandGrandChildSpan = getSpan('custom-grand-grand-grandchild', 'custom');
3942

4043
expect(queryMiddlewareSpan).toBeDefined();
4144
expect(corsMiddlewareSpan).toBeDefined();
4245
expect(requestHandlerSpan).toBeDefined();
4346
expect(httpServerSpan).toBeDefined();
4447
expect(customSpan).toBeDefined();
48+
expect(customGrandchildSpan).toBeDefined();
49+
expect(customGrandchild2Span).toBeDefined();
50+
expect(customGrandGrandGrandChildSpan).toBeDefined();
4551

52+
expect(customGrandchildSpan?.parent_span_id).toBe(customSpan?.span_id);
53+
expect(customGrandchild2Span?.parent_span_id).toBe(customSpan?.span_id);
54+
expect(customGrandGrandGrandChildSpan?.parent_span_id).toBe(customGrandchildSpan?.span_id);
4655
expect(customSpan?.parent_span_id).toBe(requestHandlerSpan?.span_id);
4756
expect(requestHandlerSpan?.parent_span_id).toBe(httpServerSpan?.span_id);
4857
expect(queryMiddlewareSpan?.parent_span_id).toBe(httpServerSpan?.span_id);

packages/opentelemetry/src/contextManager.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,19 @@ export function wrapContextManagerClass<ContextManagerInstance extends ContextMa
5959
thisArg?: ThisParameterType<F>,
6060
...args: A
6161
): ReturnType<F> {
62-
// Remove ignored spans from context so children naturally parent to the grandparent
62+
// Remove ignored spans from context and restore the parent span so children
63+
// naturally parent to the grandparent instead of starting a new trace.
64+
// At this point, this.active() still holds the outer context (before super.with()
65+
// updates AsyncLocalStorage), which has the grandparent span we want to restore.
6366
const span = trace.getSpan(context);
64-
const effectiveContext =
65-
span?.spanContext().traceState?.get(SENTRY_TRACE_STATE_CHILD_IGNORED) === '1'
66-
? trace.deleteSpan(context)
67-
: context;
67+
let effectiveContext: Context;
68+
if (span?.spanContext().traceState?.get(SENTRY_TRACE_STATE_CHILD_IGNORED) === '1') {
69+
const contextWithoutSpan = trace.deleteSpan(context);
70+
const parentSpan = trace.getSpan(this.active());
71+
effectiveContext = parentSpan ? trace.setSpan(contextWithoutSpan, parentSpan) : contextWithoutSpan;
72+
} else {
73+
effectiveContext = context;
74+
}
6875

6976
const currentScopes = getScopesFromContext(effectiveContext);
7077
const currentScope = currentScopes?.scope || getCurrentScope();

0 commit comments

Comments
 (0)