Merge branch 'main' of github.com:open-telemetry/opentelemetry-js into config-yml

This commit is contained in:
maryliag 2025-09-19 22:43:11 -04:00
commit b158b36f29
27 changed files with 1810 additions and 0 deletions

View File

@ -10,6 +10,7 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2
### :rocket: Features
* feat(sampler-composite): Added experimental implementations of draft composite sampling spec [#5839](https://github.com/open-telemetry/opentelemetry-js/pull/5839) @anuraaga
* feat(opentelemetry-configuration): Parse of Configuration File [#5875](https://github.com/open-telemetry/opentelemetry-js/pull/5875) @maryliag
### :bug: Bug Fixes

View File

@ -0,0 +1 @@
build

View File

@ -0,0 +1,8 @@
module.exports = {
"env": {
"mocha": true,
"commonjs": true,
"shared-node-browser": true
},
...require('../../../eslint.base.js')
}

View File

@ -0,0 +1,57 @@
# OpenTelemetry Composite Sampling
[![NPM Published Version][npm-img]][npm-url]
[![Apache License][license-image]][license-image]
**Note: This is an experimental package under active development. New releases may include breaking changes.**
This package provides implementations of composite samplers that propagate sampling information across a trace.
This implements the [experimental specification][probability-sampling].
Currently `ComposableRuleBased` and `ComposableAnnotating` are not implemented.
## Quick Start
To get started you will need to install a compatible OpenTelemetry SDK.
### Samplers
This module exports samplers that follow the general behavior of the standard SDK samplers, but ensuring
it is consistent across a trace by using the tracestate header. Notably, the tracestate can be examined
in exported spans to reconstruct population metrics.
```typescript
import {
createCompositeSampler,
createComposableAlwaysOffSampler,
createComposableAlwaysOnSampler,
createComposableParentThresholdSampler,
createComposableTraceIDRatioBasedSampler,
} from '@opentelemetry/sampler-composite';
// never sample
const sampler = createCompositeSampler(createComposableAlwaysOffSampler());
// always sample
const sampler = createCompositeSampler(createComposableAlwaysOnSampler());
// follow the parent, or otherwise sample with a probability if root
const sampler = createCompositeSampler(
createComposableParentThresholdSampler(createComposableTraceIDRatioBasedSampler(0.3)));
```
## Useful links
- For more information on OpenTelemetry, visit: <https://opentelemetry.io/>
- For more about OpenTelemetry JavaScript: <https://github.com/open-telemetry/opentelemetry-js>
- For help or feedback on this project, join us in [GitHub Discussions][discussions-url]
## License
Apache 2.0 - See [LICENSE][license-url] for more information.
[discussions-url]: https://github.com/open-telemetry/opentelemetry-js/discussions
[license-url]: https://github.com/open-telemetry/opentelemetry-js/blob/main/LICENSE
[license-image]: https://img.shields.io/badge/license-Apache_2.0-green.svg?style=flat
[npm-url]: https://www.npmjs.com/package/@opentelemetry/sampler-composite
[npm-img]: https://badge.fury.io/js/%40opentelemetry%sampler-composite.svg
[probability-sampling]: https://opentelemetry.io/docs/specs/otel/trace/tracestate-probability-sampling/

View File

@ -0,0 +1,70 @@
{
"name": "@opentelemetry/sampler-composite",
"private": false,
"publishConfig": {
"access": "public"
},
"version": "0.205.0",
"description": "Composite samplers for OpenTelemetry tracing",
"module": "build/esm/index.js",
"esnext": "build/esnext/index.js",
"types": "build/src/index.d.ts",
"main": "build/src/index.js",
"repository": "open-telemetry/opentelemetry-js",
"scripts": {
"prepublishOnly": "npm run compile",
"compile": "tsc --build",
"clean": "tsc --build --clean",
"test": "nyc mocha 'test/**/*.test.ts'",
"tdd": "npm run test -- --watch-extensions ts --watch",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"version": "node ../../../scripts/version-update.js",
"watch": "tsc --build --watch",
"precompile": "lerna run version --scope @opentelemetry/sampler-composite --include-dependencies",
"prewatch": "npm run precompile",
"peer-api-check": "node ../../../scripts/peer-api-check.js",
"align-api-deps": "node ../../../scripts/align-api-deps.js"
},
"keywords": [
"opentelemetry",
"nodejs",
"sampling",
"tracing"
],
"author": "OpenTelemetry Authors",
"license": "Apache-2.0",
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"files": [
"build/esm/**/*.js",
"build/esm/**/*.js.map",
"build/esm/**/*.d.ts",
"build/esnext/**/*.js",
"build/esnext/**/*.js.map",
"build/esnext/**/*.d.ts",
"build/src/**/*.js",
"build/src/**/*.js.map",
"build/src/**/*.d.ts",
"LICENSE",
"README.md"
],
"peerDependencies": {
"@opentelemetry/api": "^1.3.0"
},
"devDependencies": {
"@opentelemetry/api": "1.9.0",
"@types/mocha": "10.0.10",
"@types/node": "18.6.5",
"lerna": "6.6.2",
"mocha": "11.1.0",
"nyc": "17.1.0"
},
"dependencies": {
"@opentelemetry/core": "2.0.1",
"@opentelemetry/sdk-trace-base": "2.0.1"
},
"homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/sampler-composite",
"sideEffects": false
}

View File

@ -0,0 +1,41 @@
/*
* 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 type { ComposableSampler, SamplingIntent } from './types';
import { INVALID_THRESHOLD } from './util';
const intent: SamplingIntent = {
threshold: INVALID_THRESHOLD,
thresholdReliable: false,
};
class ComposableAlwaysOffSampler implements ComposableSampler {
getSamplingIntent(): SamplingIntent {
return intent;
}
toString(): string {
return 'ComposableAlwaysOffSampler';
}
}
const _sampler = new ComposableAlwaysOffSampler();
/**
* Returns a composable sampler that does not sample any span.
*/
export function createComposableAlwaysOffSampler(): ComposableSampler {
return _sampler;
}

View File

@ -0,0 +1,41 @@
/*
* 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 type { ComposableSampler, SamplingIntent } from './types';
import { MIN_THRESHOLD } from './util';
const intent: SamplingIntent = {
threshold: MIN_THRESHOLD,
thresholdReliable: true,
};
class ComposableAlwaysOnSampler implements ComposableSampler {
getSamplingIntent(): SamplingIntent {
return intent;
}
toString(): string {
return 'ComposableAlwaysOnSampler';
}
}
const _sampler = new ComposableAlwaysOnSampler();
/**
* Returns a composable sampler that samples all span.
*/
export function createComposableAlwaysOnSampler(): ComposableSampler {
return _sampler;
}

View File

@ -0,0 +1,126 @@
/*
* 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,
SpanKind,
Attributes,
Link,
TraceState,
trace,
} from '@opentelemetry/api';
import { TraceState as CoreTraceState } from '@opentelemetry/core';
import {
Sampler,
SamplingDecision,
SamplingResult,
} from '@opentelemetry/sdk-trace-base';
import { ComposableSampler } from './types';
import { parseOtelTraceState, serializeTraceState } from './tracestate';
import {
INVALID_THRESHOLD,
isValidRandomValue,
isValidThreshold,
} from './util';
class CompositeSampler implements Sampler {
constructor(private readonly delegate: ComposableSampler) {}
shouldSample(
context: Context,
traceId: string,
spanName: string,
spanKind: SpanKind,
attributes: Attributes,
links: Link[]
): SamplingResult {
const spanContext = trace.getSpanContext(context);
const traceState = spanContext?.traceState;
let otTraceState = parseOtelTraceState(traceState);
const intent = this.delegate.getSamplingIntent(
context,
traceId,
spanName,
spanKind,
attributes,
links
);
let adjustedCountCorrect = false;
let sampled = false;
if (isValidThreshold(intent.threshold)) {
adjustedCountCorrect = intent.thresholdReliable;
let randomness: bigint;
if (isValidRandomValue(otTraceState.randomValue)) {
randomness = otTraceState.randomValue;
} else {
// Use last 56 bits of trace_id as randomness.
randomness = BigInt(`0x${traceId.slice(-14)}`);
}
sampled = intent.threshold <= randomness;
}
const decision = sampled
? SamplingDecision.RECORD_AND_SAMPLED
: SamplingDecision.NOT_RECORD;
if (sampled && adjustedCountCorrect) {
otTraceState = {
...otTraceState,
threshold: intent.threshold,
};
} else {
otTraceState = {
...otTraceState,
threshold: INVALID_THRESHOLD,
};
}
const otts = serializeTraceState(otTraceState);
let newTraceState: TraceState | undefined;
if (traceState) {
newTraceState = traceState;
if (intent.updateTraceState) {
newTraceState = intent.updateTraceState(newTraceState);
}
}
if (otts) {
if (!newTraceState) {
newTraceState = new CoreTraceState();
}
newTraceState = newTraceState.set('ot', otts);
}
return {
decision,
attributes: intent.attributes,
traceState: newTraceState,
};
}
toString(): string {
return this.delegate.toString();
}
}
/**
* Returns a composite sampler that uses a composable sampler to make its
* sampling decisions while handling tracestate.
*/
export function createCompositeSampler(delegate: ComposableSampler): Sampler {
return new CompositeSampler(delegate);
}

View File

@ -0,0 +1,22 @@
/*
* 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 { createComposableAlwaysOffSampler } from './alwaysoff';
export { createComposableAlwaysOnSampler } from './alwayson';
export { createComposableTraceIDRatioBasedSampler } from './traceidratio';
export { createComposableParentThresholdSampler } from './parentthreshold';
export { createCompositeSampler } from './composite';
export type { ComposableSampler, SamplingIntent } from './types';

View File

@ -0,0 +1,89 @@
/*
* 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 {
Attributes,
Context,
isSpanContextValid,
Link,
SpanKind,
TraceFlags,
trace,
} from '@opentelemetry/api';
import { ComposableSampler, SamplingIntent } from './types';
import { parseOtelTraceState } from './tracestate';
import { INVALID_THRESHOLD, isValidThreshold, MIN_THRESHOLD } from './util';
class ComposableParentThresholdSampler implements ComposableSampler {
private readonly description: string;
constructor(private readonly rootSampler: ComposableSampler) {
this.description = `ComposableParentThresholdSampler(rootSampler=${rootSampler})`;
}
getSamplingIntent(
context: Context,
traceId: string,
spanName: string,
spanKind: SpanKind,
attributes: Attributes,
links: Link[]
): SamplingIntent {
const parentSpanContext = trace.getSpanContext(context);
if (!parentSpanContext || !isSpanContextValid(parentSpanContext)) {
return this.rootSampler.getSamplingIntent(
context,
traceId,
spanName,
spanKind,
attributes,
links
);
}
const otTraceState = parseOtelTraceState(parentSpanContext.traceState);
if (isValidThreshold(otTraceState.threshold)) {
return {
threshold: otTraceState.threshold,
thresholdReliable: true,
};
}
const threshold =
parentSpanContext.traceFlags & TraceFlags.SAMPLED
? MIN_THRESHOLD
: INVALID_THRESHOLD;
return {
threshold,
thresholdReliable: false,
};
}
toString(): string {
return this.description;
}
}
/**
* Returns a composable sampler that respects the sampling decision of the
* parent span or falls back to the given sampler if it is a root span.
*/
export function createComposableParentThresholdSampler(
rootSampler: ComposableSampler
): ComposableSampler {
return new ComposableParentThresholdSampler(rootSampler);
}

View File

@ -0,0 +1,78 @@
/*
* 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 { ComposableSampler, SamplingIntent } from './types';
import { INVALID_THRESHOLD, MAX_THRESHOLD } from './util';
import { serializeTh } from './tracestate';
class ComposableTraceIDRatioBasedSampler implements ComposableSampler {
private readonly intent: SamplingIntent;
private readonly description: string;
constructor(ratio: number) {
if (ratio < 0 || ratio > 1) {
throw new Error(
`Invalid sampling probability: ${ratio}. Must be between 0 and 1.`
);
}
const threshold = calculateThreshold(ratio);
const thresholdStr =
threshold === MAX_THRESHOLD ? 'max' : serializeTh(threshold);
if (threshold !== MAX_THRESHOLD) {
this.intent = {
threshold: threshold,
thresholdReliable: true,
};
} else {
// Same as AlwaysOff, notably the threshold is not considered reliable. The spec mentions
// returning an instance of ComposableAlwaysOffSampler in this case but it seems clearer
// if the description of the sampler matches the user's request.
this.intent = {
threshold: INVALID_THRESHOLD,
thresholdReliable: false,
};
}
this.description = `ComposableTraceIDRatioBasedSampler(threshold=${thresholdStr}, ratio=${ratio})`;
}
getSamplingIntent(): SamplingIntent {
return this.intent;
}
toString(): string {
return this.description;
}
}
/**
* Returns a composable sampler that samples each span with a fixed ratio.
*/
export function createComposableTraceIDRatioBasedSampler(
ratio: number
): ComposableSampler {
return new ComposableTraceIDRatioBasedSampler(ratio);
}
const probabilityThresholdScale = Math.pow(2, 56);
// TODO: Reduce threshold precision following spec recommendation of 4
// to reduce size of serialized tracestate.
function calculateThreshold(samplingProbability: number): bigint {
return (
MAX_THRESHOLD -
BigInt(Math.round(samplingProbability * probabilityThresholdScale))
);
}

View File

@ -0,0 +1,156 @@
/*
* 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 { TraceState } from '@opentelemetry/api';
import {
INVALID_RANDOM_VALUE,
INVALID_THRESHOLD,
isValidRandomValue,
isValidThreshold,
MAX_THRESHOLD,
} from './util';
export type OtelTraceState = {
/** The random value for sampling decisions in the trace. */
randomValue: bigint;
/** The upstream threshold for sampling decisions. */
threshold: bigint;
/** The rest of the "ot" tracestate value. */
rest?: string[];
};
export const INVALID_TRACE_STATE: OtelTraceState = Object.freeze({
randomValue: INVALID_RANDOM_VALUE,
threshold: INVALID_THRESHOLD,
});
const TRACE_STATE_SIZE_LIMIT = 256;
const MAX_VALUE_LENGTH = 14; // 56 bits, 4 bits per hex digit
export function parseOtelTraceState(
traceState: TraceState | undefined
): OtelTraceState {
const ot = traceState?.get('ot');
if (!ot || ot.length > TRACE_STATE_SIZE_LIMIT) {
return INVALID_TRACE_STATE;
}
let threshold = INVALID_THRESHOLD;
let randomValue = INVALID_RANDOM_VALUE;
// Parse based on https://opentelemetry.io/docs/specs/otel/trace/tracestate-handling/
const members = ot.split(';');
let rest: string[] | undefined;
for (const member of members) {
if (member.startsWith('th:')) {
threshold = parseTh(member.slice('th:'.length), INVALID_THRESHOLD);
continue;
}
if (member.startsWith('rv:')) {
randomValue = parseRv(member.slice('rv:'.length), INVALID_RANDOM_VALUE);
continue;
}
if (!rest) {
rest = [];
}
rest.push(member);
}
return {
randomValue,
threshold,
rest,
};
}
export function serializeTraceState(otTraceState: OtelTraceState): string {
if (
!isValidThreshold(otTraceState.threshold) &&
!isValidRandomValue(otTraceState.randomValue) &&
!otTraceState.rest
) {
return '';
}
const parts: string[] = [];
if (
isValidThreshold(otTraceState.threshold) &&
otTraceState.threshold !== MAX_THRESHOLD
) {
parts.push(`th:${serializeTh(otTraceState.threshold)}`);
}
if (isValidRandomValue(otTraceState.randomValue)) {
parts.push(`rv:${serializeRv(otTraceState.randomValue)}`);
}
if (otTraceState.rest) {
parts.push(...otTraceState.rest);
}
let res = parts.join(';');
while (res.length > TRACE_STATE_SIZE_LIMIT) {
const lastSemicolon = res.lastIndexOf(';');
if (lastSemicolon === -1) {
break;
}
res = res.slice(0, lastSemicolon);
}
return res;
}
function parseTh(value: string, defaultValue: bigint): bigint {
if (!value || value.length > MAX_VALUE_LENGTH) {
return defaultValue;
}
try {
return BigInt('0x' + value.padEnd(MAX_VALUE_LENGTH, '0'));
} catch {
return defaultValue;
}
}
function parseRv(value: string, defaultValue: bigint): bigint {
if (!value || value.length !== MAX_VALUE_LENGTH) {
return defaultValue;
}
try {
return BigInt(`0x${value}`);
} catch {
return defaultValue;
}
}
// hex value without trailing zeros
export function serializeTh(threshold: bigint): string {
if (threshold === 0n) {
return '0';
}
const value = threshold.toString(16).padStart(MAX_VALUE_LENGTH, '0');
let idxAfterNonZero = value.length;
for (let i = value.length - 1; i >= 0; i--) {
if (value[i] !== '0') {
idxAfterNonZero = i + 1;
break;
}
}
// Checked at beginning so there is definitely a nonzero.
return value.slice(0, idxAfterNonZero);
}
function serializeRv(randomValue: bigint): string {
return randomValue.toString(16).padStart(MAX_VALUE_LENGTH, '0');
}

View File

@ -0,0 +1,44 @@
/*
* 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 { Attributes, TraceState } from '@opentelemetry/api';
import { type Sampler } from '@opentelemetry/sdk-trace-base';
/** Information to make a sampling decision. */
export type SamplingIntent = {
/** The sampling threshold value. A lower threshold increases the likelihood of sampling. */
threshold: bigint;
/** Whether the threshold can be reliably used for Span-to-Metrics estimation. */
thresholdReliable: boolean;
/** Any attributes to add to the span for the sampling result. */
attributes?: Attributes;
/** How to update the TraceState for the span. */
updateTraceState?: (ts: TraceState | undefined) => TraceState | undefined;
};
/** A sampler that can be composed to make a final sampling decision. */
export interface ComposableSampler {
/** Returns the information to make a sampling decision. */
getSamplingIntent(
...args: Parameters<Sampler['shouldSample']>
): SamplingIntent;
/** Returns the sampler name or short description with the configuration. */
toString(): string;
}

View File

@ -0,0 +1,31 @@
/*
* 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 const INVALID_THRESHOLD = -1n;
export const INVALID_RANDOM_VALUE = -1n;
const RANDOM_VALUE_BITS = 56n;
export const MAX_THRESHOLD = 1n << RANDOM_VALUE_BITS; // 0% sampling
export const MIN_THRESHOLD = 0n; // 100% sampling
const MAX_RANDOM_VALUE = MAX_THRESHOLD - 1n;
export function isValidThreshold(threshold: bigint): boolean {
return threshold >= MIN_THRESHOLD && threshold <= MAX_THRESHOLD;
}
export function isValidRandomValue(randomValue: bigint): boolean {
return randomValue >= 0n && randomValue <= MAX_RANDOM_VALUE;
}

View File

@ -0,0 +1,72 @@
/*
* 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 * as assert from 'assert';
import { context, SpanKind } from '@opentelemetry/api';
import { SamplingDecision } from '@opentelemetry/sdk-trace-base';
import {
createCompositeSampler,
createComposableAlwaysOffSampler,
} from '../src';
import { traceIdGenerator } from './util';
describe('ComposableAlwaysOffSampler', () => {
const composableSampler = createComposableAlwaysOffSampler();
it('should have a description', () => {
assert.strictEqual(
composableSampler.toString(),
'ComposableAlwaysOffSampler'
);
});
it('should have a constant threshold', () => {
assert.strictEqual(
composableSampler.getSamplingIntent(
context.active(),
'unused',
'span',
SpanKind.SERVER,
{},
[]
).threshold,
-1n
);
});
it('should never sample', () => {
const sampler = createCompositeSampler(composableSampler);
const generator = traceIdGenerator();
let numSampled = 0;
for (let i = 0; i < 10000; i++) {
const result = sampler.shouldSample(
context.active(),
generator(),
'span',
SpanKind.SERVER,
{},
[]
);
if (result.decision === SamplingDecision.RECORD_AND_SAMPLED) {
numSampled++;
}
assert.strictEqual(result.traceState, undefined);
}
assert.strictEqual(numSampled, 0);
});
});

View File

@ -0,0 +1,72 @@
/*
* 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 * as assert from 'assert';
import { context, SpanKind } from '@opentelemetry/api';
import { SamplingDecision } from '@opentelemetry/sdk-trace-base';
import {
createCompositeSampler,
createComposableAlwaysOnSampler,
} from '../src';
import { traceIdGenerator } from './util';
describe('ComposableAlwaysOnSampler', () => {
const composableSampler = createComposableAlwaysOnSampler();
it('should have a description', () => {
assert.strictEqual(
composableSampler.toString(),
'ComposableAlwaysOnSampler'
);
});
it('should have a constant threshold', () => {
assert.strictEqual(
composableSampler.getSamplingIntent(
context.active(),
'unused',
'span',
SpanKind.SERVER,
{},
[]
).threshold,
0n
);
});
it('should always sample', () => {
const sampler = createCompositeSampler(composableSampler);
const generator = traceIdGenerator();
let numSampled = 0;
for (let i = 0; i < 10000; i++) {
const result = sampler.shouldSample(
context.active(),
generator(),
'span',
SpanKind.SERVER,
{},
[]
);
if (result.decision === SamplingDecision.RECORD_AND_SAMPLED) {
numSampled++;
}
assert.strictEqual(result.traceState?.get('ot'), 'th:0');
}
assert.strictEqual(numSampled, 10000);
});
});

View File

@ -0,0 +1,192 @@
/*
* 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 * as assert from 'assert';
import {
context,
SpanContext,
SpanKind,
TraceFlags,
trace,
} from '@opentelemetry/api';
import { SamplingDecision } from '@opentelemetry/sdk-trace-base';
import {
createCompositeSampler,
createComposableAlwaysOffSampler,
createComposableAlwaysOnSampler,
createComposableParentThresholdSampler,
createComposableTraceIDRatioBasedSampler,
} from '../src';
import { INVALID_RANDOM_VALUE, INVALID_THRESHOLD } from '../src/util';
import {
INVALID_TRACE_STATE,
parseOtelTraceState,
serializeTraceState,
} from '../src/tracestate';
import { TraceState } from '@opentelemetry/core';
describe('ConsistentSampler', () => {
const traceId = '00112233445566778800000000000000';
const spanId = '0123456789abcdef';
[
{
sampler: createComposableAlwaysOnSampler(),
parentSampled: true,
parentThreshold: undefined,
parentRandomValue: undefined,
sampled: true,
threshold: 0n,
randomValue: INVALID_RANDOM_VALUE,
testId: 'min threshold no parent random value',
},
{
sampler: createComposableAlwaysOnSampler(),
parentSampled: true,
parentThreshold: undefined,
parentRandomValue: 0x7f99aa40c02744n,
sampled: true,
threshold: 0n,
randomValue: 0x7f99aa40c02744n,
testId: 'min threshold with parent random value',
},
{
sampler: createComposableAlwaysOffSampler(),
parentSampled: true,
parentThreshold: undefined,
parentRandomValue: undefined,
sampled: false,
threshold: INVALID_THRESHOLD,
randomValue: INVALID_RANDOM_VALUE,
testId: 'max threshold',
},
{
sampler: createComposableParentThresholdSampler(
createComposableAlwaysOnSampler()
),
parentSampled: false,
parentThreshold: 0x7f99aa40c02744n,
parentRandomValue: 0x7f99aa40c02744n,
sampled: true,
threshold: 0x7f99aa40c02744n,
randomValue: 0x7f99aa40c02744n,
testId: 'parent based in consistent mode',
},
{
sampler: createComposableParentThresholdSampler(
createComposableAlwaysOnSampler()
),
parentSampled: true,
parentThreshold: undefined,
parentRandomValue: undefined,
sampled: true,
threshold: INVALID_THRESHOLD,
randomValue: INVALID_RANDOM_VALUE,
testId: 'parent based in legacy mode',
},
{
sampler: createComposableTraceIDRatioBasedSampler(0.5),
parentSampled: true,
parentThreshold: undefined,
parentRandomValue: 0x7fffffffffffffn,
sampled: false,
threshold: INVALID_THRESHOLD,
randomValue: 0x7fffffffffffffn,
testId: 'half threshold not sampled',
},
{
sampler: createComposableTraceIDRatioBasedSampler(0.5),
parentSampled: false,
parentThreshold: undefined,
parentRandomValue: 0x80000000000000n,
sampled: true,
threshold: 0x80000000000000n,
randomValue: 0x80000000000000n,
testId: 'half threshold sampled',
},
{
sampler: createComposableTraceIDRatioBasedSampler(1.0),
parentSampled: false,
parentThreshold: 0x80000000000000n,
parentRandomValue: 0x80000000000000n,
sampled: true,
threshold: 0n,
randomValue: 0x80000000000000n,
testId: 'parent inviolating invariant',
},
].forEach(
({
sampler,
parentSampled,
parentThreshold,
parentRandomValue,
sampled,
threshold,
randomValue,
testId,
}) => {
it(`should sample with ${testId}`, () => {
let parentOtTraceState = INVALID_TRACE_STATE;
if (parentThreshold !== undefined) {
parentOtTraceState = {
...parentOtTraceState,
threshold: parentThreshold,
};
}
if (parentRandomValue !== undefined) {
parentOtTraceState = {
...parentOtTraceState,
randomValue: parentRandomValue,
};
}
const parentOt = serializeTraceState(parentOtTraceState);
const parentTraceState = parentOt
? new TraceState().set('ot', parentOt)
: undefined;
const traceFlags = parentSampled ? TraceFlags.SAMPLED : TraceFlags.NONE;
const parentSpanContext: SpanContext = {
traceId,
spanId,
traceFlags,
traceState: parentTraceState,
};
const parentContext = trace.setSpanContext(
context.active(),
parentSpanContext
);
const result = createCompositeSampler(sampler).shouldSample(
parentContext,
traceId,
'name',
SpanKind.INTERNAL,
{},
[]
);
const expectedDecision = sampled
? SamplingDecision.RECORD_AND_SAMPLED
: SamplingDecision.NOT_RECORD;
const state = parseOtelTraceState(result.traceState);
assert.strictEqual(result.decision, expectedDecision);
assert.strictEqual(state.threshold, threshold);
assert.strictEqual(state.randomValue, randomValue);
});
}
);
});

View File

@ -0,0 +1,87 @@
/*
* 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 * as assert from 'assert';
import { context, SpanKind } from '@opentelemetry/api';
import { SamplingDecision } from '@opentelemetry/sdk-trace-base';
import {
createCompositeSampler,
createComposableTraceIDRatioBasedSampler,
} from '../src';
import { traceIdGenerator } from './util';
import { parseOtelTraceState } from '../src/tracestate';
import { INVALID_RANDOM_VALUE } from '../src/util';
describe('ComposableTraceIDRatioBasedSampler', () => {
[
{ ratio: 1.0, thresholdStr: '0' },
{ ratio: 0.5, thresholdStr: '8' },
{ ratio: 0.25, thresholdStr: 'c' },
{ ratio: 1e-300, thresholdStr: 'max' },
{ ratio: 0, thresholdStr: 'max' },
].forEach(({ ratio, thresholdStr }) => {
it(`should have a description for ratio ${ratio}`, () => {
const sampler = createComposableTraceIDRatioBasedSampler(ratio);
assert.strictEqual(
sampler.toString(),
`ComposableTraceIDRatioBasedSampler(threshold=${thresholdStr}, ratio=${ratio})`
);
});
});
[
{ ratio: 1.0, threshold: 0n },
{ ratio: 0.5, threshold: 36028797018963968n },
{ ratio: 0.25, threshold: 54043195528445952n },
{ ratio: 0.125, threshold: 63050394783186944n },
{ ratio: 0.0, threshold: 72057594037927936n },
{ ratio: 0.45, threshold: 39631676720860364n },
{ ratio: 0.2, threshold: 57646075230342348n },
{ ratio: 0.13, threshold: 62690106812997304n },
{ ratio: 0.05, threshold: 68454714336031539n },
].forEach(({ ratio, threshold }) => {
it(`should sample spans with ratio ${ratio}`, () => {
const sampler = createCompositeSampler(
createComposableTraceIDRatioBasedSampler(ratio)
);
const generator = traceIdGenerator();
let numSampled = 0;
for (let i = 0; i < 10000; i++) {
const result = sampler.shouldSample(
context.active(),
generator(),
'span',
SpanKind.SERVER,
{},
[]
);
if (result.decision === SamplingDecision.RECORD_AND_SAMPLED) {
numSampled++;
const otTraceState = parseOtelTraceState(result.traceState);
assert.strictEqual(otTraceState?.threshold, threshold);
assert.strictEqual(otTraceState?.randomValue, INVALID_RANDOM_VALUE);
}
}
const expectedNumSampled = 10000 * ratio;
assert.ok(
Math.abs(numSampled - expectedNumSampled) < 50,
`expected ${expectedNumSampled}, have ${numSampled}`
);
});
});
});

View File

@ -0,0 +1,70 @@
/*
* 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 * as assert from 'assert';
import { serializeTraceState, parseOtelTraceState } from '../src/tracestate';
import { TraceState } from '@opentelemetry/core';
describe('OtelTraceState', () => {
[
{ input: 'a', output: 'a' },
{ input: '#', output: '#' },
{ input: 'rv:1234567890abcd', output: 'rv:1234567890abcd' },
{ input: 'rv:01020304050607', output: 'rv:01020304050607' },
{ input: 'rv:1234567890abcde', output: '' },
{ input: 'th:1234567890abcd', output: 'th:1234567890abcd' },
{ input: 'th:1234567890abcd', output: 'th:1234567890abcd' },
{ input: 'th:10000000000000', output: 'th:1' },
{ input: 'th:1234500000000', output: 'th:12345' },
{ input: 'th:0', output: 'th:0' },
{ input: 'th:100000000000000', output: '' },
{ input: 'th:1234567890abcde', output: '' },
{
input: `a:${''.padEnd(214, 'X')};rv:1234567890abcd;th:1234567890abcd;x:3`,
output: `th:1234567890abcd;rv:1234567890abcd;a:${''.padEnd(214, 'X')};x:3`,
testId: 'long',
},
{ input: 'th:x', output: '' },
{ input: 'th:100000000000000', output: '' },
{ input: 'th:10000000000000', output: 'th:1' },
{ input: 'th:1000000000000', output: 'th:1' },
{ input: 'th:100000000000', output: 'th:1' },
{ input: 'th:10000000000', output: 'th:1' },
{ input: 'th:1000000000', output: 'th:1' },
{ input: 'th:100000000', output: 'th:1' },
{ input: 'th:10000000', output: 'th:1' },
{ input: 'th:1000000', output: 'th:1' },
{ input: 'th:100000', output: 'th:1' },
{ input: 'th:10000', output: 'th:1' },
{ input: 'th:1000', output: 'th:1' },
{ input: 'th:100', output: 'th:1' },
{ input: 'th:10', output: 'th:1' },
{ input: 'th:1', output: 'th:1' },
{ input: 'th:10000000000001', output: 'th:10000000000001' },
{ input: 'th:10000000000010', output: 'th:1000000000001' },
{ input: 'rv:x', output: '' },
{ input: 'rv:100000000000000', output: '' },
{ input: 'rv:10000000000000', output: 'rv:10000000000000' },
{ input: 'rv:1000000000000', output: '' },
].forEach(({ input, output, testId }) => {
it(`should round trip ${testId || `from ${input} to ${output}`}`, () => {
const result = serializeTraceState(
parseOtelTraceState(new TraceState().set('ot', input))
);
assert.strictEqual(result, output);
});
});
});

View File

@ -0,0 +1,48 @@
/*
* 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.
*/
// Use a fixed seed simple but reasonable random number generator for consistent tests.
// Unlike many languages, there isn't a way to set the seed of the built-in random.
function splitmix32(a: number) {
return function () {
a |= 0;
a = (a + 0x9e3779b9) | 0;
let t = a ^ (a >>> 16);
t = Math.imul(t, 0x21f0aaad);
t = t ^ (t >>> 15);
t = Math.imul(t, 0x735a2d97);
return ((t = t ^ (t >>> 15)) >>> 0) / 4294967296;
};
}
export function traceIdGenerator(): () => string {
const seed = 0xdeadbeef;
const random = splitmix32(seed);
// Pre-mix the state.
for (let i = 0; i < 15; i++) {
random();
}
return () => {
const parts: string[] = [];
// 32-bit randoms, concatenate 4 of them
for (let i = 0; i < 4; i++) {
const val = Math.round(random() * 0xffffffff);
parts.push(val.toString(16).padStart(8, '0'));
}
return parts.join('');
};
}

View File

@ -0,0 +1,23 @@
{
"extends": "../../../tsconfig.base.esm.json",
"compilerOptions": {
"allowJs": true,
"outDir": "build/esm",
"rootDir": "src",
"tsBuildInfoFile": "build/esm/tsconfig.esm.tsbuildinfo"
},
"include": [
"src/**/*.ts"
],
"references": [
{
"path": "../../../api"
},
{
"path": "../../../packages/opentelemetry-core"
},
{
"path": "../../../packages/opentelemetry-sdk-trace-base"
}
]
}

View File

@ -0,0 +1,23 @@
{
"extends": "../../../tsconfig.base.esnext.json",
"compilerOptions": {
"allowJs": true,
"outDir": "build/esnext",
"rootDir": "src",
"tsBuildInfoFile": "build/esnext/tsconfig.esnext.tsbuildinfo"
},
"include": [
"src/**/*.ts"
],
"references": [
{
"path": "../../../api"
},
{
"path": "../../../packages/opentelemetry-core"
},
{
"path": "../../../packages/opentelemetry-sdk-trace-base"
}
]
}

View File

@ -0,0 +1,24 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"allowJs": true,
"outDir": "build",
"rootDir": "."
},
"files": [],
"include": [
"src/**/*.ts",
"test/**/*.ts"
],
"references": [
{
"path": "../../../api"
},
{
"path": "../../../packages/opentelemetry-core"
},
{
"path": "../../../packages/opentelemetry-sdk-trace-base"
}
]
}

424
package-lock.json generated
View File

@ -1449,6 +1449,257 @@
"@opentelemetry/api": "^1.3.0"
}
},
"experimental/packages/sampler-composite": {
"name": "@opentelemetry/sampler-composite",
"version": "0.205.0",
"license": "Apache-2.0",
"dependencies": {
"@opentelemetry/core": "2.0.1",
"@opentelemetry/sdk-trace-base": "2.0.1"
},
"devDependencies": {
"@opentelemetry/api": "1.9.0",
"@types/mocha": "10.0.10",
"@types/node": "18.6.5",
"lerna": "6.6.2",
"mocha": "11.1.0",
"nyc": "17.1.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"peerDependencies": {
"@opentelemetry/api": "^1.3.0"
}
},
"experimental/packages/sampler-composite/node_modules/@opentelemetry/core": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz",
"integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==",
"license": "Apache-2.0",
"dependencies": {
"@opentelemetry/semantic-conventions": "^1.29.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.0.0 <1.10.0"
}
},
"experimental/packages/sampler-composite/node_modules/@opentelemetry/resources": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz",
"integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==",
"license": "Apache-2.0",
"dependencies": {
"@opentelemetry/core": "2.0.1",
"@opentelemetry/semantic-conventions": "^1.29.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.3.0 <1.10.0"
}
},
"experimental/packages/sampler-composite/node_modules/@opentelemetry/sdk-trace-base": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz",
"integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==",
"license": "Apache-2.0",
"dependencies": {
"@opentelemetry/core": "2.0.1",
"@opentelemetry/resources": "2.0.1",
"@opentelemetry/semantic-conventions": "^1.29.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.3.0 <1.10.0"
}
},
"experimental/packages/sampler-composite/node_modules/@types/node": {
"version": "18.6.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.5.tgz",
"integrity": "sha512-Xjt5ZGUa5WusGZJ4WJPbOT8QOqp6nDynVFRKcUt32bOgvXEoc6o085WNkYTMO7ifAj2isEfQQ2cseE+wT6jsRw==",
"dev": true,
"license": "MIT"
},
"experimental/packages/sampler-composite/node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0"
},
"experimental/packages/sampler-composite/node_modules/diff": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
"integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
},
"experimental/packages/sampler-composite/node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"dev": true,
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"experimental/packages/sampler-composite/node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"experimental/packages/sampler-composite/node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"experimental/packages/sampler-composite/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true,
"license": "ISC"
},
"experimental/packages/sampler-composite/node_modules/mocha": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz",
"integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-colors": "^4.1.3",
"browser-stdout": "^1.3.1",
"chokidar": "^3.5.3",
"debug": "^4.3.5",
"diff": "^5.2.0",
"escape-string-regexp": "^4.0.0",
"find-up": "^5.0.0",
"glob": "^10.4.5",
"he": "^1.2.0",
"js-yaml": "^4.1.0",
"log-symbols": "^4.1.0",
"minimatch": "^5.1.6",
"ms": "^2.1.3",
"serialize-javascript": "^6.0.2",
"strip-json-comments": "^3.1.1",
"supports-color": "^8.1.1",
"workerpool": "^6.5.1",
"yargs": "^17.7.2",
"yargs-parser": "^21.1.1",
"yargs-unparser": "^2.0.0"
},
"bin": {
"_mocha": "bin/_mocha",
"mocha": "bin/mocha.js"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"experimental/packages/sampler-composite/node_modules/mocha/node_modules/minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"experimental/packages/sampler-composite/node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"experimental/packages/sampler-composite/node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"experimental/packages/sampler-composite/node_modules/workerpool": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz",
"integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==",
"dev": true,
"license": "Apache-2.0"
},
"experimental/packages/sampler-composite/node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"experimental/packages/sampler-jaeger-remote": {
"name": "@opentelemetry/sampler-jaeger-remote",
"version": "0.205.0",
@ -5963,6 +6214,10 @@
"resolved": "packages/opentelemetry-resources",
"link": true
},
"node_modules/@opentelemetry/sampler-composite": {
"resolved": "experimental/packages/sampler-composite",
"link": true
},
"node_modules/@opentelemetry/sampler-jaeger-remote": {
"resolved": "experimental/packages/sampler-jaeger-remote",
"link": true
@ -31041,6 +31296,175 @@
}
}
},
"@opentelemetry/sampler-composite": {
"version": "file:experimental/packages/sampler-composite",
"requires": {
"@opentelemetry/api": "1.9.0",
"@opentelemetry/core": "2.0.1",
"@opentelemetry/sdk-trace-base": "2.0.1",
"@types/mocha": "10.0.10",
"@types/node": "18.6.5",
"lerna": "6.6.2",
"mocha": "11.1.0",
"nyc": "17.1.0"
},
"dependencies": {
"@opentelemetry/core": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz",
"integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==",
"requires": {
"@opentelemetry/semantic-conventions": "^1.29.0"
}
},
"@opentelemetry/resources": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz",
"integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==",
"requires": {
"@opentelemetry/core": "2.0.1",
"@opentelemetry/semantic-conventions": "^1.29.0"
}
},
"@opentelemetry/sdk-trace-base": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz",
"integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==",
"requires": {
"@opentelemetry/core": "2.0.1",
"@opentelemetry/resources": "2.0.1",
"@opentelemetry/semantic-conventions": "^1.29.0"
}
},
"@types/node": {
"version": "18.6.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.5.tgz",
"integrity": "sha512-Xjt5ZGUa5WusGZJ4WJPbOT8QOqp6nDynVFRKcUt32bOgvXEoc6o085WNkYTMO7ifAj2isEfQQ2cseE+wT6jsRw==",
"dev": true
},
"argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
},
"diff": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
"integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
"dev": true
},
"glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"dev": true,
"requires": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
}
},
"jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"dev": true,
"requires": {
"@isaacs/cliui": "^8.0.2",
"@pkgjs/parseargs": "^0.11.0"
}
},
"js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"requires": {
"argparse": "^2.0.1"
}
},
"lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true
},
"mocha": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz",
"integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==",
"dev": true,
"requires": {
"ansi-colors": "^4.1.3",
"browser-stdout": "^1.3.1",
"chokidar": "^3.5.3",
"debug": "^4.3.5",
"diff": "^5.2.0",
"escape-string-regexp": "^4.0.0",
"find-up": "^5.0.0",
"glob": "^10.4.5",
"he": "^1.2.0",
"js-yaml": "^4.1.0",
"log-symbols": "^4.1.0",
"minimatch": "^5.1.6",
"ms": "^2.1.3",
"serialize-javascript": "^6.0.2",
"strip-json-comments": "^3.1.1",
"supports-color": "^8.1.1",
"workerpool": "^6.5.1",
"yargs": "^17.7.2",
"yargs-parser": "^21.1.1",
"yargs-unparser": "^2.0.0"
},
"dependencies": {
"minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"dev": true,
"requires": {
"brace-expansion": "^2.0.1"
}
}
}
},
"path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"dev": true,
"requires": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
}
},
"supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"workerpool": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz",
"integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==",
"dev": true
},
"yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true
}
}
},
"@opentelemetry/sampler-jaeger-remote": {
"version": "file:experimental/packages/sampler-jaeger-remote",
"requires": {

View File

@ -44,6 +44,9 @@
{
"path": "experimental/packages/otlp-transformer/tsconfig.esm.json"
},
{
"path": "experimental/packages/sampler-composite/tsconfig.esm.json"
},
{
"path": "experimental/packages/sdk-logs/tsconfig.esm.json"
},

View File

@ -44,6 +44,9 @@
{
"path": "experimental/packages/otlp-transformer/tsconfig.esnext.json"
},
{
"path": "experimental/packages/sampler-composite/tsconfig.esnext.json"
},
{
"path": "experimental/packages/sdk-logs/tsconfig.esnext.json"
},

View File

@ -26,6 +26,7 @@
"experimental/packages/otlp-exporter-base",
"experimental/packages/otlp-grpc-exporter-base",
"experimental/packages/otlp-transformer",
"experimental/packages/sampler-composite",
"experimental/packages/sampler-jaeger-remote",
"experimental/packages/sdk-logs",
"experimental/packages/shim-opencensus",
@ -126,6 +127,9 @@
{
"path": "experimental/packages/otlp-transformer"
},
{
"path": "experimental/packages/sampler-composite"
},
{
"path": "experimental/packages/sampler-jaeger-remote"
},