dd-trace 5.106.0 → 5.107.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 (96) hide show
  1. package/index.d.ts +20 -1
  2. package/package.json +5 -7
  3. package/packages/datadog-core/src/storage.js +47 -48
  4. package/packages/datadog-esbuild/index.js +6 -1
  5. package/packages/datadog-instrumentations/src/ai.js +12 -3
  6. package/packages/datadog-instrumentations/src/body-parser.js +5 -2
  7. package/packages/datadog-instrumentations/src/connect.js +3 -2
  8. package/packages/datadog-instrumentations/src/cookie-parser.js +3 -2
  9. package/packages/datadog-instrumentations/src/cucumber.js +7 -0
  10. package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +7 -5
  11. package/packages/datadog-instrumentations/src/express-session.js +12 -11
  12. package/packages/datadog-instrumentations/src/express.js +24 -20
  13. package/packages/datadog-instrumentations/src/fastify.js +18 -6
  14. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +27 -12
  15. package/packages/datadog-instrumentations/src/http/client.js +9 -12
  16. package/packages/datadog-instrumentations/src/http/server.js +30 -16
  17. package/packages/datadog-instrumentations/src/http2/client.js +15 -12
  18. package/packages/datadog-instrumentations/src/http2/server.js +15 -8
  19. package/packages/datadog-instrumentations/src/jest/bail-reporter.js +42 -0
  20. package/packages/datadog-instrumentations/src/jest.js +143 -73
  21. package/packages/datadog-instrumentations/src/mocha/main.js +43 -8
  22. package/packages/datadog-instrumentations/src/mocha/utils.js +128 -17
  23. package/packages/datadog-instrumentations/src/multer.js +3 -2
  24. package/packages/datadog-instrumentations/src/mysql2.js +34 -0
  25. package/packages/datadog-instrumentations/src/net.js +8 -6
  26. package/packages/datadog-instrumentations/src/openai.js +19 -7
  27. package/packages/datadog-instrumentations/src/pg.js +19 -0
  28. package/packages/datadog-instrumentations/src/router.js +12 -10
  29. package/packages/datadog-instrumentations/src/vitest.js +29 -4
  30. package/packages/datadog-plugin-aws-sdk/src/base.js +0 -3
  31. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +62 -11
  32. package/packages/datadog-plugin-cucumber/src/index.js +2 -0
  33. package/packages/datadog-plugin-cypress/src/support.js +31 -1
  34. package/packages/datadog-plugin-http/src/client.js +0 -3
  35. package/packages/datadog-plugin-http/src/server.js +11 -1
  36. package/packages/datadog-plugin-mocha/src/index.js +2 -0
  37. package/packages/datadog-plugin-pg/src/index.js +10 -0
  38. package/packages/dd-trace/src/aiguard/index.js +34 -15
  39. package/packages/dd-trace/src/aiguard/sdk.js +34 -3
  40. package/packages/dd-trace/src/aiguard/tags.js +6 -0
  41. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
  42. package/packages/dd-trace/src/config/defaults.js +14 -0
  43. package/packages/dd-trace/src/config/generated-config-types.d.ts +1 -1
  44. package/packages/dd-trace/src/config/helper.js +1 -0
  45. package/packages/dd-trace/src/config/index.js +5 -9
  46. package/packages/dd-trace/src/config/parsers.js +8 -0
  47. package/packages/dd-trace/src/config/supported-configurations.json +13 -6
  48. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
  49. package/packages/dd-trace/src/datastreams/writer.js +1 -2
  50. package/packages/dd-trace/src/debugger/config.js +1 -1
  51. package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -2
  52. package/packages/dd-trace/src/debugger/index.js +1 -2
  53. package/packages/dd-trace/src/dogstatsd.js +2 -3
  54. package/packages/dd-trace/src/encode/0.4.js +49 -41
  55. package/packages/dd-trace/src/encode/agentless-json.js +5 -1
  56. package/packages/dd-trace/src/encode/tags-processors.js +14 -0
  57. package/packages/dd-trace/src/exporters/agent/index.js +1 -2
  58. package/packages/dd-trace/src/exporters/agentless/index.js +6 -10
  59. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +1 -2
  60. package/packages/dd-trace/src/exporters/common/request.js +26 -0
  61. package/packages/dd-trace/src/exporters/span-stats/index.js +1 -2
  62. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +4 -0
  63. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +45 -0
  64. package/packages/dd-trace/src/llmobs/sdk.js +4 -1
  65. package/packages/dd-trace/src/llmobs/span_processor.js +17 -1
  66. package/packages/dd-trace/src/llmobs/tagger.js +5 -3
  67. package/packages/dd-trace/src/llmobs/util.js +54 -0
  68. package/packages/dd-trace/src/llmobs/writers/base.js +1 -2
  69. package/packages/dd-trace/src/llmobs/writers/util.js +1 -2
  70. package/packages/dd-trace/src/openfeature/writers/base.js +1 -10
  71. package/packages/dd-trace/src/openfeature/writers/util.js +1 -2
  72. package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +26 -13
  73. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +7 -10
  74. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +92 -0
  75. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +3 -2
  76. package/packages/dd-trace/src/opentracing/span.js +23 -18
  77. package/packages/dd-trace/src/opentracing/tracer.js +16 -12
  78. package/packages/dd-trace/src/plugins/ci_plugin.js +131 -46
  79. package/packages/dd-trace/src/priority_sampler.js +6 -5
  80. package/packages/dd-trace/src/profiling/config.js +1 -2
  81. package/packages/dd-trace/src/proxy.js +13 -10
  82. package/packages/dd-trace/src/remote_config/index.js +1 -2
  83. package/packages/dd-trace/src/runtime_metrics/client.js +30 -0
  84. package/packages/dd-trace/src/runtime_metrics/index.js +12 -2
  85. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +284 -0
  86. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +2 -11
  87. package/packages/dd-trace/src/service-naming/source-resolver.js +5 -1
  88. package/packages/dd-trace/src/span_format.js +33 -25
  89. package/packages/dd-trace/src/span_stats.js +1 -1
  90. package/packages/dd-trace/src/startup-log.js +1 -2
  91. package/packages/dd-trace/src/telemetry/send-data.js +1 -1
  92. package/packages/dd-trace/src/tracer.js +1 -1
  93. package/vendor/dist/@apm-js-collab/code-transformer/index.js +2 -2
  94. package/vendor/dist/shell-quote/index.js +1 -1
  95. package/packages/dd-trace/src/agent/url.js +0 -28
  96. package/scripts/preinstall.js +0 -34
@@ -0,0 +1,284 @@
1
+ 'use strict'
2
+
3
+ const v8 = require('node:v8')
4
+ const process = require('node:process')
5
+ const { performance, monitorEventLoopDelay, PerformanceObserver, constants } = require('node:perf_hooks')
6
+ const { metrics } = require('@opentelemetry/api')
7
+ const log = require('../log')
8
+ const { createMetricsClient } = require('./client')
9
+
10
+ const METER_NAME = 'datadog.runtime_metrics'
11
+
12
+ const ATTR_ELU_STATE_IDLE = { 'nodejs.eventloop.state': 'idle' }
13
+ const ATTR_ELU_STATE_ACTIVE = { 'nodejs.eventloop.state': 'active' }
14
+
15
+ // Pre-allocated `{ 'v8js.gc.type': <type> }` attribute objects so the observer
16
+ // doesn't allocate a new one per entry under GC pressure.
17
+ const ATTR_GC_TYPE_MINOR = { 'v8js.gc.type': 'minor' }
18
+ const ATTR_GC_TYPE_MAJOR = { 'v8js.gc.type': 'major' }
19
+ const ATTR_GC_TYPE_INCREMENTAL = { 'v8js.gc.type': 'incremental' }
20
+ const ATTR_GC_TYPE_WEAKCB = { 'v8js.gc.type': 'weakcb' }
21
+
22
+ // Kind 2 is V8's MinorMarkSweep (Node 20+) and not exposed via perf_hooks.constants.
23
+ const GC_ATTR_BY_KIND = new Map([
24
+ [constants.NODE_PERFORMANCE_GC_MINOR, ATTR_GC_TYPE_MINOR],
25
+ [2, ATTR_GC_TYPE_MINOR],
26
+ [constants.NODE_PERFORMANCE_GC_MAJOR, ATTR_GC_TYPE_MAJOR],
27
+ [constants.NODE_PERFORMANCE_GC_INCREMENTAL, ATTR_GC_TYPE_INCREMENTAL],
28
+ [constants.NODE_PERFORMANCE_GC_WEAKCB, ATTR_GC_TYPE_WEAKCB],
29
+ ])
30
+
31
+ let meter = null
32
+ let eventLoopHistogram = null
33
+ let gcObserver = null
34
+ let lastElu = null
35
+
36
+ // Cache `{ 'v8js.heap.space.name': <name> }` per V8 space name to avoid per-scrape allocations.
37
+ const HEAP_SPACE_ATTR_CACHE = new Map()
38
+ function getHeapSpaceAttr (name) {
39
+ let attr = HEAP_SPACE_ATTR_CACHE.get(name)
40
+ if (!attr) {
41
+ attr = { 'v8js.heap.space.name': name }
42
+ HEAP_SPACE_ATTR_CACHE.set(name, attr)
43
+ }
44
+ return attr
45
+ }
46
+
47
+ // getMeter() returns a cached meter, so without tracking what we registered we'd
48
+ // stack callbacks every time start() runs.
49
+ const registeredCallbacks = []
50
+ const registeredBatchCallbacks = []
51
+
52
+ // DD-proprietary tracer metrics (runtime.node.spans.*, datadog.tracer.*) have no OTel
53
+ // equivalent; keep a DogStatsD client so OTLP-path customers don't lose them.
54
+ let client = null
55
+ let flushInterval = null
56
+
57
+ module.exports = {
58
+ /**
59
+ * @param {import('../config/config-base')} config - Tracer configuration
60
+ */
61
+ start (config) {
62
+ this.stop()
63
+
64
+ client = createMetricsClient(config)
65
+ flushInterval = setInterval(() => {
66
+ client.flush()
67
+ }, config.DD_RUNTIME_METRICS_FLUSH_INTERVAL ?? 10_000)
68
+ flushInterval.unref?.()
69
+
70
+ meter = metrics.getMeterProvider().getMeter(METER_NAME)
71
+
72
+ const trackEventLoop = config.runtimeMetrics.eventLoop !== false
73
+ const trackGc = config.runtimeMetrics.gc !== false
74
+ if (trackEventLoop) {
75
+ eventLoopHistogram = monitorEventLoopDelay({ resolution: 4 })
76
+ eventLoopHistogram.enable()
77
+ }
78
+
79
+ const heapUsed = createHeapInstrument('v8js.memory.heap.used', 'V8 heap memory used.')
80
+ const heapLimit = createHeapInstrument('v8js.memory.heap.limit', 'V8 heap memory total available size.')
81
+ const heapSpaceAvailable = createHeapInstrument(
82
+ 'v8js.memory.heap.space.available_size', 'V8 heap space available size.')
83
+ const heapSpacePhysical = createHeapInstrument(
84
+ 'v8js.memory.heap.space.physical_size', 'V8 heap space physical size.')
85
+ const heapSpaceSize = createHeapInstrument(
86
+ 'v8js.memory.heap.space.size', 'Total heap memory size pre-allocated for a heap space.')
87
+
88
+ registerBatchCallback(
89
+ (result) => {
90
+ const stats = v8.getHeapStatistics()
91
+ result.observe(heapLimit, stats.heap_size_limit)
92
+
93
+ const spaces = v8.getHeapSpaceStatistics()
94
+ for (let i = 0; i < spaces.length; i++) {
95
+ const space = spaces[i]
96
+ const attr = getHeapSpaceAttr(space.space_name)
97
+ result.observe(heapUsed, space.space_used_size, attr)
98
+ result.observe(heapSpaceAvailable, space.space_available_size, attr)
99
+ result.observe(heapSpacePhysical, space.physical_space_size, attr)
100
+ result.observe(heapSpaceSize, space.space_size, attr)
101
+ }
102
+ },
103
+ [heapUsed, heapLimit, heapSpaceAvailable, heapSpacePhysical, heapSpaceSize]
104
+ )
105
+
106
+ const activeResource = meter.createObservableGauge('v8js.resource.active', {
107
+ unit: '{resource}',
108
+ description: 'Gauge of the active resources that are currently keeping the event loop alive.',
109
+ })
110
+ registerCallback((result) => {
111
+ const counts = new Map()
112
+ // Stable since Node 22.16; available on 18+ as experimental.
113
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
114
+ for (const resource of process.getActiveResourcesInfo()) {
115
+ counts.set(resource, (counts.get(resource) ?? 0) + 1)
116
+ }
117
+ for (const [type, count] of counts) {
118
+ result.observe(count, { 'v8js.resource.type': type })
119
+ }
120
+ }, activeResource)
121
+
122
+ // Spec wants nodejs.eventloop.delay.* in seconds; perf_hooks gives nanoseconds.
123
+ // Match @opentelemetry/instrumentation-runtime-node EventLoopDelayCollector: one batch
124
+ // callback, guard on sample count, emit, then reset so each interval is independent.
125
+ if (trackEventLoop) {
126
+ const delayMin = createDelayGauge('nodejs.eventloop.delay.min', 'Event loop minimum delay.')
127
+ const delayMax = createDelayGauge('nodejs.eventloop.delay.max', 'Event loop maximum delay.')
128
+ const delayMean = createDelayGauge('nodejs.eventloop.delay.mean', 'Event loop mean delay.')
129
+ const delayStddev = createDelayGauge('nodejs.eventloop.delay.stddev', 'Event loop standard deviation delay.')
130
+ const delayP50 = createDelayGauge('nodejs.eventloop.delay.p50', 'Event loop 50th percentile delay.')
131
+ const delayP90 = createDelayGauge('nodejs.eventloop.delay.p90', 'Event loop 90th percentile delay.')
132
+ const delayP99 = createDelayGauge('nodejs.eventloop.delay.p99', 'Event loop 99th percentile delay.')
133
+
134
+ registerBatchCallback((result) => {
135
+ const h = eventLoopHistogram
136
+ if (!h || h.count < 5) return
137
+ result.observe(delayMin, h.min / 1e9)
138
+ result.observe(delayMax, h.max / 1e9)
139
+ result.observe(delayMean, h.mean / 1e9)
140
+ result.observe(delayStddev, h.stddev / 1e9)
141
+ result.observe(delayP50, h.percentile(50) / 1e9)
142
+ result.observe(delayP90, h.percentile(90) / 1e9)
143
+ result.observe(delayP99, h.percentile(99) / 1e9)
144
+ h.reset()
145
+ }, [delayMin, delayMax, delayMean, delayStddev, delayP50, delayP90, delayP99])
146
+
147
+ if (performance.eventLoopUtilization) {
148
+ // Baseline so the first observation isn't 1.0.
149
+ lastElu = performance.eventLoopUtilization()
150
+
151
+ const eluTime = meter.createObservableCounter('nodejs.eventloop.time', {
152
+ unit: 's',
153
+ description: 'Cumulative duration of time the event loop has been in each state.',
154
+ })
155
+ registerCallback((result) => {
156
+ const elu = performance.eventLoopUtilization()
157
+ result.observe(elu.idle / 1000, ATTR_ELU_STATE_IDLE)
158
+ result.observe(elu.active / 1000, ATTR_ELU_STATE_ACTIVE)
159
+ }, eluTime)
160
+
161
+ const eluGauge = meter.createObservableGauge('nodejs.eventloop.utilization', {
162
+ unit: '1',
163
+ description: 'Event loop utilization.',
164
+ })
165
+ registerCallback((result) => {
166
+ const current = performance.eventLoopUtilization()
167
+ const idle = current.idle - lastElu.idle
168
+ const active = current.active - lastElu.active
169
+ lastElu = current
170
+ const total = idle + active
171
+ result.observe(total > 0 ? active / total : 0)
172
+ }, eluGauge)
173
+ }
174
+ }
175
+
176
+ if (trackGc) {
177
+ const gcHistogram = meter.createHistogram('v8js.gc.duration', {
178
+ unit: 's',
179
+ description: 'Garbage collection duration.',
180
+ })
181
+ gcObserver = new PerformanceObserver(list => {
182
+ const entries = list.getEntries()
183
+ for (let i = 0; i < entries.length; i++) {
184
+ const entry = entries[i]
185
+ const attr = GC_ATTR_BY_KIND.get(entry.detail?.kind ?? entry.kind)
186
+ if (attr === undefined) continue
187
+ gcHistogram.record(entry.duration / 1000, attr)
188
+ }
189
+ })
190
+ gcObserver.observe({ type: 'gc' })
191
+ }
192
+
193
+ log.debug('Started OTLP runtime metrics with OTel-native naming (v8js.*, nodejs.*)')
194
+ },
195
+
196
+ /**
197
+ * @returns {void}
198
+ */
199
+ stop () {
200
+ if (eventLoopHistogram) {
201
+ eventLoopHistogram.disable()
202
+ eventLoopHistogram = null
203
+ }
204
+ gcObserver?.disconnect()
205
+ gcObserver = null
206
+ for (let i = 0; i < registeredCallbacks.length; i++) {
207
+ const [callback, instrument] = registeredCallbacks[i]
208
+ instrument.removeCallback(callback)
209
+ }
210
+ registeredCallbacks.length = 0
211
+ if (meter) {
212
+ for (let i = 0; i < registeredBatchCallbacks.length; i++) {
213
+ const [callback, instruments] = registeredBatchCallbacks[i]
214
+ meter.removeBatchObservableCallback(callback, instruments)
215
+ }
216
+ }
217
+ registeredBatchCallbacks.length = 0
218
+ meter = null
219
+ lastElu = null
220
+ if (flushInterval) {
221
+ clearInterval(flushInterval)
222
+ flushInterval = null
223
+ }
224
+ client = null
225
+ },
226
+
227
+ // Tied to @datadog/native-metrics which the OTLP path doesn't enable; noop with expected shape.
228
+ track () { return { finish () {} } },
229
+
230
+ boolean (name, value, tag) {
231
+ client?.boolean(name, value, tag)
232
+ },
233
+ histogram (name, value, tag) {
234
+ client?.histogram(name, value, tag)
235
+ },
236
+ count (name, count, tag, monotonic = false) {
237
+ client?.count(name, count, tag, monotonic)
238
+ },
239
+ gauge (name, value, tag) {
240
+ client?.gauge(name, value, tag)
241
+ },
242
+ increment (name, tag, monotonic) {
243
+ this.count(name, 1, tag, monotonic)
244
+ },
245
+ decrement (name, tag) {
246
+ this.count(name, -1, tag)
247
+ },
248
+ }
249
+
250
+ /**
251
+ * @param {Function} callback
252
+ * @param {object} instrument
253
+ */
254
+ function registerCallback (callback, instrument) {
255
+ instrument.addCallback(callback)
256
+ registeredCallbacks.push([callback, instrument])
257
+ }
258
+
259
+ /**
260
+ * @param {Function} callback
261
+ * @param {Array} instruments
262
+ */
263
+ function registerBatchCallback (callback, instruments) {
264
+ meter.addBatchObservableCallback(callback, instruments)
265
+ registeredBatchCallbacks.push([callback, instruments])
266
+ }
267
+
268
+ /**
269
+ * @param {string} name
270
+ * @param {string} description
271
+ * @returns {object}
272
+ */
273
+ function createHeapInstrument (name, description) {
274
+ return meter.createObservableUpDownCounter(name, { unit: 'By', description })
275
+ }
276
+
277
+ /**
278
+ * @param {string} name
279
+ * @param {string} description
280
+ * @returns {object}
281
+ */
282
+ function createDelayGauge (name, description) {
283
+ return meter.createObservableGauge(name, { unit: 's', description })
284
+ }
@@ -6,11 +6,9 @@ const v8 = require('v8')
6
6
  const os = require('os')
7
7
  const process = require('process')
8
8
  const { performance, PerformanceObserver, monitorEventLoopDelay } = require('perf_hooks')
9
- const { DogStatsDClient, MetricsAggregationClient } = require('../dogstatsd')
10
9
  const log = require('../log')
11
-
12
10
  const { NODE_MAJOR } = require('../../../../version')
13
- const processTags = require('../process-tags')
11
+ const { createMetricsClient } = require('./client')
14
12
 
15
13
  const eventLoopDelayResolution = 4
16
14
 
@@ -37,18 +35,11 @@ module.exports = {
37
35
  this.stop()
38
36
  // The agent expects a flush every ten seconds, so this is for tests only.
39
37
  const flushIntervalMs = config.DD_RUNTIME_METRICS_FLUSH_INTERVAL
40
- const clientConfig = DogStatsDClient.generateClientConfig(config)
41
-
42
- if (config.DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED) {
43
- for (const tag of processTags.tagsArray) {
44
- clientConfig.tags.push(tag)
45
- }
46
- }
47
38
 
48
39
  const trackEventLoop = config.runtimeMetrics.eventLoop !== false
49
40
  const trackGc = config.runtimeMetrics.gc !== false
50
41
 
51
- client = new MetricsAggregationClient(new DogStatsDClient(clientConfig))
42
+ client = createMetricsClient(config)
52
43
 
53
44
  if (trackGc) {
54
45
  startGCObserver()
@@ -26,7 +26,11 @@ function resolveServiceSource (span, tracerService) {
26
26
 
27
27
  if (currentService === tracerService) {
28
28
  if (existingSource === undefined) return
29
- spanContext.deleteTag(SVC_SRC_KEY)
29
+ // Clear by assigning undefined rather than deleting: `delete` on the plain
30
+ // `_tags` object drops it into dictionary (slow) mode, so the per-span
31
+ // extractTags scan that follows pays the slow path. The encode loop skips
32
+ // undefined values, so the emitted meta is unchanged.
33
+ spanContext.setTag(SVC_SRC_KEY, undefined)
30
34
  return
31
35
  }
32
36
 
@@ -48,9 +48,10 @@ const { IGNORE_OTEL_ERROR } = constants
48
48
  * @property {Array} links
49
49
  * @property {Array<SpanEvent> | undefined} span_events
50
50
  *
51
- * @typedef {object} SpanEvent
51
+ * @typedef {object} SpanEvent Raw span event as stored on the span; the encoder
52
+ * layer derives `time_unix_nano` from `startTime` via `eventTimeNano`.
52
53
  * @property {string} name
53
- * @property {number} time_unix_nano
54
+ * @property {number} startTime Milliseconds with sub-millisecond precision.
54
55
  * @property {Record<string, string>} [attributes]
55
56
  */
56
57
 
@@ -88,7 +89,6 @@ function formatSpan (span) {
88
89
  metrics: {},
89
90
  start: Math.round(span._startTime * 1e6),
90
91
  duration: Math.round(span._duration * 1e6),
91
- links: [],
92
92
  span_events: undefined,
93
93
  }
94
94
  }
@@ -112,24 +112,32 @@ function setSingleSpanIngestionTags (formattedSpan, options) {
112
112
  * @param {import('./opentracing/span')} span
113
113
  */
114
114
  function extractSpanLinks (formattedSpan, span) {
115
- if (!span._links?.length) {
115
+ const links = span._links
116
+ if (!links?.length) {
116
117
  return
117
118
  }
118
- const links = span._links.map(({ context, attributes }) => {
119
- const formattedLink = {
120
- trace_id: context.toTraceId(true),
121
- span_id: context.toSpanId(true),
119
+ // Build the `_dd.span_links` JSON directly. The trace / span ids are decimal
120
+ // strings (no escaping); attributes are pre-sanitized to a string map and
121
+ // `undefined` when empty, so they only need a presence check. Avoids the
122
+ // throwaway array of formatted-link objects the previous `map` allocated and
123
+ // the second walk `JSON.stringify` does over them.
124
+ let serialized = '['
125
+ for (let i = 0; i < links.length; i++) {
126
+ if (i > 0) serialized += ','
127
+ const { context, attributes } = links[i]
128
+ serialized += `{"trace_id":"${context.toTraceId(true)}","span_id":"${context.toSpanId(true)}"`
129
+ if (attributes !== undefined) {
130
+ serialized += `,"attributes":${JSON.stringify(attributes)}`
122
131
  }
123
-
124
- if (attributes && Object.keys(attributes).length > 0) {
125
- formattedLink.attributes = attributes
132
+ if (context?._sampling?.priority >= 0) {
133
+ serialized += `,"flags":${context._sampling.priority > 0 ? 1 : 0}`
126
134
  }
127
- if (context?._sampling?.priority >= 0) formattedLink.flags = context._sampling.priority > 0 ? 1 : 0
128
- if (context?._tracestate) formattedLink.tracestate = context._tracestate.toString()
129
-
130
- return formattedLink
131
- })
132
- let serialized = JSON.stringify(links)
135
+ if (context?._tracestate) {
136
+ serialized += `,"tracestate":${JSON.stringify(context._tracestate.toString())}`
137
+ }
138
+ serialized += '}'
139
+ }
140
+ serialized += ']'
133
141
  if (serialized.length > MAX_META_VALUE_LENGTH) {
134
142
  serialized = `${serialized.slice(0, MAX_META_VALUE_LENGTH)}...`
135
143
  }
@@ -137,6 +145,12 @@ function extractSpanLinks (formattedSpan, span) {
137
145
  }
138
146
 
139
147
  /**
148
+ * Hand the raw `_events` array to the encoder layer instead of copying it into
149
+ * reshaped `{ name, time_unix_nano, attributes }` objects. Each encoder derives
150
+ * `time_unix_nano` from `event.startTime` via `eventTimeNano` and drops empty
151
+ * attribute objects itself, so the per-event allocation here is pure waste on
152
+ * every event-bearing span.
153
+ *
140
154
  * @param {FormattedSpan} formattedSpan
141
155
  * @param {import('./opentracing/span')} span
142
156
  */
@@ -144,13 +158,7 @@ function extractSpanEvents (formattedSpan, span) {
144
158
  if (!span._events?.length) {
145
159
  return
146
160
  }
147
- formattedSpan.span_events = span._events.map(event => {
148
- return {
149
- name: event.name,
150
- time_unix_nano: Math.round(event.startTime * 1e6),
151
- attributes: event.attributes && Object.keys(event.attributes).length > 0 ? event.attributes : undefined,
152
- }
153
- })
161
+ formattedSpan.span_events = span._events
154
162
  }
155
163
 
156
164
  function extractTags (formattedSpan, span) {
@@ -168,7 +176,7 @@ function extractTags (formattedSpan, span) {
168
176
  metrics[MEASURED] = 1
169
177
  }
170
178
 
171
- const tracerService = span.tracer()._service.toLowerCase()
179
+ const tracerService = span.tracer().serviceLower
172
180
  if (tags['service.name']?.toLowerCase() !== tracerService) {
173
181
  span.setTag(BASE_SERVICE, tracerService)
174
182
 
@@ -190,7 +190,7 @@ class SpanStatsProcessor {
190
190
  if (!this.enabled) return
191
191
  if (!span.metrics[TOP_LEVEL_KEY] && !span.metrics[MEASURED]) return
192
192
 
193
- const spanEndNs = span.startTime + span.duration
193
+ const spanEndNs = span.start + span.duration
194
194
  const bucketTime = spanEndNs - (spanEndNs % this.bucketSizeNs)
195
195
 
196
196
  this.buckets.forTime(bucketTime)
@@ -3,7 +3,6 @@
3
3
  const os = require('os')
4
4
  const { inspect } = require('util')
5
5
  const tracerVersion = require('../../../package.json').version
6
- const { getAgentUrl } = require('./agent/url')
7
6
  const { warn } = require('./log/writer')
8
7
 
9
8
  const errors = {}
@@ -76,7 +75,7 @@ function logGenericError (message) {
76
75
  * @returns {Record<string, unknown>}
77
76
  */
78
77
  function configInfo () {
79
- const url = getAgentUrl(config)
78
+ const url = config.url
80
79
 
81
80
  return {
82
81
  [inspect.custom] () {
@@ -146,7 +146,7 @@ function sendData (config, application, host, reqType, payload = {}, cb = () =>
146
146
 
147
147
  if (isCiVisibilityAgentlessMode) {
148
148
  try {
149
- url ||= new URL(getAgentlessTelemetryEndpoint(config.site))
149
+ url = config.DD_CIVISIBILITY_AGENTLESS_URL ?? new URL(getAgentlessTelemetryEndpoint(config.site))
150
150
  } catch (err) {
151
151
  log.error('Telemetry endpoint url is invalid', err)
152
152
  // No point to do the request if the URL is invalid
@@ -9,7 +9,7 @@ const { isError } = require('./util')
9
9
  const { setStartupLogConfig } = require('./startup-log')
10
10
  const { DataStreamsCheckpointer, DataStreamsManager, DataStreamsProcessor } = require('./datastreams')
11
11
  const { IS_SERVERLESS } = require('./serverless')
12
- const log = require('./log/writer')
12
+ const log = require('./log')
13
13
 
14
14
  const SPAN_TYPE = tags.SPAN_TYPE
15
15
  const RESOURCE_NAME = tags.RESOURCE_NAME