dd-trace 5.44.0 → 5.46.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 (62) hide show
  1. package/LICENSE-3rdparty.csv +1 -1
  2. package/ci/init.js +8 -0
  3. package/ext/exporters.d.ts +2 -1
  4. package/ext/exporters.js +2 -1
  5. package/package.json +3 -3
  6. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  7. package/packages/datadog-instrumentations/src/helpers/register.js +41 -1
  8. package/packages/datadog-instrumentations/src/mariadb.js +19 -0
  9. package/packages/datadog-instrumentations/src/playwright.js +321 -46
  10. package/packages/datadog-instrumentations/src/router.js +1 -7
  11. package/packages/datadog-plugin-mongodb-core/src/index.js +20 -0
  12. package/packages/datadog-plugin-playwright/src/index.js +115 -8
  13. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +39 -15
  14. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +1 -1
  15. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +1 -1
  16. package/packages/dd-trace/src/appsec/rasp/command_injection.js +1 -1
  17. package/packages/dd-trace/src/appsec/rasp/index.js +4 -2
  18. package/packages/dd-trace/src/appsec/rasp/lfi.js +1 -1
  19. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +1 -1
  20. package/packages/dd-trace/src/appsec/rasp/ssrf.js +1 -1
  21. package/packages/dd-trace/src/appsec/rasp/utils.js +12 -7
  22. package/packages/dd-trace/src/appsec/recommended.json +256 -84
  23. package/packages/dd-trace/src/appsec/reporter.js +6 -4
  24. package/packages/dd-trace/src/appsec/sdk/track_event.js +7 -0
  25. package/packages/dd-trace/src/appsec/telemetry/index.js +35 -4
  26. package/packages/dd-trace/src/appsec/telemetry/rasp.js +70 -6
  27. package/packages/dd-trace/src/appsec/telemetry/user.js +9 -1
  28. package/packages/dd-trace/src/appsec/telemetry/waf.js +0 -30
  29. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -0
  30. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +8 -3
  31. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +6 -4
  32. package/packages/dd-trace/src/constants.js +1 -0
  33. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +102 -22
  34. package/packages/dd-trace/src/debugger/devtools_client/condition.js +263 -0
  35. package/packages/dd-trace/src/debugger/devtools_client/index.js +69 -36
  36. package/packages/dd-trace/src/debugger/devtools_client/lock.js +8 -0
  37. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +1 -7
  38. package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -2
  39. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +15 -10
  40. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +3 -3
  41. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +69 -62
  42. package/packages/dd-trace/src/debugger/devtools_client/state.js +3 -2
  43. package/packages/dd-trace/src/debugger/index.js +3 -0
  44. package/packages/dd-trace/src/dogstatsd.js +94 -77
  45. package/packages/dd-trace/src/encode/0.4.js +24 -17
  46. package/packages/dd-trace/src/exporter.js +1 -0
  47. package/packages/dd-trace/src/format.js +58 -60
  48. package/packages/dd-trace/src/histogram.js +12 -23
  49. package/packages/dd-trace/src/llmobs/index.js +3 -0
  50. package/packages/dd-trace/src/llmobs/telemetry.js +27 -1
  51. package/packages/dd-trace/src/llmobs/writers/base.js +4 -0
  52. package/packages/dd-trace/src/llmobs/writers/spans/base.js +3 -3
  53. package/packages/dd-trace/src/opentelemetry/span.js +4 -4
  54. package/packages/dd-trace/src/plugin_manager.js +2 -0
  55. package/packages/dd-trace/src/plugins/util/test.js +4 -0
  56. package/packages/dd-trace/src/profiler.js +1 -1
  57. package/packages/dd-trace/src/profiling/config.js +6 -0
  58. package/packages/dd-trace/src/profiling/profiler.js +4 -3
  59. package/packages/dd-trace/src/profiling/profilers/wall.js +10 -8
  60. package/packages/dd-trace/src/remote_config/manager.js +5 -0
  61. package/packages/dd-trace/src/tagger.js +38 -26
  62. package/packages/dd-trace/src/util.js +1 -7
@@ -6,6 +6,7 @@ const { URL, format } = require('url')
6
6
  const logger = require('../../log')
7
7
 
8
8
  const { encodeUnicode } = require('../util')
9
+ const telemetry = require('../telemetry')
9
10
  const log = require('../../log')
10
11
 
11
12
  class BaseLLMObsWriter {
@@ -45,6 +46,7 @@ class BaseLLMObsWriter {
45
46
  append (event, byteLength) {
46
47
  if (this._buffer.length >= this._bufferLimit) {
47
48
  logger.warn(`${this.constructor.name} event buffer full (limit is ${this._bufferLimit}), dropping event`)
49
+ telemetry.recordDroppedPayload(1, this._eventType, 'buffer_full')
48
50
  return
49
51
  }
50
52
 
@@ -76,10 +78,12 @@ class BaseLLMObsWriter {
76
78
  logger.error(
77
79
  'Error sending %d LLMObs %s events to %s: %s', events.length, this._eventType, this._url, err.message, err
78
80
  )
81
+ telemetry.recordDroppedPayload(events.length, this._eventType, 'request_error')
79
82
  } else if (code >= 300) {
80
83
  logger.error(
81
84
  'Error sending %d LLMObs %s events to %s: %s', events.length, this._eventType, this._url, code
82
85
  )
86
+ telemetry.recordDroppedPayload(events.length, this._eventType, 'http_error')
83
87
  } else {
84
88
  logger.debug(`Sent ${events.length} LLMObs ${this._eventType} events to ${this._url}`)
85
89
  }
@@ -41,12 +41,12 @@ class LLMObsSpanWriter extends BaseWriter {
41
41
  }
42
42
 
43
43
  makePayload (events) {
44
- return {
44
+ return events.map(event => ({
45
45
  '_dd.stage': 'raw',
46
46
  '_dd.tracer_version': tracerVersion,
47
47
  event_type: this._eventType,
48
- spans: events
49
- }
48
+ spans: [event]
49
+ }))
50
50
  }
51
51
 
52
52
  _truncateSpanEvent (event) {
@@ -9,7 +9,7 @@ const { timeInputToHrTime } = require('@opentelemetry/core')
9
9
 
10
10
  const tracer = require('../../')
11
11
  const DatadogSpan = require('../opentracing/span')
12
- const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../constants')
12
+ const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK, IGNORE_OTEL_ERROR } = require('../constants')
13
13
  const { SERVICE_NAME, RESOURCE_NAME } = require('../../../../ext/tags')
14
14
  const kinds = require('../../../../ext/kinds')
15
15
 
@@ -237,7 +237,8 @@ class Span {
237
237
  this._hasStatus = true
238
238
  if (code === 2) {
239
239
  this._ddSpan.addTags({
240
- [ERROR_MESSAGE]: message
240
+ [ERROR_MESSAGE]: message,
241
+ [IGNORE_OTEL_ERROR]: false
241
242
  })
242
243
  }
243
244
  }
@@ -278,12 +279,11 @@ class Span {
278
279
  }
279
280
 
280
281
  recordException (exception, timeInput) {
281
- // HACK: identifier is added so that trace.error remains unchanged after a call to otel.recordException
282
282
  this._ddSpan.addTags({
283
283
  [ERROR_TYPE]: exception.name,
284
284
  [ERROR_MESSAGE]: exception.message,
285
285
  [ERROR_STACK]: exception.stack,
286
- doNotSetTraceError: true
286
+ [IGNORE_OTEL_ERROR]: this._ddSpan.context()._tags[IGNORE_OTEL_ERROR] ?? true
287
287
  })
288
288
  const attributes = {}
289
289
  if (exception.message) attributes['exception.message'] = exception.message
@@ -133,6 +133,7 @@ module.exports = class PluginManager {
133
133
  dbmPropagationMode,
134
134
  dsmEnabled,
135
135
  clientIpEnabled,
136
+ clientIpHeader,
136
137
  memcachedCommandEnabled,
137
138
  ciVisibilityTestSessionName,
138
139
  ciVisAgentlessLogSubmissionEnabled,
@@ -148,6 +149,7 @@ module.exports = class PluginManager {
148
149
  site,
149
150
  url,
150
151
  headers: headerTags || [],
152
+ clientIpHeader,
151
153
  ciVisibilityTestSessionName,
152
154
  ciVisAgentlessLogSubmissionEnabled,
153
155
  isTestDynamicInstrumentationEnabled,
@@ -96,6 +96,9 @@ const CUCUMBER_WORKER_TRACE_PAYLOAD_CODE = 70
96
96
  // mocha worker variables
97
97
  const MOCHA_WORKER_TRACE_PAYLOAD_CODE = 80
98
98
 
99
+ // playwright worker variables
100
+ const PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE = 90
101
+
99
102
  // Early flake detection util strings
100
103
  const EFD_STRING = "Retried by Datadog's Early Flake Detection"
101
104
  const EFD_TEST_NAME_REGEX = new RegExp(EFD_STRING + ' \\(#\\d+\\): ', 'g')
@@ -162,6 +165,7 @@ module.exports = {
162
165
  JEST_WORKER_LOGS_PAYLOAD_CODE,
163
166
  CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
164
167
  MOCHA_WORKER_TRACE_PAYLOAD_CODE,
168
+ PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE,
165
169
  TEST_SOURCE_START,
166
170
  TEST_SKIPPED_BY_ITR,
167
171
  TEST_IS_NEW,
@@ -14,7 +14,7 @@ module.exports = {
14
14
  debug: (message) => log.debug(message),
15
15
  info: (message) => log.info(message),
16
16
  warn: (message) => log.warn(message),
17
- error: (message) => log.error(message)
17
+ error: (...args) => log.error(...args)
18
18
  }
19
19
 
20
20
  const libraryInjected = injectionEnabled.length > 0
@@ -15,6 +15,7 @@ const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags')
15
15
  const { tagger } = require('./tagger')
16
16
  const { isFalse, isTrue } = require('../util')
17
17
  const { getAzureTagsFromMetadata, getAzureAppMetadata } = require('../azure_metadata')
18
+ const satisfies = require('semifies')
18
19
 
19
20
  class Config {
20
21
  constructor (options = {}) {
@@ -22,6 +23,7 @@ class Config {
22
23
  DD_AGENT_HOST,
23
24
  DD_ENV,
24
25
  DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED, // used for testing
26
+ DD_PROFILING_ASYNC_ID_ENABLED,
25
27
  DD_PROFILING_CODEHOTSPOTS_ENABLED,
26
28
  DD_PROFILING_CPU_ENABLED,
27
29
  DD_PROFILING_DEBUG_SOURCE_MAPS,
@@ -179,6 +181,10 @@ class Config {
179
181
  this.timelineSamplingEnabled = isTrue(coalesce(options.timelineSamplingEnabled,
180
182
  DD_INTERNAL_PROFILING_TIMELINE_SAMPLING_ENABLED, true))
181
183
 
184
+ // Async ID gathering only works reliably on Node >= 22.10.0
185
+ this.asyncIdEnabled = isTrue(coalesce(options.asyncIdEnabled,
186
+ DD_PROFILING_ASYNC_ID_ENABLED, this.timelineEnabled && satisfies(process.versions.node, '>=22.10.0')))
187
+
182
188
  this.codeHotspotsEnabled = isTrue(coalesce(options.codeHotspotsEnabled,
183
189
  DD_PROFILING_CODEHOTSPOTS_ENABLED,
184
190
  DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED, samplingContextsAvailable))
@@ -18,9 +18,9 @@ function maybeSourceMap (sourceMap, SourceMapper, debug) {
18
18
  ], debug)
19
19
  }
20
20
 
21
- function logError (logger, err) {
21
+ function logError (logger, ...args) {
22
22
  if (logger) {
23
- logger.error(err)
23
+ logger.error(...args)
24
24
  }
25
25
  }
26
26
 
@@ -52,7 +52,8 @@ class Profiler extends EventEmitter {
52
52
 
53
53
  start (options) {
54
54
  return this._start(options).catch((err) => {
55
- logError(options.logger, err)
55
+ logError(options.logger, 'Error starting profiler. For troubleshooting tips, see ' +
56
+ '<https://dtdg.co/nodejs-profiler-troubleshooting>', err)
56
57
  return false
57
58
  })
58
59
  }
@@ -70,12 +70,14 @@ function ensureChannelsActivated () {
70
70
  class NativeWallProfiler {
71
71
  constructor (options = {}) {
72
72
  this.type = 'wall'
73
- this._samplingIntervalMicros = options.samplingInterval || 1e6 / 99 // 99hz
74
- this._flushIntervalMillis = options.flushInterval || 60 * 1e3 // 60 seconds
73
+ this._asyncIdEnabled = !!options.asyncIdEnabled
75
74
  this._codeHotspotsEnabled = !!options.codeHotspotsEnabled
75
+ this._cpuProfilingEnabled = !!options.cpuProfilingEnabled
76
76
  this._endpointCollectionEnabled = !!options.endpointCollectionEnabled
77
+ this._flushIntervalMillis = options.flushInterval || 60 * 1e3 // 60 seconds
78
+ this._samplingIntervalMicros = options.samplingInterval || 1e6 / 99 // 99hz
77
79
  this._timelineEnabled = !!options.timelineEnabled
78
- this._cpuProfilingEnabled = !!options.cpuProfilingEnabled
80
+ this._v8ProfilerBugWorkaroundEnabled = !!options.v8ProfilerBugWorkaroundEnabled
79
81
  // We need to capture span data into the sample context for either code hotspots
80
82
  // or endpoint collection.
81
83
  this._captureSpanData = this._codeHotspotsEnabled || this._endpointCollectionEnabled
@@ -84,7 +86,6 @@ class NativeWallProfiler {
84
86
  // timestamps require the sample contexts feature in the pprof wall profiler), or
85
87
  // cpu profiling is enabled.
86
88
  this._withContexts = this._captureSpanData || this._timelineEnabled || this._cpuProfilingEnabled
87
- this._v8ProfilerBugWorkaroundEnabled = !!options.v8ProfilerBugWorkaroundEnabled
88
89
  this._mapper = undefined
89
90
  this._pprof = undefined
90
91
 
@@ -127,13 +128,14 @@ class NativeWallProfiler {
127
128
  }
128
129
 
129
130
  this._pprof.time.start({
130
- intervalMicros: this._samplingIntervalMicros,
131
+ collectAsyncId: this._asyncIdEnabled,
132
+ collectCpuTime: this._cpuProfilingEnabled,
131
133
  durationMillis: this._flushIntervalMillis,
134
+ intervalMicros: this._samplingIntervalMicros,
135
+ lineNumbers: false,
132
136
  sourceMapper: this._mapper,
133
137
  withContexts: this._withContexts,
134
- lineNumbers: false,
135
- workaroundV8Bug: this._v8ProfilerBugWorkaroundEnabled,
136
- collectCpuTime: this._cpuProfilingEnabled
138
+ workaroundV8Bug: this._v8ProfilerBugWorkaroundEnabled
137
139
  })
138
140
 
139
141
  if (this._withContexts) {
@@ -10,6 +10,7 @@ const { getExtraServices } = require('../service-naming/extra-services')
10
10
  const { UNACKNOWLEDGED, ACKNOWLEDGED, ERROR } = require('./apply_states')
11
11
  const Scheduler = require('./scheduler')
12
12
  const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags')
13
+ const tagger = require('../tagger')
13
14
 
14
15
  const clientId = uuid()
15
16
 
@@ -34,6 +35,10 @@ class RemoteConfigManager extends EventEmitter {
34
35
  port: config.port
35
36
  }))
36
37
 
38
+ tagger.add(config.tags, {
39
+ '_dd.rc.client_id': clientId
40
+ })
41
+
37
42
  const tags = config.repositoryUrl
38
43
  ? {
39
44
  ...config.tags,
@@ -1,43 +1,55 @@
1
1
  'use strict'
2
2
 
3
- const constants = require('./constants')
4
3
  const log = require('./log')
5
- const ERROR_MESSAGE = constants.ERROR_MESSAGE
6
- const ERROR_STACK = constants.ERROR_STACK
7
- const ERROR_TYPE = constants.ERROR_TYPE
4
+
5
+ function addNonEmpty (carrier, key, value) {
6
+ if (key !== '') {
7
+ carrier[key] = value
8
+ }
9
+ }
8
10
 
9
11
  function add (carrier, keyValuePairs) {
10
- if (!carrier || !keyValuePairs) return
12
+ if (!carrier) return
11
13
 
12
- if (Array.isArray(keyValuePairs)) {
13
- return keyValuePairs.forEach(tags => add(carrier, tags))
14
- }
15
14
  try {
16
15
  if (typeof keyValuePairs === 'string') {
17
- const segments = keyValuePairs.split(',')
18
- for (const segment of segments) {
19
- const separatorIndex = segment.indexOf(':')
20
-
21
- let value = ''
22
- let key = segment
23
- if (separatorIndex !== -1) {
24
- key = segment.slice(0, separatorIndex)
25
- value = segment.slice(separatorIndex + 1)
16
+ let valueStart = 0
17
+ let keyStart = 0
18
+
19
+ for (let i = 0; i < keyValuePairs.length; i++) {
20
+ const char = keyValuePairs[i]
21
+
22
+ if (char === ':') {
23
+ if (valueStart === 0) {
24
+ valueStart = i
25
+ }
26
+ } else if (char === ',') {
27
+ valueStart ||= i
28
+ addNonEmpty(
29
+ carrier,
30
+ keyValuePairs.slice(keyStart, valueStart).trim(),
31
+ keyValuePairs.slice(valueStart + 1, i).trim()
32
+ )
33
+ keyStart = i + 1
34
+ valueStart = 0
26
35
  }
36
+ }
27
37
 
28
- carrier[key.trim()] = value.trim()
38
+ if (keyValuePairs.at(-1) !== ',') {
39
+ valueStart ||= keyValuePairs.length
40
+ addNonEmpty(
41
+ carrier,
42
+ keyValuePairs.slice(keyStart, valueStart).trim(),
43
+ keyValuePairs.slice(valueStart + 1).trim()
44
+ )
29
45
  }
46
+ } else if (Array.isArray(keyValuePairs)) {
47
+ return keyValuePairs.forEach(tags => add(carrier, tags))
30
48
  } else {
31
- // HACK: to ensure otel.recordException does not influence trace.error
32
- if (ERROR_MESSAGE in keyValuePairs || ERROR_STACK in keyValuePairs || ERROR_TYPE in keyValuePairs) {
33
- if (!('doNotSetTraceError' in keyValuePairs)) {
34
- carrier.setTraceError = true
35
- }
36
- }
37
49
  Object.assign(carrier, keyValuePairs)
38
50
  }
39
- } catch (e) {
40
- log.error('Error adding tags', e)
51
+ } catch (error) {
52
+ log.error('Error adding tags', error)
41
53
  }
42
54
  }
43
55
 
@@ -13,13 +13,7 @@ function isFalse (str) {
13
13
  }
14
14
 
15
15
  function isError (value) {
16
- if (value instanceof Error) {
17
- return true
18
- }
19
- if (value && value.message) {
20
- return true
21
- }
22
- return false
16
+ return Boolean(value?.message || value instanceof Error)
23
17
  }
24
18
 
25
19
  // Matches a glob pattern to a given subject string