Skip to content

Commit

Permalink
feat(jaeger-propagator): propagate/extract baggage #2137 (#2158)
Browse files Browse the repository at this point in the history
  • Loading branch information
vmarchaud authored May 11, 2021
1 parent 5cd02aa commit 5f7ec00
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 18 deletions.
5 changes: 5 additions & 0 deletions packages/opentelemetry-propagator-jaeger/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ provider.register({
});
```

## Baggage Notes

Jeager Baggage is represented as multiple headers where the names are carrier dependent. For this reason, they are omitted from the `fields` method. This behavior should be taken into account if your application relies on the `fields` functionality. See the [specification][fields-spec-url] for more details.

## Trace on Jaeger UI

![example image](jaeger-tracing.png)
Expand All @@ -58,3 +62,4 @@ Apache 2.0 - See [LICENSE][license-url] for more information.
[devDependencies-url]: https://david-dm.org/open-telemetry/opentelemetry-js?path=packages%2Fopentelemetry-propagator-jaeger&type=dev
[npm-url]: https://www.npmjs.com/package/@opentelemetry/propagator-jaeger
[npm-img]: https://badge.fury.io/js/%40opentelemetry%2Fpropagator-jaeger.svg
[fields-spec-url]: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/context/api-propagators.md#fields
77 changes: 59 additions & 18 deletions packages/opentelemetry-propagator-jaeger/src/JaegerPropagator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,22 @@

import {
Context,
getBaggage,
getSpanContext,
setBaggage,
setSpanContext,
SpanContext,
TraceFlags,
TextMapGetter,
TextMapPropagator,
TextMapSetter,
isInstrumentationSuppressed,
createBaggage,
} from '@opentelemetry/api';

export const UBER_TRACE_ID_HEADER = 'uber-trace-id';
export const UBER_BAGGAGE_HEADER_PREFIX = 'uberctx';
const UBER_BAGGAGE_HEADER_REGEX = /^uberctx-(.+)/i;

/**
* Propagates {@link SpanContext} through Trace Context format propagation.
Expand Down Expand Up @@ -55,31 +60,67 @@ export class JaegerPropagator implements TextMapPropagator {

inject(context: Context, carrier: unknown, setter: TextMapSetter) {
const spanContext = getSpanContext(context);
if (!spanContext || isInstrumentationSuppressed(context)) return;

const traceFlags = `0${(spanContext.traceFlags || TraceFlags.NONE).toString(
16
)}`;

setter.set(
carrier,
this._jaegerTraceHeader,
`${spanContext.traceId}:${spanContext.spanId}:0:${traceFlags}`
);
const baggage = getBaggage(context);
if (spanContext && isInstrumentationSuppressed(context) === false) {
const traceFlags = `0${(
spanContext.traceFlags || TraceFlags.NONE
).toString(16)}`;

setter.set(
carrier,
this._jaegerTraceHeader,
`${spanContext.traceId}:${spanContext.spanId}:0:${traceFlags}`
);
}

if (baggage) {
for (const [key, entry] of baggage.getAllEntries()) {
setter.set(
carrier,
`${UBER_BAGGAGE_HEADER_PREFIX}-${key}`,
encodeURIComponent(entry.value)
);
}
}
}

extract(context: Context, carrier: unknown, getter: TextMapGetter): Context {
const uberTraceIdHeader = getter.get(carrier, this._jaegerTraceHeader);
const uberTraceId = Array.isArray(uberTraceIdHeader)
? uberTraceIdHeader[0]
: uberTraceIdHeader;

if (typeof uberTraceId !== 'string') return context;

const spanContext = deserializeSpanContext(uberTraceId);
if (!spanContext) return context;

return setSpanContext(context, spanContext);
const baggageValues = getter
.keys(carrier)
.filter(key => UBER_BAGGAGE_HEADER_REGEX.test(key))
.map(key => {
const value = getter.get(carrier, key);
return {
key: key.substring(UBER_BAGGAGE_HEADER_PREFIX.length + 1),
value: Array.isArray(value) ? value[0] : value,
};
});

let newContext = context;
// if the trace id header is present and valid, inject it into the context
if (typeof uberTraceId === 'string') {
const spanContext = deserializeSpanContext(uberTraceId);
if (spanContext) {
newContext = setSpanContext(newContext, spanContext);
}
}
if (baggageValues.length === 0) return newContext;

// if baggage values are present, inject it into the current baggage
let currentBaggage = getBaggage(context) ?? createBaggage();
for (const baggageEntry of baggageValues) {
if (baggageEntry.value === undefined) continue;
currentBaggage = currentBaggage.setEntry(baggageEntry.key, {
value: decodeURIComponent(baggageEntry.value),
});
}
newContext = setBaggage(newContext, currentBaggage);

return newContext;
}

fields(): string[] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@
*/

import {
createBaggage,
defaultTextMapGetter,
defaultTextMapSetter,
getBaggage,
getSpanContext,
ROOT_CONTEXT,
setBaggage,
setSpanContext,
SpanContext,
suppressInstrumentation,
Expand All @@ -29,6 +32,7 @@ import * as assert from 'assert';
import {
JaegerPropagator,
UBER_TRACE_ID_HEADER,
UBER_BAGGAGE_HEADER_PREFIX,
} from '../src/JaegerPropagator';

describe('JaegerPropagator', () => {
Expand Down Expand Up @@ -92,6 +96,28 @@ describe('JaegerPropagator', () => {
);
assert.strictEqual(carrier[UBER_TRACE_ID_HEADER], undefined);
});

it('should propagate baggage with url encoded values', () => {
const baggage = createBaggage({
test: {
value: '1',
},
myuser: {
value: '%id%',
},
});

jaegerPropagator.inject(
setBaggage(ROOT_CONTEXT, baggage),
carrier,
defaultTextMapSetter
);
assert.strictEqual(carrier[`${UBER_BAGGAGE_HEADER_PREFIX}-test`], '1');
assert.strictEqual(
carrier[`${UBER_BAGGAGE_HEADER_PREFIX}-myuser`],
encodeURIComponent('%id%')
);
});
});

describe('.extract()', () => {
Expand Down Expand Up @@ -177,6 +203,64 @@ describe('JaegerPropagator', () => {
undefined
);
});

it('should extract baggage from carrier', () => {
carrier[`${UBER_BAGGAGE_HEADER_PREFIX}-test`] = 'value';
carrier[`${UBER_BAGGAGE_HEADER_PREFIX}-myuser`] = '%25id%25';
const extractedBaggage = getBaggage(
jaegerPropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter)
);

const firstEntry = extractedBaggage?.getEntry('test');
assert(typeof firstEntry !== 'undefined');
assert(firstEntry.value === 'value');
const secondEntry = extractedBaggage?.getEntry('myuser');
assert(typeof secondEntry !== 'undefined');
assert(secondEntry.value === '%id%');
});

it('should extract baggage from carrier and not override current one', () => {
carrier[`${UBER_BAGGAGE_HEADER_PREFIX}-test`] = 'value';
carrier[`${UBER_BAGGAGE_HEADER_PREFIX}-myuser`] = '%25id%25';
const extractedBaggage = getBaggage(
jaegerPropagator.extract(
setBaggage(ROOT_CONTEXT, createBaggage({ one: { value: 'two' } })),
carrier,
defaultTextMapGetter
)
);

const firstEntry = extractedBaggage?.getEntry('test');
assert(typeof firstEntry !== 'undefined');
assert(firstEntry.value === 'value');
const secondEntry = extractedBaggage?.getEntry('myuser');
assert(typeof secondEntry !== 'undefined');
assert(secondEntry.value === '%id%');
const alreadyExistingEntry = extractedBaggage?.getEntry('one');
assert(typeof alreadyExistingEntry !== 'undefined');
assert(alreadyExistingEntry.value === 'two');
});

it('should handle invalid baggage from carrier (undefined)', () => {
carrier[`${UBER_BAGGAGE_HEADER_PREFIX}-test`] = undefined;
const extractedBaggage = getBaggage(
jaegerPropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter)
);

const firstEntry = extractedBaggage?.getEntry('test');
assert(typeof firstEntry === 'undefined');
});

it('should handle invalid baggage from carrier (array)', () => {
carrier[`${UBER_BAGGAGE_HEADER_PREFIX}-test`] = ['one', 'two'];
const extractedBaggage = getBaggage(
jaegerPropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter)
);

const firstEntry = extractedBaggage?.getEntry('test');
assert(typeof firstEntry !== 'undefined');
assert(firstEntry.value === 'one');
});
});

describe('.fields()', () => {
Expand Down

0 comments on commit 5f7ec00

Please sign in to comment.