Skip to content

Commit 91dec5a

Browse files
wconti27tlhunter
authored andcommitted
add span events as a top level field for v0.4 encoding (#5229)
* adds span events at top level for v0.4 encoding * only serializes span events as top level if DD_TRACE_NATIVE_SPAN_EVENTS=true Co-authored-by: Thomas Hunter II <tlhunter@datadog.com>
1 parent 4c7a71d commit 91dec5a

File tree

11 files changed

+702
-398
lines changed

11 files changed

+702
-398
lines changed

packages/dd-trace/src/config.js

+4
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,7 @@ class Config {
595595
this._setValue(defaults, 'vertexai.spanCharLimit', 128)
596596
this._setValue(defaults, 'vertexai.spanPromptCompletionSampleRate', 1.0)
597597
this._setValue(defaults, 'trace.aws.addSpanPointers', true)
598+
this._setValue(defaults, 'trace.nativeSpanEvents', false)
598599
}
599600

600601
_applyLocalStableConfig () {
@@ -765,6 +766,7 @@ class Config {
765766
DD_VERTEXAI_SPAN_PROMPT_COMPLETION_SAMPLE_RATE,
766767
DD_VERTEXAI_SPAN_CHAR_LIMIT,
767768
DD_TRACE_INFERRED_PROXY_SERVICES_ENABLED,
769+
DD_TRACE_NATIVE_SPAN_EVENTS,
768770
OTEL_METRICS_EXPORTER,
769771
OTEL_PROPAGATORS,
770772
OTEL_RESOURCE_ATTRIBUTES,
@@ -977,6 +979,7 @@ class Config {
977979
this._setBoolean(env, 'trace.aws.addSpanPointers', DD_TRACE_AWS_ADD_SPAN_POINTERS)
978980
this._setString(env, 'trace.dynamoDb.tablePrimaryKeys', DD_TRACE_DYNAMODB_TABLE_PRIMARY_KEYS)
979981
this._setArray(env, 'graphqlErrorExtensions', DD_TRACE_GRAPHQL_ERROR_EXTENSIONS)
982+
this._setBoolean(env, 'trace.nativeSpanEvents', DD_TRACE_NATIVE_SPAN_EVENTS)
980983
this._setValue(
981984
env,
982985
'vertexai.spanPromptCompletionSampleRate',
@@ -1114,6 +1117,7 @@ class Config {
11141117
this._setString(opts, 'version', options.version || tags.version)
11151118
this._setBoolean(opts, 'inferredProxyServicesEnabled', options.inferredProxyServicesEnabled)
11161119
this._setBoolean(opts, 'graphqlErrorExtensions', options.graphqlErrorExtensions)
1120+
this._setBoolean(opts, 'trace.nativeSpanEvents', options.trace?.nativeSpanEvents)
11171121

11181122
// For LLMObs, we want the environment variable to take precedence over the options.
11191123
// This is reliant on environment config being set before options.

packages/dd-trace/src/encode/0.4.js

+108-12
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,22 @@ const { Chunk, MsgpackEncoder } = require('../msgpack')
55
const log = require('../log')
66
const { isTrue } = require('../util')
77
const coalesce = require('koalas')
8+
const { memoize } = require('../log/utils')
89

910
const SOFT_LIMIT = 8 * 1024 * 1024 // 8MB
1011

11-
function formatSpan (span) {
12-
return normalizeSpan(truncateSpan(span, false))
12+
function formatSpan (span, config) {
13+
span = normalizeSpan(truncateSpan(span, false))
14+
if (span.span_events) {
15+
// ensure span events are encoded as tags if agent doesn't support native top level span events
16+
if (!config?.trace?.nativeSpanEvents) {
17+
span.meta.events = JSON.stringify(span.span_events)
18+
delete span.span_events
19+
} else {
20+
formatSpanEvents(span)
21+
}
22+
}
23+
return span
1324
}
1425

1526
class AgentEncoder {
@@ -24,6 +35,7 @@ class AgentEncoder {
2435
process.env.DD_TRACE_ENCODING_DEBUG,
2536
false
2637
))
38+
this._config = this._writer?._config
2739
}
2840

2941
count () {
@@ -74,16 +86,18 @@ class AgentEncoder {
7486
this._encodeArrayPrefix(bytes, trace)
7587

7688
for (let span of trace) {
77-
span = formatSpan(span)
89+
span = formatSpan(span, this._config)
7890
bytes.reserve(1)
7991

80-
if (span.type && span.meta_struct) {
81-
bytes.buffer[bytes.length - 1] = 0x8d
82-
} else if (span.type || span.meta_struct) {
83-
bytes.buffer[bytes.length - 1] = 0x8c
84-
} else {
85-
bytes.buffer[bytes.length - 1] = 0x8b
86-
}
92+
// this is the original size of the fixed map for span attributes that always exist
93+
let mapSize = 11
94+
95+
// increment the payload map size depending on if some optional fields exist
96+
if (span.type) mapSize += 1
97+
if (span.meta_struct) mapSize += 1
98+
if (span.span_events) mapSize += 1
99+
100+
bytes.buffer[bytes.length - 1] = 0x80 + mapSize
87101

88102
if (span.type) {
89103
this._encodeString(bytes, 'type')
@@ -112,6 +126,10 @@ class AgentEncoder {
112126
this._encodeMap(bytes, span.meta)
113127
this._encodeString(bytes, 'metrics')
114128
this._encodeMap(bytes, span.metrics)
129+
if (span.span_events) {
130+
this._encodeString(bytes, 'span_events')
131+
this._encodeObjectAsArray(bytes, span.span_events, new Set())
132+
}
115133
if (span.meta_struct) {
116134
this._encodeString(bytes, 'meta_struct')
117135
this._encodeMetaStruct(bytes, span.meta_struct)
@@ -200,6 +218,9 @@ class AgentEncoder {
200218
case 'number':
201219
this._encodeFloat(bytes, value)
202220
break
221+
case 'boolean':
222+
this._encodeBool(bytes, value)
223+
break
203224
default:
204225
// should not happen
205226
}
@@ -258,7 +279,7 @@ class AgentEncoder {
258279
this._encodeObjectAsArray(bytes, value, circularReferencesDetector)
259280
} else if (value !== null && typeof value === 'object') {
260281
this._encodeObjectAsMap(bytes, value, circularReferencesDetector)
261-
} else if (typeof value === 'string' || typeof value === 'number') {
282+
} else if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
262283
this._encodeValue(bytes, value)
263284
}
264285
}
@@ -268,7 +289,7 @@ class AgentEncoder {
268289
const validKeys = keys.filter(key => {
269290
const v = value[key]
270291
return typeof v === 'string' ||
271-
typeof v === 'number' ||
292+
typeof v === 'number' || typeof v === 'boolean' ||
272293
(v !== null && typeof v === 'object' && !circularReferencesDetector.has(v))
273294
})
274295

@@ -319,4 +340,79 @@ class AgentEncoder {
319340
}
320341
}
321342

343+
const memoizedLogDebug = memoize((key, message) => {
344+
log.debug(message)
345+
// return something to store in memoize cache
346+
return true
347+
})
348+
349+
function formatSpanEvents (span) {
350+
for (const spanEvent of span.span_events) {
351+
if (spanEvent.attributes) {
352+
for (const [key, value] of Object.entries(spanEvent.attributes)) {
353+
const newValue = convertSpanEventAttributeValues(key, value)
354+
if (newValue !== undefined) {
355+
spanEvent.attributes[key] = newValue
356+
} else {
357+
delete spanEvent.attributes[key] // delete from attributes if undefined
358+
}
359+
}
360+
if (Object.entries(spanEvent.attributes).length === 0) {
361+
delete spanEvent.attributes
362+
}
363+
}
364+
}
365+
}
366+
367+
function convertSpanEventAttributeValues (key, value, depth = 0) {
368+
if (typeof value === 'string') {
369+
return {
370+
type: 0,
371+
string_value: value
372+
}
373+
} else if (typeof value === 'boolean') {
374+
return {
375+
type: 1,
376+
bool_value: value
377+
}
378+
} else if (Number.isInteger(value)) {
379+
return {
380+
type: 2,
381+
int_value: value
382+
}
383+
} else if (typeof value === 'number') {
384+
return {
385+
type: 3,
386+
double_value: value
387+
}
388+
} else if (Array.isArray(value)) {
389+
if (depth === 0) {
390+
const convertedArray = value
391+
.map((val) => convertSpanEventAttributeValues(key, val, 1))
392+
.filter((convertedVal) => convertedVal !== undefined)
393+
394+
// Only include array_value if there are valid elements
395+
if (convertedArray.length > 0) {
396+
return {
397+
type: 4,
398+
array_value: convertedArray
399+
}
400+
} else {
401+
// If all elements were unsupported, return undefined
402+
return undefined
403+
}
404+
} else {
405+
memoizedLogDebug(key, 'Encountered nested array data type for span event v0.4 encoding. ' +
406+
`Skipping encoding key: ${key}: with value: ${typeof value}.`
407+
)
408+
return undefined
409+
}
410+
} else {
411+
memoizedLogDebug(key, 'Encountered unsupported data type for span event v0.4 encoding, key: ' +
412+
`${key}: with value: ${typeof value}. Skipping encoding of pair.`
413+
)
414+
return undefined
415+
}
416+
}
417+
322418
module.exports = { AgentEncoder }

packages/dd-trace/src/encode/0.5.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ const ARRAY_OF_TWO = 0x92
77
const ARRAY_OF_TWELVE = 0x9c
88

99
function formatSpan (span) {
10-
return normalizeSpan(truncateSpan(span, false))
10+
span = normalizeSpan(truncateSpan(span, false))
11+
// ensure span events are encoded as tags
12+
if (span.span_events) {
13+
span.meta.events = JSON.stringify(span.span_events)
14+
delete span.span_events
15+
}
16+
return span
1117
}
1218

1319
class AgentEncoder extends BaseEncoder {

packages/dd-trace/src/exporters/agent/index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ class AgentExporter {
2424
prioritySampler,
2525
lookup,
2626
protocolVersion,
27-
headers
27+
headers,
28+
config
2829
})
2930

3031
this._timer = undefined

packages/dd-trace/src/exporters/agent/writer.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,16 @@ const BaseWriter = require('../common/writer')
1010
const METRIC_PREFIX = 'datadog.tracer.node.exporter.agent'
1111

1212
class Writer extends BaseWriter {
13-
constructor ({ prioritySampler, lookup, protocolVersion, headers }) {
13+
constructor ({ prioritySampler, lookup, protocolVersion, headers, config = {} }) {
1414
super(...arguments)
1515
const AgentEncoder = getEncoder(protocolVersion)
1616

1717
this._prioritySampler = prioritySampler
1818
this._lookup = lookup
1919
this._protocolVersion = protocolVersion
20-
this._encoder = new AgentEncoder(this)
2120
this._headers = headers
21+
this._config = config
22+
this._encoder = new AgentEncoder(this)
2223
}
2324

2425
_sendPayload (data, count, done) {

0 commit comments

Comments
 (0)