@@ -5,11 +5,22 @@ const { Chunk, MsgpackEncoder } = require('../msgpack')
55const log = require ( '../log' )
66const { isTrue } = require ( '../util' )
77const coalesce = require ( 'koalas' )
8+ const { memoize } = require ( '../log/utils' )
89
910const 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
1526class 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+
322418module . exports = { AgentEncoder }
0 commit comments