@uphold/opentelemetry-mutable-baggage 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintignore ADDED
@@ -0,0 +1 @@
1
+ dist
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Uphold
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # @uphold/opentelemetry-mutable-baggage
2
+
3
+ Package that allows an OpenTelemetry baggage to be mutable.
4
+
5
+ ## Installation
6
+
7
+ ```sh
8
+ npm install @uphold/opentelemetry-mutable-baggage
9
+ ```
10
+
11
+ ## Why?
12
+
13
+ The default OpenTelemetry Baggage is immutable. Any change to it will create a new instance that you will need to save on the context. Because context is also immutable, saving the new baggage on the context will also create a new context. This makes it unsuitable for use-cases in which a piece of code needs to have visibility of items set on the baggage made by other pieces of code.
14
+
15
+ One common use-case is in frameworks with stack based middleware, such as Koa:
16
+
17
+ ```js
18
+ // 1st middleware:
19
+ const requestLifecycleMiddleware = (context, next) => {
20
+ const { request } = context;
21
+ const createdAt = new Date();
22
+ const startedAt = process.hrtime();
23
+
24
+ try {
25
+ await next();
26
+ } finally {
27
+ // This will always be undefined!
28
+ const userId = api.propagation.getActiveBaggage()?.getEntry('user-id');
29
+
30
+ log.info({ userId }, `Request to ${request.method} ${request.path}`);
31
+ }
32
+ };
33
+
34
+ // 2nd middleware:
35
+ const authenticationMiddleware = (context, next) => {
36
+ const baggage = api.propagation.getActiveBaggage();
37
+ const newBaggage = baggage?.setEntry('user-id', { value: 'faaebf49-c915-43ae-84fc-96c1711d2394' });
38
+
39
+ await api.context.with(api.context.setBaggage(baggage), next);
40
+ };
41
+ ```
42
+
43
+ In the example above, `requestLifecycleMiddleware` will never see the `user-id` entry on the active baggage.
44
+
45
+ ## Usage
46
+
47
+ Replace `W3CBaggagePropagator` with `W3CMutableBaggagePropagator`, since it's a drop-in replacement. Here's how it looks if you are using the NodeSDK:
48
+
49
+ ```js
50
+ import { CompositePropagator, W3CTraceContextPropagator } from '@opentelemetry/core';
51
+ import { W3CMutableBaggagePropagator } from '@uphold/opentelemetry-mutable-baggage';
52
+
53
+ const textMapPropagator = new CompositePropagator({
54
+ propagators: [new W3CTraceContextPropagator(), new W3CMutableBaggagePropagator()]
55
+ });
56
+
57
+ const sdk = new NodeSDK({
58
+ // ...
59
+ textMapPropagator
60
+ });
61
+ ```
62
+
63
+ > ⚠️ 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.
64
+
65
+ Then, use the regular `propagation` api to interact with the baggage:
66
+
67
+ ```js
68
+ import { propagation } from '@opentelemetry/api';
69
+
70
+ // `setEntry` will no longer create a new baggage!
71
+ propagation.getActiveBaggage()?.setEntry('foo', 'bar');
72
+ ```
73
+
74
+ ## Tests
75
+
76
+ ```sh
77
+ npm test
78
+ ```
79
+
80
+ ## License
81
+
82
+ Licensed under MIT.
@@ -0,0 +1,7 @@
1
+ import { BaggageEntry, Context } from '@opentelemetry/api';
2
+ import { MutableBaggageImpl } from './mutable-baggage-impl';
3
+ export declare const getActiveMutableBaggage: () => MutableBaggageImpl | undefined;
4
+ export declare const getMutableBaggage: (context: Context) => MutableBaggageImpl | undefined;
5
+ export declare const createMutableBaggage: (entries?: Record<string, BaggageEntry>) => MutableBaggageImpl;
6
+ export declare const setMutableBaggage: (context: Context, baggage: MutableBaggageImpl) => Context;
7
+ //# sourceMappingURL=context-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-helpers.d.ts","sourceRoot":"","sources":["../src/context-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,OAAO,EAA6B,MAAM,oBAAoB,CAAC;AACtF,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAI5D,eAAO,MAAM,uBAAuB,QAAO,kBAAkB,GAAG,SACsB,CAAC;AAEvF,eAAO,MAAM,iBAAiB,YAAa,OAAO,KAAG,kBAAkB,GAAG,SACG,CAAC;AAE9E,eAAO,MAAM,oBAAoB,aAAa,OAAO,MAAM,EAAE,YAAY,CAAC,KAAQ,kBACxB,CAAC;AAE3D,eAAO,MAAM,iBAAiB,YAAa,OAAO,WAAW,kBAAkB,KAAG,OACjC,CAAC"}
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setMutableBaggage = exports.createMutableBaggage = exports.getMutableBaggage = exports.getActiveMutableBaggage = void 0;
4
+ const api_1 = require("@opentelemetry/api");
5
+ const mutable_baggage_impl_1 = require("./mutable-baggage-impl");
6
+ const MUTTABLE_BAGGAGE_KEY = (0, api_1.createContextKey)('OpenTelemetry Mutable Baggage Key');
7
+ const getActiveMutableBaggage = () => api_1.context.active().getValue(MUTTABLE_BAGGAGE_KEY) ?? undefined;
8
+ exports.getActiveMutableBaggage = getActiveMutableBaggage;
9
+ const getMutableBaggage = (context) => context.getValue(MUTTABLE_BAGGAGE_KEY) ?? undefined;
10
+ exports.getMutableBaggage = getMutableBaggage;
11
+ const createMutableBaggage = (entries = {}) => new mutable_baggage_impl_1.MutableBaggageImpl(new Map(Object.entries(entries)));
12
+ exports.createMutableBaggage = createMutableBaggage;
13
+ const setMutableBaggage = (context, baggage) => context.setValue(MUTTABLE_BAGGAGE_KEY, baggage);
14
+ exports.setMutableBaggage = setMutableBaggage;
15
+ //# sourceMappingURL=context-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-helpers.js","sourceRoot":"","sources":["../src/context-helpers.ts"],"names":[],"mappings":";;;AAAA,4CAAsF;AACtF,iEAA4D;AAE5D,MAAM,oBAAoB,GAAG,IAAA,sBAAgB,EAAC,mCAAmC,CAAC,CAAC;AAE5E,MAAM,uBAAuB,GAAG,GAAmC,EAAE,CACzE,aAAO,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAwB,IAAI,SAAS,CAAC;AAD1E,QAAA,uBAAuB,2BACmD;AAEhF,MAAM,iBAAiB,GAAG,CAAC,OAAgB,EAAkC,EAAE,CACnF,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAwB,IAAI,SAAS,CAAC;AADjE,QAAA,iBAAiB,qBACgD;AAEvE,MAAM,oBAAoB,GAAG,CAAC,UAAwC,EAAE,EAAsB,EAAE,CACrG,IAAI,yCAAkB,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAD9C,QAAA,oBAAoB,wBAC0B;AAEpD,MAAM,iBAAiB,GAAG,CAAC,OAAgB,EAAE,OAA2B,EAAW,EAAE,CAC1F,OAAO,CAAC,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC,CAAC;AADrC,QAAA,iBAAiB,qBACoB","sourcesContent":["import { BaggageEntry, Context, context, createContextKey } from '@opentelemetry/api';\nimport { MutableBaggageImpl } from './mutable-baggage-impl';\n\nconst MUTTABLE_BAGGAGE_KEY = createContextKey('OpenTelemetry Mutable Baggage Key');\n\nexport const getActiveMutableBaggage = (): MutableBaggageImpl | undefined =>\n (context.active().getValue(MUTTABLE_BAGGAGE_KEY) as MutableBaggageImpl) ?? undefined;\n\nexport const getMutableBaggage = (context: Context): MutableBaggageImpl | undefined =>\n (context.getValue(MUTTABLE_BAGGAGE_KEY) as MutableBaggageImpl) ?? undefined;\n\nexport const createMutableBaggage = (entries: Record<string, BaggageEntry> = {}): MutableBaggageImpl =>\n new MutableBaggageImpl(new Map(Object.entries(entries)));\n\nexport const setMutableBaggage = (context: Context, baggage: MutableBaggageImpl): Context =>\n context.setValue(MUTTABLE_BAGGAGE_KEY, baggage);\n"]}
@@ -0,0 +1,3 @@
1
+ export { W3CMutableBaggagePropagator } from './w3c-mutable-baggage-propagator';
2
+ export { MutableBaggageImpl } from './mutable-baggage-impl';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AAC/E,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MutableBaggageImpl = exports.W3CMutableBaggagePropagator = void 0;
4
+ var w3c_mutable_baggage_propagator_1 = require("./w3c-mutable-baggage-propagator");
5
+ Object.defineProperty(exports, "W3CMutableBaggagePropagator", { enumerable: true, get: function () { return w3c_mutable_baggage_propagator_1.W3CMutableBaggagePropagator; } });
6
+ var mutable_baggage_impl_1 = require("./mutable-baggage-impl");
7
+ Object.defineProperty(exports, "MutableBaggageImpl", { enumerable: true, get: function () { return mutable_baggage_impl_1.MutableBaggageImpl; } });
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,mFAA+E;AAAtE,6IAAA,2BAA2B,OAAA;AACpC,+DAA4D;AAAnD,0HAAA,kBAAkB,OAAA","sourcesContent":["export { W3CMutableBaggagePropagator } from './w3c-mutable-baggage-propagator';\nexport { MutableBaggageImpl } from './mutable-baggage-impl';\n"]}
@@ -0,0 +1,12 @@
1
+ import { Baggage, BaggageEntry } from '@opentelemetry/api';
2
+ export declare class MutableBaggageImpl implements Baggage {
3
+ private entries;
4
+ constructor(entries?: Map<string, BaggageEntry>);
5
+ getEntry(key: string): BaggageEntry | undefined;
6
+ getAllEntries(): [string, BaggageEntry][];
7
+ setEntry(key: string, entry: BaggageEntry): MutableBaggageImpl;
8
+ removeEntry(key: string): MutableBaggageImpl;
9
+ removeEntries(...keys: string[]): MutableBaggageImpl;
10
+ clear(): MutableBaggageImpl;
11
+ }
12
+ //# sourceMappingURL=mutable-baggage-impl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutable-baggage-impl.d.ts","sourceRoot":"","sources":["../src/mutable-baggage-impl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE3D,qBAAa,kBAAmB,YAAW,OAAO;IAChD,OAAO,CAAC,OAAO,CAA4B;gBAE/B,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC;IAI/C,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAU/C,aAAa,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE;IAIzC,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,kBAAkB;IAM9D,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB;IAM5C,aAAa,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,kBAAkB;IAQpD,KAAK,IAAI,kBAAkB;CAK5B"}
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MutableBaggageImpl = void 0;
4
+ class MutableBaggageImpl {
5
+ entries;
6
+ constructor(entries) {
7
+ this.entries = entries ? new Map(entries) : new Map();
8
+ }
9
+ getEntry(key) {
10
+ const entry = this.entries.get(key);
11
+ if (!entry) {
12
+ return undefined;
13
+ }
14
+ return Object.assign({}, entry);
15
+ }
16
+ getAllEntries() {
17
+ return Array.from(this.entries.entries()).map(([key, value]) => [key, value]);
18
+ }
19
+ setEntry(key, entry) {
20
+ this.entries.set(key, entry);
21
+ return this;
22
+ }
23
+ removeEntry(key) {
24
+ this.entries.delete(key);
25
+ return this;
26
+ }
27
+ removeEntries(...keys) {
28
+ for (const key of keys) {
29
+ this.entries.delete(key);
30
+ }
31
+ return this;
32
+ }
33
+ clear() {
34
+ this.entries.clear();
35
+ return this;
36
+ }
37
+ }
38
+ exports.MutableBaggageImpl = MutableBaggageImpl;
39
+ //# sourceMappingURL=mutable-baggage-impl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutable-baggage-impl.js","sourceRoot":"","sources":["../src/mutable-baggage-impl.ts"],"names":[],"mappings":";;;AAEA,MAAa,kBAAkB;IACrB,OAAO,CAA4B;IAE3C,YAAY,OAAmC;QAC7C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC;IACxD,CAAC;IAED,QAAQ,CAAC,GAAW;QAClB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAEpC,IAAI,CAAC,KAAK,EAAE;YACV,OAAO,SAAS,CAAC;SAClB;QAED,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAClC,CAAC;IAED,aAAa;QACX,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;IAChF,CAAC;IAED,QAAQ,CAAC,GAAW,EAAE,KAAmB;QACvC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAE7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,WAAW,CAAC,GAAW;QACrB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAEzB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,aAAa,CAAC,GAAG,IAAc;QAC7B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE;YACtB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;SAC1B;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK;QACH,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QAErB,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AA9CD,gDA8CC","sourcesContent":["import { Baggage, BaggageEntry } from '@opentelemetry/api';\n\nexport class MutableBaggageImpl implements Baggage {\n private entries: Map<string, BaggageEntry>;\n\n constructor(entries?: Map<string, BaggageEntry>) {\n this.entries = entries ? new Map(entries) : new Map();\n }\n\n getEntry(key: string): BaggageEntry | undefined {\n const entry = this.entries.get(key);\n\n if (!entry) {\n return undefined;\n }\n\n return Object.assign({}, entry);\n }\n\n getAllEntries(): [string, BaggageEntry][] {\n return Array.from(this.entries.entries()).map(([key, value]) => [key, value]);\n }\n\n setEntry(key: string, entry: BaggageEntry): MutableBaggageImpl {\n this.entries.set(key, entry);\n\n return this;\n }\n\n removeEntry(key: string): MutableBaggageImpl {\n this.entries.delete(key);\n\n return this;\n }\n\n removeEntries(...keys: string[]): MutableBaggageImpl {\n for (const key of keys) {\n this.entries.delete(key);\n }\n\n return this;\n }\n\n clear(): MutableBaggageImpl {\n this.entries.clear();\n\n return this;\n }\n}\n"]}
@@ -0,0 +1,13 @@
1
+ import { Context, TextMapGetter, TextMapPropagator, TextMapSetter } from '@opentelemetry/api';
2
+ /**
3
+ * Propagates Baggage through Context format propagation.
4
+ *
5
+ * Based on the Baggage specification:
6
+ * https://w3c.github.io/baggage/
7
+ */
8
+ export declare class W3CMutableBaggagePropagator implements TextMapPropagator {
9
+ inject(context: Context, carrier: unknown, setter: TextMapSetter): void;
10
+ extract(context: Context, carrier: unknown, getter: TextMapGetter): Context;
11
+ fields(): string[];
12
+ }
13
+ //# sourceMappingURL=w3c-mutable-baggage-propagator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"w3c-mutable-baggage-propagator.d.ts","sourceRoot":"","sources":["../src/w3c-mutable-baggage-propagator.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,OAAO,EACP,aAAa,EACb,iBAAiB,EACjB,aAAa,EAEd,MAAM,oBAAoB,CAAC;AAS5B;;;;;GAKG;AAEH,qBAAa,2BAA4B,YAAW,iBAAiB;IACnE,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI;IAqBvE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO;IA0B3E,MAAM,IAAI,MAAM,EAAE;CAGnB"}
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.W3CMutableBaggagePropagator = void 0;
4
+ const api_1 = require("@opentelemetry/api");
5
+ const mutable_baggage_impl_1 = require("./mutable-baggage-impl");
6
+ const core_1 = require("@opentelemetry/core");
7
+ const BAGGAGE_HEADER = 'baggage';
8
+ const BAGGAGE_ITEMS_SEPARATOR = ',';
9
+ const BAGGAGE_MAX_NAME_VALUE_PAIRS = 180;
10
+ const BAGGAGE_MAX_PER_NAME_VALUE_PAIRS = 4096;
11
+ /**
12
+ * Propagates Baggage through Context format propagation.
13
+ *
14
+ * Based on the Baggage specification:
15
+ * https://w3c.github.io/baggage/
16
+ */
17
+ class W3CMutableBaggagePropagator {
18
+ inject(context, carrier, setter) {
19
+ const baggage = api_1.propagation.getBaggage(context);
20
+ if (!baggage || (0, core_1.isTracingSuppressed)(context)) {
21
+ return;
22
+ }
23
+ const keyPairs = core_1.baggageUtils
24
+ .getKeyPairs(baggage)
25
+ .filter(pair => {
26
+ return pair.length <= BAGGAGE_MAX_PER_NAME_VALUE_PAIRS;
27
+ })
28
+ .slice(0, BAGGAGE_MAX_NAME_VALUE_PAIRS);
29
+ const headerValue = core_1.baggageUtils.serializeKeyPairs(keyPairs);
30
+ if (headerValue.length > 0) {
31
+ setter.set(carrier, BAGGAGE_HEADER, headerValue);
32
+ }
33
+ }
34
+ extract(context, carrier, getter) {
35
+ const headerValue = getter.get(carrier, BAGGAGE_HEADER);
36
+ const baggageString = Array.isArray(headerValue) ? headerValue.join(BAGGAGE_ITEMS_SEPARATOR) : headerValue;
37
+ const entries = {};
38
+ const pairs = (baggageString ?? '').split(BAGGAGE_ITEMS_SEPARATOR);
39
+ pairs.forEach(entry => {
40
+ const keyPair = core_1.baggageUtils.parsePairKeyValue(entry);
41
+ if (keyPair) {
42
+ const baggageEntry = { value: keyPair.value };
43
+ if (keyPair.metadata) {
44
+ baggageEntry.metadata = keyPair.metadata;
45
+ }
46
+ entries[keyPair.key] = baggageEntry;
47
+ }
48
+ });
49
+ const baggage = new mutable_baggage_impl_1.MutableBaggageImpl(new Map(Object.entries(entries)));
50
+ return api_1.propagation.setBaggage(context, baggage);
51
+ }
52
+ fields() {
53
+ return [BAGGAGE_HEADER];
54
+ }
55
+ }
56
+ exports.W3CMutableBaggagePropagator = W3CMutableBaggagePropagator;
57
+ //# sourceMappingURL=w3c-mutable-baggage-propagator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"w3c-mutable-baggage-propagator.js","sourceRoot":"","sources":["../src/w3c-mutable-baggage-propagator.ts"],"names":[],"mappings":";;;AAAA,4CAO4B;AAC5B,iEAA4D;AAC5D,8CAAwE;AAExE,MAAM,cAAc,GAAG,SAAS,CAAC;AACjC,MAAM,uBAAuB,GAAG,GAAG,CAAC;AACpC,MAAM,4BAA4B,GAAG,GAAG,CAAC;AACzC,MAAM,gCAAgC,GAAG,IAAI,CAAC;AAE9C;;;;;GAKG;AAEH,MAAa,2BAA2B;IACtC,MAAM,CAAC,OAAgB,EAAE,OAAgB,EAAE,MAAqB;QAC9D,MAAM,OAAO,GAAG,iBAAW,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QAEhD,IAAI,CAAC,OAAO,IAAI,IAAA,0BAAmB,EAAC,OAAO,CAAC,EAAE;YAC5C,OAAO;SACR;QAED,MAAM,QAAQ,GAAG,mBAAY;aAC1B,WAAW,CAAC,OAAO,CAAC;aACpB,MAAM,CAAC,IAAI,CAAC,EAAE;YACb,OAAO,IAAI,CAAC,MAAM,IAAI,gCAAgC,CAAC;QACzD,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,EAAE,4BAA4B,CAAC,CAAC;QAE1C,MAAM,WAAW,GAAG,mBAAY,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QAE7D,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE;YAC1B,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,cAAc,EAAE,WAAW,CAAC,CAAC;SAClD;IACH,CAAC;IAED,OAAO,CAAC,OAAgB,EAAE,OAAgB,EAAE,MAAqB;QAC/D,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;QACxD,MAAM,aAAa,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;QAE3G,MAAM,OAAO,GAAG,EAAkC,CAAC;QACnD,MAAM,KAAK,GAAG,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;QAEnE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;YACpB,MAAM,OAAO,GAAG,mBAAY,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;YAEtD,IAAI,OAAO,EAAE;gBACX,MAAM,YAAY,GAAiB,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;gBAE5D,IAAI,OAAO,CAAC,QAAQ,EAAE;oBACpB,YAAY,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;iBAC1C;gBAED,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC;aACrC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,IAAI,yCAAkB,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAEzE,OAAO,iBAAW,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAClD,CAAC;IAED,MAAM;QACJ,OAAO,CAAC,cAAc,CAAC,CAAC;IAC1B,CAAC;CACF;AAnDD,kEAmDC","sourcesContent":["import {\n BaggageEntry,\n Context,\n TextMapGetter,\n TextMapPropagator,\n TextMapSetter,\n propagation\n} from '@opentelemetry/api';\nimport { MutableBaggageImpl } from './mutable-baggage-impl';\nimport { baggageUtils, isTracingSuppressed } from '@opentelemetry/core';\n\nconst BAGGAGE_HEADER = 'baggage';\nconst BAGGAGE_ITEMS_SEPARATOR = ',';\nconst BAGGAGE_MAX_NAME_VALUE_PAIRS = 180;\nconst BAGGAGE_MAX_PER_NAME_VALUE_PAIRS = 4096;\n\n/**\n * Propagates Baggage through Context format propagation.\n *\n * Based on the Baggage specification:\n * https://w3c.github.io/baggage/\n */\n\nexport class W3CMutableBaggagePropagator implements TextMapPropagator {\n inject(context: Context, carrier: unknown, setter: TextMapSetter): void {\n const baggage = propagation.getBaggage(context);\n\n if (!baggage || isTracingSuppressed(context)) {\n return;\n }\n\n const keyPairs = baggageUtils\n .getKeyPairs(baggage)\n .filter(pair => {\n return pair.length <= BAGGAGE_MAX_PER_NAME_VALUE_PAIRS;\n })\n .slice(0, BAGGAGE_MAX_NAME_VALUE_PAIRS);\n\n const headerValue = baggageUtils.serializeKeyPairs(keyPairs);\n\n if (headerValue.length > 0) {\n setter.set(carrier, BAGGAGE_HEADER, headerValue);\n }\n }\n\n extract(context: Context, carrier: unknown, getter: TextMapGetter): Context {\n const headerValue = getter.get(carrier, BAGGAGE_HEADER);\n const baggageString = Array.isArray(headerValue) ? headerValue.join(BAGGAGE_ITEMS_SEPARATOR) : headerValue;\n\n const entries = {} as Record<string, BaggageEntry>;\n const pairs = (baggageString ?? '').split(BAGGAGE_ITEMS_SEPARATOR);\n\n pairs.forEach(entry => {\n const keyPair = baggageUtils.parsePairKeyValue(entry);\n\n if (keyPair) {\n const baggageEntry: BaggageEntry = { value: keyPair.value };\n\n if (keyPair.metadata) {\n baggageEntry.metadata = keyPair.metadata;\n }\n\n entries[keyPair.key] = baggageEntry;\n }\n });\n\n const baggage = new MutableBaggageImpl(new Map(Object.entries(entries)));\n\n return propagation.setBaggage(context, baggage);\n }\n\n fields(): string[] {\n return [BAGGAGE_HEADER];\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@uphold/opentelemetry-mutable-baggage",
3
+ "version": "0.0.0",
4
+ "description": "Package that allows an OpenTelemetry baggage to be mutable.",
5
+ "main": "dist/index.js",
6
+ "author": "Uphold",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/uphold/opentelemetry-js-contrib.git"
11
+ },
12
+ "keywords": [
13
+ "opentelemetry",
14
+ "instrumentation",
15
+ "nodejs",
16
+ "tracing",
17
+ "baggage"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "scripts": {
23
+ "prebuild": "rm -rf dist",
24
+ "build": "tsc",
25
+ "lint": "eslint --ext .ts,.js .",
26
+ "postlint": "tsc --noEmit",
27
+ "test": "vitest",
28
+ "release": "release-it"
29
+ },
30
+ "lint-staged": {
31
+ "*.{ts,js}": [
32
+ "eslint --ext .ts,.js"
33
+ ]
34
+ },
35
+ "dependencies": {
36
+ "@opentelemetry/core": "^1.17.1"
37
+ },
38
+ "peerDependencies": {
39
+ "@opentelemetry/api": ">=1 <2"
40
+ },
41
+ "devDependencies": {
42
+ "@opentelemetry/context-async-hooks": "^1.17.1"
43
+ }
44
+ }
@@ -0,0 +1,9 @@
1
+ import * as exports from '.';
2
+ import { expect, it } from 'vitest';
3
+
4
+ it('should have the correct exports', () => {
5
+ expect({ ...exports }).toEqual({
6
+ MutableBaggageImpl: expect.any(Function),
7
+ W3CMutableBaggagePropagator: expect.any(Function)
8
+ });
9
+ });
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { W3CMutableBaggagePropagator } from './w3c-mutable-baggage-propagator';
2
+ export { MutableBaggageImpl } from './mutable-baggage-impl';
@@ -0,0 +1,149 @@
1
+ import { MutableBaggageImpl } from './mutable-baggage-impl';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ describe('constructor()', () => {
5
+ it('should create an instance with no entries', () => {
6
+ const mutableBaggageImpl = new MutableBaggageImpl();
7
+
8
+ expect(mutableBaggageImpl.getAllEntries().length).toEqual(0);
9
+ });
10
+
11
+ it('should create an instance with a set of entries', () => {
12
+ const entries = new Map([
13
+ ['key1', { value: 'd4cda95b652f4a1592b449d5929fda1b' }],
14
+ ['key2', { value: 'c88815a7-0fa9-4d95-a1f1-cdccce3c5c2a' }]
15
+ ]);
16
+ const mutableBaggageImpl = new MutableBaggageImpl(entries);
17
+
18
+ const allEntries = mutableBaggageImpl.getAllEntries();
19
+
20
+ expect(allEntries.length).toEqual(2);
21
+ expect(allEntries).toEqual([
22
+ ['key1', { value: 'd4cda95b652f4a1592b449d5929fda1b' }],
23
+ ['key2', { value: 'c88815a7-0fa9-4d95-a1f1-cdccce3c5c2a' }]
24
+ ]);
25
+ });
26
+ });
27
+
28
+ describe('getEntry()', () => {
29
+ const entries = new Map([
30
+ ['key1', { value: 'd4cda95b652f4a1592b449d5929fda1b' }],
31
+ ['key2', { value: 'c88815a7-0fa9-4d95-a1f1-cdccce3c5c2a' }]
32
+ ]);
33
+ const mutableBaggageImpl = new MutableBaggageImpl(entries);
34
+
35
+ it('should return undefined if the entry does not exist', () => {
36
+ expect(mutableBaggageImpl.getEntry('foo')).toBeUndefined();
37
+ });
38
+
39
+ it('should return the correct value if the entry exists', () => {
40
+ expect(mutableBaggageImpl.getEntry('key1')).toEqual({ value: 'd4cda95b652f4a1592b449d5929fda1b' });
41
+ });
42
+ });
43
+
44
+ describe('getAllEntries()', () => {
45
+ it('should return an empty array', () => {
46
+ const mutableBaggageImpl = new MutableBaggageImpl();
47
+
48
+ expect(mutableBaggageImpl.getAllEntries()).toEqual([]);
49
+ });
50
+
51
+ it('should return the entries set on the baggage', () => {
52
+ const entries = new Map([
53
+ ['key1', { value: 'd4cda95b652f4a1592b449d5929fda1b' }],
54
+ ['key2', { value: 'c88815a7-0fa9-4d95-a1f1-cdccce3c5c2a' }]
55
+ ]);
56
+ const mutableBaggageImpl = new MutableBaggageImpl(entries);
57
+
58
+ expect(mutableBaggageImpl.getAllEntries()).toEqual([
59
+ ['key1', { value: 'd4cda95b652f4a1592b449d5929fda1b' }],
60
+ ['key2', { value: 'c88815a7-0fa9-4d95-a1f1-cdccce3c5c2a' }]
61
+ ]);
62
+ });
63
+ });
64
+
65
+ describe('setEntry()', () => {
66
+ it('should add an entry', () => {
67
+ const mutableBaggageImpl = new MutableBaggageImpl();
68
+
69
+ mutableBaggageImpl.setEntry('foo', { value: 'c88815a7-0fa9-4d95-a1f1-cdccce3c5c2a' });
70
+
71
+ expect(mutableBaggageImpl.getAllEntries()).toEqual([['foo', { value: 'c88815a7-0fa9-4d95-a1f1-cdccce3c5c2a' }]]);
72
+ });
73
+
74
+ it('should update an entry', () => {
75
+ const entries = new Map([
76
+ ['key1', { value: 'd4cda95b652f4a1592b449d5929fda1b' }],
77
+ ['key2', { value: 'c88815a7-0fa9-4d95-a1f1-cdccce3c5c2a' }]
78
+ ]);
79
+ const mutableBaggageImpl = new MutableBaggageImpl(entries);
80
+
81
+ mutableBaggageImpl.setEntry('key1', { value: 'foo' });
82
+
83
+ expect(mutableBaggageImpl.getAllEntries()).toEqual([
84
+ ['key1', { value: 'foo' }],
85
+ ['key2', { value: 'c88815a7-0fa9-4d95-a1f1-cdccce3c5c2a' }]
86
+ ]);
87
+ });
88
+ });
89
+
90
+ describe('removeEntry()', () => {
91
+ it('should not remove an entry if does not exist', () => {
92
+ const mutableBaggageImpl = new MutableBaggageImpl();
93
+
94
+ mutableBaggageImpl.removeEntry('foo');
95
+
96
+ expect(mutableBaggageImpl.getAllEntries()).toEqual([]);
97
+ });
98
+
99
+ it('should remove an entry', () => {
100
+ const entries = new Map([
101
+ ['key1', { value: 'd4cda95b652f4a1592b449d5929fda1b' }],
102
+ ['key2', { value: 'c88815a7-0fa9-4d95-a1f1-cdccce3c5c2a' }]
103
+ ]);
104
+ const mutableBaggageImpl = new MutableBaggageImpl(entries);
105
+
106
+ mutableBaggageImpl.removeEntry('key1');
107
+
108
+ expect(mutableBaggageImpl.getAllEntries()).toEqual([['key2', { value: 'c88815a7-0fa9-4d95-a1f1-cdccce3c5c2a' }]]);
109
+ });
110
+ });
111
+
112
+ describe('removeEntries()', () => {
113
+ it('should not remove entries if they do not exist', () => {
114
+ const mutableBaggageImpl = new MutableBaggageImpl();
115
+
116
+ mutableBaggageImpl.removeEntries('foo', 'biz');
117
+
118
+ expect(mutableBaggageImpl.getAllEntries()).toEqual([]);
119
+ });
120
+
121
+ it('should remove an entry', () => {
122
+ const entries = new Map([
123
+ ['key1', { value: 'd4cda95b652f4a1592b449d5929fda1b' }],
124
+ ['key2', { value: 'c88815a7-0fa9-4d95-a1f1-cdccce3c5c2a' }],
125
+ ['key3', { value: 'biz' }]
126
+ ]);
127
+ const mutableBaggageImpl = new MutableBaggageImpl(entries);
128
+
129
+ mutableBaggageImpl.removeEntries('key1', 'key2');
130
+
131
+ expect(mutableBaggageImpl.getAllEntries()).toEqual([['key3', { value: 'biz' }]]);
132
+ });
133
+ });
134
+
135
+ describe('clear()', () => {
136
+ it('should clear all entries', () => {
137
+ const entries = new Map([
138
+ ['key1', { value: 'd4cda95b652f4a1592b449d5929fda1b' }],
139
+ ['key2', { value: 'c88815a7-0fa9-4d95-a1f1-cdccce3c5c2a' }]
140
+ ]);
141
+ const mutableBaggageImpl = new MutableBaggageImpl(entries);
142
+
143
+ expect(mutableBaggageImpl.getAllEntries()).toHaveLength(2);
144
+
145
+ mutableBaggageImpl.clear();
146
+
147
+ expect(mutableBaggageImpl.getAllEntries()).toHaveLength(0);
148
+ });
149
+ });
@@ -0,0 +1,49 @@
1
+ import { Baggage, BaggageEntry } from '@opentelemetry/api';
2
+
3
+ export class MutableBaggageImpl implements Baggage {
4
+ private entries: Map<string, BaggageEntry>;
5
+
6
+ constructor(entries?: Map<string, BaggageEntry>) {
7
+ this.entries = entries ? new Map(entries) : new Map();
8
+ }
9
+
10
+ getEntry(key: string): BaggageEntry | undefined {
11
+ const entry = this.entries.get(key);
12
+
13
+ if (!entry) {
14
+ return undefined;
15
+ }
16
+
17
+ return Object.assign({}, entry);
18
+ }
19
+
20
+ getAllEntries(): [string, BaggageEntry][] {
21
+ return Array.from(this.entries.entries()).map(([key, value]) => [key, value]);
22
+ }
23
+
24
+ setEntry(key: string, entry: BaggageEntry): MutableBaggageImpl {
25
+ this.entries.set(key, entry);
26
+
27
+ return this;
28
+ }
29
+
30
+ removeEntry(key: string): MutableBaggageImpl {
31
+ this.entries.delete(key);
32
+
33
+ return this;
34
+ }
35
+
36
+ removeEntries(...keys: string[]): MutableBaggageImpl {
37
+ for (const key of keys) {
38
+ this.entries.delete(key);
39
+ }
40
+
41
+ return this;
42
+ }
43
+
44
+ clear(): MutableBaggageImpl {
45
+ this.entries.clear();
46
+
47
+ return this;
48
+ }
49
+ }
@@ -0,0 +1,182 @@
1
+ import {
2
+ BaggageEntry,
3
+ ROOT_CONTEXT,
4
+ baggageEntryMetadataFromString,
5
+ defaultTextMapGetter,
6
+ defaultTextMapSetter,
7
+ propagation
8
+ } from '@opentelemetry/api';
9
+ import { MutableBaggageImpl } from './mutable-baggage-impl';
10
+ import { W3CMutableBaggagePropagator } from './w3c-mutable-baggage-propagator';
11
+ import { beforeEach, describe, expect, it } from 'vitest';
12
+ import { suppressTracing } from '@opentelemetry/core';
13
+
14
+ const mutableBaggagePropagator = new W3CMutableBaggagePropagator();
15
+
16
+ let carrier: { [key: string]: unknown };
17
+
18
+ beforeEach(() => {
19
+ carrier = {};
20
+ });
21
+
22
+ describe('.inject()', () => {
23
+ it('should set baggage header', () => {
24
+ const entries = new Map([
25
+ ['key1', { value: 'd4cda95b652f4a1592b449d5929fda1b' }],
26
+ ['key3', { value: 'c88815a7-0fa9-4d95-a1f1-cdccce3c5c2a' }],
27
+ [
28
+ 'key4',
29
+ {
30
+ metadata: baggageEntryMetadataFromString('key4prop1=value1;key4prop2=value2;key4prop3WithNoValue'),
31
+ value: 'foo'
32
+ }
33
+ ],
34
+ ['with/slash', { value: 'with spaces' }]
35
+ ]);
36
+ const baggage = new MutableBaggageImpl(entries);
37
+ const context = propagation.setBaggage(ROOT_CONTEXT, baggage);
38
+
39
+ mutableBaggagePropagator.inject(context, carrier, defaultTextMapSetter);
40
+
41
+ expect(carrier.baggage).toBe(
42
+ 'key1=d4cda95b652f4a1592b449d5929fda1b,key3=c88815a7-0fa9-4d95-a1f1-cdccce3c5c2a,key4=foo;key4prop1=value1;key4prop2=value2;key4prop3WithNoValue,with%2Fslash=with%20spaces'
43
+ );
44
+ });
45
+
46
+ it('should not set baggage header if baggage is undefined', () => {
47
+ mutableBaggagePropagator.inject(ROOT_CONTEXT, carrier, defaultTextMapSetter);
48
+
49
+ expect(carrier.baggage).toBeUndefined();
50
+ });
51
+
52
+ it('should not set baggage header if tracing is suppressed', () => {
53
+ const entries = new Map([['key1', { value: 'd4cda95b652f4a1592b449d5929fda1b' }]]);
54
+ const baggage = new MutableBaggageImpl(entries);
55
+ const context = propagation.setBaggage(suppressTracing(ROOT_CONTEXT), baggage);
56
+
57
+ mutableBaggagePropagator.inject(context, carrier, defaultTextMapSetter);
58
+
59
+ expect(carrier.baggage).toBeUndefined();
60
+ });
61
+
62
+ it('should skip all entries whose length exceeds the W3C standard limit of 4096 bytes', () => {
63
+ const longKey = Array(96).fill('k').join('');
64
+ const shortKey = Array(95).fill('k').join('');
65
+ const value = Array(4000).fill('v').join('');
66
+
67
+ const baggage = new MutableBaggageImpl(
68
+ new Map([
69
+ ['aa', { value: 'shortvalue' }],
70
+ ['a'.repeat(4097), { value: 'foo' }],
71
+ [`${[shortKey]}`, { value }],
72
+ [`${[longKey]}`, { value }]
73
+ ])
74
+ );
75
+ const context = propagation.setBaggage(ROOT_CONTEXT, baggage);
76
+
77
+ mutableBaggagePropagator.inject(context, carrier, defaultTextMapSetter);
78
+
79
+ expect(carrier.baggage).toBe(`aa=shortvalue,${shortKey}=${value}`);
80
+ });
81
+
82
+ it('should not exceed the W3C standard header length limit of 8192 bytes', () => {
83
+ const longKey0 = Array(48).fill('0').join('');
84
+ const longKey1 = Array(49).fill('1').join('');
85
+ const longValue = Array(4000).fill('v').join('');
86
+
87
+ const baggage = new MutableBaggageImpl(
88
+ new Map([
89
+ ['aa', { value: Array(8000).fill('v').join('') }],
90
+ [`${[longKey0]}`, { value: longValue }],
91
+ [`${[longKey1]}`, { value: longValue }]
92
+ ])
93
+ );
94
+ const context = propagation.setBaggage(ROOT_CONTEXT, baggage);
95
+
96
+ mutableBaggagePropagator.inject(context, carrier, defaultTextMapSetter);
97
+
98
+ const header = carrier.baggage as string;
99
+
100
+ expect(header.length).toEqual(8100);
101
+ expect(carrier.baggage).toBe(`${longKey0}=${longValue},${longKey1}=${longValue}`);
102
+ });
103
+
104
+ it('should not exceed the W3C standard header entry limit of 180 entries', () => {
105
+ const entries: Map<string, BaggageEntry> = new Map();
106
+
107
+ Array(200)
108
+ .fill(0)
109
+ .forEach((_, i) => {
110
+ entries.set(`${i}`, { value: 'v' });
111
+ });
112
+
113
+ const baggage = new MutableBaggageImpl(entries);
114
+ const context = propagation.setBaggage(ROOT_CONTEXT, baggage);
115
+
116
+ mutableBaggagePropagator.inject(context, carrier, defaultTextMapSetter);
117
+
118
+ const header = carrier.baggage as string;
119
+
120
+ expect(header.length).toEqual(969);
121
+ expect(header.split(',').length).toEqual(180);
122
+ });
123
+ });
124
+
125
+ describe('.extract()', () => {
126
+ const baggageValue =
127
+ 'key1=d4cda95b==,key3=c88815a7,key4=foo;key4prop1=value1;k>ey4prop2=value2;key4prop3WithNoValue, keyn = valn, keym =valm';
128
+ const expected = new MutableBaggageImpl(
129
+ new Map([
130
+ ['key1', { value: 'd4cda95b==' }],
131
+ ['key3', { value: 'c88815a7' }],
132
+ [
133
+ 'key4',
134
+ {
135
+ value: 'foo',
136
+ // eslint-disable-next-line sort-keys-fix/sort-keys-fix
137
+ metadata: baggageEntryMetadataFromString('key4prop1=value1;key4prop2=value2;key4prop3WithNoValue')
138
+ }
139
+ ],
140
+ ['keyn', { value: 'valn' }],
141
+ ['keym', { value: 'valm' }]
142
+ ])
143
+ );
144
+
145
+ it('should extract context of a sampled span from carrier', () => {
146
+ carrier.baggage = baggageValue;
147
+
148
+ const extractedContext = mutableBaggagePropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter);
149
+ const extractedBaggage = propagation.getBaggage(extractedContext);
150
+
151
+ expect(JSON.stringify(extractedBaggage?.getAllEntries())).toEqual(JSON.stringify(expected.getAllEntries()));
152
+ });
153
+
154
+ it('should extract context of a sampled span when the headerValue comes as array', () => {
155
+ carrier.baggage = [baggageValue];
156
+ const extractedBaggage = propagation.getBaggage(
157
+ mutableBaggagePropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter)
158
+ );
159
+
160
+ expect(JSON.stringify(extractedBaggage?.getAllEntries())).toEqual(JSON.stringify(expected.getAllEntries()));
161
+ });
162
+
163
+ it('should extract context of a sampled span when the headerValue comes as array with multiple items', () => {
164
+ carrier.baggage = [
165
+ 'key1=d4cda95b==,key3=c88815a7,key4=foo;key4prop1=value1;k>ey4prop2=value2;key4prop3WithNoValue, keyn = valn',
166
+ 'keym =valm'
167
+ ];
168
+
169
+ const extractedContext = mutableBaggagePropagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter);
170
+ const extractedBaggage = propagation.getBaggage(extractedContext);
171
+
172
+ expect(JSON.stringify(extractedBaggage?.getAllEntries())).toEqual(JSON.stringify(expected.getAllEntries()));
173
+ });
174
+ });
175
+
176
+ describe('.fields()', () => {
177
+ it('returns the fields used by the baggage spec', () => {
178
+ const propagator = new W3CMutableBaggagePropagator();
179
+
180
+ expect(propagator.fields()).toEqual(['baggage']);
181
+ });
182
+ });
@@ -0,0 +1,68 @@
1
+ import {
2
+ BaggageEntry,
3
+ Context,
4
+ TextMapGetter,
5
+ TextMapPropagator,
6
+ TextMapSetter,
7
+ propagation
8
+ } from '@opentelemetry/api';
9
+ import { MutableBaggageImpl } from './mutable-baggage-impl';
10
+ import { baggageUtils, isTracingSuppressed } from '@opentelemetry/core';
11
+
12
+ const BAGGAGE_HEADER = 'baggage';
13
+ const BAGGAGE_ITEMS_SEPARATOR = ',';
14
+ const BAGGAGE_MAX_NAME_VALUE_PAIRS = 180;
15
+ const BAGGAGE_MAX_PER_NAME_VALUE_PAIRS = 4096;
16
+
17
+ export class W3CMutableBaggagePropagator implements TextMapPropagator {
18
+ inject(context: Context, carrier: unknown, setter: TextMapSetter): void {
19
+ const baggage = propagation.getBaggage(context);
20
+
21
+ if (!baggage || isTracingSuppressed(context)) {
22
+ return;
23
+ }
24
+
25
+ const keyPairs = baggageUtils
26
+ .getKeyPairs(baggage)
27
+ .filter(pair => {
28
+ return pair.length <= BAGGAGE_MAX_PER_NAME_VALUE_PAIRS;
29
+ })
30
+ .slice(0, BAGGAGE_MAX_NAME_VALUE_PAIRS);
31
+
32
+ const headerValue = baggageUtils.serializeKeyPairs(keyPairs);
33
+
34
+ if (headerValue.length > 0) {
35
+ setter.set(carrier, BAGGAGE_HEADER, headerValue);
36
+ }
37
+ }
38
+
39
+ extract(context: Context, carrier: unknown, getter: TextMapGetter): Context {
40
+ const headerValue = getter.get(carrier, BAGGAGE_HEADER);
41
+ const baggageString = Array.isArray(headerValue) ? headerValue.join(BAGGAGE_ITEMS_SEPARATOR) : headerValue;
42
+
43
+ const entries = {} as Record<string, BaggageEntry>;
44
+ const pairs = (baggageString ?? '').split(BAGGAGE_ITEMS_SEPARATOR);
45
+
46
+ pairs.forEach(entry => {
47
+ const keyPair = baggageUtils.parsePairKeyValue(entry);
48
+
49
+ if (keyPair) {
50
+ const baggageEntry: BaggageEntry = { value: keyPair.value };
51
+
52
+ if (keyPair.metadata) {
53
+ baggageEntry.metadata = keyPair.metadata;
54
+ }
55
+
56
+ entries[keyPair.key] = baggageEntry;
57
+ }
58
+ });
59
+
60
+ const baggage = new MutableBaggageImpl(new Map(Object.entries(entries)));
61
+
62
+ return propagation.setBaggage(context, baggage);
63
+ }
64
+
65
+ fields(): string[] {
66
+ return [BAGGAGE_HEADER];
67
+ }
68
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "../../tsconfig.base",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist"
6
+ },
7
+ "include": [
8
+ "src/**/*.ts",
9
+ ],
10
+ "exclude": [
11
+ "node_modules",
12
+ "src/**/*.test.ts"
13
+ ]
14
+ }