dd-trace 5.104.0 → 5.105.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/LICENSE-3rdparty.csv +90 -102
  2. package/index.d.ts +82 -3
  3. package/package.json +15 -15
  4. package/packages/datadog-core/src/storage.js +1 -1
  5. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  6. package/packages/datadog-instrumentations/src/ai.js +8 -7
  7. package/packages/datadog-instrumentations/src/aws-sdk.js +13 -0
  8. package/packages/datadog-instrumentations/src/azure-cosmos.js +7 -0
  9. package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
  10. package/packages/datadog-instrumentations/src/cucumber.js +78 -5
  11. package/packages/datadog-instrumentations/src/dns.js +54 -18
  12. package/packages/datadog-instrumentations/src/fastify.js +142 -82
  13. package/packages/datadog-instrumentations/src/graphql.js +188 -62
  14. package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
  15. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  16. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
  17. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
  18. package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
  19. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  20. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -3
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +37 -236
  26. package/packages/datadog-instrumentations/src/hono.js +54 -3
  27. package/packages/datadog-instrumentations/src/http/server.js +9 -4
  28. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
  29. package/packages/datadog-instrumentations/src/jest.js +360 -150
  30. package/packages/datadog-instrumentations/src/kafkajs.js +120 -16
  31. package/packages/datadog-instrumentations/src/mocha/main.js +128 -17
  32. package/packages/datadog-instrumentations/src/nats.js +182 -0
  33. package/packages/datadog-instrumentations/src/nyc.js +38 -1
  34. package/packages/datadog-instrumentations/src/openai.js +33 -18
  35. package/packages/datadog-instrumentations/src/oracledb.js +6 -1
  36. package/packages/datadog-instrumentations/src/pino.js +17 -5
  37. package/packages/datadog-instrumentations/src/playwright.js +515 -292
  38. package/packages/datadog-instrumentations/src/router.js +76 -32
  39. package/packages/datadog-instrumentations/src/stripe.js +1 -1
  40. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
  41. package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
  42. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
  43. package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
  44. package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
  45. package/packages/datadog-plugin-bunyan/src/index.js +28 -0
  46. package/packages/datadog-plugin-cucumber/src/index.js +17 -3
  47. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +199 -28
  48. package/packages/datadog-plugin-cypress/src/support.js +69 -1
  49. package/packages/datadog-plugin-dns/src/lookup.js +8 -6
  50. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
  51. package/packages/datadog-plugin-graphql/src/execute.js +2 -0
  52. package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
  53. package/packages/datadog-plugin-http/src/server.js +40 -15
  54. package/packages/datadog-plugin-jest/src/index.js +11 -3
  55. package/packages/datadog-plugin-jest/src/util.js +15 -8
  56. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
  57. package/packages/datadog-plugin-kafkajs/src/producer.js +3 -0
  58. package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
  59. package/packages/datadog-plugin-mocha/src/index.js +19 -4
  60. package/packages/datadog-plugin-mongodb-core/src/index.js +281 -40
  61. package/packages/datadog-plugin-nats/src/consumer.js +43 -0
  62. package/packages/datadog-plugin-nats/src/index.js +20 -0
  63. package/packages/datadog-plugin-nats/src/producer.js +62 -0
  64. package/packages/datadog-plugin-nats/src/util.js +33 -0
  65. package/packages/datadog-plugin-next/src/index.js +5 -3
  66. package/packages/datadog-plugin-openai/src/tracing.js +15 -2
  67. package/packages/datadog-plugin-oracledb/src/index.js +13 -2
  68. package/packages/datadog-plugin-pino/src/index.js +42 -0
  69. package/packages/datadog-plugin-playwright/src/index.js +4 -4
  70. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
  71. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  72. package/packages/datadog-plugin-router/src/index.js +33 -44
  73. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  74. package/packages/datadog-plugin-vitest/src/index.js +5 -13
  75. package/packages/datadog-plugin-winston/src/index.js +30 -0
  76. package/packages/datadog-shimmer/src/shimmer.js +33 -40
  77. package/packages/dd-trace/src/aiguard/index.js +1 -1
  78. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  79. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  80. package/packages/dd-trace/src/appsec/index.js +1 -1
  81. package/packages/dd-trace/src/appsec/reporter.js +5 -6
  82. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  83. package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
  84. package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
  85. package/packages/dd-trace/src/baggage.js +7 -1
  86. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
  87. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
  88. package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
  89. package/packages/dd-trace/src/config/generated-config-types.d.ts +6 -2
  90. package/packages/dd-trace/src/config/supported-configurations.json +27 -8
  91. package/packages/dd-trace/src/datastreams/writer.js +2 -4
  92. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
  93. package/packages/dd-trace/src/encode/0.4.js +124 -108
  94. package/packages/dd-trace/src/encode/0.5.js +114 -26
  95. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +31 -23
  96. package/packages/dd-trace/src/encode/agentless-json.js +4 -2
  97. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
  98. package/packages/dd-trace/src/encode/span-stats.js +16 -16
  99. package/packages/dd-trace/src/encode/tags-processors.js +16 -0
  100. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  101. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  102. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  103. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
  104. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  105. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
  106. package/packages/dd-trace/src/llmobs/sdk.js +0 -16
  107. package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
  108. package/packages/dd-trace/src/llmobs/tagger.js +9 -1
  109. package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
  110. package/packages/dd-trace/src/llmobs/util.js +66 -3
  111. package/packages/dd-trace/src/log/index.js +1 -1
  112. package/packages/dd-trace/src/msgpack/chunk.js +394 -10
  113. package/packages/dd-trace/src/msgpack/index.js +96 -2
  114. package/packages/dd-trace/src/openfeature/encoding.js +70 -0
  115. package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
  116. package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
  117. package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
  118. package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
  119. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  120. package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
  121. package/packages/dd-trace/src/opentracing/propagation/text_map.js +62 -67
  122. package/packages/dd-trace/src/opentracing/span.js +59 -19
  123. package/packages/dd-trace/src/opentracing/span_context.js +49 -0
  124. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -20
  125. package/packages/dd-trace/src/plugins/database.js +7 -6
  126. package/packages/dd-trace/src/plugins/index.js +4 -0
  127. package/packages/dd-trace/src/plugins/log_injection.js +56 -0
  128. package/packages/dd-trace/src/plugins/log_plugin.js +3 -48
  129. package/packages/dd-trace/src/plugins/outbound.js +1 -1
  130. package/packages/dd-trace/src/plugins/plugin.js +15 -17
  131. package/packages/dd-trace/src/plugins/tracing.js +43 -5
  132. package/packages/dd-trace/src/plugins/util/test.js +236 -13
  133. package/packages/dd-trace/src/plugins/util/web.js +79 -65
  134. package/packages/dd-trace/src/priority_sampler.js +2 -2
  135. package/packages/dd-trace/src/profiling/profiler.js +2 -2
  136. package/packages/dd-trace/src/profiling/profilers/wall.js +10 -4
  137. package/packages/dd-trace/src/sampling_rule.js +7 -7
  138. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
  139. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  140. package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
  141. package/packages/dd-trace/src/span_format.js +190 -58
  142. package/packages/dd-trace/src/spanleak.js +1 -1
  143. package/packages/dd-trace/src/standalone/index.js +3 -3
  144. package/packages/dd-trace/src/tagger.js +0 -2
  145. package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
  146. package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
  147. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  148. package/vendor/dist/protobufjs/index.js +1 -1
  149. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  150. package/packages/dd-trace/src/msgpack/encoder.js +0 -308
  151. package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
@@ -109,7 +109,7 @@ function matcher (pattern, locator) {
109
109
  * @returns {Locator}
110
110
  */
111
111
  function makeTagLocator (tag) {
112
- return (span) => span.context()._tags[tag]
112
+ return (span) => span.context().getTag(tag)
113
113
  }
114
114
 
115
115
  /**
@@ -129,9 +129,9 @@ function nameLocator (span) {
129
129
  * @returns {string|undefined}
130
130
  */
131
131
  function serviceLocator (span) {
132
- const { _tags: tags } = span.context()
133
- return tags.service ||
134
- tags['service.name'] ||
132
+ const context = span.context()
133
+ return context.getTag('service') ||
134
+ context.getTag('service.name') ||
135
135
  span.tracer()._service
136
136
  }
137
137
 
@@ -142,9 +142,9 @@ function serviceLocator (span) {
142
142
  * @returns {string|undefined}
143
143
  */
144
144
  function resourceLocator (span) {
145
- const { _tags: tags } = span.context()
146
- return tags.resource ||
147
- tags['resource.name']
145
+ const context = span.context()
146
+ return context.getTag('resource') ||
147
+ context.getTag('resource.name')
148
148
  }
149
149
 
150
150
  /**
@@ -55,6 +55,11 @@ const messaging = {
55
55
  serviceName: ({ tracerService }) => `${tracerService}-kafka`,
56
56
  serviceSource: integrationSource('kafka'),
57
57
  },
58
+ nats: {
59
+ opName: () => 'nats.publish',
60
+ serviceName: ({ tracerService }) => `${tracerService}-nats`,
61
+ serviceSource: integrationSource('nats'),
62
+ },
58
63
  rhea: {
59
64
  opName: () => 'amqp.send',
60
65
  serviceName: ({ tracerService }) => `${tracerService}-amqp-producer`,
@@ -119,6 +124,11 @@ const messaging = {
119
124
  serviceName: ({ tracerService }) => `${tracerService}-kafka`,
120
125
  serviceSource: integrationSource('kafka'),
121
126
  },
127
+ nats: {
128
+ opName: () => 'nats.consume',
129
+ serviceName: ({ tracerService }) => `${tracerService}-nats`,
130
+ serviceSource: integrationSource('nats'),
131
+ },
122
132
  rhea: {
123
133
  opName: () => 'amqp.receive',
124
134
  serviceName: identityService,
@@ -44,6 +44,10 @@ const messaging = {
44
44
  opName: () => 'kafka.send',
45
45
  serviceName: identityService,
46
46
  },
47
+ nats: {
48
+ opName: () => 'nats.send',
49
+ serviceName: identityService,
50
+ },
47
51
  rhea: amqpOutbound,
48
52
  sqs: {
49
53
  opName: () => 'aws.sqs.send',
@@ -89,6 +93,10 @@ const messaging = {
89
93
  opName: () => 'kafka.process',
90
94
  serviceName: identityService,
91
95
  },
96
+ nats: {
97
+ opName: () => 'nats.process',
98
+ serviceName: identityService,
99
+ },
92
100
  rhea: amqpInbound,
93
101
  sqs: {
94
102
  opName: () => 'aws.sqs.process',
@@ -0,0 +1,46 @@
1
+ 'use strict'
2
+
3
+ const { SVC_SRC_KEY } = require('../constants')
4
+
5
+ const INTEGRATION_SERVICE = Symbol('dd.integrationService')
6
+ const MANUAL = 'm'
7
+
8
+ /**
9
+ * Reconcile `_dd.svc_src` against the span's final `service.name`. Called from
10
+ * `Span#finish` once all writes are in.
11
+ *
12
+ * Rules:
13
+ * - service.name equals the tracer default → clear any svc_src
14
+ * - integration marker exists and equals current service.name → integration
15
+ * owns the value; leave the source label the integration set
16
+ * - otherwise → user wrote (no marker) or overrode the integration value;
17
+ * stamp 'm'
18
+ *
19
+ * @param {object} span Internal DatadogSpan instance.
20
+ * @param {string|undefined} tracerService The tracer's configured default service.
21
+ */
22
+ function resolveServiceSource (span, tracerService) {
23
+ const spanContext = span._spanContext
24
+ const currentService = spanContext.getTag('service.name')
25
+ const existingSource = spanContext.getTag(SVC_SRC_KEY)
26
+
27
+ if (currentService === tracerService) {
28
+ if (existingSource === undefined) return
29
+ spanContext.deleteTag(SVC_SRC_KEY)
30
+ return
31
+ }
32
+
33
+ const marker = span[INTEGRATION_SERVICE]
34
+
35
+ if (marker === currentService) {
36
+ return
37
+ }
38
+
39
+ spanContext.setTag(SVC_SRC_KEY, MANUAL)
40
+ }
41
+
42
+ module.exports = {
43
+ INTEGRATION_SERVICE,
44
+ MANUAL,
45
+ resolveServiceSource,
46
+ }
@@ -30,14 +30,6 @@ const ERROR_STACK = constants.ERROR_STACK
30
30
  const ERROR_TYPE = constants.ERROR_TYPE
31
31
  const { IGNORE_OTEL_ERROR } = constants
32
32
 
33
- // TODO(BridgeAR)[31.03.2025]: Should these land in the constants file?
34
- const map = {
35
- 'operation.name': 'name',
36
- 'service.name': 'service',
37
- 'span.type': 'type',
38
- 'resource.name': 'resource',
39
- }
40
-
41
33
  /**
42
34
  * @typedef {object} FormattedSpan
43
35
  * @property {import('./id').Identifier} trace_id
@@ -45,6 +37,8 @@ const map = {
45
37
  * @property {import('./id').Identifier} parent_id
46
38
  * @property {string} name
47
39
  * @property {string} resource
40
+ * @property {string | undefined} service
41
+ * @property {string | undefined} type
48
42
  * @property {number} error
49
43
  * @property {Record<string, string>} meta
50
44
  * @property {Record<string, number>} metrics
@@ -52,7 +46,12 @@ const map = {
52
46
  * @property {number} start
53
47
  * @property {number} duration
54
48
  * @property {Array} links
55
- * @property {Array<{ name: string, time_unix_nano: number, attributes?: Record<string, string> }>} [span_events]
49
+ * @property {Array<SpanEvent> | undefined} span_events
50
+ *
51
+ * @typedef {object} SpanEvent
52
+ * @property {string} name
53
+ * @property {number} time_unix_nano
54
+ * @property {Record<string, string>} [attributes]
56
55
  */
57
56
 
58
57
  function format (span, isFirstSpanInChunk = false, tagForFirstSpanInChunk = false) {
@@ -61,7 +60,9 @@ function format (span, isFirstSpanInChunk = false, tagForFirstSpanInChunk = fals
61
60
  extractSpanLinks(formatted, span)
62
61
  extractSpanEvents(formatted, span)
63
62
  extractRootTags(formatted, span)
64
- extractChunkTags(formatted, span, isFirstSpanInChunk, tagForFirstSpanInChunk)
63
+ if (isFirstSpanInChunk) {
64
+ extractChunkTags(formatted, span, tagForFirstSpanInChunk)
65
+ }
65
66
  extractTags(formatted, span)
66
67
 
67
68
  return formatted
@@ -69,13 +70,18 @@ function format (span, isFirstSpanInChunk = false, tagForFirstSpanInChunk = fals
69
70
 
70
71
  function formatSpan (span) {
71
72
  const spanContext = span.context()
72
-
73
+ // Pre-initialise the `service`, `type`, and `span_events` slots so every
74
+ // formatted span shares one V8 hidden class regardless of which optional
75
+ // tags fire later. Downstream encoders gate on truthy values for each,
76
+ // so `undefined` stays byte-identical on the msgpack wire.
73
77
  return {
74
78
  trace_id: spanContext._traceId,
75
79
  span_id: spanContext._spanId,
76
80
  parent_id: spanContext._parentId || id('0'),
77
81
  name: String(spanContext._name),
78
82
  resource: String(spanContext._name),
83
+ service: undefined,
84
+ type: undefined,
79
85
  error: 0,
80
86
  meta: {},
81
87
  meta_struct: span.meta_struct,
@@ -83,14 +89,22 @@ function formatSpan (span) {
83
89
  start: Math.round(span._startTime * 1e6),
84
90
  duration: Math.round(span._duration * 1e6),
85
91
  links: [],
92
+ span_events: undefined,
86
93
  }
87
94
  }
88
95
 
89
- function setSingleSpanIngestionTags (span, options) {
96
+ function setSingleSpanIngestionTags (formattedSpan, options) {
90
97
  if (!options) return
91
- addTag({}, span.metrics, SPAN_SAMPLING_MECHANISM, SAMPLING_MECHANISM_SPAN)
92
- addTag({}, span.metrics, SPAN_SAMPLING_RULE_RATE, options.sampleRate)
93
- addTag({}, span.metrics, SPAN_SAMPLING_MAX_PER_SECOND, options.maxPerSecond)
98
+ const metrics = formattedSpan.metrics
99
+ metrics[SPAN_SAMPLING_MECHANISM] = SAMPLING_MECHANISM_SPAN
100
+ const sampleRate = options.sampleRate
101
+ if (typeof sampleRate === 'number') {
102
+ metrics[SPAN_SAMPLING_RULE_RATE] = sampleRate
103
+ }
104
+ const maxPerSecond = options.maxPerSecond
105
+ if (typeof maxPerSecond === 'number') {
106
+ metrics[SPAN_SAMPLING_MAX_PER_SECOND] = maxPerSecond
107
+ }
94
108
  }
95
109
 
96
110
  /**
@@ -144,12 +158,14 @@ function extractTags (formattedSpan, span) {
144
158
  const origin = context._trace.origin
145
159
  // TODO(BridgeAR)[31.03.2025]: Look into changing the way we store tags. Using
146
160
  // a map is likely faster short term.
147
- const tags = context._tags
161
+ const tags = context.getTags()
148
162
  const hostname = context._hostname
149
163
  const priority = context._sampling.priority
164
+ const meta = formattedSpan.meta
165
+ const metrics = formattedSpan.metrics
150
166
 
151
167
  if (tags['span.kind'] && tags['span.kind'] !== 'internal') {
152
- addTag({}, formattedSpan.metrics, MEASURED, 1)
168
+ metrics[MEASURED] = 1
153
169
  }
154
170
 
155
171
  const tracerService = span.tracer()._service.toLowerCase()
@@ -159,27 +175,51 @@ function extractTags (formattedSpan, span) {
159
175
  registerExtraService(tags['service.name'])
160
176
  }
161
177
 
162
- for (const [tag, value] of Object.entries(tags)) {
163
- // TODO(BridgeAR)[31.03.2025]: Check how many tags are defined in average.
164
- // In case there are more than 2 tags in average, check for all special
165
- // cases up front and loop over the tags afterwards, skipping the already
166
- // visited property names by checking a map with these keys.
178
+ for (const tag of Object.keys(tags)) {
179
+ const value = tags[tag]
180
+ // The typed-helper bodies are inlined per case: V8 was not inlining
181
+ // `addStringTag` / `addNumberTag` / `addMixedTag` here at the call rate
182
+ // this loop runs in HTTP-server traces (10+ tags × 1M spans/sec), so each
183
+ // one paid an extra call frame the helper body was small enough to
184
+ // expand inline.
167
185
  switch (tag) {
168
186
  case 'service.name':
187
+ if (typeof value === 'string') {
188
+ formattedSpan.service = value.length > MAX_META_VALUE_LENGTH
189
+ ? `${value.slice(0, MAX_META_VALUE_LENGTH)}...`
190
+ : value
191
+ }
192
+ break
169
193
  case 'span.type':
194
+ if (typeof value === 'string') {
195
+ formattedSpan.type = value.length > MAX_META_VALUE_LENGTH
196
+ ? `${value.slice(0, MAX_META_VALUE_LENGTH)}...`
197
+ : value
198
+ }
199
+ break
170
200
  case 'resource.name':
171
- addTag(formattedSpan, {}, map[tag], value)
201
+ if (typeof value === 'string') {
202
+ formattedSpan.resource = value.length > MAX_META_VALUE_LENGTH
203
+ ? `${value.slice(0, MAX_META_VALUE_LENGTH)}...`
204
+ : value
205
+ }
172
206
  break
173
207
  // HACK: remove when Datadog supports numeric status code
174
- case 'http.status_code':
175
- addTag(formattedSpan.meta, {}, tag, value && String(value))
208
+ case 'http.status_code': {
209
+ const stringValue = value && String(value)
210
+ if (typeof stringValue === 'string') {
211
+ meta[tag] = stringValue.length > MAX_META_VALUE_LENGTH
212
+ ? `${stringValue.slice(0, MAX_META_VALUE_LENGTH)}...`
213
+ : stringValue
214
+ }
176
215
  break
216
+ }
177
217
  case 'analytics.event':
178
- addTag({}, formattedSpan.metrics, ANALYTICS, value === undefined || value ? 1 : 0)
218
+ metrics[ANALYTICS] = value === undefined || value ? 1 : 0
179
219
  break
180
220
  case HOSTNAME_KEY:
181
221
  case MEASURED:
182
- addTag({}, formattedSpan.metrics, tag, value === undefined || value ? 1 : 0)
222
+ metrics[tag] = value === undefined || value ? 1 : 0
183
223
  break
184
224
  // TODO(BridgeAR)[31.03.2025]: How come we use two different ways to pass
185
225
  // through errors? Can we just unify the behavior to always use one way?
@@ -190,52 +230,115 @@ function extractTags (formattedSpan, span) {
190
230
  break
191
231
  case ERROR_TYPE:
192
232
  case ERROR_MESSAGE:
193
- case ERROR_STACK:
233
+ case ERROR_STACK: {
194
234
  // HACK: remove when implemented in the backend
195
- if (context._name === 'fs.operation') {
196
- break
197
- }
235
+ if (context._name === 'fs.operation') break
198
236
  // otel.recordException should not influence trace.error
199
237
  if (!tags[IGNORE_OTEL_ERROR]) {
200
238
  formattedSpan.error = 1
201
239
  }
202
- default: // eslint-disable-line no-fallthrough
203
- addTag(formattedSpan.meta, formattedSpan.metrics, tag, value)
240
+ if (value != null) writeErrorMeta(meta, tag, value)
241
+ break
242
+ }
243
+ default: {
244
+ const valueType = typeof value
245
+ if (valueType === 'string') {
246
+ let writeKey = tag
247
+ if (writeKey.length > MAX_META_KEY_LENGTH) {
248
+ writeKey = `${writeKey.slice(0, MAX_META_KEY_LENGTH)}...`
249
+ }
250
+ meta[writeKey] = value.length > MAX_META_VALUE_LENGTH
251
+ ? `${value.slice(0, MAX_META_VALUE_LENGTH)}...`
252
+ : value
253
+ } else if (valueType === 'number') {
254
+ if (!Number.isNaN(value)) {
255
+ let writeKey = tag
256
+ if (writeKey.length > MAX_METRIC_KEY_LENGTH) {
257
+ writeKey = `${writeKey.slice(0, MAX_METRIC_KEY_LENGTH)}...`
258
+ }
259
+ metrics[writeKey] = value
260
+ }
261
+ } else if (valueType === 'boolean') {
262
+ let writeKey = tag
263
+ if (writeKey.length > MAX_METRIC_KEY_LENGTH) {
264
+ writeKey = `${writeKey.slice(0, MAX_METRIC_KEY_LENGTH)}...`
265
+ }
266
+ metrics[writeKey] = value ? 1 : 0
267
+ } else {
268
+ addMixedTag(meta, metrics, tag, value)
269
+ }
270
+ }
204
271
  }
205
272
  }
206
273
  setSingleSpanIngestionTags(formattedSpan, context._spanSampling)
207
274
 
208
- addTag(formattedSpan.meta, formattedSpan.metrics, 'language', 'javascript')
209
- addTag(formattedSpan.meta, formattedSpan.metrics, PROCESS_ID, process.pid)
210
- addTag(formattedSpan.meta, formattedSpan.metrics, SAMPLING_PRIORITY_KEY, priority)
211
- addTag(formattedSpan.meta, formattedSpan.metrics, ORIGIN_KEY, origin)
212
- addTag(formattedSpan.meta, formattedSpan.metrics, HOSTNAME_KEY, hostname)
275
+ meta.language = 'javascript'
276
+ metrics[PROCESS_ID] = process.pid
277
+ if (typeof priority === 'number') {
278
+ metrics[SAMPLING_PRIORITY_KEY] = priority
279
+ }
280
+ if (typeof origin === 'string') {
281
+ meta[ORIGIN_KEY] = origin.length > MAX_META_VALUE_LENGTH
282
+ ? `${origin.slice(0, MAX_META_VALUE_LENGTH)}...`
283
+ : origin
284
+ }
285
+ if (typeof hostname === 'string') {
286
+ meta[HOSTNAME_KEY] = hostname.length > MAX_META_VALUE_LENGTH
287
+ ? `${hostname.slice(0, MAX_META_VALUE_LENGTH)}...`
288
+ : hostname
289
+ }
213
290
  }
214
291
 
215
292
  function extractRootTags (formattedSpan, span) {
216
293
  const context = span.context()
217
- const isLocalRoot = span === context._trace.started[0]
218
294
  const parentId = context._parentId
219
295
 
220
- if (!isLocalRoot || (parentId && parentId.toString(10) !== '0')) return
296
+ if (span !== context._trace.started[0] || (parentId && parentId.toString(10) !== '0')) return
221
297
 
222
- addTag({}, formattedSpan.metrics, SAMPLING_RULE_DECISION, context._trace[SAMPLING_RULE_DECISION])
223
- addTag({}, formattedSpan.metrics, SAMPLING_LIMIT_DECISION, context._trace[SAMPLING_LIMIT_DECISION])
224
- addTag({}, formattedSpan.metrics, SAMPLING_AGENT_DECISION, context._trace[SAMPLING_AGENT_DECISION])
225
- addTag({}, formattedSpan.metrics, TOP_LEVEL_KEY, 1)
298
+ const trace = context._trace
299
+ const metrics = formattedSpan.metrics
300
+ const ruleDecision = trace[SAMPLING_RULE_DECISION]
301
+ if (typeof ruleDecision === 'number') {
302
+ metrics[SAMPLING_RULE_DECISION] = ruleDecision
303
+ }
304
+ const limitDecision = trace[SAMPLING_LIMIT_DECISION]
305
+ if (typeof limitDecision === 'number') {
306
+ metrics[SAMPLING_LIMIT_DECISION] = limitDecision
307
+ }
308
+ const agentDecision = trace[SAMPLING_AGENT_DECISION]
309
+ if (typeof agentDecision === 'number') {
310
+ metrics[SAMPLING_AGENT_DECISION] = agentDecision
311
+ }
312
+ metrics[TOP_LEVEL_KEY] = 1
226
313
  }
227
314
 
228
- function extractChunkTags (formattedSpan, span, isFirstSpanInChunk, tagForFirstSpanInChunk) {
229
- const context = span.context()
230
-
231
- if (!isFirstSpanInChunk) return
232
-
233
- if (tagForFirstSpanInChunk) {
234
- addTag(formattedSpan.meta, formattedSpan.metrics, TRACING_FIELD_NAME, tagForFirstSpanInChunk)
315
+ function extractChunkTags (formattedSpan, span, tagForFirstSpanInChunk) {
316
+ const meta = formattedSpan.meta
317
+ if (typeof tagForFirstSpanInChunk === 'string') {
318
+ meta[TRACING_FIELD_NAME] = tagForFirstSpanInChunk.length > MAX_META_VALUE_LENGTH
319
+ ? `${tagForFirstSpanInChunk.slice(0, MAX_META_VALUE_LENGTH)}...`
320
+ : tagForFirstSpanInChunk
235
321
  }
236
322
 
237
- for (const [key, value] of Object.entries(context._trace.tags)) {
238
- addTag(formattedSpan.meta, formattedSpan.metrics, key, value)
323
+ // Chunk tags are always strings in production (`_dd.p.dm`, `_dd.p.tid`,
324
+ // `_dd.p.ts`, `baggage.*`). Inline only the string branch; non-string
325
+ // values fall through to `addMixedTag` so we don't carry duplicate
326
+ // truncation logic for branches no real chunk tag ever takes.
327
+ const metrics = formattedSpan.metrics
328
+ const traceTags = span.context()._trace.tags
329
+ for (const key of Object.keys(traceTags)) {
330
+ const value = traceTags[key]
331
+ if (typeof value === 'string') {
332
+ let writeKey = key
333
+ if (writeKey.length > MAX_META_KEY_LENGTH) {
334
+ writeKey = `${writeKey.slice(0, MAX_META_KEY_LENGTH)}...`
335
+ }
336
+ meta[writeKey] = value.length > MAX_META_VALUE_LENGTH
337
+ ? `${value.slice(0, MAX_META_VALUE_LENGTH)}...`
338
+ : value
339
+ } else {
340
+ addMixedTag(meta, metrics, key, value)
341
+ }
239
342
  }
240
343
  }
241
344
 
@@ -248,13 +351,42 @@ function extractError (formattedSpan, error) {
248
351
  // AggregateError only has a code and no message.
249
352
  // TODO(BridgeAR)[31.03.2025]: An AggregateError can have a message. Should
250
353
  // the code just generally be added, if available?
251
- addTag(formattedSpan.meta, formattedSpan.metrics, ERROR_MESSAGE, error.message || error.code)
252
- addTag(formattedSpan.meta, formattedSpan.metrics, ERROR_TYPE, error.name)
253
- addTag(formattedSpan.meta, formattedSpan.metrics, ERROR_STACK, error.stack)
354
+ const meta = formattedSpan.meta
355
+ const message = error.message || error.code
356
+ if (message != null) writeErrorMeta(meta, ERROR_MESSAGE, message)
357
+ if (error.name != null) writeErrorMeta(meta, ERROR_TYPE, error.name)
358
+ if (error.stack != null) writeErrorMeta(meta, ERROR_STACK, error.stack)
254
359
  }
255
360
  }
256
361
 
257
- function addTag (meta, metrics, key, value, nested) {
362
+ /**
363
+ * Coerces `value` to string and truncates at `MAX_META_VALUE_LENGTH` before
364
+ * writing it to one of the three error meta fields.
365
+ *
366
+ * @param {Record<string, string>} meta
367
+ * @param {string} key
368
+ * @param {unknown} value
369
+ */
370
+ function writeErrorMeta (meta, key, value) {
371
+ const stringValue = typeof value === 'string' ? value : String(value)
372
+ meta[key] = stringValue.length > MAX_META_VALUE_LENGTH
373
+ ? `${stringValue.slice(0, MAX_META_VALUE_LENGTH)}...`
374
+ : stringValue
375
+ }
376
+
377
+ /**
378
+ * Mixed-type dispatch retained for `extractError` and the slow-path fallback
379
+ * inside the inlined per-tag loops in `extractTags` / `extractChunkTags`.
380
+ * The scalar branches are kept here so a single `addMixedTag` call covers
381
+ * recursion (nested object values) without re-entering the inlined paths.
382
+ *
383
+ * @param {Record<string, string>} meta
384
+ * @param {Record<string, number>} metrics
385
+ * @param {string} key
386
+ * @param {unknown} value
387
+ * @param {boolean} [nested]
388
+ */
389
+ function addMixedTag (meta, metrics, key, value, nested) {
258
390
  switch (typeof value) {
259
391
  case 'string':
260
392
  if (key.length > MAX_META_KEY_LENGTH) {
@@ -290,7 +422,7 @@ function addTag (meta, metrics, key, value, nested) {
290
422
  metrics[key] = value.toString()
291
423
  } else if (!Array.isArray(value) && !nested) {
292
424
  for (const [prop, val] of Object.entries(value)) {
293
- addTag(meta, metrics, `${key}.${prop}`, val, true)
425
+ addMixedTag(meta, metrics, `${key}.${prop}`, val, true)
294
426
  }
295
427
  }
296
428
  }
@@ -66,7 +66,7 @@ module.exports.startScrubber = function () {
66
66
  if (!gc) continue // everything after this point is related to manual GC
67
67
 
68
68
  // TODO: what else can we do to alleviate memory usage
69
- span.context()._tags = Object.create(null)
69
+ span.context().clearTags()
70
70
  }
71
71
 
72
72
  console.log('expired spans:' +
@@ -29,12 +29,12 @@ function configure (config) {
29
29
  }
30
30
 
31
31
  function onSpanStart ({ span, fields }) {
32
- const tags = span.context?.()?._tags
33
- if (!tags) return
32
+ const context = span.context?.()
33
+ if (!context) return
34
34
 
35
35
  const { parent } = fields
36
36
  if (!parent || parent._isRemote) {
37
- tags[APM_TRACING_ENABLED_KEY] = 0
37
+ context.setTag(APM_TRACING_ENABLED_KEY, 0)
38
38
  }
39
39
  }
40
40
 
@@ -9,8 +9,6 @@ function addNonEmpty (carrier, key, value) {
9
9
  }
10
10
 
11
11
  function add (carrier, keyValuePairs, valueSeparator = ':') {
12
- if (!carrier) return
13
-
14
12
  if (typeof keyValuePairs === 'string') {
15
13
  let valueStart = 0
16
14
  let keyStart = 0