Skip to content

Commit 91fb5e7

Browse files
fix(llmobs): prevent duplicate tags in x-datadog-tags on injection
When injecting LLMObs trace context into outbound carriers, handleLLMObsInjection previously appended propagated keys without checking whether existing x-datadog-tags already contained entries for those keys (for example, from standard trace tag propagation or upstream service headers). This resulted in duplicate entries such as multiple _dd.p.llmobs_ml_app values in x-datadog-tags. Use stripTagsetEntry before appending resolved LLMObs keys so existing entries are cleanly replaced without duplication. Fixes #9714
1 parent 98d97b5 commit 91fb5e7

2 files changed

Lines changed: 67 additions & 6 deletions

File tree

packages/dd-trace/src/llmobs/index.js

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,12 +163,30 @@ function handleLLMObsInjection ({ carrier }) {
163163
// tags, so it may be undefined here — coalesce before appending.
164164
const existing = readDatadogTags(carrier)
165165
let tags = existing || ''
166-
if (parentId) tags += `${tags ? ',' : ''}${PROPAGATED_PARENT_ID_KEY}=${parentId}`
167-
if (mlApp) tags += `${tags ? ',' : ''}${PROPAGATED_ML_APP_KEY}=${mlApp}`
168-
if (sessionId) tags += `${tags ? ',' : ''}${PROPAGATED_SESSION_ID_KEY}=${sessionId}`
169-
if (sampleRate != null) tags += `${tags ? ',' : ''}${PROPAGATED_SAMPLE_RATE_KEY}=${sampleRate}`
170-
if (samplingDecision != null) tags += `${tags ? ',' : ''}${PROPAGATED_SAMPLING_DECISION_KEY}=${samplingDecision}`
171-
if (propagatedTraceId != null) tags += `${tags ? ',' : ''}${PROPAGATED_TRACE_ID_KEY}=${propagatedTraceId}`
166+
if (parentId) {
167+
tags = stripTagsetEntry(tags, PROPAGATED_PARENT_ID_KEY)
168+
tags += `${tags ? ',' : ''}${PROPAGATED_PARENT_ID_KEY}=${parentId}`
169+
}
170+
if (mlApp) {
171+
tags = stripTagsetEntry(tags, PROPAGATED_ML_APP_KEY)
172+
tags += `${tags ? ',' : ''}${PROPAGATED_ML_APP_KEY}=${mlApp}`
173+
}
174+
if (sessionId) {
175+
tags = stripTagsetEntry(tags, PROPAGATED_SESSION_ID_KEY)
176+
tags += `${tags ? ',' : ''}${PROPAGATED_SESSION_ID_KEY}=${sessionId}`
177+
}
178+
if (sampleRate != null) {
179+
tags = stripTagsetEntry(tags, PROPAGATED_SAMPLE_RATE_KEY)
180+
tags += `${tags ? ',' : ''}${PROPAGATED_SAMPLE_RATE_KEY}=${sampleRate}`
181+
}
182+
if (samplingDecision != null) {
183+
tags = stripTagsetEntry(tags, PROPAGATED_SAMPLING_DECISION_KEY)
184+
tags += `${tags ? ',' : ''}${PROPAGATED_SAMPLING_DECISION_KEY}=${samplingDecision}`
185+
}
186+
if (propagatedTraceId != null) {
187+
tags = stripTagsetEntry(tags, PROPAGATED_TRACE_ID_KEY)
188+
tags += `${tags ? ',' : ''}${PROPAGATED_TRACE_ID_KEY}=${propagatedTraceId}`
189+
}
172190
// When a local agent attribution is resolved, strip any stale upstream pagent entries that
173191
// `_injectTags` may have already written into the carrier (it propagates all `_dd.p.*` from
174192
// `_trace.tags`). This ensures the downstream sees a consistent id-only or id+name pair

packages/dd-trace/test/llmobs/index.spec.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,49 @@ describe('module', () => {
283283
)
284284
})
285285

286+
it('does not duplicate _dd.p.llmobs_ml_app when already present in x-datadog-tags', () => {
287+
llmobsModule.enable({ llmobs: { mlApp: 'test', agentlessEnabled: false } })
288+
289+
const carrier = {
290+
'x-datadog-tags': '_dd.p.tid=69fe014200000000,_dd.p.dm=-0,_dd.p.llmobs_ml_app=test',
291+
}
292+
injectCh.publish({ carrier })
293+
294+
assert.strictEqual(
295+
carrier['x-datadog-tags'],
296+
'_dd.p.tid=69fe014200000000,_dd.p.dm=-0,_dd.p.llmobs_ml_app=test'
297+
)
298+
})
299+
300+
it('updates existing LLMObs tags in x-datadog-tags without duplicating keys', () => {
301+
llmobsModule.enable({ llmobs: { mlApp: 'test', agentlessEnabled: false } })
302+
store.span = {
303+
context () {
304+
return {
305+
toSpanId () {
306+
return 'new-parent-id'
307+
},
308+
}
309+
},
310+
}
311+
LLMObsTagger.tagMap.set(store.span, {
312+
[SESSION_ID]: 'new-session',
313+
[SAMPLE_RATE]: '0.8',
314+
[SAMPLING_DECISION]: '1',
315+
})
316+
317+
const carrier = {
318+
'x-datadog-tags':
319+
'_dd.p.tid=69fe014200000000,_dd.p.llmobs_parent_id=old-id,_dd.p.llmobs_ml_app=old-app,_dd.p.llmobs_sid=old-session',
320+
}
321+
injectCh.publish({ carrier })
322+
323+
assert.strictEqual(
324+
carrier['x-datadog-tags'],
325+
'_dd.p.tid=69fe014200000000,_dd.p.llmobs_parent_id=new-parent-id,_dd.p.llmobs_ml_app=test,_dd.p.llmobs_sid=new-session,_dd.p.llmobs_sr=0.8,_dd.p.llmobs_sd=1'
326+
)
327+
})
328+
286329
describe('with DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH=0', () => {
287330
let config
288331

0 commit comments

Comments
 (0)