Skip to content

Commit 759be56

Browse files
committed
fix(opentelemetry): apply global/resource tags to OTel-bridged spans (#9229)
OTEL_RESOURCE_ATTRIBUTES and DD_TAGS are parsed into config.tags but were only applied to spans created via the native tracer (opentracing/tracer.js). Spans created through the OTel API bridge were built directly with only service/resource/span.kind, so global/resource tags never reached them — documented opt-outs like OTEL_RESOURCE_ATTRIBUTES=dd_llmobs_enabled=false had no effect on OTel-bridged spans. Seed the bridge span's tags with config.tags as defaults, keeping explicit service/resource/span.kind and user-set attributes as overrides, matching the native path's precedence.
1 parent 1d8446f commit 759be56

2 files changed

Lines changed: 51 additions & 0 deletions

File tree

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,11 @@ class Span extends BridgeSpanBase {
152152
hostname: _tracer._hostname,
153153
integrationName: parentTracer?._isOtelLibrary ? 'otel.library' : 'otel',
154154
tags: {
155+
// Apply global/resource tags (DD_TAGS, OTEL_RESOURCE_ATTRIBUTES) as
156+
// defaults, mirroring the native path in opentracing/tracer.js. The
157+
// explicit service/resource/span.kind below take precedence, and any
158+
// user-set OTel attributes applied via setAttributes() still win.
159+
..._tracer._config.tags,
155160
[SERVICE_NAME]: _tracer._service,
156161
[RESOURCE_NAME]: spanName,
157162
[SPAN_KIND]: spanKindNames[kind],

packages/dd-trace/test/opentelemetry/span.spec.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,52 @@ describe('OTel Span', () => {
4848
assert.strictEqual(context._hostname, tracer._hostname)
4949
})
5050

51+
it('should apply global config tags (DD_TAGS / OTEL_RESOURCE_ATTRIBUTES) to bridged spans', () => {
52+
// OTEL_RESOURCE_ATTRIBUTES and DD_TAGS are parsed into config.tags; the OTel
53+
// bridge must apply them to bridged spans just like the native path does.
54+
const { tags } = tracer._tracer._config
55+
tags.dd_llmobs_enabled = 'false'
56+
try {
57+
const span = makeSpan('name')
58+
assert.strictEqual(span._ddSpan.context().getTag('dd_llmobs_enabled'), 'false')
59+
} finally {
60+
delete tags.dd_llmobs_enabled
61+
}
62+
})
63+
64+
it('should let explicit span attributes take precedence over global config tags', () => {
65+
const { tags } = tracer._tracer._config
66+
tags.custom_tag = 'from-config'
67+
try {
68+
const span = makeSpan('name', { attributes: { custom_tag: 'from-span' } })
69+
assert.strictEqual(span._ddSpan.context().getTag('custom_tag'), 'from-span')
70+
} finally {
71+
delete tags.custom_tag
72+
}
73+
})
74+
75+
it('should keep explicit service/resource/span.kind over colliding global config tags', () => {
76+
// OTEL_RESOURCE_ATTRIBUTES commonly carries service.name (the reserved
77+
// SERVICE_NAME key), so it lands in config.tags and collides with the value
78+
// the bridge sets deliberately. The reserved value must win regardless of
79+
// ordering, so the span's service/resource/span.kind stay correct.
80+
const { tags } = tracer._tracer._config
81+
tags[SERVICE_NAME] = 'from-config-tags'
82+
tags[RESOURCE_NAME] = 'from-config-tags'
83+
tags[SPAN_KIND] = 'from-config-tags'
84+
try {
85+
const span = makeSpan('name', { kind: api.SpanKind.CONSUMER })
86+
const context = span._ddSpan.context()
87+
assert.strictEqual(context.getTag(SERVICE_NAME), tracer._tracer._service)
88+
assert.strictEqual(context.getTag(RESOURCE_NAME), 'name')
89+
assert.strictEqual(context.getTag(SPAN_KIND), kinds.CONSUMER)
90+
} finally {
91+
delete tags[SERVICE_NAME]
92+
delete tags[RESOURCE_NAME]
93+
delete tags[SPAN_KIND]
94+
}
95+
})
96+
5197
it('should expose parent span id', () => {
5298
tracer.trace('outer', (outer) => {
5399
const span = makeSpan('name', {})

0 commit comments

Comments
 (0)