Skip to content
This repository was archived by the owner on Nov 21, 2025. It is now read-only.

Commit 6b427ab

Browse files
authored
fix!: always assign a trace ID to each request (#1033)
BREAKING CHANGE: When initialized with `clsMechanism: 'none'`, calling `Tracer#createChildSpan` will potentially result in a warning, as these spans are considered to be uncorrelated. To ensure that warnings do not occur, disable any plugins that patch modules that create outgoing RPCs (gRPC, HTTP client and database calls). (Use of the custom span API `Tracer#createChildSpan` is not recommended in this configuration -- use `RootSpan#createChildSpan` instead.)
1 parent 23a990a commit 6b427ab

File tree

9 files changed

+112
-53
lines changed

9 files changed

+112
-53
lines changed

src/cls.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { SingularCLS } from './cls/singular';
2525
import { SpanType } from './constants';
2626
import { Logger } from './logger';
2727
import { RootSpan } from './plugin-types';
28-
import { UNCORRELATED_ROOT_SPAN, UNTRACED_ROOT_SPAN } from './span-data';
28+
import { UNCORRELATED_ROOT_SPAN, DISABLED_ROOT_SPAN } from './span-data';
2929
import { Trace, TraceSpan } from './trace';
3030
import { Singleton } from './util';
3131

@@ -38,7 +38,7 @@ export interface RealRootContext {
3838
}
3939

4040
export interface PhantomRootContext {
41-
readonly type: SpanType.UNCORRELATED | SpanType.UNTRACED;
41+
readonly type: SpanType.UNCORRELATED | SpanType.UNSAMPLED | SpanType.DISABLED;
4242
}
4343

4444
/**
@@ -104,7 +104,7 @@ export class TraceCLS implements CLS<RootContext> {
104104
private enabled = false;
105105

106106
static UNCORRELATED: RootContext = UNCORRELATED_ROOT_SPAN;
107-
static UNTRACED: RootContext = UNTRACED_ROOT_SPAN;
107+
static DISABLED: RootContext = DISABLED_ROOT_SPAN;
108108

109109
/**
110110
* Stack traces are captured when a root span is started. Because the stack
@@ -147,7 +147,7 @@ export class TraceCLS implements CLS<RootContext> {
147147
this.logger.info(
148148
`TraceCLS#constructor: Created [${config.mechanism}] CLS instance.`
149149
);
150-
this.currentCLS = new NullCLS(TraceCLS.UNTRACED);
150+
this.currentCLS = new NullCLS(TraceCLS.DISABLED);
151151
this.currentCLS.enable();
152152
}
153153

@@ -156,9 +156,7 @@ export class TraceCLS implements CLS<RootContext> {
156156
}
157157

158158
enable(): void {
159-
// if this.CLSClass = NullCLS, the user specifically asked not to use
160-
// any context propagation mechanism. So nothing should change.
161-
if (!this.enabled && this.CLSClass !== NullCLS) {
159+
if (!this.enabled) {
162160
this.logger.info('TraceCLS#enable: Enabling CLS.');
163161
this.currentCLS.disable();
164162
this.currentCLS = new this.CLSClass(TraceCLS.UNCORRELATED);
@@ -171,7 +169,7 @@ export class TraceCLS implements CLS<RootContext> {
171169
if (this.enabled && this.CLSClass !== NullCLS) {
172170
this.logger.info('TraceCLS#disable: Disabling CLS.');
173171
this.currentCLS.disable();
174-
this.currentCLS = new NullCLS(TraceCLS.UNTRACED);
172+
this.currentCLS = new NullCLS(TraceCLS.DISABLED);
175173
this.currentCLS.enable();
176174
}
177175
this.enabled = false;

src/constants.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,17 @@ export enum SpanType {
5757
UNCORRELATED = 'UNCORRELATED',
5858

5959
/**
60-
* This span object was created in circumstances where a trace span could not
61-
* be created for one of the following reasons:
62-
* (1) The Trace Agent is disabled, either explicitly or because a project ID
63-
* couldn't be determined.
64-
* (2) The configured tracing policy disallows tracing for this request
65-
* (due to sampling restrictions, ignored URLs, etc.)
66-
* (3) The current incoming request contains trace context headers that
67-
* explicitly disable local tracing for the request.
60+
* This span object was created by a disabled Trace Agent, either explicitly
61+
* or because a project ID couldn't be determined.
62+
*/
63+
DISABLED = 'DISABLED',
64+
65+
/**
66+
* This span object represents an unsampled request, and will not be
67+
* published.
6868
* Getting a span object of this type should not be considered an error.
6969
*/
70-
UNTRACED = 'UNTRACED',
70+
UNSAMPLED = 'UNSAMPLED',
7171

7272
/**
7373
* This span object was created by StackdriverTracer#runInRootSpan, and

src/span-data.ts

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import * as crypto from 'crypto';
1818
import * as util from 'util';
1919

2020
import { Constants, SpanType } from './constants';
21-
import { RootSpan, Span, SpanOptions } from './plugin-types';
21+
import { RootSpan, Span, SpanOptions, TraceContext } from './plugin-types';
2222
import { SpanKind, Trace, TraceSpan } from './trace';
2323
import { TraceLabels } from './trace-labels';
2424
import { traceWriter } from './trace-writer';
@@ -226,6 +226,47 @@ function createPhantomSpanData<T extends SpanType>(
226226
);
227227
}
228228

229+
/**
230+
* Helper (and base) class for UntracedRootSpanData. Represents an untraced
231+
* child span.
232+
*/
233+
class UntracedSpanData implements Span {
234+
readonly type = SpanType.UNSAMPLED;
235+
protected readonly traceContext: TraceContext;
236+
237+
constructor(traceId: string) {
238+
this.traceContext = {
239+
traceId,
240+
spanId: randomSpanId(),
241+
options: 0, // Not traced.
242+
};
243+
}
244+
245+
getTraceContext(): traceUtil.TraceContext | null {
246+
return this.traceContext;
247+
}
248+
249+
// No-op.
250+
addLabel(): void {}
251+
// No-op.
252+
endSpan(): void {}
253+
}
254+
255+
/**
256+
* Represents an "untraced" root span (aka not published).
257+
* For distributed trace context propagation purposes.
258+
*/
259+
export class UntracedRootSpanData extends UntracedSpanData implements RootSpan {
260+
private child: Span | null = null;
261+
262+
createChildSpan(): Span {
263+
if (!this.child) {
264+
this.child = new UntracedSpanData(this.traceContext.traceId);
265+
}
266+
return this.child;
267+
}
268+
}
269+
229270
/**
230271
* A virtual trace span that indicates that a real child span couldn't be
231272
* created because the correct root span couldn't be determined.
@@ -236,10 +277,9 @@ export const UNCORRELATED_CHILD_SPAN = createPhantomSpanData(
236277

237278
/**
238279
* A virtual trace span that indicates that a real child span couldn't be
239-
* created because the corresponding root span was disallowed by user
240-
* configuration.
280+
* created because the Trace Agent was disabled.
241281
*/
242-
export const UNTRACED_CHILD_SPAN = createPhantomSpanData(SpanType.UNTRACED);
282+
export const DISABLED_CHILD_SPAN = createPhantomSpanData(SpanType.DISABLED);
243283

244284
/**
245285
* A virtual trace span that indicates that a real root span couldn't be
@@ -260,13 +300,13 @@ export const UNCORRELATED_ROOT_SPAN = Object.freeze(
260300
* A virtual trace span that indicates that a real root span couldn't be
261301
* created because it was disallowed by user configuration.
262302
*/
263-
export const UNTRACED_ROOT_SPAN = Object.freeze(
303+
export const DISABLED_ROOT_SPAN = Object.freeze(
264304
Object.assign(
265305
{
266306
createChildSpan() {
267-
return UNTRACED_CHILD_SPAN;
307+
return DISABLED_CHILD_SPAN;
268308
},
269309
},
270-
UNTRACED_CHILD_SPAN
310+
DISABLED_CHILD_SPAN
271311
)
272312
);

src/trace-api.ts

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ import {
3434
RootSpanData,
3535
UNCORRELATED_CHILD_SPAN,
3636
UNCORRELATED_ROOT_SPAN,
37-
UNTRACED_CHILD_SPAN,
38-
UNTRACED_ROOT_SPAN,
37+
DISABLED_CHILD_SPAN,
38+
DISABLED_ROOT_SPAN,
39+
UntracedRootSpanData,
3940
} from './span-data';
4041
import { TraceLabels } from './trace-labels';
4142
import { traceWriter } from './trace-writer';
@@ -191,7 +192,7 @@ export class StackdriverTracer implements Tracer {
191192

192193
runInRootSpan<T>(options: RootSpanOptions, fn: (span: RootSpan) => T): T {
193194
if (!this.isActive()) {
194-
return fn(UNTRACED_ROOT_SPAN);
195+
return fn(DISABLED_ROOT_SPAN);
195196
}
196197

197198
options = options || { name: '' };
@@ -234,23 +235,25 @@ export class StackdriverTracer implements Tracer {
234235
options,
235236
});
236237

238+
const traceId = traceContext
239+
? traceContext.traceId
240+
: uuid
241+
.v4()
242+
.split('-')
243+
.join('');
237244
let rootContext: RootSpan & RootContext;
238245

239-
// Don't create a root span if the trace policy disallows it.
246+
// Create an "untraced" root span (one that won't be published) if the
247+
// trace policy disallows it.
240248
if (!shouldTrace) {
241-
rootContext = UNTRACED_ROOT_SPAN;
249+
rootContext = new UntracedRootSpanData(traceId);
242250
} else {
243251
// Create a new root span, and invoke fn with it.
244252
rootContext = new RootSpanData(
245253
// Trace object
246254
{
247255
projectId: '',
248-
traceId: traceContext
249-
? traceContext.traceId
250-
: uuid
251-
.v4()
252-
.split('-')
253-
.join(''),
256+
traceId,
254257
spans: [],
255258
},
256259
// Span name
@@ -269,7 +272,7 @@ export class StackdriverTracer implements Tracer {
269272

270273
getCurrentRootSpan(): RootSpan {
271274
if (!this.isActive()) {
272-
return UNTRACED_ROOT_SPAN;
275+
return DISABLED_ROOT_SPAN;
273276
}
274277
return cls.get().getContext();
275278
}
@@ -301,7 +304,7 @@ export class StackdriverTracer implements Tracer {
301304

302305
createChildSpan(options?: SpanOptions): Span {
303306
if (!this.isActive()) {
304-
return UNTRACED_CHILD_SPAN;
307+
return DISABLED_CHILD_SPAN;
305308
}
306309

307310
options = options || { name: '' };
@@ -387,10 +390,11 @@ export class StackdriverTracer implements Tracer {
387390
}] Created child span [${options.name}]`
388391
);
389392
return childContext;
390-
} else if (rootSpan.type === SpanType.UNTRACED) {
391-
// Context wasn't lost, but there's no root span, indicating that this
392-
// request should not be traced.
393-
return UNTRACED_CHILD_SPAN;
393+
} else if (rootSpan.type === SpanType.UNSAMPLED) {
394+
// "Untraced" child spans don't incur a memory penalty.
395+
return rootSpan.createChildSpan();
396+
} else if (rootSpan.type === SpanType.DISABLED) {
397+
return DISABLED_CHILD_SPAN;
394398
} else {
395399
// Context was lost.
396400
this.logger!.warn(

test/test-cls.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ describe('Continuation-Local Storage', () => {
242242
},
243243
{
244244
config: { mechanism: TraceCLSMechanism.NONE },
245-
expectedDefaultType: SpanType.UNTRACED,
245+
expectedDefaultType: SpanType.UNCORRELATED,
246246
},
247247
];
248248
if (asyncAwaitSupported) {
@@ -273,7 +273,7 @@ describe('Continuation-Local Storage', () => {
273273
it(`when disabled, doesn't throw and has reasonable default values`, () => {
274274
c.disable();
275275
assert.ok(!c.isEnabled());
276-
assert.ok(c.getContext().type, SpanType.UNTRACED);
276+
assert.ok(c.getContext().type, SpanType.UNSAMPLED);
277277
assert.ok(c.runWithContext(() => 'hi', TraceCLS.UNCORRELATED), 'hi');
278278
const fn = () => {};
279279
assert.strictEqual(c.bindWithCurrentContext(fn), fn);

test/test-default-ignore-ah-health.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ describe('Trace API', () => {
2222
it('should ignore /_ah/health traces by default', () => {
2323
const traceApi = trace.start();
2424
traceApi.runInRootSpan({ name: 'root', url: '/_ah/health' }, rootSpan => {
25-
assert.strictEqual(rootSpan.type, traceApi.spanTypes.UNTRACED);
25+
assert.strictEqual(rootSpan.type, traceApi.spanTypes.UNSAMPLED);
2626
});
2727
});
2828
});

test/test-index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,18 @@ describe('index.js', function() {
3131
assert.ok(!disabledAgent.isActive()); // ensure it's disabled first
3232
let ranInRootSpan = false;
3333
disabledAgent.runInRootSpan({ name: '' }, (span) => {
34-
assert.strictEqual(span.type, SpanType.UNTRACED);
34+
assert.strictEqual(span.type, SpanType.DISABLED);
3535
ranInRootSpan = true;
3636
});
3737
assert.ok(ranInRootSpan);
3838
assert.strictEqual(disabledAgent.enhancedDatabaseReportingEnabled(), false);
3939
assert.strictEqual(disabledAgent.getCurrentContextId(), null);
4040
assert.strictEqual(disabledAgent.getWriterProjectId(), null);
41-
assert.strictEqual(disabledAgent.getCurrentRootSpan().type, SpanType.UNTRACED);
41+
assert.strictEqual(disabledAgent.getCurrentRootSpan().type, SpanType.DISABLED);
4242
// getting project ID should reject.
4343
await disabledAgent.getProjectId().then(
4444
() => Promise.reject(new Error()), () => Promise.resolve());
45-
assert.strictEqual(disabledAgent.createChildSpan({ name: '' }).type, SpanType.UNTRACED);
45+
assert.strictEqual(disabledAgent.createChildSpan({ name: '' }).type, SpanType.DISABLED);
4646
assert.strictEqual(disabledAgent.getResponseTraceContext({
4747
traceId: '1',
4848
spanId: '1'

test/test-trace-api-none-cls.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ describe('Custom Trace API with CLS disabled', () => {
7474
traceApi.runInRootSpan({ name: 'root' }, identity)
7575
);
7676
const child = traceApi.createChildSpan({ name: 'child' });
77-
assert.strictEqual(child.type, SpanType.UNTRACED);
77+
assert.strictEqual(child.type, SpanType.UNCORRELATED);
7878
child.endSpan();
7979
root.endSpan();
8080
});

test/test-trace-api.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
StackdriverTracerConfig,
3333
} from '../src/trace-api';
3434
import { traceWriter } from '../src/trace-writer';
35-
import { alwaysTrace } from '../src/tracing-policy';
35+
import { alwaysTrace, neverTrace } from '../src/tracing-policy';
3636
import { FORCE_NEW, TraceContext } from '../src/util';
3737

3838
import { TestLogger } from './logger';
@@ -200,21 +200,38 @@ describe('Trace Interface', () => {
200200
});
201201
});
202202

203-
it('should return null context id when one does not exist', () => {
203+
it('should return null context ID when one does not exist', () => {
204204
const traceAPI = createTraceAgent();
205205
assert.strictEqual(traceAPI.getCurrentContextId(), null);
206206
});
207207

208-
it('should return the appropriate trace id', () => {
208+
it('should return the appropriate context ID', () => {
209209
const traceAPI = createTraceAgent();
210210
traceAPI.runInRootSpan({ name: 'root' }, rootSpan => {
211211
const id = traceAPI.getCurrentContextId();
212+
assert.ok(rootSpan.getTraceContext());
213+
assert.strictEqual(id, rootSpan.getTraceContext()!.traceId);
212214
rootSpan.endSpan();
213215
// getOneTrace asserts that there is exactly one trace.
214216
testTraceModule.getOneTrace(trace => trace.traceId === id);
215217
});
216218
});
217219

220+
it('should return a context ID even if in an untraced request', () => {
221+
const traceAPI = createTraceAgent({}, { tracePolicy: neverTrace() });
222+
traceAPI.runInRootSpan({ name: '' }, rootSpan => {
223+
assert.strictEqual(rootSpan.type, SpanType.UNSAMPLED);
224+
assert.notStrictEqual(traceAPI.getCurrentContextId(), null);
225+
assert.ok(rootSpan.getTraceContext());
226+
assert.strictEqual(
227+
traceAPI.getCurrentContextId(),
228+
rootSpan.getTraceContext()!.traceId
229+
);
230+
assert.ok(rootSpan.createChildSpan().getTraceContext());
231+
assert.ok(traceAPI.createChildSpan().getTraceContext());
232+
});
233+
});
234+
218235
it('should return the project ID from the Trace Writer (promise api)', async () => {
219236
const traceApi = createTraceAgent();
220237
assert.strictEqual(await traceApi.getProjectId(), 'project-1');
@@ -245,7 +262,7 @@ describe('Trace Interface', () => {
245262
};
246263
const beforeRootSpan = Date.now();
247264
traceAPI.runInRootSpan(rootSpanOptions, rootSpan => {
248-
assert.strictEqual(rootSpan.type, SpanType.UNTRACED);
265+
assert.strictEqual(rootSpan.type, SpanType.UNSAMPLED);
249266
rootSpan.endSpan();
250267
});
251268
const afterRootSpan = Date.now();
@@ -267,7 +284,7 @@ describe('Trace Interface', () => {
267284
{
268285
const rootSpanOptions = { name: 'root' };
269286
traceAPI.runInRootSpan(rootSpanOptions, rootSpan => {
270-
assert.strictEqual(rootSpan.type, SpanType.UNTRACED);
287+
assert.strictEqual(rootSpan.type, SpanType.UNSAMPLED);
271288
rootSpan.endSpan();
272289
});
273290
assert.ok(tracePolicy.capturedShouldTraceParam);

0 commit comments

Comments
 (0)