dd-trace 5.107.0 → 5.108.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.107.0",
3
+ "version": "5.108.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -169,7 +169,7 @@
169
169
  "@datadog/native-iast-taint-tracking": "4.2.0",
170
170
  "@datadog/native-metrics": "3.1.2",
171
171
  "@datadog/openfeature-node-server": "2.0.0",
172
- "@datadog/pprof": "5.14.4",
172
+ "@datadog/pprof": "5.15.0",
173
173
  "@datadog/wasm-js-rewriter": "5.0.1",
174
174
  "@opentelemetry/api": ">=1.0.0 <1.10.0",
175
175
  "@opentelemetry/api-logs": "<1.0.0",
@@ -188,7 +188,7 @@
188
188
  "@types/mocha": "^10.0.10",
189
189
  "@types/node": "^18.19.106",
190
190
  "@types/sinon": "^21.0.1",
191
- "axios": "^1.16.1",
191
+ "axios": "^1.17.0",
192
192
  "benchmark": "^2.1.4",
193
193
  "body-parser": "^2.2.2",
194
194
  "bun": "1.3.14",
@@ -196,7 +196,7 @@
196
196
  "eslint": "^9.39.2",
197
197
  "eslint-plugin-cypress": "^6.4.1",
198
198
  "eslint-plugin-import": "^2.32.0",
199
- "eslint-plugin-jsdoc": "^63.0.0",
199
+ "eslint-plugin-jsdoc": "^63.0.1",
200
200
  "eslint-plugin-mocha": "^11.3.0",
201
201
  "eslint-plugin-n": "^18.0.1",
202
202
  "eslint-plugin-promise": "^7.3.0",
@@ -222,7 +222,7 @@
222
222
  "proxyquire": "^2.1.3",
223
223
  "retry": "^0.13.1",
224
224
  "semifies": "^1.0.0",
225
- "semver": "^7.8.1",
225
+ "semver": "^7.8.2",
226
226
  "sinon": "^22.0.0",
227
227
  "tiktoken": "^1.0.21",
228
228
  "typescript": "^6.0.3",
@@ -2,6 +2,7 @@
2
2
 
3
3
  const web = require('../plugins/util/web')
4
4
  const log = require('../log')
5
+ const { isEmpty } = require('../util')
5
6
  const {
6
7
  HTTP_OUTGOING_METHOD,
7
8
  HTTP_OUTGOING_HEADERS,
@@ -137,7 +138,7 @@ function extractRequestData (ctx) {
137
138
  addresses[HTTP_OUTGOING_METHOD] = getMethod(options.method)
138
139
 
139
140
  const headers = options?.headers
140
- if (headers && Object.keys(headers).length > 0) {
141
+ if (headers && !isEmpty(headers)) {
141
142
  addresses[HTTP_OUTGOING_HEADERS] = lowercaseHeaderKeys(headers)
142
143
  }
143
144
 
@@ -177,7 +178,7 @@ function extractResponseData (res, responseBody) {
177
178
  }
178
179
 
179
180
  const headers = res.headers
180
- if (headers && Object.keys(headers).length > 0) {
181
+ if (headers && !isEmpty(headers)) {
181
182
  addresses[HTTP_OUTGOING_RESPONSE_HEADERS] = headers
182
183
  }
183
184
 
@@ -3,6 +3,7 @@
3
3
  const dc = require('dc-polyfill')
4
4
  const web = require('../../plugins/util/web')
5
5
  const { storage } = require('../../../../datadog-core')
6
+ const { isEmpty } = require('../../util')
6
7
  const { enable: enableFsPlugin, disable: disableFsPlugin, IAST_MODULE } = require('../rasp/fs-plugin')
7
8
  const { incomingHttpRequestStart, incomingHttpRequestEnd, responseWriteHead } = require('../channels')
8
9
  const vulnerabilityReporter = require('./vulnerability-reporter')
@@ -96,7 +97,7 @@ function onIncomingHttpRequestEnd (data) {
96
97
 
97
98
  iastResponseEnd.publish({ ...data, storedHeaders })
98
99
 
99
- if (Object.keys(storedHeaders).length) {
100
+ if (!isEmpty(storedHeaders)) {
100
101
  collectedResponseHeaders.delete(data.res)
101
102
  }
102
103
 
@@ -118,7 +119,7 @@ function onIncomingHttpRequestEnd (data) {
118
119
  function onResponseWriteHeadCollect ({ res, responseHeaders = {} }) {
119
120
  if (!res) return
120
121
 
121
- if (Object.keys(responseHeaders).length) {
122
+ if (!isEmpty(responseHeaders)) {
122
123
  collectedResponseHeaders.set(res, responseHeaders)
123
124
  }
124
125
  }
@@ -9,6 +9,7 @@ const addresses = require('../addresses')
9
9
  const web = require('../../plugins/util/web')
10
10
  const { getActiveRequest } = require('../store')
11
11
  const waf = require('../waf')
12
+ const { isEmpty } = require('../../util')
12
13
  const downstream = require('../downstream_requests')
13
14
  const { updateRaspRuleMatchMetricTags } = require('../telemetry')
14
15
  const { RULE_TYPES, handleResult } = require('./utils')
@@ -85,7 +86,7 @@ function handleResponseFinish ({ ctx, res, body }) {
85
86
  function runResponseEvaluation (res, req, responseBody) {
86
87
  const responseAddresses = downstream.extractResponseData(res, responseBody)
87
88
 
88
- if (!Object.keys(responseAddresses).length) return
89
+ if (isEmpty(responseAddresses)) return
89
90
 
90
91
  const raspRule = { type: RULE_TYPES.SSRF, variant: 'response' }
91
92
  const result = waf.run({ ephemeral: responseAddresses }, req, raspRule)
@@ -461,7 +461,7 @@ function truncateRequestBody (target, depth = 0) {
461
461
  }
462
462
 
463
463
  function reportRequestBody (rootSpan, requestBody, comesFromRaspAction = false) {
464
- if (!requestBody || Object.keys(requestBody).length === 0) return
464
+ if (!requestBody || isEmpty(requestBody)) return
465
465
 
466
466
  if (!rootSpan.meta_struct) {
467
467
  rootSpan.meta_struct = {}
@@ -130,6 +130,7 @@ export interface GeneratedConfig {
130
130
  DD_MINI_AGENT_PATH: string | undefined;
131
131
  DD_PIPELINE_EXECUTION_ID: string | undefined;
132
132
  DD_PLAYWRIGHT_WORKER: string | undefined;
133
+ DD_PROFILING_ALLOCATION_ENABLED: boolean;
133
134
  DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED: boolean;
134
135
  DD_PROFILING_CODEHOTSPOTS_ENABLED: boolean;
135
136
  DD_PROFILING_CPU_ENABLED: boolean;
@@ -1308,6 +1308,13 @@
1308
1308
  "default": null
1309
1309
  }
1310
1310
  ],
1311
+ "DD_PROFILING_ALLOCATION_ENABLED": [
1312
+ {
1313
+ "implementation": "A",
1314
+ "type": "boolean",
1315
+ "default": "false"
1316
+ }
1317
+ ],
1311
1318
  "DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED": [
1312
1319
  {
1313
1320
  "implementation": "A",
@@ -86,6 +86,7 @@ class Config {
86
86
 
87
87
  this.timelineEnabled = options.DD_PROFILING_TIMELINE_ENABLED
88
88
  this.timelineSamplingEnabled = options.DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED
89
+ this.allocationProfilingEnabled = options.DD_PROFILING_ALLOCATION_ENABLED
89
90
  this.codeHotspotsEnabled = options.DD_PROFILING_CODEHOTSPOTS_ENABLED
90
91
  this.cpuProfilingEnabled = options.DD_PROFILING_CPU_ENABLED
91
92
  this.heapSamplingInterval = options.DD_PROFILING_HEAP_SAMPLING_INTERVAL
@@ -139,6 +140,7 @@ class Config {
139
140
 
140
141
  get systemInfoReport () {
141
142
  const report = {
143
+ allocationProfilingEnabled: this.allocationProfilingEnabled,
142
144
  asyncContextFrameEnabled: this.asyncContextFrameEnabled,
143
145
  codeHotspotsEnabled: this.codeHotspotsEnabled,
144
146
  cpuProfilingEnabled: this.cpuProfilingEnabled,
@@ -66,12 +66,11 @@ class GCDecorator {
66
66
  constructor (stringTable) {
67
67
  this.stringTable = stringTable
68
68
  this.reasonLabelKey = stringTable.dedup('gc reason')
69
+ this.kindLabelKey = stringTable.dedup('gc type')
69
70
  this.kindLabels = []
70
71
  this.reasonLabels = []
71
72
  this.flagObj = {}
72
73
 
73
- const kindLabelKey = stringTable.dedup('gc type')
74
-
75
74
  // Create labels for all GC performance flags and kinds of GC
76
75
  for (const [key, value] of Object.entries(constants)) {
77
76
  if (key.startsWith('NODE_PERFORMANCE_GC_FLAGS_')) {
@@ -79,20 +78,43 @@ class GCDecorator {
79
78
  } else if (key.startsWith('NODE_PERFORMANCE_GC_')) {
80
79
  // It's a constant for a kind of GC
81
80
  const kind = key.slice(20).toLowerCase()
82
- this.kindLabels[value] = labelFromStr(stringTable, kindLabelKey, kind)
81
+ this.kindLabels[value] = labelFromStr(stringTable, this.kindLabelKey, kind)
83
82
  }
84
83
  }
84
+
85
+ // V8's young-generation collector emits GC events with kind 2, but Node.js
86
+ // doesn't expose a matching NODE_PERFORMANCE_GC_* constant for it, so we map it
87
+ // explicitly. The collector was renamed from Minor Mark-Compact to Minor
88
+ // Mark-Sweep in the V8 version that shipped with Node 22. See equivalent
89
+ // mapping in runtime_metrics.js.
90
+ const minorMarkGCKind = 2
91
+ if (this.kindLabels[minorMarkGCKind] === undefined) {
92
+ const { NODE_MAJOR } = require('../../../../../version')
93
+ const minorGCLabel = NODE_MAJOR >= 22 ? 'minor_mark_sweep' : 'minor_mark_compact'
94
+ this.kindLabels[minorMarkGCKind] = labelFromStr(stringTable, this.kindLabelKey, minorGCLabel)
95
+ }
85
96
  }
86
97
 
87
98
  decorateSample (sampleInput, item) {
88
99
  const { kind, flags } = item.detail
89
- sampleInput.label.push(this.kindLabels[kind])
100
+ sampleInput.label.push(this.getKindLabel(kind))
90
101
  const reasonLabel = this.getReasonLabel(flags)
91
102
  if (reasonLabel) {
92
103
  sampleInput.label.push(reasonLabel)
93
104
  }
94
105
  }
95
106
 
107
+ getKindLabel (kind) {
108
+ let kindLabel = this.kindLabels[kind]
109
+ if (kindLabel === undefined) {
110
+ // Gracefully handle GC kinds we don't have a label for (e.g. a value
111
+ // introduced by a future Node.js/V8 version).
112
+ kindLabel = labelFromStr(this.stringTable, this.kindLabelKey, `unknown_${kind}`)
113
+ this.kindLabels[kind] = kindLabel
114
+ }
115
+ return kindLabel
116
+ }
117
+
96
118
  getReasonLabel (flags) {
97
119
  if (flags === 0) {
98
120
  return null
@@ -13,12 +13,14 @@ class NativeSpaceProfiler {
13
13
  #mapper
14
14
  #oomMonitoring
15
15
  #pprof
16
+ #allocationProfilingEnabled = false
16
17
  #samplingInterval = 512 * 1024
17
18
  #started = false
18
19
 
19
20
  constructor (options = {}) {
20
21
  // TODO: Remove default value. It is only used in testing.
21
22
  this.#samplingInterval = options.heapSamplingInterval || 512 * 1024
23
+ this.#allocationProfilingEnabled = options.allocationProfilingEnabled
22
24
  this.#oomMonitoring = options.oomMonitoring || {}
23
25
  }
24
26
 
@@ -31,7 +33,7 @@ class NativeSpaceProfiler {
31
33
 
32
34
  this.#mapper = mapper
33
35
  this.#pprof = require('@datadog/pprof')
34
- this.#pprof.heap.start(this.#samplingInterval, STACK_DEPTH)
36
+ this.#pprof.heap.start(this.#samplingInterval, STACK_DEPTH, this.#allocationProfilingEnabled)
35
37
  if (this.#oomMonitoring.enabled) {
36
38
  const strategies = this.#oomMonitoring.exportStrategies
37
39
  this.#pprof.heap.monitorOutOfMemory(