@@ -5,11 +5,22 @@ const { Chunk, MsgpackEncoder } = require('../msgpack')
5
5
const log = require ( '../log' )
6
6
const { isTrue } = require ( '../util' )
7
7
const coalesce = require ( 'koalas' )
8
+ const { memoize } = require ( '../log/utils' )
8
9
9
10
const SOFT_LIMIT = 8 * 1024 * 1024 // 8MB
10
11
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
13
24
}
14
25
15
26
class AgentEncoder {
@@ -24,6 +35,7 @@ class AgentEncoder {
24
35
process . env . DD_TRACE_ENCODING_DEBUG ,
25
36
false
26
37
) )
38
+ this . _config = this . _writer ?. _config
27
39
}
28
40
29
41
count ( ) {
@@ -74,16 +86,18 @@ class AgentEncoder {
74
86
this . _encodeArrayPrefix ( bytes , trace )
75
87
76
88
for ( let span of trace ) {
77
- span = formatSpan ( span )
89
+ span = formatSpan ( span , this . _config )
78
90
bytes . reserve ( 1 )
79
91
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
87
101
88
102
if ( span . type ) {
89
103
this . _encodeString ( bytes , 'type' )
@@ -112,6 +126,10 @@ class AgentEncoder {
112
126
this . _encodeMap ( bytes , span . meta )
113
127
this . _encodeString ( bytes , 'metrics' )
114
128
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
+ }
115
133
if ( span . meta_struct ) {
116
134
this . _encodeString ( bytes , 'meta_struct' )
117
135
this . _encodeMetaStruct ( bytes , span . meta_struct )
@@ -200,6 +218,9 @@ class AgentEncoder {
200
218
case 'number' :
201
219
this . _encodeFloat ( bytes , value )
202
220
break
221
+ case 'boolean' :
222
+ this . _encodeBool ( bytes , value )
223
+ break
203
224
default :
204
225
// should not happen
205
226
}
@@ -258,7 +279,7 @@ class AgentEncoder {
258
279
this . _encodeObjectAsArray ( bytes , value , circularReferencesDetector )
259
280
} else if ( value !== null && typeof value === 'object' ) {
260
281
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' ) {
262
283
this . _encodeValue ( bytes , value )
263
284
}
264
285
}
@@ -268,7 +289,7 @@ class AgentEncoder {
268
289
const validKeys = keys . filter ( key => {
269
290
const v = value [ key ]
270
291
return typeof v === 'string' ||
271
- typeof v === 'number' ||
292
+ typeof v === 'number' || typeof v === 'boolean' ||
272
293
( v !== null && typeof v === 'object' && ! circularReferencesDetector . has ( v ) )
273
294
} )
274
295
@@ -319,4 +340,79 @@ class AgentEncoder {
319
340
}
320
341
}
321
342
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
+
322
418
module . exports = { AgentEncoder }
0 commit comments