diff --git a/packages/opentelemetry-mutable-baggage/README.md b/packages/opentelemetry-mutable-baggage/README.md index 2561c07..9ae7641 100644 --- a/packages/opentelemetry-mutable-baggage/README.md +++ b/packages/opentelemetry-mutable-baggage/README.md @@ -25,7 +25,7 @@ const requestLifecycleMiddleware = (context, next) => { await next(); } finally { // This will always be undefined! - const userId = baggage.api.propagation.getActiveBaggage()?.getEntry('user-id'); + const userId = api.propagation.getActiveBaggage()?.getEntry('user-id'); log.info({ userId }, `Request to ${request.method} ${request.path}`); } @@ -34,7 +34,7 @@ const requestLifecycleMiddleware = (context, next) => { // 2nd middleware: const authenticationMiddleware = (context, next) => { const baggage = api.propagation.getActiveBaggage(); - const newBaggage = baggage.setEntry('user-id', { value: 'faaebf49-c915-43ae-84fc-96c1711d2394' }); + const newBaggage = baggage?.setEntry('user-id', { value: 'faaebf49-c915-43ae-84fc-96c1711d2394' }); await api.context.with(api.context.setBaggage(baggage), next); }; @@ -50,20 +50,25 @@ Replace `W3CBaggagePropagator` with `W3CMutableBaggagePropagator`, since it's a import { CompositePropagator, W3CTraceContextPropagator } from '@opentelemetry/core'; import { W3CMutableBaggagePropagator } from '@uphold/opentelemetry-mutable-baggage'; +const textMapPropagator = new CompositePropagator({ + propagators: [new W3CTraceContextPropagator(), new W3CMutableBaggagePropagator()] +}); + const sdk = new NodeSDK({ - textMapPropagator: new CompositePropagator({ - propagators: [new W3CTraceContextPropagator(), new W3CMutableBaggagePropagator()] - }); + // ... + textMapPropagator }); ``` -Then, to use the baggage throughout your code: +> ⚠️ The `textMapPropagator` option of the SDK will be ignored if both `spanProcessor` or `traceExporter` are not defined in the options. Either define those or call `api.propagation.setGlobalPropagator(textMapPropagator)` manually. + +Then, use the regular `propagation` api to interact with the baggage: ```js -import { getActiveMutableBaggage } from '@uphold/opentelemetry-mutable-baggage'; +import { propagation } from '@uphold/api'; -getActiveMutableBaggage().setEntry('foo', 'bar'); -getActiveMutableBaggage().getEntry('foo'); // bar +// `setEntry` will no longer create a new baggage! +propagation.getActiveBaggage()?.setEntry('foo', 'bar'); ``` ## Tests diff --git a/packages/opentelemetry-mutable-baggage/src/context-helpers.test.ts b/packages/opentelemetry-mutable-baggage/src/context-helpers.test.ts deleted file mode 100644 index d26167a..0000000 --- a/packages/opentelemetry-mutable-baggage/src/context-helpers.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks'; -import { MutableBaggageImpl } from './mutable-baggage-impl'; -import { ROOT_CONTEXT, context as contextApi } from '@opentelemetry/api'; -import { beforeAll, describe, expect, it } from 'vitest'; -import { createMutableBaggage, getMutableBaggage, setMutableBaggage } from './context-helpers'; -import { getActiveMutableBaggage } from '.'; - -beforeAll(() => { - contextApi.setGlobalContextManager(new AsyncHooksContextManager()); -}); - -describe('getActiveMutableBaggage()', () => { - it('should return undefined if there is no mutable baggage in the active context', () => { - expect(getActiveMutableBaggage()).toBeUndefined(); - }); - - it('should return the mutable baggage on the active context', async () => { - const context = setMutableBaggage(ROOT_CONTEXT, createMutableBaggage({ foo: { value: 'bar' } })); - - await contextApi.with(context, () => { - expect(getActiveMutableBaggage()).toBeInstanceOf(MutableBaggageImpl); - expect(getActiveMutableBaggage()?.getAllEntries()).toEqual([['foo', { value: 'bar' }]]); - }); - }); -}); - -describe('getMutableBaggage()', () => { - it('should return undefined if there is no mutable baggage set on the context', () => { - expect(getMutableBaggage(ROOT_CONTEXT)).toBeUndefined(); - }); - - it('should get the mutable baggage set on the context', () => { - const context = setMutableBaggage(ROOT_CONTEXT, createMutableBaggage({ foo: { value: 'bar' } })); - - expect(getMutableBaggage(context)).toBeInstanceOf(MutableBaggageImpl); - expect(getMutableBaggage(context)?.getAllEntries()).toEqual([['foo', { value: 'bar' }]]); - }); -}); - -describe('createMutableBaggage()', () => { - it('should create an empty mutable baggage', () => { - const baggage = createMutableBaggage(); - - expect(baggage).toBeInstanceOf(MutableBaggageImpl); - expect(baggage.getAllEntries()).toEqual([]); - }); - - it('should create a mutable baggage with entries', () => { - const baggage = createMutableBaggage({ foo: { value: 'bar' } }); - - expect(baggage).toBeInstanceOf(MutableBaggageImpl); - expect(baggage.getAllEntries()).toEqual([['foo', { value: 'bar' }]]); - }); -}); - -describe('setMutableBaggage()', () => { - it('should set a mutable baggage on a new context', () => { - const context = setMutableBaggage(ROOT_CONTEXT, createMutableBaggage({ foo: { value: 'bar' } })); - - expect(context).not.toEqual(ROOT_CONTEXT); - expect(getMutableBaggage(context)).toBeInstanceOf(MutableBaggageImpl); - expect(getMutableBaggage(context)?.getAllEntries()).toEqual([['foo', { value: 'bar' }]]); - }); -}); diff --git a/packages/opentelemetry-mutable-baggage/src/context-helpers.ts b/packages/opentelemetry-mutable-baggage/src/context-helpers.ts deleted file mode 100644 index 29b6032..0000000 --- a/packages/opentelemetry-mutable-baggage/src/context-helpers.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { BaggageEntry, Context, context, createContextKey } from '@opentelemetry/api'; -import { MutableBaggageImpl } from './mutable-baggage-impl'; - -const MUTTABLE_BAGGAGE_KEY = createContextKey('OpenTelemetry Mutable Baggage Key'); - -export const getActiveMutableBaggage = (): MutableBaggageImpl | undefined => - (context.active().getValue(MUTTABLE_BAGGAGE_KEY) as MutableBaggageImpl) ?? undefined; - -export const getMutableBaggage = (context: Context): MutableBaggageImpl | undefined => - (context.getValue(MUTTABLE_BAGGAGE_KEY) as MutableBaggageImpl) ?? undefined; - -export const createMutableBaggage = (entries: Record = {}): MutableBaggageImpl => - new MutableBaggageImpl(new Map(Object.entries(entries))); - -export const setMutableBaggage = (context: Context, baggage: MutableBaggageImpl): Context => - context.setValue(MUTTABLE_BAGGAGE_KEY, baggage); diff --git a/packages/opentelemetry-mutable-baggage/src/index.test.ts b/packages/opentelemetry-mutable-baggage/src/index.test.ts index 3b9d2f6..f80b2ba 100644 --- a/packages/opentelemetry-mutable-baggage/src/index.test.ts +++ b/packages/opentelemetry-mutable-baggage/src/index.test.ts @@ -4,10 +4,6 @@ import { expect, it } from 'vitest'; it('should have the correct exports', () => { expect({ ...exports }).toEqual({ MutableBaggageImpl: expect.any(Function), - W3CMutableBaggagePropagator: expect.any(Function), - createMutableBaggage: expect.any(Function), - getActiveMutableBaggage: expect.any(Function), - getMutableBaggage: expect.any(Function), - setMutableBaggage: expect.any(Function) + W3CMutableBaggagePropagator: expect.any(Function) }); }); diff --git a/packages/opentelemetry-mutable-baggage/src/index.ts b/packages/opentelemetry-mutable-baggage/src/index.ts index 8b8a720..f5bfef5 100644 --- a/packages/opentelemetry-mutable-baggage/src/index.ts +++ b/packages/opentelemetry-mutable-baggage/src/index.ts @@ -1,3 +1,2 @@ export { W3CMutableBaggagePropagator } from './w3c-mutable-baggage-propagator'; -export { getActiveMutableBaggage, getMutableBaggage, createMutableBaggage, setMutableBaggage } from './context-helpers'; export { MutableBaggageImpl } from './mutable-baggage-impl'; diff --git a/packages/opentelemetry-mutable-baggage/src/w3c-mutable-baggage-propagator.test.ts b/packages/opentelemetry-mutable-baggage/src/w3c-mutable-baggage-propagator.test.ts index bcf5bb0..3f83299 100644 --- a/packages/opentelemetry-mutable-baggage/src/w3c-mutable-baggage-propagator.test.ts +++ b/packages/opentelemetry-mutable-baggage/src/w3c-mutable-baggage-propagator.test.ts @@ -1,12 +1,14 @@ import { + BaggageEntry, ROOT_CONTEXT, baggageEntryMetadataFromString, defaultTextMapGetter, - defaultTextMapSetter + defaultTextMapSetter, + propagation } from '@opentelemetry/api'; +import { MutableBaggageImpl } from './mutable-baggage-impl'; import { W3CMutableBaggagePropagator } from './w3c-mutable-baggage-propagator'; import { beforeEach, describe, expect, it } from 'vitest'; -import { createMutableBaggage, getMutableBaggage, setMutableBaggage } from './context-helpers'; import { suppressTracing } from '@opentelemetry/core'; const mutableBaggagePropagator = new W3CMutableBaggagePropagator(); @@ -19,18 +21,20 @@ beforeEach(() => { describe('.inject()', () => { it('should set baggage header', () => { - const entries = { - key1: { value: 'd4cda95b652f4a1592b449d5929fda1b' }, - key3: { value: 'c88815a7-0fa9-4d95-a1f1-cdccce3c5c2a' }, - key4: { - metadata: baggageEntryMetadataFromString('key4prop1=value1;key4prop2=value2;key4prop3WithNoValue'), - value: 'foo' - }, - 'with/slash': { value: 'with spaces' } - }; - - const baggage = createMutableBaggage(entries); - const context = setMutableBaggage(ROOT_CONTEXT, baggage); + const entries = new Map([ + ['key1', { value: 'd4cda95b652f4a1592b449d5929fda1b' }], + ['key3', { value: 'c88815a7-0fa9-4d95-a1f1-cdccce3c5c2a' }], + [ + 'key4', + { + metadata: baggageEntryMetadataFromString('key4prop1=value1;key4prop2=value2;key4prop3WithNoValue'), + value: 'foo' + } + ], + ['with/slash', { value: 'with spaces' }] + ]); + const baggage = new MutableBaggageImpl(entries); + const context = propagation.setBaggage(ROOT_CONTEXT, baggage); mutableBaggagePropagator.inject(context, carrier, defaultTextMapSetter); @@ -46,9 +50,9 @@ describe('.inject()', () => { }); it('should not set baggage header if tracing is suppressed', () => { - const entries = { key1: { value: 'd4cda95b652f4a1592b449d5929fda1b' } }; - const baggage = createMutableBaggage(entries); - const context = setMutableBaggage(suppressTracing(ROOT_CONTEXT), baggage); + const entries = new Map([['key1', { value: 'd4cda95b652f4a1592b449d5929fda1b' }]]); + const baggage = new MutableBaggageImpl(entries); + const context = propagation.setBaggage(suppressTracing(ROOT_CONTEXT), baggage); mutableBaggagePropagator.inject(context, carrier, defaultTextMapSetter); @@ -60,13 +64,15 @@ describe('.inject()', () => { const shortKey = Array(95).fill('k').join(''); const value = Array(4000).fill('v').join(''); - const baggage = createMutableBaggage({ - aa: { value: 'shortvalue' }, - ['a'.repeat(4097)]: { value: 'foo' }, - [`${[shortKey]}`]: { value }, - [`${[longKey]}`]: { value } - }); - const context = setMutableBaggage(ROOT_CONTEXT, baggage); + const baggage = new MutableBaggageImpl( + new Map([ + ['aa', { value: 'shortvalue' }], + ['a'.repeat(4097), { value: 'foo' }], + [`${[shortKey]}`, { value }], + [`${[longKey]}`, { value }] + ]) + ); + const context = propagation.setBaggage(ROOT_CONTEXT, baggage); mutableBaggagePropagator.inject(context, carrier, defaultTextMapSetter); @@ -78,12 +84,14 @@ describe('.inject()', () => { const longKey1 = Array(49).fill('1').join(''); const longValue = Array(4000).fill('v').join(''); - const baggage = createMutableBaggage({ - aa: { value: Array(8000).fill('v').join('') }, - [`${[longKey0]}`]: { value: longValue }, - [`${[longKey1]}`]: { value: longValue } - }); - const context = setMutableBaggage(ROOT_CONTEXT, baggage); + const baggage = new MutableBaggageImpl( + new Map([ + ['aa', { value: Array(8000).fill('v').join('') }], + [`${[longKey0]}`, { value: longValue }], + [`${[longKey1]}`, { value: longValue }] + ]) + ); + const context = propagation.setBaggage(ROOT_CONTEXT, baggage); mutableBaggagePropagator.inject(context, carrier, defaultTextMapSetter); @@ -94,16 +102,16 @@ describe('.inject()', () => { }); it('should not exceed the W3C standard header entry limit of 180 entries', () => { - const entries = {}; + const entries: Map = new Map(); Array(200) .fill(0) .forEach((_, i) => { - entries[`${i}`] = { value: 'v' }; + entries.set(`${i}`, { value: 'v' }); }); - const baggage = createMutableBaggage(entries); - const context = setMutableBaggage(ROOT_CONTEXT, baggage); + const baggage = new MutableBaggageImpl(entries); + const context = propagation.setBaggage(ROOT_CONTEXT, baggage); mutableBaggagePropagator.inject(context, carrier, defaultTextMapSetter); @@ -117,31 +125,35 @@ describe('.inject()', () => { describe('.extract()', () => { const baggageValue = 'key1=d4cda95b==,key3=c88815a7,key4=foo;key4prop1=value1;k>ey4prop2=value2;key4prop3WithNoValue, keyn = valn, keym =valm'; - /* eslint-disable sort-keys-fix/sort-keys-fix */ - const expected = createMutableBaggage({ - key1: { value: 'd4cda95b==' }, - key3: { value: 'c88815a7' }, - key4: { - value: 'foo', - metadata: baggageEntryMetadataFromString('key4prop1=value1;key4prop2=value2;key4prop3WithNoValue') - }, - keyn: { value: 'valn' }, - keym: { value: 'valm' } - }); - /* eslint-enable sort-keys-fix/sort-keys-fix */ + const expected = new MutableBaggageImpl( + new Map([ + ['key1', { value: 'd4cda95b==' }], + ['key3', { value: 'c88815a7' }], + [ + 'key4', + { + value: 'foo', + // eslint-disable-next-line sort-keys-fix/sort-keys-fix + metadata: baggageEntryMetadataFromString('key4prop1=value1;key4prop2=value2;key4prop3WithNoValue') + } + ], + ['keyn', { value: 'valn' }], + ['keym', { value: 'valm' }] + ]) + ); it('should extract context of a sampled span from carrier', () => { carrier.baggage = baggageValue; - const extractedBaggage = getMutableBaggage( - mutableBaggagePropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter) - ); + + const extractedContext = mutableBaggagePropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); + const extractedBaggage = propagation.getBaggage(extractedContext); expect(JSON.stringify(extractedBaggage?.getAllEntries())).toEqual(JSON.stringify(expected.getAllEntries())); }); it('should extract context of a sampled span when the headerValue comes as array', () => { carrier.baggage = [baggageValue]; - const extractedBaggage = getMutableBaggage( + const extractedBaggage = propagation.getBaggage( mutableBaggagePropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter) ); @@ -153,9 +165,9 @@ describe('.extract()', () => { 'key1=d4cda95b==,key3=c88815a7,key4=foo;key4prop1=value1;k>ey4prop2=value2;key4prop3WithNoValue, keyn = valn', 'keym =valm' ]; - const extractedBaggage = getMutableBaggage( - mutableBaggagePropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter) - ); + + const extractedContext = mutableBaggagePropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); + const extractedBaggage = propagation.getBaggage(extractedContext); expect(JSON.stringify(extractedBaggage?.getAllEntries())).toEqual(JSON.stringify(expected.getAllEntries())); }); diff --git a/packages/opentelemetry-mutable-baggage/src/w3c-mutable-baggage-propagator.ts b/packages/opentelemetry-mutable-baggage/src/w3c-mutable-baggage-propagator.ts index 0607cd6..75f346d 100644 --- a/packages/opentelemetry-mutable-baggage/src/w3c-mutable-baggage-propagator.ts +++ b/packages/opentelemetry-mutable-baggage/src/w3c-mutable-baggage-propagator.ts @@ -1,6 +1,13 @@ -import { BaggageEntry, Context, TextMapGetter, TextMapPropagator, TextMapSetter } from '@opentelemetry/api'; +import { + BaggageEntry, + Context, + TextMapGetter, + TextMapPropagator, + TextMapSetter, + propagation +} from '@opentelemetry/api'; +import { MutableBaggageImpl } from './mutable-baggage-impl'; import { baggageUtils, isTracingSuppressed } from '@opentelemetry/core'; -import { createMutableBaggage, getMutableBaggage, setMutableBaggage } from './context-helpers'; const BAGGAGE_HEADER = 'baggage'; const BAGGAGE_ITEMS_SEPARATOR = ','; @@ -16,7 +23,7 @@ const BAGGAGE_MAX_PER_NAME_VALUE_PAIRS = 4096; export class W3CMutableBaggagePropagator implements TextMapPropagator { inject(context: Context, carrier: unknown, setter: TextMapSetter): void { - const baggage = getMutableBaggage(context); + const baggage = propagation.getBaggage(context); if (!baggage || isTracingSuppressed(context)) { return; @@ -57,7 +64,9 @@ export class W3CMutableBaggagePropagator implements TextMapPropagator { } }); - return setMutableBaggage(context, createMutableBaggage(entries)); + const baggage = new MutableBaggageImpl(new Map(Object.entries(entries))); + + return propagation.setBaggage(context, baggage); } fields(): string[] {