@@ -22,7 +22,9 @@ const PROCESS_ID = constants.PROCESS_ID
2222const ERROR_MESSAGE = constants . ERROR_MESSAGE
2323const ERROR_STACK = constants . ERROR_STACK
2424const ERROR_TYPE = constants . ERROR_TYPE
25+ const { IGNORE_OTEL_ERROR } = constants
2526
27+ // TODO(BridgeAR)[31.03.2025]: Should these land in the constants file?
2628const map = {
2729 'operation.name' : 'name' ,
2830 'service.name' : 'service' ,
@@ -69,48 +71,46 @@ function setSingleSpanIngestionTags (span, options) {
6971}
7072
7173function extractSpanLinks ( formattedSpan , span ) {
72- const links = [ ]
73- if ( span . _links ) {
74- for ( const link of span . _links ) {
75- const { context, attributes } = link
76- const formattedLink = { }
77-
78- formattedLink . trace_id = context . toTraceId ( true )
79- formattedLink . span_id = context . toSpanId ( true )
80-
81- if ( attributes && Object . keys ( attributes ) . length > 0 ) {
82- formattedLink . attributes = attributes
83- }
84- if ( context ?. _sampling ?. priority >= 0 ) formattedLink . flags = context . _sampling . priority > 0 ? 1 : 0
85- if ( context ?. _tracestate ) formattedLink . tracestate = context . _tracestate . toString ( )
74+ if ( ! span . _links ?. length ) {
75+ return
76+ }
77+ const links = span . _links . map ( link => {
78+ const { context, attributes } = link
79+ const formattedLink = {
80+ trace_id : context . toTraceId ( true ) ,
81+ span_id : context . toSpanId ( true )
82+ }
8683
87- links . push ( formattedLink )
84+ if ( attributes && Object . keys ( attributes ) . length > 0 ) {
85+ formattedLink . attributes = attributes
8886 }
89- }
90- if ( links . length > 0 ) { formattedSpan . meta [ '_dd.span_links' ] = JSON . stringify ( links ) }
87+ if ( context ?. _sampling ?. priority >= 0 ) formattedLink . flags = context . _sampling . priority > 0 ? 1 : 0
88+ if ( context ?. _tracestate ) formattedLink . tracestate = context . _tracestate . toString ( )
89+
90+ return formattedLink
91+ } )
92+ formattedSpan . meta [ '_dd.span_links' ] = JSON . stringify ( links )
9193}
9294
9395function extractSpanEvents ( formattedSpan , span ) {
94- const events = [ ]
95- if ( span . _events ) {
96- for ( const event of span . _events ) {
97- const formattedEvent = {
98- name : event . name ,
99- time_unix_nano : Math . round ( event . startTime * 1e6 ) ,
100- attributes : event . attributes && Object . keys ( event . attributes ) . length > 0 ? event . attributes : undefined
101- }
102-
103- events . push ( formattedEvent )
104- }
105- }
106- if ( events . length > 0 ) {
107- formattedSpan . span_events = events
96+ if ( ! span . _events ?. length ) {
97+ return
10898 }
99+ const events = span . _events . map ( event => {
100+ return {
101+ name : event . name ,
102+ time_unix_nano : Math . round ( event . startTime * 1e6 ) ,
103+ attributes : event . attributes && Object . keys ( event . attributes ) . length > 0 ? event . attributes : undefined
104+ }
105+ } )
106+ formattedSpan . span_events = events
109107}
110108
111109function extractTags ( formattedSpan , span ) {
112110 const context = span . context ( )
113111 const origin = context . _trace . origin
112+ // TODO(BridgeAR)[31.03.2025]: Look into changing the way we store tags. Using
113+ // a map is likely faster short term.
114114 const tags = context . _tags
115115 const hostname = context . _hostname
116116 const priority = context . _sampling . priority
@@ -126,43 +126,48 @@ function extractTags (formattedSpan, span) {
126126 registerExtraService ( tags [ 'service.name' ] )
127127 }
128128
129- for ( const tag in tags ) {
129+ for ( const [ tag , value ] of Object . entries ( tags ) ) {
130+ // TODO(BridgeAR)[31.03.2025]: Check how many tags are defined in average.
131+ // In case there are more than 2 tags in average, check for all special
132+ // cases up front and loop over the tags afterwards, skipping the already
133+ // visited property names by checking a map with these keys.
130134 switch ( tag ) {
131135 case 'service.name' :
132136 case 'span.type' :
133137 case 'resource.name' :
134- addTag ( formattedSpan , { } , map [ tag ] , tags [ tag ] )
138+ addTag ( formattedSpan , { } , map [ tag ] , value )
135139 break
136140 // HACK: remove when Datadog supports numeric status code
137141 case 'http.status_code' :
138- addTag ( formattedSpan . meta , { } , tag , tags [ tag ] && String ( tags [ tag ] ) )
142+ addTag ( formattedSpan . meta , { } , tag , value && String ( value ) )
139143 break
140144 case 'analytics.event' :
141- addTag ( { } , formattedSpan . metrics , ANALYTICS , tags [ tag ] === undefined || tags [ tag ] ? 1 : 0 )
145+ addTag ( { } , formattedSpan . metrics , ANALYTICS , value === undefined || value ? 1 : 0 )
142146 break
143147 case HOSTNAME_KEY :
144148 case MEASURED :
145- addTag ( { } , formattedSpan . metrics , tag , tags [ tag ] === undefined || tags [ tag ] ? 1 : 0 )
149+ addTag ( { } , formattedSpan . metrics , tag , value === undefined || value ? 1 : 0 )
146150 break
151+ // TODO(BridgeAR)[31.03.2025]: How come we use two different ways to pass
152+ // through errors? Can we just unify the behavior to always use one way?
147153 case 'error' :
148154 if ( context . _name !== 'fs.operation' ) {
149- extractError ( formattedSpan , tags [ tag ] )
155+ extractError ( formattedSpan , value )
150156 }
151157 break
152158 case ERROR_TYPE :
153159 case ERROR_MESSAGE :
154160 case ERROR_STACK :
155161 // HACK: remove when implemented in the backend
156- if ( context . _name !== 'fs.operation' ) {
157- // HACK: to ensure otel.recordException does not influence formattedSpan.error
158- if ( tags . setTraceError ) {
159- formattedSpan . error = 1
160- }
161- } else {
162+ if ( context . _name === 'fs.operation' ) {
162163 break
163164 }
165+ // otel.recordException should not influence trace.error
166+ if ( ! tags [ IGNORE_OTEL_ERROR ] ) {
167+ formattedSpan . error = 1
168+ }
164169 default : // eslint-disable-line no-fallthrough
165- addTag ( formattedSpan . meta , formattedSpan . metrics , tag , tags [ tag ] )
170+ addTag ( formattedSpan . meta , formattedSpan . metrics , tag , value )
166171 }
167172 }
168173 setSingleSpanIngestionTags ( formattedSpan , context . _spanSampling )
@@ -193,8 +198,8 @@ function extractChunkTags (formattedSpan, span) {
193198
194199 if ( ! isLocalRoot ) return
195200
196- for ( const key in context . _trace . tags ) {
197- addTag ( formattedSpan . meta , formattedSpan . metrics , key , context . _trace . tags [ key ] )
201+ for ( const [ key , value ] of Object . entries ( context . _trace . tags ) ) {
202+ addTag ( formattedSpan . meta , formattedSpan . metrics , key , value )
198203 }
199204}
200205
@@ -205,6 +210,8 @@ function extractError (formattedSpan, error) {
205210
206211 if ( isError ( error ) ) {
207212 // AggregateError only has a code and no message.
213+ // TODO(BridgeAR)[31.03.2025]: An AggregateError can have a message. Should
214+ // the code just generally be added, if available?
208215 addTag ( formattedSpan . meta , formattedSpan . metrics , ERROR_MESSAGE , error . message || error . code )
209216 addTag ( formattedSpan . meta , formattedSpan . metrics , ERROR_TYPE , error . name )
210217 addTag ( formattedSpan . meta , formattedSpan . metrics , ERROR_STACK , error . stack )
@@ -223,30 +230,21 @@ function addTag (meta, metrics, key, value, nested) {
223230 case 'boolean' :
224231 metrics [ key ] = value ? 1 : 0
225232 break
226- case 'undefined' :
227- break
228- case 'object' :
229- if ( value === null ) break
233+ default :
234+ if ( value == null ) break
230235
231236 // Special case for Node.js Buffer and URL
237+ // TODO(BridgeAR)[31.03.2025]: Figure out if all typed arrays should be treated as buffers.
232238 if ( isNodeBuffer ( value ) || isUrl ( value ) ) {
233239 metrics [ key ] = value . toString ( )
234240 } else if ( ! Array . isArray ( value ) && ! nested ) {
235- for ( const prop in value ) {
236- if ( ! hasOwn ( value , prop ) ) continue
237-
238- addTag ( meta , metrics , `${ key } .${ prop } ` , value [ prop ] , true )
241+ for ( const [ prop , val ] of Object . entries ( value ) ) {
242+ addTag ( meta , metrics , `${ key } .${ prop } ` , val , true )
239243 }
240244 }
241-
242- break
243245 }
244246}
245247
246- function hasOwn ( object , prop ) {
247- return Object . prototype . hasOwnProperty . call ( object , prop )
248- }
249-
250248function isNodeBuffer ( obj ) {
251249 return obj . constructor && obj . constructor . name === 'Buffer' &&
252250 typeof obj . readInt8 === 'function' &&
0 commit comments