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 2 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
10 changes: 4 additions & 6 deletions packages/opentelemetry-core/src/utils/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,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 +69,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 +101,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_ARG: '',
obecny marked this conversation as resolved.
Show resolved Hide resolved
OTEL_TRACES_SAMPLER: 'parentbased_always_on',
};

/**
Expand Down Expand Up @@ -196,10 +198,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: 6 additions & 18 deletions packages/opentelemetry-core/test/utils/environment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,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 +117,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 +146,12 @@ describe('environment', () => {
it('should remove a mock environment', () => {
mockEnvironment({
OTEL_LOG_LEVEL: 'DEBUG',
OTEL_SAMPLING_PROBABILITY: 0.5,
OTEL_TRACES_SAMPLER: 'always_off',
});
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, 'parentbased_always_on');
});
});
});
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
82 changes: 80 additions & 2 deletions packages/opentelemetry-tracing/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,17 @@
* limitations under the License.
*/

import { AlwaysOnSampler, getEnv } from '@opentelemetry/core';
import { diag, Sampler } from '@opentelemetry/api';
import {
AlwaysOffSampler,
AlwaysOnSampler,
getEnv,
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 +33,78 @@ 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,
},
};

/**
* 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 'always_on':
return new AlwaysOnSampler();
case 'always_off':
return new AlwaysOffSampler();
case 'parentbased_always_on':
return new ParentBasedSampler({
root: new AlwaysOnSampler(),
});
case 'parentbased_always_off':
return new ParentBasedSampler({
root: new AlwaysOffSampler(),
});
case 'traceidratio':
return new TraceIdRatioBasedSampler(getSamplerProbabilityFromEnv(env));
case 'parentbased_traceidratio':
return new ParentBasedSampler({
root: new TraceIdRatioBasedSampler(getSamplerProbabilityFromEnv(env)),
});
default:
diag.error(
`OTEL_TRACES_SAMPLER value "${env.OTEL_TRACES_SAMPLER} invalid, defaulting to always_on".`
);
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