Skip to content
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
1 change: 1 addition & 0 deletions experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2
* feat(configuration): bump config schema to v1.1.0; rename `without_scope_info` → `scope_info_enabled` and `without_target_info/development` → `target_info_enabled/development` on the Prometheus pull exporter (semantics inverted), rename `with_resource_constant_labels` → `resource_constant_labels`. Validate `file_format` per the configuration versioning spec: accept any minor version of major `1` (e.g. `1.0`, `1.1`), warn when the minor version is newer than supported, and reject other major versions. [#6781](https://github.com/open-telemetry/opentelemetry-js/pull/6781) @MikeGoldsmith
* feat(sdk-node): wire up `id_generator` from declarative config [#6782](https://github.com/open-telemetry/opentelemetry-js/pull/6782) @MikeGoldsmith
* feat(propagator-env-carrier): empty name normalization [#6827](https://github.com/open-telemetry/opentelemetry-js/pull/6827) @pellared
* feat(propagator-env-carrier): make `EnvironmentGetter` read the current `process.env` [#6853](https://github.com/open-telemetry/opentelemetry-js/pull/6853) @pellared

### :bug: Bug Fixes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ console.log(output);
```

Use `EnvironmentGetter` during process startup to extract context that was
provided through environment variables. It reads from its own snapshot, so pass
`undefined` as the carrier when extracting.
provided through environment variables. It reads from the current `process.env`,
so pass `undefined` as the carrier when extracting.

```javascript
const { ROOT_CONTEXT, trace } = require('@opentelemetry/api');
Expand All @@ -72,9 +72,9 @@ console.log(spanContext.traceId);
// 0102030405060708090a0b0c0d0e0f10
```

`EnvironmentGetter` snapshots `process.env` when it is constructed. Later
changes to `process.env` are not reflected in that getter. Only environment
variables whose names are already normalized are included in the snapshot.
`EnvironmentGetter` reads `process.env` each time `get()` or `keys()` is called.
Later changes to `process.env` are reflected in the same getter. Its `keys()`
method returns only environment variables whose names are already normalized.

## Key Normalization

Expand All @@ -92,10 +92,10 @@ For example, `traceparent` becomes `TRACEPARENT`, `trace-state` becomes

`EnvironmentSetter` always writes normalized key names to its environment map.
`EnvironmentGetter` normalizes the requested propagator key and reads the
corresponding normalized environment variable name from its snapshot. Its
`keys()` method returns only environment variable names that are already
normalized. For example, a propagator request for `x-b3-traceid` matches
`X_B3_TRACEID` from `process.env`, but does not match `x-b3-traceid`.
corresponding normalized environment variable name from the current
`process.env`. Its `keys()` method returns only environment variable names that
are already normalized. For example, a propagator request for `x-b3-traceid`
matches `X_B3_TRACEID` from `process.env`, but does not match `x-b3-traceid`.

Name collisions after normalization should be avoided. For example,
`trace-state` and `TRACE_STATE` both normalize to `TRACE_STATE`. When injecting,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,35 +94,33 @@ function normalizeKey(key: string): string {
}

/**
* TextMapGetter that reads propagation values from a process environment
* snapshot.
* TextMapGetter that reads propagation values from the process environment.
*
* `EnvironmentGetter` snapshots `process.env` when it is constructed and
* ignores the carrier passed to `get()` and `keys()`. Pass `undefined` as the
* carrier when using this getter with a `TextMapPropagator`.
* `EnvironmentGetter` reads the current `process.env` and ignores the carrier
* passed to `get()` and `keys()`. Pass `undefined` as the carrier when using
* this getter with a `TextMapPropagator`.
*
* Only environment variables whose names are already normalized are stored in
* the snapshot. The requested propagator key is normalized before lookup.
* `keys()` returns only environment variables whose names are already
* normalized. The requested propagator key is normalized before lookup.
*
* @see https://opentelemetry.io/docs/specs/otel/context/env-carriers/
*/
export class EnvironmentGetter implements TextMapGetter<void> {
private readonly _carrier: Record<string, string> = {};
get(_carrier: void, key: string): string | undefined {
const normalizedKey = normalizeKey(key);
return process.env[normalizedKey];
}

keys(_carrier: void): string[] {
const keys: string[] = [];

constructor() {
for (const [key, value] of Object.entries(process.env)) {
if (value !== undefined && isNormalizedKey(key)) {
this._carrier[key] = value;
keys.push(key);
}
}
}

get(_carrier: void, key: string): string | undefined {
return this._carrier[normalizeKey(key)];
}

keys(_carrier: void): string[] {
return Object.keys(this._carrier);
return keys;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ describe('EnvironmentGetter and EnvironmentSetter', () => {
assert.strictEqual(getter.get(undefined, 'x-b3-traceid'), 'b3-value');
});

it('should return only already normalized snapshot keys', () => {
it('should return only already normalized environment keys', () => {
process.env.TRACEPARENT = 'traceparent-value';
process.env.TRACE_STATE = 'tracestate-value';
process.env['trace-state'] = 'ignored';
Expand All @@ -159,13 +159,13 @@ describe('EnvironmentGetter and EnvironmentSetter', () => {
]);
});

it('should return empty keys for an empty environment snapshot', () => {
it('should return empty keys for an empty environment', () => {
const getter = new EnvironmentGetter();

assert.deepStrictEqual(getter.keys(undefined), []);
});

it('should ignore empty environment names when snapshotting', () => {
it('should ignore empty environment names when listing keys', () => {
const env = process.env;
process.env = {
'': 'ignored',
Expand All @@ -191,17 +191,13 @@ describe('EnvironmentGetter and EnvironmentSetter', () => {
});

it('should ignore non-normalized environment names when reading values', () => {
process.env.traceparent = 'ignored';
process.env['trace-state'] = 'ignored';
process.env['x-b3-traceid'] = 'ignored';
process.env['1ABC'] = 'ignored';

const getter = new EnvironmentGetter();

assert.strictEqual(getter.get(undefined, 'traceparent'), undefined);
assert.strictEqual(getter.get(undefined, 'trace-state'), undefined);
assert.strictEqual(getter.get(undefined, 'x-b3-traceid'), undefined);
assert.strictEqual(getter.get(undefined, '1abc'), undefined);
assert.deepStrictEqual(getter.keys(undefined), []);
});

Expand All @@ -213,18 +209,28 @@ describe('EnvironmentGetter and EnvironmentSetter', () => {
assert.strictEqual(getter.get(undefined, 'empty'), '');
});

it('should snapshot process.env at construction time', () => {
it('should read current process.env values after construction', () => {
process.env.TRACEPARENT = 'original';
process.env.TRACESTATE = 'initial';
const getter = new EnvironmentGetter();

process.env.TRACEPARENT = 'updated';
process.env.TRACESTATE = 'added-after-construction';
process.env.BAGGAGE = 'added-after-construction';
delete process.env.TRACESTATE;

assert.strictEqual(getter.get(undefined, 'traceparent'), 'original');
assert.strictEqual(getter.get(undefined, 'traceparent'), 'updated');
assert.strictEqual(getter.get(undefined, 'tracestate'), undefined);
assert.strictEqual(
getter.get(undefined, 'baggage'),
'added-after-construction'
);
assert.deepStrictEqual(getter.keys(undefined).sort(), [
'BAGGAGE',
'TRACEPARENT',
]);
});

it('should read from the environment snapshot without a carrier', () => {
it('should read from process.env without a carrier', () => {
process.env.TRACEPARENT = 'environment-value';

const getter = new EnvironmentGetter();
Expand Down Expand Up @@ -258,7 +264,7 @@ describe('EnvironmentGetter and EnvironmentSetter', () => {
});
});

it('should extract W3C trace context from an environment snapshot', () => {
it('should extract W3C trace context from process.env', () => {
process.env.TRACEPARENT = `00-${traceId}-${spanId}-01`;
process.env.TRACESTATE = 'vendor1=value1,vendor2=value2';

Expand All @@ -278,20 +284,6 @@ describe('EnvironmentGetter and EnvironmentSetter', () => {
assert.strictEqual(spanContext?.traceState?.get('vendor2'), 'value2');
});

it('should ignore W3C trace context in non-normalized environment names', () => {
process.env.traceparent = `00-${traceId}-${spanId}-01`;
process.env.tracestate = 'vendor1=value1,vendor2=value2';

const propagator = new W3CTraceContextPropagator();
const context = propagator.extract(
ROOT_CONTEXT,
undefined,
new EnvironmentGetter()
);

assert.strictEqual(trace.getSpanContext(context), undefined);
});

it('should inject W3C baggage into an environment carrier', () => {
const propagator = new W3CBaggagePropagator();
const context = propagation.setBaggage(
Expand All @@ -308,7 +300,7 @@ describe('EnvironmentGetter and EnvironmentSetter', () => {
assert.strictEqual(carrier.BAGGAGE, 'first=one,second=two');
});

it('should extract W3C baggage from an environment snapshot', () => {
it('should extract W3C baggage from process.env', () => {
process.env.BAGGAGE = 'first=one,second=two';

const propagator = new W3CBaggagePropagator();
Expand Down
Loading