src/span-data.ts
Represents an "untraced" root span (aka not published). For distributed trace context propagation purposes.
Properties |
|
Methods |
Readonly type |
Default value : SpanType.UNSAMPLED
|
Inherited from
UntracedSpanData
|
Defined in
UntracedSpanData:230
|
createChildSpan |
createChildSpan()
|
Defined in src/span-data.ts:258
|
Returns :
Span
|
addLabel |
addLabel()
|
Inherited from
UntracedSpanData
|
Defined in
UntracedSpanData:246
|
Returns :
void
|
endSpan |
endSpan()
|
Inherited from
UntracedSpanData
|
Defined in
UntracedSpanData:248
|
Returns :
void
|
getTraceContext |
getTraceContext()
|
Inherited from
UntracedSpanData
|
Defined in
UntracedSpanData:241
|
Returns :
traceUtil.TraceContext | null
|
import * as crypto from 'crypto';
import * as util from 'util';
import {Constants, SpanType} from './constants';
import {RootSpan, Span, SpanOptions, TraceContext} from './plugin-types';
import {SpanKind, Trace, TraceSpan} from './trace';
import {TraceLabels} from './trace-labels';
import {traceWriter} from './trace-writer';
import * as traceUtil from './util';
// Use 6 bytes of randomness only as JS numbers are doubles not 64-bit ints.
const SPAN_ID_RANDOM_BYTES = 6;
// Use the faster crypto.randomFillSync when available (Node 7+) falling back to
// using crypto.randomBytes.
const spanIdBuffer = Buffer.alloc(SPAN_ID_RANDOM_BYTES);
const randomFillSync = crypto.randomFillSync;
const randomBytes = crypto.randomBytes;
const spanRandomBuffer = randomFillSync
? () => randomFillSync(spanIdBuffer)
: () => randomBytes(SPAN_ID_RANDOM_BYTES);
function randomSpanId() {
return parseInt(spanRandomBuffer().toString('hex'), 16).toString();
}
/**
* Represents a real trace span.
*/
export abstract class BaseSpanData implements Span {
readonly span: TraceSpan;
abstract readonly type: SpanType;
/**
* Creates a trace context object.
* @param trace The object holding the spans comprising this trace.
* @param spanName The name of the span.
* @param parentSpanId The ID of the parent span, or '0' to specify that there
* is none.
* @param skipFrames the number of frames to remove from the top of the stack
* when collecting the stack trace.
*/
constructor(
readonly trace: Trace,
spanName: string,
parentSpanId: string,
skipFrames: number
) {
this.span = {
name: traceUtil.truncate(
spanName,
Constants.TRACE_SERVICE_SPAN_NAME_LIMIT
),
startTime: new Date().toISOString(),
endTime: '',
spanId: randomSpanId(),
kind: SpanKind.SPAN_KIND_UNSPECIFIED,
parentSpanId,
labels: {},
};
this.trace.spans.push(this.span);
const stackFrames = traceUtil.createStackTrace(
traceWriter.get().getConfig().stackTraceLimit,
skipFrames,
this.constructor
);
if (stackFrames.length > 0) {
// Developer note: This is not equivalent to using addLabel, because the
// stack trace label has its own size constraints.
this.span.labels[TraceLabels.STACK_TRACE_DETAILS_KEY] =
traceUtil.truncate(
JSON.stringify({stack_frame: stackFrames}),
Constants.TRACE_SERVICE_LABEL_VALUE_LIMIT
);
}
}
getTraceContext() {
return {
traceId: this.trace.traceId.toString(),
spanId: this.span.spanId.toString(),
options: 1, // always traced
};
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
addLabel(key: string, value: any) {
const k = traceUtil.truncate(key, Constants.TRACE_SERVICE_LABEL_KEY_LIMIT);
const stringValue = typeof value === 'string' ? value : util.inspect(value);
const v = traceUtil.truncate(
stringValue,
traceWriter.get().getConfig().maximumLabelValueSize
);
this.span.labels[k] = v;
}
endSpan(timestamp?: Date) {
if (this.span.endTime) {
return;
}
timestamp = timestamp || new Date();
this.span.endTime = timestamp.toISOString();
}
}
/**
* Represents a real root span, which corresponds to an incoming request.
*/
export class RootSpanData extends BaseSpanData implements RootSpan {
readonly type = SpanType.ROOT;
// Locally-tracked list of children. Used only to determine, once this span
// ends, whether a child still needs to be published.
private children: ChildSpanData[] = [];
constructor(
trace: Trace,
spanName: string,
parentSpanId: string,
skipFrames: number
) {
super(trace, spanName, parentSpanId, skipFrames);
this.span.kind = SpanKind.RPC_SERVER;
}
createChildSpan(options?: SpanOptions): Span {
options = options || {name: ''};
const skipFrames = options.skipFrames ? options.skipFrames + 1 : 1;
const child = new ChildSpanData(
this.trace /* Trace object */,
options.name /* Span name */,
this.span.spanId /* Parent's span ID */,
skipFrames
); /* # of frames to skip in stack trace */
this.children.push(child);
return child;
}
endSpan(timestamp?: Date) {
if (this.span.endTime) {
return;
}
super.endSpan(timestamp);
traceWriter.get().writeTrace(this.trace);
this.children.forEach(child => {
if (!child.span.endTime) {
// Child hasn't ended yet.
// Inform the child that it needs to self-publish.
child.shouldSelfPublish = true;
}
});
// We no longer need to keep track of our children.
this.children = [];
}
}
/**
* Represents a real child span, which corresponds to an outgoing RPC.
*/
export class ChildSpanData extends BaseSpanData {
readonly type = SpanType.CHILD;
// Whether this span should publish itself. This is meant to be set to true
// by the parent RootSpanData.
shouldSelfPublish = false;
constructor(
trace: Trace,
spanName: string,
parentSpanId: string,
skipFrames: number
) {
super(trace, spanName, parentSpanId, skipFrames);
this.span.kind = SpanKind.RPC_CLIENT;
}
endSpan(timestamp?: Date) {
if (this.span.endTime) {
return;
}
super.endSpan(timestamp);
if (this.shouldSelfPublish) {
// Also, publish just this span.
traceWriter.get().writeTrace({
projectId: this.trace.projectId,
traceId: this.trace.traceId,
spans: [this.span],
});
}
}
}
// Helper function to generate static virtual trace spans.
function createPhantomSpanData<T extends SpanType>(
spanType: T
): Span & {readonly type: T} {
return Object.freeze(
Object.assign(
{
getTraceContext() {
return null;
},
// eslint-disable-next-line
addLabel(key: string, value: any) {},
endSpan() {},
},
{type: spanType}
)
);
}
/**
* Helper (and base) class for UntracedRootSpanData. Represents an untraced
* child span.
*/
class UntracedSpanData implements Span {
readonly type = SpanType.UNSAMPLED;
protected readonly traceContext: TraceContext;
constructor(traceId: string) {
this.traceContext = {
traceId,
spanId: randomSpanId(),
options: 0, // Not traced.
};
}
getTraceContext(): traceUtil.TraceContext | null {
return this.traceContext;
}
// No-op.
addLabel(): void {}
// No-op.
endSpan(): void {}
}
/**
* Represents an "untraced" root span (aka not published).
* For distributed trace context propagation purposes.
*/
export class UntracedRootSpanData extends UntracedSpanData implements RootSpan {
private child: Span | null = null;
createChildSpan(): Span {
if (!this.child) {
this.child = new UntracedSpanData(this.traceContext.traceId);
}
return this.child;
}
}
/**
* A virtual trace span that indicates that a real child span couldn't be
* created because the correct root span couldn't be determined.
*/
export const UNCORRELATED_CHILD_SPAN = createPhantomSpanData(
SpanType.UNCORRELATED
);
/**
* A virtual trace span that indicates that a real child span couldn't be
* created because the Trace Agent was disabled.
*/
export const DISABLED_CHILD_SPAN = createPhantomSpanData(SpanType.DISABLED);
/**
* A virtual trace span that indicates that a real root span couldn't be
* created because an active root span context already exists.
*/
export const UNCORRELATED_ROOT_SPAN = Object.freeze(
Object.assign(
{
createChildSpan() {
return UNCORRELATED_CHILD_SPAN;
},
},
UNCORRELATED_CHILD_SPAN
)
);
/**
* A virtual trace span that indicates that a real root span couldn't be
* created because it was disallowed by user configuration.
*/
export const DISABLED_ROOT_SPAN = Object.freeze(
Object.assign(
{
createChildSpan() {
return DISABLED_CHILD_SPAN;
},
},
DISABLED_CHILD_SPAN
)
);