799 lines
29 KiB
TypeScript
799 lines
29 KiB
TypeScript
/*
|
|
* Copyright The OpenTelemetry Authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* https://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import {
|
|
context,
|
|
trace,
|
|
SpanContext,
|
|
TraceFlags,
|
|
ROOT_CONTEXT,
|
|
TextMapPropagator,
|
|
TextMapSetter,
|
|
Context,
|
|
TextMapGetter,
|
|
propagation,
|
|
diag,
|
|
} from '@opentelemetry/api';
|
|
import { CompositePropagator } from '@opentelemetry/core';
|
|
import { TraceState, W3CTraceContextPropagator } from '@opentelemetry/core';
|
|
import { Resource } from '@opentelemetry/resources';
|
|
import * as assert from 'assert';
|
|
import * as sinon from 'sinon';
|
|
import {
|
|
BasicTracerProvider,
|
|
NoopSpanProcessor,
|
|
Span,
|
|
AlwaysOnSampler,
|
|
AlwaysOffSampler,
|
|
ConsoleSpanExporter,
|
|
SimpleSpanProcessor,
|
|
} from '../../src';
|
|
import { SpanImpl } from '../../src/Span';
|
|
import { MultiSpanProcessor } from '../../src/MultiSpanProcessor';
|
|
|
|
class DummyPropagator implements TextMapPropagator {
|
|
inject(context: Context, carrier: any, setter: TextMapSetter<any>): void {
|
|
throw new Error('Method not implemented.');
|
|
}
|
|
extract(context: Context, carrier: any, getter: TextMapGetter<any>): Context {
|
|
throw new Error('Method not implemented.');
|
|
}
|
|
fields(): string[] {
|
|
throw new Error('Method not implemented.');
|
|
}
|
|
}
|
|
|
|
describe('BasicTracerProvider', () => {
|
|
let envSource: Record<string, any>;
|
|
let setGlobalPropagatorStub: sinon.SinonSpy<[TextMapPropagator], boolean>;
|
|
|
|
if (global.process?.versions?.node === undefined) {
|
|
envSource = globalThis as unknown as Record<string, any>;
|
|
} else {
|
|
envSource = process.env as Record<string, any>;
|
|
}
|
|
|
|
beforeEach(() => {
|
|
// to avoid actually registering the TraceProvider and leaking env to other tests
|
|
sinon.stub(trace, 'setGlobalTracerProvider');
|
|
setGlobalPropagatorStub = sinon.spy(propagation, 'setGlobalPropagator');
|
|
|
|
context.disable();
|
|
});
|
|
|
|
afterEach(() => {
|
|
sinon.restore();
|
|
});
|
|
|
|
describe('constructor', () => {
|
|
describe('when options not defined', () => {
|
|
it('should construct an instance', () => {
|
|
const tracer = new BasicTracerProvider();
|
|
assert.ok(tracer instanceof BasicTracerProvider);
|
|
});
|
|
|
|
it('should use empty span processor by default', () => {
|
|
const errorStub = sinon.spy(diag, 'error');
|
|
const tracer = new BasicTracerProvider();
|
|
|
|
assert.ok(tracer['_activeSpanProcessor'] instanceof MultiSpanProcessor);
|
|
assert.strictEqual(
|
|
tracer['_activeSpanProcessor']['_spanProcessors'].length,
|
|
0
|
|
);
|
|
sinon.assert.notCalled(errorStub);
|
|
});
|
|
});
|
|
|
|
describe('when user sets span processors', () => {
|
|
it('should use the span processors defined in the config', () => {
|
|
const traceExporter = new ConsoleSpanExporter();
|
|
const spanProcessor = new SimpleSpanProcessor(traceExporter);
|
|
const tracer = new BasicTracerProvider({
|
|
spanProcessors: [spanProcessor],
|
|
});
|
|
|
|
assert.ok(tracer['_activeSpanProcessor'] instanceof MultiSpanProcessor);
|
|
assert.ok(
|
|
tracer['_activeSpanProcessor']['_spanProcessors'].length === 1
|
|
);
|
|
assert.ok(
|
|
tracer['_activeSpanProcessor']['_spanProcessors'][0] instanceof
|
|
SimpleSpanProcessor
|
|
);
|
|
assert.ok(
|
|
tracer['_activeSpanProcessor']['_spanProcessors'][0][
|
|
'_exporter'
|
|
] instanceof ConsoleSpanExporter
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('when "sampler" option defined', () => {
|
|
it('should have an instance with sampler', () => {
|
|
const tracer = new BasicTracerProvider({
|
|
sampler: new AlwaysOnSampler(),
|
|
});
|
|
assert.ok(tracer instanceof BasicTracerProvider);
|
|
});
|
|
});
|
|
|
|
describe('generalLimits', () => {
|
|
describe('when not defined default values', () => {
|
|
it('should have tracer with default values', () => {
|
|
const tracer = new BasicTracerProvider({}).getTracer('default');
|
|
assert.deepStrictEqual(tracer.getGeneralLimits(), {
|
|
attributeValueLengthLimit: Infinity,
|
|
attributeCountLimit: 128,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('when "attributeCountLimit" is defined', () => {
|
|
it('should have tracer with defined value', () => {
|
|
const tracer = new BasicTracerProvider({
|
|
generalLimits: {
|
|
attributeCountLimit: 100,
|
|
},
|
|
}).getTracer('default');
|
|
const generalLimits = tracer.getGeneralLimits();
|
|
assert.strictEqual(generalLimits.attributeCountLimit, 100);
|
|
});
|
|
});
|
|
|
|
describe('when "attributeValueLengthLimit" is defined', () => {
|
|
it('should have tracer with defined value', () => {
|
|
const tracer = new BasicTracerProvider({
|
|
generalLimits: {
|
|
attributeValueLengthLimit: 10,
|
|
},
|
|
}).getTracer('default');
|
|
const generalLimits = tracer.getGeneralLimits();
|
|
assert.strictEqual(generalLimits.attributeValueLengthLimit, 10);
|
|
});
|
|
|
|
it('should have tracer with negative "attributeValueLengthLimit" value', () => {
|
|
const tracer = new BasicTracerProvider({
|
|
generalLimits: {
|
|
attributeValueLengthLimit: -10,
|
|
},
|
|
}).getTracer('default');
|
|
const generalLimits = tracer.getGeneralLimits();
|
|
assert.strictEqual(generalLimits.attributeValueLengthLimit, -10);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('spanLimits', () => {
|
|
describe('when not defined default values', () => {
|
|
it('should have tracer with default values', () => {
|
|
const tracer = new BasicTracerProvider({}).getTracer('default');
|
|
assert.deepStrictEqual(tracer.getSpanLimits(), {
|
|
attributeValueLengthLimit: Infinity,
|
|
attributeCountLimit: 128,
|
|
eventCountLimit: 128,
|
|
linkCountLimit: 128,
|
|
attributePerEventCountLimit: 128,
|
|
attributePerLinkCountLimit: 128,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('when "attributeCountLimit" is defined', () => {
|
|
it('should have tracer with defined value', () => {
|
|
const tracer = new BasicTracerProvider({
|
|
spanLimits: {
|
|
attributeCountLimit: 100,
|
|
},
|
|
}).getTracer('default');
|
|
const spanLimits = tracer.getSpanLimits();
|
|
assert.strictEqual(spanLimits.attributeCountLimit, 100);
|
|
});
|
|
});
|
|
|
|
describe('when "attributeValueLengthLimit" is defined', () => {
|
|
it('should have tracer with defined value', () => {
|
|
const tracer = new BasicTracerProvider({
|
|
spanLimits: {
|
|
attributeValueLengthLimit: 10,
|
|
},
|
|
}).getTracer('default');
|
|
const spanLimits = tracer.getSpanLimits();
|
|
assert.strictEqual(spanLimits.attributeValueLengthLimit, 10);
|
|
});
|
|
|
|
it('should have tracer with negative "attributeValueLengthLimit" value', () => {
|
|
const tracer = new BasicTracerProvider({
|
|
spanLimits: {
|
|
attributeValueLengthLimit: -10,
|
|
},
|
|
}).getTracer('default');
|
|
const spanLimits = tracer.getSpanLimits();
|
|
assert.strictEqual(spanLimits.attributeValueLengthLimit, -10);
|
|
});
|
|
});
|
|
|
|
describe('when attribute value length limit is defined via env', () => {
|
|
it('should have general attribute value length limits value as defined with env', () => {
|
|
envSource.OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT = '115';
|
|
const tracer = new BasicTracerProvider().getTracer('default');
|
|
const generalLimits = tracer.getGeneralLimits();
|
|
assert.strictEqual(generalLimits.attributeValueLengthLimit, 115);
|
|
delete envSource.OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT;
|
|
});
|
|
it('should have span attribute value length limit value same as general limit value', () => {
|
|
envSource.OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT = '125';
|
|
const tracer = new BasicTracerProvider().getTracer('default');
|
|
const generalLimits = tracer.getGeneralLimits();
|
|
const spanLimits = tracer.getSpanLimits();
|
|
assert.strictEqual(generalLimits.attributeValueLengthLimit, 125);
|
|
assert.strictEqual(spanLimits.attributeValueLengthLimit, 125);
|
|
delete envSource.OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT;
|
|
});
|
|
it('should have span and general attribute value length limits as defined in env', () => {
|
|
envSource.OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT = '125';
|
|
envSource.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT = '109';
|
|
const tracer = new BasicTracerProvider().getTracer('default');
|
|
const spanLimits = tracer.getSpanLimits();
|
|
const generalLimits = tracer.getGeneralLimits();
|
|
assert.strictEqual(generalLimits.attributeValueLengthLimit, 125);
|
|
assert.strictEqual(spanLimits.attributeValueLengthLimit, 109);
|
|
delete envSource.OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT;
|
|
delete envSource.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT;
|
|
});
|
|
it('should have span attribute value length limit as default of Infinity', () => {
|
|
envSource.OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT = '125';
|
|
envSource.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT = 'Infinity';
|
|
const tracer = new BasicTracerProvider().getTracer('default');
|
|
const spanLimits = tracer.getSpanLimits();
|
|
const generalLimits = tracer.getGeneralLimits();
|
|
assert.strictEqual(generalLimits.attributeValueLengthLimit, 125);
|
|
assert.strictEqual(spanLimits.attributeValueLengthLimit, Infinity);
|
|
delete envSource.OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT;
|
|
delete envSource.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT;
|
|
});
|
|
});
|
|
|
|
describe('when attribute value length limit is not defined via env', () => {
|
|
it('should use default value of Infinity', () => {
|
|
const tracer = new BasicTracerProvider().getTracer('default');
|
|
const spanLimits = tracer.getSpanLimits();
|
|
const generalLimits = tracer.getGeneralLimits();
|
|
assert.strictEqual(generalLimits.attributeValueLengthLimit, Infinity);
|
|
assert.strictEqual(spanLimits.attributeValueLengthLimit, Infinity);
|
|
});
|
|
});
|
|
|
|
describe('when attribute count limit is defined via env', () => {
|
|
it('should general attribute count limit as defined with env', () => {
|
|
envSource.OTEL_ATTRIBUTE_COUNT_LIMIT = '25';
|
|
const tracer = new BasicTracerProvider({}).getTracer('default');
|
|
const generalLimits = tracer.getGeneralLimits();
|
|
assert.strictEqual(generalLimits.attributeCountLimit, 25);
|
|
delete envSource.OTEL_ATTRIBUTE_COUNT_LIMIT;
|
|
});
|
|
it('should have span attribute count limit value same as general limit value', () => {
|
|
envSource.OTEL_ATTRIBUTE_COUNT_LIMIT = '20';
|
|
const tracer = new BasicTracerProvider().getTracer('default');
|
|
const generalLimits = tracer.getGeneralLimits();
|
|
const spanLimits = tracer.getSpanLimits();
|
|
assert.strictEqual(generalLimits.attributeCountLimit, 20);
|
|
assert.strictEqual(spanLimits.attributeCountLimit, 20);
|
|
delete envSource.OTEL_ATTRIBUTE_COUNT_LIMIT;
|
|
});
|
|
it('should have span and general attribute count limits as defined in env', () => {
|
|
envSource.OTEL_ATTRIBUTE_COUNT_LIMIT = '20';
|
|
envSource.OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT = '35';
|
|
const tracer = new BasicTracerProvider().getTracer('default');
|
|
const spanLimits = tracer.getSpanLimits();
|
|
const generalLimits = tracer.getGeneralLimits();
|
|
assert.strictEqual(generalLimits.attributeCountLimit, 20);
|
|
assert.strictEqual(spanLimits.attributeCountLimit, 35);
|
|
delete envSource.OTEL_ATTRIBUTE_COUNT_LIMIT;
|
|
delete envSource.OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT;
|
|
});
|
|
it('should have span attribute count limit as default of 128', () => {
|
|
envSource.OTEL_ATTRIBUTE_COUNT_LIMIT = '20';
|
|
envSource.OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT = '128';
|
|
const tracer = new BasicTracerProvider().getTracer('default');
|
|
const spanLimits = tracer.getSpanLimits();
|
|
const generalLimits = tracer.getGeneralLimits();
|
|
assert.strictEqual(generalLimits.attributeCountLimit, 20);
|
|
assert.strictEqual(spanLimits.attributeCountLimit, 128);
|
|
delete envSource.OTEL_ATTRIBUTE_COUNT_LIMIT;
|
|
delete envSource.OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT;
|
|
});
|
|
});
|
|
|
|
describe('when attribute count limit is not defined via env', () => {
|
|
it('should use default value of 128', () => {
|
|
const tracer = new BasicTracerProvider().getTracer('default');
|
|
const spanLimits = tracer.getSpanLimits();
|
|
const generalLimits = tracer.getGeneralLimits();
|
|
assert.strictEqual(generalLimits.attributeCountLimit, 128);
|
|
assert.strictEqual(spanLimits.attributeCountLimit, 128);
|
|
});
|
|
});
|
|
|
|
describe('when "eventCountLimit" is defined', () => {
|
|
it('should have tracer with defined value', () => {
|
|
const tracer = new BasicTracerProvider({
|
|
spanLimits: {
|
|
eventCountLimit: 300,
|
|
},
|
|
}).getTracer('default');
|
|
const spanLimits = tracer.getSpanLimits();
|
|
assert.strictEqual(spanLimits.eventCountLimit, 300);
|
|
});
|
|
});
|
|
|
|
describe('when "linkCountLimit" is defined', () => {
|
|
it('should have tracer with defined value', () => {
|
|
const tracer = new BasicTracerProvider({
|
|
spanLimits: {
|
|
linkCountLimit: 10,
|
|
},
|
|
}).getTracer('default');
|
|
const spanLimits = tracer.getSpanLimits();
|
|
assert.strictEqual(spanLimits.linkCountLimit, 10);
|
|
});
|
|
});
|
|
|
|
describe('when only generalLimits are defined', () => {
|
|
it('should have span limits as general limits', () => {
|
|
const tracer = new BasicTracerProvider({
|
|
generalLimits: {
|
|
attributeValueLengthLimit: 100,
|
|
attributeCountLimit: 200,
|
|
},
|
|
}).getTracer('default');
|
|
const spanLimits = tracer.getSpanLimits();
|
|
assert.strictEqual(spanLimits.attributeValueLengthLimit, 100);
|
|
assert.strictEqual(spanLimits.attributeCountLimit, 200);
|
|
});
|
|
});
|
|
|
|
describe('when both generalLimits and spanLimits defined', () => {
|
|
it('should have span limits as priority than general limits', () => {
|
|
const tracer = new BasicTracerProvider({
|
|
generalLimits: {
|
|
attributeValueLengthLimit: 100,
|
|
attributeCountLimit: 200,
|
|
},
|
|
spanLimits: {
|
|
attributeValueLengthLimit: 10,
|
|
attributeCountLimit: 20,
|
|
},
|
|
}).getTracer('default');
|
|
const spanLimits = tracer.getSpanLimits();
|
|
assert.strictEqual(spanLimits.attributeValueLengthLimit, 10);
|
|
assert.strictEqual(spanLimits.attributeCountLimit, 20);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Custom TracerProvider through inheritance', () => {
|
|
beforeEach(() => {
|
|
envSource.OTEL_TRACES_EXPORTER = 'custom-exporter';
|
|
envSource.OTEL_PROPAGATORS = 'custom-propagator';
|
|
});
|
|
|
|
afterEach(() => {
|
|
delete envSource.OTEL_TRACES_EXPORTER;
|
|
delete envSource.OTEL_PROPAGATORS;
|
|
sinon.restore();
|
|
});
|
|
|
|
it('can be extended by overriding registered components', () => {
|
|
class CustomTracerProvider extends BasicTracerProvider {
|
|
protected static override readonly _registeredPropagators = new Map<
|
|
string,
|
|
() => TextMapPropagator
|
|
>([
|
|
...BasicTracerProvider._registeredPropagators,
|
|
['custom-propagator', () => new DummyPropagator()],
|
|
]);
|
|
}
|
|
|
|
const provider = new CustomTracerProvider({});
|
|
assert.ok(
|
|
provider['_getPropagator']('tracecontext') instanceof
|
|
W3CTraceContextPropagator
|
|
);
|
|
/* BasicTracerProvider has no exporters by default, so skipping testing the exporter getter */
|
|
provider.register();
|
|
assert.strictEqual(
|
|
provider['_activeSpanProcessor']['_spanProcessors'].length,
|
|
0
|
|
);
|
|
|
|
sinon.assert.calledOnceWithExactly(
|
|
setGlobalPropagatorStub,
|
|
sinon.match.instanceOf(DummyPropagator)
|
|
);
|
|
});
|
|
|
|
it('the old way of extending still works', () => {
|
|
// this is an anti-pattern, but we test that for backwards compatibility
|
|
class CustomTracerProvider extends BasicTracerProvider {
|
|
protected static override readonly _registeredPropagators = new Map<
|
|
string,
|
|
() => TextMapPropagator
|
|
>([['custom-propagator', () => new DummyPropagator()]]);
|
|
|
|
protected override _getPropagator(
|
|
name: string
|
|
): TextMapPropagator | undefined {
|
|
return (
|
|
super._getPropagator(name) ||
|
|
CustomTracerProvider._registeredPropagators.get(name)?.()
|
|
);
|
|
}
|
|
}
|
|
|
|
const provider = new CustomTracerProvider({});
|
|
provider.register();
|
|
assert.strictEqual(
|
|
provider['_activeSpanProcessor']['_spanProcessors'].length,
|
|
0
|
|
);
|
|
|
|
sinon.assert.calledOnceWithExactly(
|
|
setGlobalPropagatorStub,
|
|
sinon.match.instanceOf(DummyPropagator)
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('.register()', () => {
|
|
describe('propagator', () => {
|
|
let originalPropagators: string | number | undefined | string[];
|
|
beforeEach(() => {
|
|
originalPropagators = envSource.OTEL_PROPAGATORS;
|
|
});
|
|
|
|
afterEach(() => {
|
|
sinon.restore();
|
|
|
|
// otherwise we may assign 'undefined' (a string)
|
|
if (originalPropagators !== undefined) {
|
|
envSource.OTEL_PROPAGATORS = originalPropagators;
|
|
} else {
|
|
delete envSource.OTEL_PROPAGATORS;
|
|
}
|
|
});
|
|
|
|
it('should be set to a given value if it it provided', () => {
|
|
const provider = new BasicTracerProvider();
|
|
provider.register({
|
|
propagator: new DummyPropagator(),
|
|
});
|
|
|
|
sinon.assert.calledOnceWithExactly(
|
|
setGlobalPropagatorStub,
|
|
sinon.match.instanceOf(DummyPropagator)
|
|
);
|
|
});
|
|
|
|
it('should be composite if 2 or more propagators provided in an environment variable', () => {
|
|
const provider = new BasicTracerProvider();
|
|
provider.register();
|
|
|
|
sinon.assert.calledOnceWithExactly(
|
|
setGlobalPropagatorStub,
|
|
sinon.match.instanceOf(CompositePropagator)
|
|
);
|
|
assert.deepStrictEqual(setGlobalPropagatorStub.args[0][0].fields(), [
|
|
'traceparent',
|
|
'tracestate',
|
|
'baggage',
|
|
]);
|
|
});
|
|
|
|
it('warns if there is no propagator registered with a given name', () => {
|
|
const warnStub = sinon.spy(diag, 'warn');
|
|
|
|
envSource.OTEL_PROPAGATORS = 'missing-propagator';
|
|
const provider = new BasicTracerProvider({});
|
|
provider.register();
|
|
|
|
sinon.assert.calledOnceWithExactly(
|
|
warnStub,
|
|
'Propagator "missing-propagator" requested through environment variable is unavailable.'
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('.startSpan()', () => {
|
|
it('should start a span with name only', () => {
|
|
const tracer = new BasicTracerProvider().getTracer('default');
|
|
const span = tracer.startSpan('my-span');
|
|
assert.ok(span);
|
|
assert.ok(span instanceof SpanImpl);
|
|
});
|
|
|
|
it('should propagate resources', () => {
|
|
const tracerProvider = new BasicTracerProvider();
|
|
const tracer = tracerProvider.getTracer('default');
|
|
const span = tracer.startSpan('my-span') as Span;
|
|
assert.strictEqual(tracer['_resource'], tracerProvider['_resource']);
|
|
assert.strictEqual(span.resource, tracerProvider['_resource']);
|
|
});
|
|
|
|
it('should start a span with name and options', () => {
|
|
const tracer = new BasicTracerProvider().getTracer('default');
|
|
const span = tracer.startSpan('my-span', {});
|
|
assert.ok(span);
|
|
assert.ok(span instanceof SpanImpl);
|
|
const context = span.spanContext();
|
|
assert.ok(context.traceId.match(/[a-f0-9]{32}/));
|
|
assert.ok(context.spanId.match(/[a-f0-9]{16}/));
|
|
assert.strictEqual(context.traceFlags, TraceFlags.SAMPLED);
|
|
assert.deepStrictEqual(context.traceState, undefined);
|
|
span.end();
|
|
});
|
|
|
|
it('should start a span with given attributes', () => {
|
|
const tracer = new BasicTracerProvider().getTracer('default');
|
|
const span = tracer.startSpan('my-span', {
|
|
attributes: { foo: 'foo', bar: 'bar' },
|
|
}) as Span;
|
|
assert.deepStrictEqual(span.attributes, { bar: 'bar', foo: 'foo' });
|
|
span.end();
|
|
});
|
|
|
|
it('should start a span with spanoptions->attributes', () => {
|
|
const tracer = new BasicTracerProvider().getTracer('default');
|
|
const span = tracer.startSpan('my-span', {
|
|
attributes: { foo: 'foo', bar: 'bar' },
|
|
}) as Span;
|
|
assert.deepStrictEqual(span.attributes, { foo: 'foo', bar: 'bar' });
|
|
span.end();
|
|
});
|
|
|
|
it('should start a span with name and parent spancontext', () => {
|
|
const tracer = new BasicTracerProvider().getTracer('default');
|
|
const state = new TraceState('a=1,b=2');
|
|
|
|
const span = tracer.startSpan(
|
|
'my-span',
|
|
{},
|
|
trace.setSpanContext(ROOT_CONTEXT, {
|
|
traceId: 'd4cda95b652f4a1592b449d5929fda1b',
|
|
spanId: '6e0c63257de34c92',
|
|
traceFlags: TraceFlags.SAMPLED,
|
|
traceState: state,
|
|
})
|
|
);
|
|
assert.ok(span instanceof SpanImpl);
|
|
const context = span.spanContext();
|
|
assert.strictEqual(context.traceId, 'd4cda95b652f4a1592b449d5929fda1b');
|
|
assert.strictEqual(context.traceFlags, TraceFlags.SAMPLED);
|
|
assert.deepStrictEqual(context.traceState, state);
|
|
span.end();
|
|
});
|
|
|
|
it('should start a span with name and parent span', () => {
|
|
const tracer = new BasicTracerProvider().getTracer('default');
|
|
const span = tracer.startSpan('my-span');
|
|
const childSpan = tracer.startSpan(
|
|
'child-span',
|
|
{},
|
|
trace.setSpan(ROOT_CONTEXT, span)
|
|
);
|
|
const context = childSpan.spanContext();
|
|
assert.strictEqual(context.traceId, span.spanContext().traceId);
|
|
assert.strictEqual(context.traceFlags, TraceFlags.SAMPLED);
|
|
span.end();
|
|
childSpan.end();
|
|
});
|
|
|
|
it('should create a root span when root is true', () => {
|
|
const tracer = new BasicTracerProvider().getTracer('default');
|
|
const span = tracer.startSpan('my-span');
|
|
const overrideParent = tracer.startSpan('my-parent-override-span');
|
|
const rootSpan = tracer.startSpan(
|
|
'root-span',
|
|
{ root: true },
|
|
trace.setSpan(ROOT_CONTEXT, span)
|
|
);
|
|
const context = rootSpan.spanContext();
|
|
assert.notStrictEqual(
|
|
context.traceId,
|
|
overrideParent.spanContext().traceId
|
|
);
|
|
span.end();
|
|
rootSpan.end();
|
|
});
|
|
|
|
it('should start a span with name and with invalid parent span', () => {
|
|
const tracer = new BasicTracerProvider({
|
|
sampler: new AlwaysOnSampler(),
|
|
}).getTracer('default');
|
|
const span = tracer.startSpan(
|
|
'my-span',
|
|
{},
|
|
trace.setSpanContext(
|
|
ROOT_CONTEXT,
|
|
'invalid-parent' as unknown as SpanContext
|
|
)
|
|
);
|
|
assert.ok(span instanceof SpanImpl);
|
|
assert.deepStrictEqual((span as Span).parentSpanId, undefined);
|
|
});
|
|
|
|
it('should start a span with name and with invalid spancontext', () => {
|
|
const tracer = new BasicTracerProvider().getTracer('default');
|
|
const span = tracer.startSpan(
|
|
'my-span',
|
|
{},
|
|
trace.setSpanContext(ROOT_CONTEXT, {
|
|
traceId: '0',
|
|
spanId: '0',
|
|
traceFlags: TraceFlags.SAMPLED,
|
|
})
|
|
);
|
|
assert.ok(span instanceof SpanImpl);
|
|
const context = span.spanContext();
|
|
assert.ok(context.traceId.match(/[a-f0-9]{32}/));
|
|
assert.ok(context.spanId.match(/[a-f0-9]{16}/));
|
|
assert.strictEqual(context.traceFlags, TraceFlags.SAMPLED);
|
|
assert.deepStrictEqual(context.traceState, undefined);
|
|
});
|
|
|
|
it('should return a non recording span when never sampling', () => {
|
|
const tracer = new BasicTracerProvider({
|
|
sampler: new AlwaysOffSampler(),
|
|
}).getTracer('default');
|
|
const span = tracer.startSpan('my-span');
|
|
assert.ok(!span.isRecording());
|
|
const context = span.spanContext();
|
|
assert.ok(context.traceId.match(/[a-f0-9]{32}/));
|
|
assert.ok(context.spanId.match(/[a-f0-9]{16}/));
|
|
assert.strictEqual(context.traceFlags, TraceFlags.NONE);
|
|
assert.deepStrictEqual(context.traceState, undefined);
|
|
span.end();
|
|
});
|
|
|
|
it('should create real span when sampled', () => {
|
|
const tracer = new BasicTracerProvider({
|
|
sampler: new AlwaysOnSampler(),
|
|
}).getTracer('default');
|
|
const span = tracer.startSpan('my-span');
|
|
assert.ok(span instanceof SpanImpl);
|
|
assert.strictEqual(span.spanContext().traceFlags, TraceFlags.SAMPLED);
|
|
assert.strictEqual(span.isRecording(), true);
|
|
});
|
|
|
|
it('should assign a resource', () => {
|
|
const tracer = new BasicTracerProvider().getTracer('default');
|
|
const span = tracer.startSpan('my-span') as Span;
|
|
assert.ok(span);
|
|
assert.ok(span.resource instanceof Resource);
|
|
});
|
|
});
|
|
|
|
describe('.withSpan()', () => {
|
|
it('should run context with NoopContextManager context manager', done => {
|
|
const tracer = new BasicTracerProvider().getTracer('default');
|
|
const span = tracer.startSpan('my-span');
|
|
context.with(trace.setSpan(context.active(), span), () => {
|
|
assert.deepStrictEqual(trace.getSpan(context.active()), undefined);
|
|
return done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('.forceFlush()', () => {
|
|
it('should call forceFlush on all registered span processors', done => {
|
|
sinon.restore();
|
|
const forceFlushStub = sinon.stub(
|
|
NoopSpanProcessor.prototype,
|
|
'forceFlush'
|
|
);
|
|
forceFlushStub.resolves();
|
|
|
|
const spanProcessorOne = new NoopSpanProcessor();
|
|
const spanProcessorTwo = new NoopSpanProcessor();
|
|
const tracerProvider = new BasicTracerProvider({
|
|
spanProcessors: [spanProcessorOne, spanProcessorTwo],
|
|
});
|
|
|
|
tracerProvider
|
|
.forceFlush()
|
|
.then(() => {
|
|
sinon.restore();
|
|
assert.ok(forceFlushStub.calledTwice);
|
|
done();
|
|
})
|
|
.catch(error => {
|
|
sinon.restore();
|
|
done(error);
|
|
});
|
|
});
|
|
|
|
it('should throw error when calling forceFlush on all registered span processors fails', done => {
|
|
sinon.restore();
|
|
|
|
const forceFlushStub = sinon.stub(
|
|
NoopSpanProcessor.prototype,
|
|
'forceFlush'
|
|
);
|
|
forceFlushStub.returns(Promise.reject('Error'));
|
|
|
|
const spanProcessorOne = new NoopSpanProcessor();
|
|
const spanProcessorTwo = new NoopSpanProcessor();
|
|
const tracerProvider = new BasicTracerProvider({
|
|
spanProcessors: [spanProcessorOne, spanProcessorTwo],
|
|
});
|
|
|
|
tracerProvider
|
|
.forceFlush()
|
|
.then(() => {
|
|
sinon.restore();
|
|
done(new Error('Successful forceFlush not expected'));
|
|
})
|
|
.catch(_error => {
|
|
sinon.restore();
|
|
sinon.assert.calledTwice(forceFlushStub);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('.bind()', () => {
|
|
it('should bind context with NoopContextManager context manager', done => {
|
|
const tracer = new BasicTracerProvider().getTracer('default');
|
|
const span = tracer.startSpan('my-span');
|
|
const fn = () => {
|
|
assert.deepStrictEqual(trace.getSpan(context.active()), undefined);
|
|
return done();
|
|
};
|
|
const patchedFn = context.bind(trace.setSpan(context.active(), span), fn);
|
|
return patchedFn();
|
|
});
|
|
});
|
|
|
|
describe('.resource', () => {
|
|
it('should use the default resource when no resource is provided', function () {
|
|
const tracerProvider = new BasicTracerProvider();
|
|
assert.deepStrictEqual(tracerProvider['_resource'], Resource.default());
|
|
});
|
|
|
|
it('should use not use the default if resource passed', function () {
|
|
const providedResource = new Resource({ foo: 'bar' });
|
|
const tracerProvider = new BasicTracerProvider({
|
|
resource: providedResource,
|
|
});
|
|
assert.deepStrictEqual(tracerProvider['_resource'], providedResource);
|
|
});
|
|
});
|
|
|
|
describe('.shutdown()', () => {
|
|
it('should trigger shutdown when manually invoked', () => {
|
|
const tracerProvider = new BasicTracerProvider();
|
|
const shutdownStub = sinon.stub(
|
|
tracerProvider['_activeSpanProcessor'],
|
|
'shutdown'
|
|
);
|
|
tracerProvider.shutdown();
|
|
sinon.assert.calledOnce(shutdownStub);
|
|
});
|
|
});
|
|
});
|