diff --git a/packages/opentelemetry-tracing/README.md b/packages/opentelemetry-tracing/README.md index 9daabdfa2a..1de732e127 100644 --- a/packages/opentelemetry-tracing/README.md +++ b/packages/opentelemetry-tracing/README.md @@ -43,6 +43,13 @@ span.setAttribute('key', 'value'); span.end(); ``` +## Config + +Tracing configuration is a merge of user supplied configuration with both the default +configuration as specified in [config.ts](./src/config.ts) and an +environmentally configurable (via `OTEL_SAMPLING_PROBABILITY`) probability +sampler delegate of a [ParentOrElse](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/sdk.md#parentorelse) sampler. + ## Example See [examples/basic-tracer-node](https://github.com/open-telemetry/opentelemetry-js/tree/master/examples/basic-tracer-node) for an end-to-end example, including exporting created spans. diff --git a/packages/opentelemetry-tracing/src/utility.ts b/packages/opentelemetry-tracing/src/utility.ts index 3143a8b1de..a3ef62ef2c 100644 --- a/packages/opentelemetry-tracing/src/utility.ts +++ b/packages/opentelemetry-tracing/src/utility.ts @@ -21,6 +21,11 @@ import { DEFAULT_MAX_LINKS_PER_SPAN, } from './config'; import { TracerConfig } from './types'; +import { + ParentOrElseSampler, + ProbabilitySampler, + getEnv, +} from '@opentelemetry/core'; /** * Function to merge Default configuration (as specified in './config') with @@ -28,7 +33,20 @@ import { TracerConfig } from './types'; */ export function mergeConfig(userConfig: TracerConfig) { const traceParams = userConfig.traceParams; - const target = Object.assign({}, DEFAULT_CONFIG, userConfig); + const otelSamplingProbability = getEnv().OTEL_SAMPLING_PROBABILITY; + + const target = Object.assign( + DEFAULT_CONFIG, + // use default AlwaysOnSampler if otelSamplingProbability is 1 + otelSamplingProbability !== undefined && otelSamplingProbability < 1 + ? { + sampler: new ParentOrElseSampler( + new ProbabilitySampler(otelSamplingProbability) + ), + } + : {}, + userConfig + ); // the user-provided value will be used to extend the default value. if (traceParams) { diff --git a/packages/opentelemetry-tracing/test/Tracer.test.ts b/packages/opentelemetry-tracing/test/Tracer.test.ts index a65b00ce96..8bf0481c85 100644 --- a/packages/opentelemetry-tracing/test/Tracer.test.ts +++ b/packages/opentelemetry-tracing/test/Tracer.test.ts @@ -15,7 +15,12 @@ */ import * as assert from 'assert'; -import { NoopSpan, Sampler, SamplingDecision } from '@opentelemetry/api'; +import { + NoopSpan, + Sampler, + SamplingDecision, + TraceFlags, +} from '@opentelemetry/api'; import { BasicTracerProvider, Tracer, Span } from '../src'; import { InstrumentationLibrary, @@ -40,6 +45,12 @@ describe('Tracer', () => { } } + afterEach(() => { + if (typeof process !== 'undefined' && process.release.name === 'node') { + delete process.env.OTEL_SAMPLING_PROBABILITY; + } + }); + it('should create a Tracer instance', () => { const tracer = new Tracer( { name: 'default', version: '0.0.1' }, @@ -49,6 +60,15 @@ describe('Tracer', () => { assert.ok(tracer instanceof Tracer); }); + it('should use an AlwaysOnSampler by default', () => { + const tracer = new Tracer( + { name: 'default', version: '0.0.1' }, + {}, + tracerProvider + ); + assert.strictEqual(tracer['_sampler'].toString(), 'AlwaysOnSampler'); + }); + it('should respect NO_RECORD sampling result', () => { const tracer = new Tracer( { name: 'default', version: '0.0.1' }, @@ -94,4 +114,64 @@ describe('Tracer', () => { assert.strictEqual(lib.name, 'default'); assert.strictEqual(lib.version, '0.0.1'); }); + + if (typeof process !== 'undefined' && process.release.name === 'node') { + it('should sample a trace when OTEL_SAMPLING_PROBABILITY is invalid', () => { + process.env.OTEL_SAMPLING_PROBABILITY = 'invalid value'; + const tracer = new Tracer( + { name: 'default', version: '0.0.1' }, + {}, + tracerProvider + ); + const span = tracer.startSpan('my-span'); + const context = span.context(); + assert.strictEqual(context.traceFlags, TraceFlags.SAMPLED); + span.end(); + }); + } + + if (typeof process !== 'undefined' && process.release.name === 'node') { + it('should sample a trace when OTEL_SAMPLING_PROBABILITY is greater than 1', () => { + process.env.OTEL_SAMPLING_PROBABILITY = '2'; + const tracer = new Tracer( + { name: 'default', version: '0.0.1' }, + {}, + tracerProvider + ); + const span = tracer.startSpan('my-span'); + const context = span.context(); + assert.strictEqual(context.traceFlags, TraceFlags.SAMPLED); + span.end(); + }); + } + + if (typeof process !== 'undefined' && process.release.name === 'node') { + it('should not sample a trace when OTEL_SAMPLING_PROBABILITY is 0', () => { + process.env.OTEL_SAMPLING_PROBABILITY = '0'; + const tracer = new Tracer( + { name: 'default', version: '0.0.1' }, + {}, + tracerProvider + ); + const span = tracer.startSpan('my-span'); + const context = span.context(); + assert.strictEqual(context.traceFlags, TraceFlags.NONE); + span.end(); + }); + } + + if (typeof process !== 'undefined' && process.release.name === 'node') { + it('should not sample a trace when OTEL_SAMPLING_PROBABILITY is less than 0', () => { + process.env.OTEL_SAMPLING_PROBABILITY = '-1'; + const tracer = new Tracer( + { name: 'default', version: '0.0.1' }, + {}, + tracerProvider + ); + const span = tracer.startSpan('my-span'); + const context = span.context(); + assert.strictEqual(context.traceFlags, TraceFlags.NONE); + span.end(); + }); + } });