Skip to content

Commit 5153ea6

Browse files
authored
Add new baggage APIs which act on currently active baggage (#5365)
* new baggage api * added unit tests * add TS documentation * inject and extract using storage when propagating baggage without spans
1 parent fd0750c commit 5153ea6

File tree

8 files changed

+169
-11
lines changed

8 files changed

+169
-11
lines changed

index.d.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,22 @@ interface Tracer extends opentracing.Tracer {
138138
* LLM Observability SDK
139139
*/
140140
llmobs: tracer.llmobs.LLMObs;
141+
142+
/**
143+
* @experimental
144+
* Provide same functionality as OpenTelemetry Baggage:
145+
* https://opentelemetry.io/docs/concepts/signals/baggage/
146+
*
147+
* Since the equivalent of OTel Context is implicit in dd-trace-js,
148+
* these APIs act on the currently active baggage
149+
*
150+
* Work with storage('baggage'), therefore do not follow the same continuity as other APIs
151+
*/
152+
setBaggageItem (key: string, value: string): Record<string, string>;
153+
getBaggageItem (key: string): string | undefined;
154+
getAllBaggageItems (): Record<string, string>;
155+
removeBaggageItem (key: string): Record<string, string>;
156+
removeAllBaggageItems (): Record<string, string>;
141157
}
142158

143159
// left out of the namespace, so it

packages/dd-trace/src/baggage.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
'use strict'
2+
3+
const { storage } = require('../../datadog-core')
4+
const baggageStorage = storage('baggage')
5+
6+
function setBaggageItem (key, value) {
7+
storage('baggage').enterWith({ ...baggageStorage.getStore(), [key]: value })
8+
return storage('baggage').getStore()
9+
}
10+
11+
function getBaggageItem (key) {
12+
return storage('baggage').getStore()?.[key]
13+
}
14+
15+
function getAllBaggageItems () {
16+
return storage('baggage').getStore()
17+
}
18+
19+
function removeBaggageItem (keyToRemove) {
20+
const { [keyToRemove]: _, ...newBaggage } = storage('baggage').getStore()
21+
storage('baggage').enterWith(newBaggage)
22+
return newBaggage
23+
}
24+
25+
function removeAllBaggageItems () {
26+
storage('baggage').enterWith({})
27+
return storage('baggage').getStore()
28+
}
29+
30+
module.exports = {
31+
setBaggageItem,
32+
getBaggageItem,
33+
getAllBaggageItems,
34+
removeBaggageItem,
35+
removeAllBaggageItems
36+
}

packages/dd-trace/src/noop/proxy.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ class NoopProxy {
1616
this.appsec = noopAppsec
1717
this.dogstatsd = noopDogStatsDClient
1818
this.llmobs = noopLLMObs
19+
this.setBaggageItem = () => {}
20+
this.getBaggageItem = () => {}
21+
this.getAllBaggageItems = () => {}
22+
this.removeBaggageItem = () => {}
23+
this.removeAllBaggageItems = () => {}
1924
}
2025

2126
init () {

packages/dd-trace/src/opentelemetry/context_manager.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class ContextManager {
1212
this._store = storage('opentelemetry')
1313
}
1414

15+
// converts dd to otel
1516
active () {
1617
const activeSpan = tracer.scope().active()
1718
const store = this._store.getStore()
@@ -54,6 +55,7 @@ class ContextManager {
5455
: wrappedContext
5556
}
5657

58+
// converts otel to dd
5759
with (context, fn, thisArg, ...args) {
5860
const span = trace.getSpan(context)
5961
const ddScope = tracer.scope()

packages/dd-trace/src/opentracing/propagation/text_map.js

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const log = require('../../log')
77
const TraceState = require('./tracestate')
88
const tags = require('../../../../../ext/tags')
99
const { channel } = require('dc-polyfill')
10+
const { setBaggageItem, getAllBaggageItems, removeAllBaggageItems } = require('../../baggage')
1011

1112
const { AUTO_KEEP, AUTO_REJECT, USER_KEEP } = require('../../../../../ext/priority')
1213

@@ -55,9 +56,10 @@ class TextMapPropagator {
5556
}
5657

5758
inject (spanContext, carrier) {
58-
if (!spanContext || !carrier) return
59-
59+
if (!carrier) return
6060
this._injectBaggageItems(spanContext, carrier)
61+
if (!spanContext) return
62+
6163
this._injectDatadog(spanContext, carrier)
6264
this._injectB3MultipleHeaders(spanContext, carrier)
6365
this._injectB3SingleHeader(spanContext, carrier)
@@ -125,7 +127,7 @@ class TextMapPropagator {
125127

126128
_injectBaggageItems (spanContext, carrier) {
127129
if (this._config.legacyBaggageEnabled) {
128-
spanContext._baggageItems && Object.keys(spanContext._baggageItems).forEach(key => {
130+
spanContext?._baggageItems && Object.keys(spanContext._baggageItems).forEach(key => {
129131
carrier[baggagePrefix + key] = String(spanContext._baggageItems[key])
130132
})
131133
}
@@ -134,7 +136,9 @@ class TextMapPropagator {
134136
let itemCounter = 0
135137
let byteCounter = 0
136138

137-
for (const [key, value] of Object.entries(spanContext._baggageItems)) {
139+
const baggageItems = spanContext ? spanContext._baggageItems : getAllBaggageItems()
140+
if (!baggageItems) return
141+
for (const [key, value] of Object.entries(baggageItems)) {
138142
const item = `${this._encodeOtelBaggageKey(String(key).trim())}=${encodeURIComponent(String(value).trim())},`
139143
itemCounter += 1
140144
byteCounter += item.length
@@ -627,23 +631,27 @@ class TextMapPropagator {
627631
_extractBaggageItems (carrier, spanContext) {
628632
if (!this._hasPropagationStyle('extract', 'baggage')) return
629633
if (!carrier || !carrier.baggage) return
630-
if (!spanContext) return
634+
if (!spanContext) removeAllBaggageItems()
631635
const baggages = carrier.baggage.split(',')
632636
for (const keyValue of baggages) {
633637
if (!keyValue.includes('=')) {
634-
spanContext._baggageItems = {}
638+
if (spanContext) spanContext._baggageItems = {}
635639
return
636640
}
637641
let [key, value] = keyValue.split('=')
638642
key = this._decodeOtelBaggageKey(key.trim())
639643
value = decodeURIComponent(value.trim())
640644
if (!key || !value) {
641-
spanContext._baggageItems = {}
645+
if (spanContext) spanContext._baggageItems = {}
642646
return
643647
}
644648
// the current code assumes precedence of ot-baggage- (legacy opentracing baggage) over baggage
645-
if (key in spanContext._baggageItems) return
646-
spanContext._baggageItems[key] = value
649+
if (spanContext) {
650+
if (key in spanContext._baggageItems) return
651+
spanContext._baggageItems[key] = value
652+
} else {
653+
setBaggageItem(key, value)
654+
}
647655
}
648656
}
649657

packages/dd-trace/src/proxy.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ const telemetry = require('./telemetry')
1010
const nomenclature = require('./service-naming')
1111
const PluginManager = require('./plugin_manager')
1212
const NoopDogStatsDClient = require('./noop/dogstatsd')
13+
const {
14+
setBaggageItem,
15+
getBaggageItem,
16+
getAllBaggageItems,
17+
removeBaggageItem,
18+
removeAllBaggageItems
19+
} = require('./baggage')
1320

1421
class LazyModule {
1522
constructor (provider) {
@@ -65,6 +72,11 @@ class Tracer extends NoopProxy {
6572
this.dogstatsd = new NoopDogStatsDClient()
6673
this._tracingInitialized = false
6774
this._flare = new LazyModule(() => require('./flare'))
75+
this.setBaggageItem = setBaggageItem
76+
this.getBaggageItem = getBaggageItem
77+
this.getAllBaggageItems = getAllBaggageItems
78+
this.removeBaggageItem = removeBaggageItem
79+
this.removeAllBaggageItems = removeAllBaggageItems
6880

6981
// these requires must work with esm bundler
7082
this._modules = {

packages/dd-trace/test/opentracing/propagation/text_map.spec.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const id = require('../../../src/id')
77
const SpanContext = require('../../../src/opentracing/span_context')
88
const TraceState = require('../../../src/opentracing/propagation/tracestate')
99
const { channel } = require('dc-polyfill')
10+
const { setBaggageItem, getBaggageItem, getAllBaggageItems, removeAllBaggageItems } = require('../../../src/baggage')
1011

1112
const { AUTO_KEEP, AUTO_REJECT, USER_KEEP } = require('../../../../../ext/priority')
1213
const { SAMPLING_MECHANISM_MANUAL } = require('../../../src/constants')
@@ -142,6 +143,16 @@ describe('TextMapPropagator', () => {
142143
expect(carrier.baggage).to.equal('raccoon=chunky')
143144
})
144145

146+
it('should inject baggage items when spanContext is null', () => {
147+
const carrier = {}
148+
const spanContext = null
149+
removeAllBaggageItems()
150+
setBaggageItem('spring', 'blossom')
151+
152+
propagator.inject(spanContext, carrier)
153+
expect(carrier.baggage).to.equal('spring=blossom')
154+
})
155+
145156
it('should inject an existing sampling priority', () => {
146157
const carrier = {}
147158
const spanContext = createContext({
@@ -492,8 +503,8 @@ describe('TextMapPropagator', () => {
492503
expect(spanContext._trace.tags).to.not.have.property('_dd.p.tid')
493504
})
494505

495-
// temporary test. On the contrary, it SHOULD extract baggage
496-
it('should not extract baggage when it is the only propagation style', () => {
506+
it('should extract baggage when it is the only propagation style', () => {
507+
removeAllBaggageItems()
497508
config = new Config({
498509
tracePropagationStyle: {
499510
extract: ['baggage']
@@ -505,6 +516,8 @@ describe('TextMapPropagator', () => {
505516
}
506517
const spanContext = propagator.extract(carrier)
507518
expect(spanContext).to.be.null
519+
expect(getBaggageItem('foo')).to.equal('bar')
520+
expect(getAllBaggageItems()).to.deep.equal({ foo: 'bar' })
508521
})
509522

510523
it('should convert signed IDs to unsigned', () => {

packages/dd-trace/test/proxy.spec.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,72 @@ describe('TracerProxy', () => {
624624
})
625625
})
626626

627+
describe('baggage', () => {
628+
afterEach(() => {
629+
proxy.removeAllBaggageItems()
630+
})
631+
632+
describe('setBaggageItem', () => {
633+
it('should set a baggage item', () => {
634+
const baggage = proxy.setBaggageItem('key', 'value')
635+
expect(baggage).to.deep.equal({ key: 'value' })
636+
})
637+
638+
it('should merge with existing baggage items', () => {
639+
proxy.setBaggageItem('key1', 'value1')
640+
const baggage = proxy.setBaggageItem('key2', 'value2')
641+
expect(baggage).to.deep.equal({ key1: 'value1', key2: 'value2' })
642+
})
643+
})
644+
645+
describe('getBaggageItem', () => {
646+
it('should get a baggage item', () => {
647+
proxy.setBaggageItem('key', 'value')
648+
expect(proxy.getBaggageItem('key')).to.equal('value')
649+
})
650+
651+
it('should return undefined for non-existent items', () => {
652+
expect(proxy.getBaggageItem('missing')).to.be.undefined
653+
})
654+
})
655+
656+
describe('getAllBaggageItems', () => {
657+
it('should get all baggage items', () => {
658+
proxy.setBaggageItem('key1', 'value1')
659+
proxy.setBaggageItem('key2', 'value2')
660+
expect(proxy.getAllBaggageItems()).to.deep.equal({ key1: 'value1', key2: 'value2' })
661+
})
662+
663+
it('should return empty object when no items exist', () => {
664+
expect(proxy.getAllBaggageItems()).to.be.undefined
665+
})
666+
})
667+
668+
describe('removeBaggageItem', () => {
669+
it('should remove a specific baggage item', () => {
670+
proxy.setBaggageItem('key1', 'value1')
671+
proxy.setBaggageItem('key2', 'value2')
672+
const baggage = proxy.removeBaggageItem('key1')
673+
expect(baggage).to.deep.equal({ key2: 'value2' })
674+
})
675+
676+
it('should handle removing non-existent items', () => {
677+
proxy.setBaggageItem('key', 'value')
678+
const baggage = proxy.removeBaggageItem('missing')
679+
expect(baggage).to.deep.equal({ key: 'value' })
680+
})
681+
})
682+
683+
describe('removeAllBaggageItems', () => {
684+
it('should remove all baggage items', () => {
685+
proxy.setBaggageItem('key1', 'value1')
686+
proxy.setBaggageItem('key2', 'value2')
687+
const baggage = proxy.removeAllBaggageItems()
688+
expect(baggage).to.deep.equal({})
689+
})
690+
})
691+
})
692+
627693
describe('appsec', () => {
628694
describe('trackUserLoginSuccessEvent', () => {
629695
it('should call the underlying NoopAppsecSdk method', () => {

0 commit comments

Comments
 (0)