Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: handle OTEL_TRACES_SAMPLER env var #2111

Merged
merged 4 commits into from
Apr 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,9 +248,11 @@ To request automatic tracing support for a module not on this list, please [file
### 0.18.x to 0.19.0

- All plugins have been removed in favor of instrumentations.

- The `@opentelemetry/propagator-b3` package previously exported three propagators: `B3Propagator`,`B3SinglePropagator`, and `B3MultiPropagator`, but now only exports the `B3Propagator`. It extracts b3 context in single and multi-header encodings, and injects context using the single-header encoding by default, but can be configured to inject context using the multi-header endcoding during construction: `new B3Propagator({ injectEncoding: B3InjectEncoding.MULTI_HEADER })`. If you were previously using the `B3SinglePropagator` or `B3MultiPropagator` directly, you should update your code to use the `B3Propagator` with the appropriate configuration. See the [readme](./packages/opentelemetry-propagator-b3/readme.md) for full details and usage.

- Sampling configuration via environment variable has changed. If you were using `OTEL_SAMPLING_PROBABILITY` then you should replace it with `OTEL_TRACES_SAMPLER=parentbased_traceidratio` and `OTEL_TRACES_SAMPLER_ARG=<number>` where `<number>` is a number in the [0..1] range, e.g. "0.25". Default is 1.0 if unset.

### 0.17.0 to 0.18.0

- `diag.setLogLevel` is removed and LogLevel can be set by an optional second parameter to `setLogger`
Expand Down
1 change: 1 addition & 0 deletions packages/opentelemetry-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ export * from './trace/TraceState';
export * from './trace/IdGenerator';
export * from './utils/url';
export * from './utils/wrap';
export * from './utils/sampling';
11 changes: 5 additions & 6 deletions packages/opentelemetry-core/src/utils/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import { DiagLogLevel } from '@opentelemetry/api';
import { TracesSamplerValues } from './sampling';

const DEFAULT_LIST_SEPARATOR = ',';

Expand All @@ -27,7 +28,6 @@ const ENVIRONMENT_NUMBERS_KEYS = [
'OTEL_BSP_MAX_EXPORT_BATCH_SIZE',
'OTEL_BSP_MAX_QUEUE_SIZE',
'OTEL_BSP_SCHEDULE_DELAY',
'OTEL_SAMPLING_PROBABILITY',
'OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT',
'OTEL_SPAN_EVENT_COUNT_LIMIT',
'OTEL_SPAN_LINK_COUNT_LIMIT',
Expand Down Expand Up @@ -70,6 +70,8 @@ export type ENVIRONMENT = {
OTEL_EXPORTER_ZIPKIN_ENDPOINT?: string;
OTEL_LOG_LEVEL?: DiagLogLevel;
OTEL_RESOURCE_ATTRIBUTES?: string;
OTEL_TRACES_SAMPLER_ARG?: string;
OTEL_TRACES_SAMPLER?: string;
} & ENVIRONMENT_NUMBERS &
ENVIRONMENT_LISTS;

Expand Down Expand Up @@ -100,10 +102,11 @@ export const DEFAULT_ENVIRONMENT: Required<ENVIRONMENT> = {
OTEL_NO_PATCH_MODULES: [],
OTEL_PROPAGATORS: ['tracecontext', 'baggage'],
OTEL_RESOURCE_ATTRIBUTES: '',
OTEL_SAMPLING_PROBABILITY: 1,
OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: 128,
OTEL_SPAN_EVENT_COUNT_LIMIT: 128,
OTEL_SPAN_LINK_COUNT_LIMIT: 128,
OTEL_TRACES_SAMPLER: TracesSamplerValues.ParentBasedAlwaysOn,
OTEL_TRACES_SAMPLER_ARG: '',
obecny marked this conversation as resolved.
Show resolved Hide resolved
};

/**
Expand Down Expand Up @@ -196,10 +199,6 @@ export function parseEnvironment(values: RAW_ENVIRONMENT): ENVIRONMENT {
const key = env as keyof ENVIRONMENT;

switch (key) {
case 'OTEL_SAMPLING_PROBABILITY':
parseNumber(key, environment, values, 0, 1);
break;

case 'OTEL_LOG_LEVEL':
setLogLevelFromEnv(key, environment, values);
break;
Expand Down
24 changes: 24 additions & 0 deletions packages/opentelemetry-core/src/utils/sampling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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.
*/

export enum TracesSamplerValues {
AlwaysOff = 'always_off',
AlwaysOn = 'always_on',
ParentBasedAlwaysOff = 'parentbased_always_off',
ParentBasedAlwaysOn = 'parentbased_always_on',
ParentBasedTraceIdRatio = 'parentbased_traceidratio',
TraceIdRatio = 'traceidratio',
}
28 changes: 10 additions & 18 deletions packages/opentelemetry-core/test/utils/environment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
import * as assert from 'assert';
import * as sinon from 'sinon';
import { DiagLogLevel } from '@opentelemetry/api';
import { TracesSamplerValues } from '../../src';

let lastMock: RAW_ENVIRONMENT = {};

Expand Down Expand Up @@ -83,15 +84,15 @@ describe('environment', () => {
OTEL_LOG_LEVEL: 'ERROR',
OTEL_NO_PATCH_MODULES: 'a,b,c',
OTEL_RESOURCE_ATTRIBUTES: '<attrs>',
OTEL_SAMPLING_PROBABILITY: '0.5',
OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: 10,
OTEL_SPAN_EVENT_COUNT_LIMIT: 20,
OTEL_SPAN_LINK_COUNT_LIMIT: 30,
OTEL_TRACES_SAMPLER: 'always_on',
OTEL_TRACES_SAMPLER_ARG: '0.5',
});
const env = getEnv();
assert.deepStrictEqual(env.OTEL_NO_PATCH_MODULES, ['a', 'b', 'c']);
assert.strictEqual(env.OTEL_LOG_LEVEL, DiagLogLevel.ERROR);
assert.strictEqual(env.OTEL_SAMPLING_PROBABILITY, 0.5);
assert.strictEqual(env.OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, 10);
assert.strictEqual(env.OTEL_SPAN_EVENT_COUNT_LIMIT, 20);
assert.strictEqual(env.OTEL_SPAN_LINK_COUNT_LIMIT, 30);
Expand All @@ -117,20 +118,8 @@ describe('environment', () => {
assert.strictEqual(env.OTEL_RESOURCE_ATTRIBUTES, '<attrs>');
assert.strictEqual(env.OTEL_BSP_MAX_EXPORT_BATCH_SIZE, 40);
assert.strictEqual(env.OTEL_BSP_SCHEDULE_DELAY, 50);
});

it('should match invalid values to closest valid equivalent', () => {
mockEnvironment({
OTEL_SAMPLING_PROBABILITY: '-0.1',
});
const minEnv = getEnv();
assert.strictEqual(minEnv.OTEL_SAMPLING_PROBABILITY, 0);

mockEnvironment({
OTEL_SAMPLING_PROBABILITY: '1.1',
});
const maxEnv = getEnv();
assert.strictEqual(maxEnv.OTEL_SAMPLING_PROBABILITY, 1);
assert.strictEqual(env.OTEL_TRACES_SAMPLER, 'always_on');
assert.strictEqual(env.OTEL_TRACES_SAMPLER_ARG, '0.5');
});

it('should parse OTEL_LOG_LEVEL despite casing', () => {
Expand Down Expand Up @@ -158,12 +147,15 @@ describe('environment', () => {
it('should remove a mock environment', () => {
mockEnvironment({
OTEL_LOG_LEVEL: 'DEBUG',
OTEL_SAMPLING_PROBABILITY: 0.5,
OTEL_TRACES_SAMPLER: TracesSamplerValues.AlwaysOff,
});
removeMockEnvironment();
const env = getEnv();
assert.strictEqual(env.OTEL_LOG_LEVEL, DiagLogLevel.INFO);
assert.strictEqual(env.OTEL_SAMPLING_PROBABILITY, 1);
assert.strictEqual(
env.OTEL_TRACES_SAMPLER,
TracesSamplerValues.ParentBasedAlwaysOn
);
});
});
});
3 changes: 1 addition & 2 deletions packages/opentelemetry-tracing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ span.end();

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 [ParentBased](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/sdk.md#parentbased) sampler.
environmentally configurable sampling (via `OTEL_TRACES_SAMPLER` and `OTEL_TRACES_SAMPLER_ARG`).

## Example

Expand Down
85 changes: 83 additions & 2 deletions packages/opentelemetry-tracing/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,18 @@
* limitations under the License.
*/

import { AlwaysOnSampler, getEnv } from '@opentelemetry/core';
import { diag, Sampler } from '@opentelemetry/api';
import {
AlwaysOffSampler,
AlwaysOnSampler,
getEnv,
TracesSamplerValues,
ParentBasedSampler,
TraceIdRatioBasedSampler,
} from '@opentelemetry/core';
import { ENVIRONMENT } from '@opentelemetry/core/src/utils/environment';

const env = getEnv();

/**
* Default configuration. For fields with primitive values, any user-provided
Expand All @@ -23,10 +34,80 @@ import { AlwaysOnSampler, getEnv } from '@opentelemetry/core';
* used to extend the default value.
*/
export const DEFAULT_CONFIG = {
sampler: new AlwaysOnSampler(),
sampler: buildSamplerFromEnv(env),
traceParams: {
numberOfAttributesPerSpan: getEnv().OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
numberOfLinksPerSpan: getEnv().OTEL_SPAN_LINK_COUNT_LIMIT,
numberOfEventsPerSpan: getEnv().OTEL_SPAN_EVENT_COUNT_LIMIT,
},
};

const FALLBACK_OTEL_TRACES_SAMPLER = TracesSamplerValues.AlwaysOn;

/**
* Based on environment, builds a sampler, complies with specification.
* @param env optional, by default uses getEnv(), but allows passing a value to reuse parsed environment
*/
export function buildSamplerFromEnv(
env: Required<ENVIRONMENT> = getEnv()
): Sampler {
switch (env.OTEL_TRACES_SAMPLER) {
obecny marked this conversation as resolved.
Show resolved Hide resolved
case TracesSamplerValues.AlwaysOn:
return new AlwaysOnSampler();
case TracesSamplerValues.AlwaysOff:
return new AlwaysOffSampler();
case TracesSamplerValues.ParentBasedAlwaysOn:
return new ParentBasedSampler({
root: new AlwaysOnSampler(),
});
case TracesSamplerValues.ParentBasedAlwaysOff:
return new ParentBasedSampler({
root: new AlwaysOffSampler(),
});
case TracesSamplerValues.TraceIdRatio:
return new TraceIdRatioBasedSampler(getSamplerProbabilityFromEnv(env));
case TracesSamplerValues.ParentBasedTraceIdRatio:
return new ParentBasedSampler({
root: new TraceIdRatioBasedSampler(getSamplerProbabilityFromEnv(env)),
});
default:
diag.error(
`OTEL_TRACES_SAMPLER value "${env.OTEL_TRACES_SAMPLER} invalid, defaulting to ${FALLBACK_OTEL_TRACES_SAMPLER}".`
);
return new AlwaysOnSampler();
}
}

const DEFAULT_RATIO = 1;

function getSamplerProbabilityFromEnv(
env: Required<ENVIRONMENT>
): number | undefined {
if (
env.OTEL_TRACES_SAMPLER_ARG === undefined ||
env.OTEL_TRACES_SAMPLER_ARG === ''
) {
diag.error(
`OTEL_TRACES_SAMPLER_ARG is blank, defaulting to ${DEFAULT_RATIO}.`
);
return DEFAULT_RATIO;
}

const probability = Number(env.OTEL_TRACES_SAMPLER_ARG);

if (isNaN(probability)) {
diag.error(
`OTEL_TRACES_SAMPLER_ARG=${env.OTEL_TRACES_SAMPLER_ARG} was given, but it is invalid, defaulting to ${DEFAULT_RATIO}.`
);
return DEFAULT_RATIO;
}

if (probability < 0 || probability > 1) {
diag.error(
`OTEL_TRACES_SAMPLER_ARG=${env.OTEL_TRACES_SAMPLER_ARG} was given, but it is out of range ([0..1]), defaulting to ${DEFAULT_RATIO}.`
);
return DEFAULT_RATIO;
}

return probability;
}
20 changes: 5 additions & 15 deletions packages/opentelemetry-tracing/src/utility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,22 @@
* limitations under the License.
*/

import { DEFAULT_CONFIG } from './config';
import { buildSamplerFromEnv, DEFAULT_CONFIG } from './config';
import { TracerConfig } from './types';
import {
ParentBasedSampler,
TraceIdRatioBasedSampler,
getEnv,
} from '@opentelemetry/core';

/**
* Function to merge Default configuration (as specified in './config') with
* user provided configurations.
*/
export function mergeConfig(userConfig: TracerConfig) {
const otelSamplingProbability = getEnv().OTEL_SAMPLING_PROBABILITY;
const perInstanceDefaults: Partial<TracerConfig> = {
sampler: buildSamplerFromEnv(),
};

const target = Object.assign(
{},
DEFAULT_CONFIG,
// use default AlwaysOnSampler if otelSamplingProbability is 1
otelSamplingProbability !== undefined && otelSamplingProbability < 1
? {
sampler: new ParentBasedSampler({
root: new TraceIdRatioBasedSampler(otelSamplingProbability),
}),
}
: {},
perInstanceDefaults,
userConfig
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,9 @@ describe('BasicTracerProvider', () => {
});

it('should start a span with name and with invalid parent span', () => {
const tracer = new BasicTracerProvider().getTracer('default');
const tracer = new BasicTracerProvider({
sampler: new AlwaysOnSampler(),
}).getTracer('default');
const span = tracer.startSpan(
'my-span',
{},
Expand Down
Loading