Skip to content
File

Blob: src/cloudflare/internal/test/tracing/tracing-log-attribution-test.js

javascript84 lines
1// Copyright (c) 2026 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5// Tests that non-span tail events (Log, Exception, DiagnosticChannelEvent) emitted
6// to a streaming tail worker carry the spanContext of the currently-active user
7// span (pushed via ctx.tracing.enterSpan / withSpan), rather than always pointing
8// at the root invocation span. See EW-10672.
9//
10// The companion tail worker in tracing-log-attribution-instrumentation-test.js
11// collects the streaming tail events and runs the assertions in a follow-up
12// `validate` test.
13 
14import { channel } from 'node:diagnostics_channel';
15 
16export const logInsideEnterSpan = {
17 async test(ctrl, env, ctx) {
18 const { withSpan } = env.tracingTest;
19 // Baseline: a log inside an enterSpan must be attributed to that span.
20 withSpan('log-attr-single', (span) => {
21 span.setAttribute('case', 'logInsideEnterSpan');
22 console.log('log-attr-single-message');
23 });
24 },
25};
26 
27export const logInsideNestedEnterSpan = {
28 async test(ctrl, env, ctx) {
29 const { withSpan } = env.tracingTest;
30 // Nested spans: the log inside the inner span must be attributed to the inner
31 // span (not the outer, and not the root invocation).
32 withSpan('log-attr-outer', (outer) => {
33 outer.setAttribute('case', 'logInsideNestedEnterSpan');
34 outer.setAttribute('role', 'outer');
35 // Log inside outer (before opening inner) -- attributed to outer.
36 console.log('log-attr-outer-before-inner');
37 withSpan('log-attr-inner', (inner) => {
38 inner.setAttribute('case', 'logInsideNestedEnterSpan');
39 inner.setAttribute('role', 'inner');
40 console.log('log-attr-inner-message');
41 });
42 // After inner closes, this log should be re-attributed to outer.
43 console.log('log-attr-outer-after-inner');
44 });
45 },
46};
47 
48export const logAcrossAwait = {
49 async test(ctrl, env, ctx) {
50 const { withSpan } = env.tracingTest;
51 // Log inside an async enterSpan AFTER an await. The fix only works if the
52 // AsyncContextFrame carrying the user span propagates across microtasks.
53 await withSpan('log-attr-async', async (span) => {
54 span.setAttribute('case', 'logAcrossAwait');
55 await new Promise((r) => setTimeout(r, 5));
56 console.log('log-attr-async-message');
57 });
58 },
59};
60 
61export const logOutsideAnySpan = {
62 async test(ctrl, env, ctx) {
63 // Logs outside any enterSpan should still land at the top-level onset span
64 // (i.e., their parentSpanId equals the root). This confirms we haven't
65 // broken the fall-back path.
66 console.log('log-attr-root-message');
67 },
68};
69 
70export const diagnosticsChannelInsideEnterSpan = {
71 async test(ctrl, env, ctx) {
72 const { withSpan } = env.tracingTest;
73 // diagnostics_channel.publish() emits a DiagnosticChannelEvent tail event when
74 // there is at least one subscriber. The event should be attributed to the
75 // enclosing user span.
76 const ch = channel('log-attr-dc-channel');
77 ch.subscribe(() => {});
78 withSpan('log-attr-dc', (span) => {
79 span.setAttribute('case', 'diagnosticsChannelInsideEnterSpan');
80 ch.publish({ hello: 'world' });
81 });
82 },
83};