dd-trace 5.28.0 → 5.29.1

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 (148) hide show
  1. package/LICENSE-3rdparty.csv +8 -2
  2. package/ci/init.js +16 -0
  3. package/index.d.ts +31 -13
  4. package/init.js +4 -68
  5. package/loader-hook.mjs +4 -0
  6. package/package.json +16 -11
  7. package/packages/datadog-core/src/storage.js +39 -2
  8. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  9. package/packages/datadog-instrumentations/src/cucumber.js +29 -3
  10. package/packages/datadog-instrumentations/src/express.js +38 -4
  11. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +3 -3
  12. package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -1
  13. package/packages/datadog-instrumentations/src/helpers/register.js +3 -4
  14. package/packages/datadog-instrumentations/src/http/client.js +1 -1
  15. package/packages/datadog-instrumentations/src/jest.js +27 -8
  16. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
  17. package/packages/datadog-instrumentations/src/mysql2.js +13 -8
  18. package/packages/datadog-instrumentations/src/next.js +7 -4
  19. package/packages/datadog-instrumentations/src/passport-http.js +2 -14
  20. package/packages/datadog-instrumentations/src/passport-local.js +2 -14
  21. package/packages/datadog-instrumentations/src/passport-utils.js +43 -19
  22. package/packages/datadog-instrumentations/src/pg.js +6 -6
  23. package/packages/datadog-instrumentations/src/playwright.js +17 -4
  24. package/packages/datadog-instrumentations/src/router.js +97 -1
  25. package/packages/datadog-instrumentations/src/sequelize.js +9 -4
  26. package/packages/datadog-instrumentations/src/url.js +4 -0
  27. package/packages/datadog-instrumentations/src/vitest.js +27 -2
  28. package/packages/datadog-plugin-avsc/src/schema_iterator.js +8 -3
  29. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +154 -0
  30. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -1
  31. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
  32. package/packages/datadog-plugin-aws-sdk/src/services/lambda.js +1 -1
  33. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +1 -1
  34. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
  35. package/packages/datadog-plugin-aws-sdk/src/util.js +92 -0
  36. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -1
  37. package/packages/datadog-plugin-cucumber/src/index.js +39 -4
  38. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +3 -3
  39. package/packages/datadog-plugin-grpc/src/client.js +2 -2
  40. package/packages/datadog-plugin-grpc/src/util.js +1 -1
  41. package/packages/datadog-plugin-jest/src/index.js +39 -4
  42. package/packages/datadog-plugin-mocha/src/index.js +36 -2
  43. package/packages/datadog-plugin-oracledb/src/index.js +1 -1
  44. package/packages/datadog-plugin-vitest/src/index.js +34 -2
  45. package/packages/datadog-shimmer/src/shimmer.js +8 -4
  46. package/packages/dd-trace/src/appsec/addresses.js +3 -0
  47. package/packages/dd-trace/src/appsec/blocked_templates.js +1 -1
  48. package/packages/dd-trace/src/appsec/channels.js +1 -0
  49. package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +4 -0
  50. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-rules.js +1 -1
  51. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-rules.js +1 -1
  52. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +1 -1
  53. package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +10 -3
  54. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +4 -0
  55. package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +4 -0
  56. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +6 -19
  57. package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +3 -3
  58. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +64 -3
  59. package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +2 -1
  60. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +2 -2
  61. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  62. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +32 -37
  63. package/packages/dd-trace/src/appsec/index.js +16 -10
  64. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +1 -0
  65. package/packages/dd-trace/src/appsec/remote_config/index.js +25 -1
  66. package/packages/dd-trace/src/appsec/reporter.js +3 -1
  67. package/packages/dd-trace/src/appsec/sdk/track_event.js +32 -19
  68. package/packages/dd-trace/src/appsec/telemetry.js +10 -0
  69. package/packages/dd-trace/src/appsec/user_tracking.js +168 -0
  70. package/packages/dd-trace/src/azure_metadata.js +4 -4
  71. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +5 -4
  72. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +39 -3
  73. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
  74. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
  75. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
  76. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
  77. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +29 -9
  78. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
  79. package/packages/dd-trace/src/config.js +24 -32
  80. package/packages/dd-trace/src/constants.js +1 -0
  81. package/packages/dd-trace/src/crashtracking/crashtracker.js +3 -2
  82. package/packages/dd-trace/src/datastreams/processor.js +4 -6
  83. package/packages/dd-trace/src/datastreams/writer.js +6 -5
  84. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +80 -0
  85. package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -1
  86. package/packages/dd-trace/src/debugger/devtools_client/defaults.js +6 -0
  87. package/packages/dd-trace/src/debugger/devtools_client/index.js +63 -8
  88. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +10 -67
  89. package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -1
  90. package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -1
  91. package/packages/dd-trace/src/debugger/devtools_client/status.js +4 -4
  92. package/packages/dd-trace/src/debugger/index.js +14 -10
  93. package/packages/dd-trace/src/dogstatsd.js +2 -2
  94. package/packages/dd-trace/src/encode/0.4.js +23 -78
  95. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +0 -32
  96. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +1 -2
  97. package/packages/dd-trace/src/encode/span-stats.js +0 -30
  98. package/packages/dd-trace/src/exporters/agent/writer.js +3 -3
  99. package/packages/dd-trace/src/exporters/common/request.js +1 -1
  100. package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -1
  101. package/packages/dd-trace/src/flare/index.js +1 -1
  102. package/packages/dd-trace/src/guardrails/index.js +64 -0
  103. package/packages/dd-trace/src/guardrails/log.js +32 -0
  104. package/packages/dd-trace/src/guardrails/telemetry.js +78 -0
  105. package/packages/dd-trace/src/guardrails/util.js +10 -0
  106. package/packages/dd-trace/src/lambda/runtime/ritm.js +2 -2
  107. package/packages/dd-trace/src/llmobs/storage.js +2 -3
  108. package/packages/dd-trace/src/llmobs/writers/base.js +2 -2
  109. package/packages/dd-trace/src/log/channels.js +9 -2
  110. package/packages/dd-trace/src/log/index.js +11 -1
  111. package/packages/dd-trace/src/log/writer.js +14 -3
  112. package/packages/dd-trace/src/{encode → msgpack}/chunk.js +8 -5
  113. package/packages/dd-trace/src/msgpack/encoder.js +309 -0
  114. package/packages/dd-trace/src/msgpack/index.js +6 -0
  115. package/packages/dd-trace/src/opentelemetry/context_manager.js +2 -2
  116. package/packages/dd-trace/src/opentracing/propagation/text_map.js +12 -9
  117. package/packages/dd-trace/src/opentracing/span.js +1 -1
  118. package/packages/dd-trace/src/opentracing/tracer.js +2 -2
  119. package/packages/dd-trace/src/plugin_manager.js +4 -2
  120. package/packages/dd-trace/src/plugins/ci_plugin.js +47 -4
  121. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  122. package/packages/dd-trace/src/plugins/tracing.js +1 -1
  123. package/packages/dd-trace/src/plugins/util/git.js +7 -7
  124. package/packages/dd-trace/src/plugins/util/test.js +36 -3
  125. package/packages/dd-trace/src/plugins/util/web.js +2 -2
  126. package/packages/dd-trace/src/priority_sampler.js +11 -1
  127. package/packages/dd-trace/src/profiling/config.js +3 -0
  128. package/packages/dd-trace/src/profiling/exporters/agent.js +9 -68
  129. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +76 -0
  130. package/packages/dd-trace/src/profiling/exporters/file.js +8 -4
  131. package/packages/dd-trace/src/profiling/profiler.js +62 -10
  132. package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +22 -12
  133. package/packages/dd-trace/src/profiling/profilers/events.js +47 -8
  134. package/packages/dd-trace/src/profiling/profilers/wall.js +2 -17
  135. package/packages/dd-trace/src/profiling/webspan-utils.js +23 -0
  136. package/packages/dd-trace/src/proxy.js +7 -2
  137. package/packages/dd-trace/src/runtime_metrics.js +107 -4
  138. package/packages/dd-trace/src/serverless.js +1 -1
  139. package/packages/dd-trace/src/span_processor.js +10 -10
  140. package/packages/dd-trace/src/tagger.js +1 -1
  141. package/packages/dd-trace/src/telemetry/index.js +1 -0
  142. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  143. package/packages/dd-trace/src/telemetry/logs/log-collector.js +10 -2
  144. package/packages/dd-trace/src/telemetry/send-data.js +2 -2
  145. package/packages/dd-trace/src/util.js +5 -16
  146. package/packages/datadog-instrumentations/src/qs.js +0 -24
  147. package/packages/dd-trace/src/appsec/passport.js +0 -110
  148. package/packages/dd-trace/src/telemetry/init-telemetry.js +0 -75
@@ -254,10 +254,10 @@ class NodeApiEventSource {
254
254
  }
255
255
 
256
256
  class DatadogInstrumentationEventSource {
257
- constructor (eventHandler) {
257
+ constructor (eventHandler, eventFilter) {
258
258
  this.plugins = ['dns_lookup', 'dns_lookupservice', 'dns_resolve', 'dns_reverse', 'net'].map(m => {
259
259
  const Plugin = require(`./event_plugins/${m}`)
260
- return new Plugin(eventHandler)
260
+ return new Plugin(eventHandler, eventFilter)
261
261
  })
262
262
 
263
263
  this.started = false
@@ -292,29 +292,68 @@ class CompositeEventSource {
292
292
  }
293
293
  }
294
294
 
295
+ function createPossionProcessSamplingFilter (samplingIntervalMillis) {
296
+ let nextSamplingInstant = performance.now()
297
+ let currentSamplingInstant = 0
298
+ setNextSamplingInstant()
299
+
300
+ return event => {
301
+ const endTime = event.startTime + event.duration
302
+ while (endTime >= nextSamplingInstant) {
303
+ setNextSamplingInstant()
304
+ }
305
+ // An event is sampled if it started before, and ended on or after a sampling instant. The above
306
+ // while loop will ensure that the ending invariant is always true for the current sampling
307
+ // instant so we don't have to test for it below. Across calls, the invariant also holds as long
308
+ // as the events arrive in endTime order. This is true for events coming from
309
+ // DatadogInstrumentationEventSource; they will be ordered by endTime by virtue of this method
310
+ // being invoked synchronously with the plugins' finish() handler which evaluates
311
+ // performance.now(). OTOH, events coming from NodeAPIEventSource (GC in typical setup) might be
312
+ // somewhat delayed as they are queued by Node, so they can arrive out of order with regard to
313
+ // events coming from the non-queued source. By omitting the endTime check, we will pass through
314
+ // some short events that started and ended before the current sampling instant. OTOH, if we
315
+ // were to check for this.currentSamplingInstant <= endTime, we would discard some long events
316
+ // that also ended before the current sampling instant. We'd rather err on the side of including
317
+ // some short events than excluding some long events.
318
+ return event.startTime < currentSamplingInstant
319
+ }
320
+
321
+ function setNextSamplingInstant () {
322
+ currentSamplingInstant = nextSamplingInstant
323
+ nextSamplingInstant -= Math.log(1 - Math.random()) * samplingIntervalMillis
324
+ }
325
+ }
326
+
295
327
  /**
296
328
  * This class generates pprof files with timeline events. It combines an event
297
- * source with an event serializer.
329
+ * source with a sampling event filter and an event serializer.
298
330
  */
299
331
  class EventsProfiler {
300
332
  constructor (options = {}) {
301
333
  this.type = 'events'
302
334
  this.eventSerializer = new EventSerializer()
303
335
 
304
- const eventHandler = event => {
305
- this.eventSerializer.addEvent(event)
336
+ const eventHandler = event => this.eventSerializer.addEvent(event)
337
+ const eventFilter = options.timelineSamplingEnabled
338
+ // options.samplingInterval comes in microseconds, we need millis
339
+ ? createPossionProcessSamplingFilter((options.samplingInterval ?? 1e6 / 99) / 1000)
340
+ : _ => true
341
+ const filteringEventHandler = event => {
342
+ if (eventFilter(event)) {
343
+ eventHandler(event)
344
+ }
306
345
  }
307
346
 
308
347
  if (options.codeHotspotsEnabled) {
309
348
  // Use Datadog instrumentation to collect events with span IDs. Still use
310
349
  // Node API for GC events.
311
350
  this.eventSource = new CompositeEventSource([
312
- new DatadogInstrumentationEventSource(eventHandler),
313
- new NodeApiEventSource(eventHandler, ['gc'])
351
+ new DatadogInstrumentationEventSource(eventHandler, eventFilter),
352
+ new NodeApiEventSource(filteringEventHandler, ['gc'])
314
353
  ])
315
354
  } else {
316
355
  // Use Node API instrumentation to collect events without span IDs
317
- this.eventSource = new NodeApiEventSource(eventHandler)
356
+ this.eventSource = new NodeApiEventSource(filteringEventHandler)
318
357
  }
319
358
  }
320
359
 
@@ -3,8 +3,6 @@
3
3
  const { storage } = require('../../../../datadog-core')
4
4
 
5
5
  const dc = require('dc-polyfill')
6
- const { HTTP_METHOD, HTTP_ROUTE, RESOURCE_NAME, SPAN_TYPE } = require('../../../../../ext/tags')
7
- const { WEB } = require('../../../../../ext/types')
8
6
  const runtimeMetrics = require('../../runtime_metrics')
9
7
  const telemetryMetrics = require('../../telemetry/metrics')
10
8
  const {
@@ -15,6 +13,8 @@ const {
15
13
  getThreadLabels
16
14
  } = require('./shared')
17
15
 
16
+ const { isWebServerSpan, endpointNameFromTags, getStartedSpans } = require('../webspan-utils')
17
+
18
18
  const beforeCh = dc.channel('dd-trace:storage:before')
19
19
  const enterCh = dc.channel('dd-trace:storage:enter')
20
20
  const spanFinishCh = dc.channel('dd-trace:span:finish')
@@ -29,21 +29,6 @@ function getActiveSpan () {
29
29
  return store && store.span
30
30
  }
31
31
 
32
- function getStartedSpans (context) {
33
- return context._trace.started
34
- }
35
-
36
- function isWebServerSpan (tags) {
37
- return tags[SPAN_TYPE] === WEB
38
- }
39
-
40
- function endpointNameFromTags (tags) {
41
- return tags[RESOURCE_NAME] || [
42
- tags[HTTP_METHOD],
43
- tags[HTTP_ROUTE]
44
- ].filter(v => v).join(' ')
45
- }
46
-
47
32
  let channelsActivated = false
48
33
  function ensureChannelsActivated () {
49
34
  if (channelsActivated) return
@@ -0,0 +1,23 @@
1
+ const { HTTP_METHOD, HTTP_ROUTE, RESOURCE_NAME, SPAN_TYPE } = require('../../../../ext/tags')
2
+ const { WEB } = require('../../../../ext/types')
3
+
4
+ function isWebServerSpan (tags) {
5
+ return tags[SPAN_TYPE] === WEB
6
+ }
7
+
8
+ function endpointNameFromTags (tags) {
9
+ return tags[RESOURCE_NAME] || [
10
+ tags[HTTP_METHOD],
11
+ tags[HTTP_ROUTE]
12
+ ].filter(v => v).join(' ')
13
+ }
14
+
15
+ function getStartedSpans (context) {
16
+ return context._trace.started
17
+ }
18
+
19
+ module.exports = {
20
+ isWebServerSpan,
21
+ endpointNameFromTags,
22
+ getStartedSpans
23
+ }
@@ -181,8 +181,13 @@ class Tracer extends NoopProxy {
181
181
  )
182
182
  }
183
183
  }
184
+
185
+ if (config.isTestDynamicInstrumentationEnabled) {
186
+ const testVisibilityDynamicInstrumentation = require('./ci-visibility/dynamic-instrumentation')
187
+ testVisibilityDynamicInstrumentation.start()
188
+ }
184
189
  } catch (e) {
185
- log.error(e)
190
+ log.error('Error initialising tracer', e)
186
191
  }
187
192
 
188
193
  return this
@@ -193,7 +198,7 @@ class Tracer extends NoopProxy {
193
198
  try {
194
199
  return require('./profiler').start(config)
195
200
  } catch (e) {
196
- log.error(e)
201
+ log.error('Error starting profiler', e)
197
202
  }
198
203
  }
199
204
 
@@ -7,11 +7,19 @@ const os = require('os')
7
7
  const { DogStatsDClient } = require('./dogstatsd')
8
8
  const log = require('./log')
9
9
  const Histogram = require('./histogram')
10
- const { performance } = require('perf_hooks')
10
+ const { performance, PerformanceObserver } = require('perf_hooks')
11
11
 
12
+ const { NODE_MAJOR, NODE_MINOR } = require('../../../version')
12
13
  const INTERVAL = 10 * 1000
13
14
 
15
+ // Node >=16 has PerformanceObserver with `gc` type, but <16.7 had a critical bug.
16
+ // See: https://github.com/nodejs/node/issues/39548
17
+ const hasGCObserver = NODE_MAJOR >= 18 || (NODE_MAJOR === 16 && NODE_MINOR >= 7)
18
+ const hasGCProfiler = NODE_MAJOR >= 20 || (NODE_MAJOR === 18 && NODE_MINOR >= 15)
19
+
14
20
  let nativeMetrics = null
21
+ let gcObserver = null
22
+ let gcProfiler = null
15
23
 
16
24
  let interval
17
25
  let client
@@ -24,15 +32,20 @@ let elu
24
32
 
25
33
  reset()
26
34
 
27
- module.exports = {
35
+ const runtimeMetrics = module.exports = {
28
36
  start (config) {
29
37
  const clientConfig = DogStatsDClient.generateClientConfig(config)
30
38
 
31
39
  try {
32
40
  nativeMetrics = require('@datadog/native-metrics')
33
- nativeMetrics.start()
41
+
42
+ if (hasGCObserver) {
43
+ nativeMetrics.start('loop') // Only add event loop watcher and not GC.
44
+ } else {
45
+ nativeMetrics.start()
46
+ }
34
47
  } catch (e) {
35
- log.error(e)
48
+ log.error('Error starting native metrics', e)
36
49
  nativeMetrics = null
37
50
  }
38
51
 
@@ -40,6 +53,9 @@ module.exports = {
40
53
 
41
54
  time = process.hrtime()
42
55
 
56
+ startGCObserver()
57
+ startGCProfiler()
58
+
43
59
  if (nativeMetrics) {
44
60
  interval = setInterval(() => {
45
61
  captureCommonMetrics()
@@ -138,6 +154,10 @@ function reset () {
138
154
  counters = {}
139
155
  histograms = {}
140
156
  nativeMetrics = null
157
+ gcObserver && gcObserver.disconnect()
158
+ gcObserver = null
159
+ gcProfiler && gcProfiler.stop()
160
+ gcProfiler = null
141
161
  }
142
162
 
143
163
  function captureCpuUsage () {
@@ -202,6 +222,29 @@ function captureHeapSpace () {
202
222
  client.gauge('runtime.node.heap.physical_size.by.space', stats[i].physical_space_size, tags)
203
223
  }
204
224
  }
225
+ function captureGCMetrics () {
226
+ if (!gcProfiler) return
227
+
228
+ const profile = gcProfiler.stop()
229
+ const pauseAll = new Histogram()
230
+ const pause = {}
231
+
232
+ for (const stat of profile.statistics) {
233
+ const type = stat.gcType.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase()
234
+
235
+ pause[type] = pause[type] || new Histogram()
236
+ pause[type].record(stat.cost)
237
+ pauseAll.record(stat.cost)
238
+ }
239
+
240
+ histogram('runtime.node.gc.pause', pauseAll)
241
+
242
+ for (const type in pause) {
243
+ histogram('runtime.node.gc.pause.by.type', pause[type], [`gc_type:${type}`])
244
+ }
245
+
246
+ gcProfiler.start()
247
+ }
205
248
 
206
249
  function captureGauges () {
207
250
  Object.keys(gauges).forEach(name => {
@@ -256,6 +299,7 @@ function captureCommonMetrics () {
256
299
  captureCounters()
257
300
  captureHistograms()
258
301
  captureELU()
302
+ captureGCMetrics()
259
303
  }
260
304
 
261
305
  function captureNativeMetrics () {
@@ -297,6 +341,11 @@ function captureNativeMetrics () {
297
341
  function histogram (name, stats, tags) {
298
342
  tags = [].concat(tags)
299
343
 
344
+ // Stats can contain garbage data when a value was never recorded.
345
+ if (stats.count === 0) {
346
+ stats = { max: 0, min: 0, sum: 0, avg: 0, median: 0, p95: 0, count: 0 }
347
+ }
348
+
300
349
  client.gauge(`${name}.min`, stats.min, tags)
301
350
  client.gauge(`${name}.max`, stats.max, tags)
302
351
  client.increment(`${name}.sum`, stats.sum, tags)
@@ -306,3 +355,57 @@ function histogram (name, stats, tags) {
306
355
  client.gauge(`${name}.median`, stats.median, tags)
307
356
  client.gauge(`${name}.95percentile`, stats.p95, tags)
308
357
  }
358
+
359
+ function startGCObserver () {
360
+ if (gcObserver || hasGCProfiler || !hasGCObserver) return
361
+
362
+ gcObserver = new PerformanceObserver(list => {
363
+ for (const entry of list.getEntries()) {
364
+ const type = gcType(entry.detail?.kind || entry.kind)
365
+
366
+ runtimeMetrics.histogram('runtime.node.gc.pause.by.type', entry.duration, `gc_type:${type}`)
367
+ runtimeMetrics.histogram('runtime.node.gc.pause', entry.duration)
368
+ }
369
+ })
370
+
371
+ gcObserver.observe({ type: 'gc' })
372
+ }
373
+
374
+ function startGCProfiler () {
375
+ if (gcProfiler || !hasGCProfiler) return
376
+
377
+ gcProfiler = new v8.GCProfiler()
378
+ gcProfiler.start()
379
+ }
380
+
381
+ function gcType (kind) {
382
+ if (NODE_MAJOR >= 22) {
383
+ switch (kind) {
384
+ case 1: return 'scavenge'
385
+ case 2: return 'minor_mark_sweep'
386
+ case 4: return 'mark_sweep_compact' // Deprecated, might be removed soon.
387
+ case 8: return 'incremental_marking'
388
+ case 16: return 'process_weak_callbacks'
389
+ case 31: return 'all'
390
+ }
391
+ } else if (NODE_MAJOR >= 18) {
392
+ switch (kind) {
393
+ case 1: return 'scavenge'
394
+ case 2: return 'minor_mark_compact'
395
+ case 4: return 'mark_sweep_compact'
396
+ case 8: return 'incremental_marking'
397
+ case 16: return 'process_weak_callbacks'
398
+ case 31: return 'all'
399
+ }
400
+ } else {
401
+ switch (kind) {
402
+ case 1: return 'scavenge'
403
+ case 2: return 'mark_sweep_compact'
404
+ case 4: return 'incremental_marking'
405
+ case 8: return 'process_weak_callbacks'
406
+ case 15: return 'all'
407
+ }
408
+ }
409
+
410
+ return 'unknown'
411
+ }
@@ -23,7 +23,7 @@ function maybeStartServerlessMiniAgent (config) {
23
23
  try {
24
24
  require('child_process').spawn(rustBinaryPath, { stdio: 'inherit' })
25
25
  } catch (err) {
26
- log.error(`Error spawning mini agent process: ${err}`)
26
+ log.error('Error spawning mini agent process: %s', err.message)
27
27
  }
28
28
  }
29
29
 
@@ -87,22 +87,22 @@ class SpanProcessor {
87
87
  const id = context.toSpanId()
88
88
 
89
89
  if (finished.has(span)) {
90
- log.error(`Span was already finished in the same trace: ${span}`)
90
+ log.error('Span was already finished in the same trace: %s', span)
91
91
  } else {
92
92
  finished.add(span)
93
93
 
94
94
  if (finishedIds.has(id)) {
95
- log.error(`Another span with the same ID was already finished in the same trace: ${span}`)
95
+ log.error('Another span with the same ID was already finished in the same trace: %s', span)
96
96
  } else {
97
97
  finishedIds.add(id)
98
98
  }
99
99
 
100
100
  if (context._trace !== trace) {
101
- log.error(`A span was finished in the wrong trace: ${span}.`)
101
+ log.error('A span was finished in the wrong trace: %s', span)
102
102
  }
103
103
 
104
104
  if (finishedSpans.has(span)) {
105
- log.error(`Span was already finished in a different trace: ${span}`)
105
+ log.error('Span was already finished in a different trace: %s', span)
106
106
  } else {
107
107
  finishedSpans.add(span)
108
108
  }
@@ -114,35 +114,35 @@ class SpanProcessor {
114
114
  const id = context.toSpanId()
115
115
 
116
116
  if (started.has(span)) {
117
- log.error(`Span was already started in the same trace: ${span}`)
117
+ log.error('Span was already started in the same trace: %s', span)
118
118
  } else {
119
119
  started.add(span)
120
120
 
121
121
  if (startedIds.has(id)) {
122
- log.error(`Another span with the same ID was already started in the same trace: ${span}`)
122
+ log.error('Another span with the same ID was already started in the same trace: %s', span)
123
123
  } else {
124
124
  startedIds.add(id)
125
125
  }
126
126
 
127
127
  if (context._trace !== trace) {
128
- log.error(`A span was started in the wrong trace: ${span}.`)
128
+ log.error('A span was started in the wrong trace: %s', span)
129
129
  }
130
130
 
131
131
  if (startedSpans.has(span)) {
132
- log.error(`Span was already started in a different trace: ${span}`)
132
+ log.error('Span was already started in a different trace: %s', span)
133
133
  } else {
134
134
  startedSpans.add(span)
135
135
  }
136
136
  }
137
137
 
138
138
  if (!finished.has(span)) {
139
- log.error(`Span started in one trace but was finished in another trace: ${span}`)
139
+ log.error('Span started in one trace but was finished in another trace: %s', span)
140
140
  }
141
141
  }
142
142
 
143
143
  for (const span of trace.finished) {
144
144
  if (!started.has(span)) {
145
- log.error(`Span finished in one trace but was started in another trace: ${span}`)
145
+ log.error('Span finished in one trace but was started in another trace: %s', span)
146
146
  }
147
147
  }
148
148
  }
@@ -44,7 +44,7 @@ function add (carrier, keyValuePairs, parseOtelTags = false) {
44
44
  Object.assign(carrier, keyValuePairs)
45
45
  }
46
46
  } catch (e) {
47
- log.error(e)
47
+ log.error('Error adding tags', e)
48
48
  }
49
49
  }
50
50
 
@@ -137,6 +137,7 @@ function appClosing () {
137
137
  sendData(config, application, host, reqType, payload)
138
138
  // We flush before shutting down.
139
139
  metricsManager.send(config, application, host)
140
+ telemetryLogger.send(config, application, host)
140
141
  }
141
142
 
142
143
  function onBeforeExit () {
@@ -40,6 +40,7 @@ function onErrorLog (msg) {
40
40
 
41
41
  const telLog = {
42
42
  level: 'ERROR',
43
+ count: 1,
43
44
 
44
45
  // existing log.error(err) without message will be reported as 'Generic Error'
45
46
  message: message ?? 'Generic Error'
@@ -47,8 +48,7 @@ function onErrorLog (msg) {
47
48
 
48
49
  if (cause) {
49
50
  telLog.stack_trace = cause.stack
50
- const errorType = cause.name ?? 'Error'
51
- telLog.message = `${errorType}: ${telLog.message}`
51
+ telLog.errorType = cause.constructor.name
52
52
  }
53
53
 
54
54
  onLog(telLog)
@@ -3,7 +3,7 @@
3
3
  const log = require('../../log')
4
4
  const { calculateDDBasePath } = require('../../util')
5
5
 
6
- const logs = new Map()
6
+ const logs = new Map() // hash -> log
7
7
 
8
8
  // NOTE: Is this a reasonable number?
9
9
  let maxEntries = 10000
@@ -47,8 +47,14 @@ function sanitize (logEntry) {
47
47
  .filter((line, index) => (isDDCode && index < firstIndex) || line.includes(ddBasePath))
48
48
  .map(line => line.replace(ddBasePath, ''))
49
49
 
50
+ if (!isDDCode && logEntry.errorType && stackLines.length) {
51
+ stackLines = [`${logEntry.errorType}: redacted`, ...stackLines]
52
+ }
53
+
54
+ delete logEntry.errorType
55
+
50
56
  logEntry.stack_trace = stackLines.join(EOL)
51
- if (logEntry.stack_trace === '' && !logEntry.message) {
57
+ if (logEntry.stack_trace === '' && (!logEntry.message || logEntry.message === 'Generic Error')) {
52
58
  // If entire stack was removed and there is no message we'd rather not log it at all.
53
59
  return null
54
60
  }
@@ -75,6 +81,8 @@ const logCollector = {
75
81
  if (!logs.has(hash)) {
76
82
  logs.set(hash, logEntry)
77
83
  return true
84
+ } else {
85
+ logs.get(hash).count++
78
86
  }
79
87
  } catch (e) {
80
88
  log.error('Unable to add log to logCollector: %s', e.message)
@@ -57,7 +57,7 @@ function sendData (config, application, host, reqType, payload = {}, cb = () =>
57
57
  try {
58
58
  url = url || new URL(getAgentlessTelemetryEndpoint(config.site))
59
59
  } catch (err) {
60
- log.error(err)
60
+ log.error('Telemetry endpoint url is invalid', err)
61
61
  // No point to do the request if the URL is invalid
62
62
  return cb(err, { payload, reqType })
63
63
  }
@@ -100,7 +100,7 @@ function sendData (config, application, host, reqType, payload = {}, cb = () =>
100
100
  path: '/api/v2/apmtelemetry'
101
101
  }
102
102
  if (backendUrl) {
103
- request(data, backendOptions, (error) => { log.error(error) })
103
+ request(data, backendOptions, (error) => { log.error('Error sending telemetry data', error) })
104
104
  } else {
105
105
  log.error('Invalid Telemetry URL')
106
106
  }
@@ -1,6 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const crypto = require('crypto')
4
3
  const path = require('path')
5
4
 
6
5
  function isTrue (str) {
@@ -25,6 +24,8 @@ function isError (value) {
25
24
 
26
25
  // Matches a glob pattern to a given subject string
27
26
  function globMatch (pattern, subject) {
27
+ if (typeof pattern === 'string') pattern = pattern.toLowerCase()
28
+ if (typeof subject === 'string') subject = subject.toLowerCase()
28
29
  let px = 0 // [p]attern inde[x]
29
30
  let sx = 0 // [s]ubject inde[x]
30
31
  let nextPx = 0
@@ -64,6 +65,8 @@ function globMatch (pattern, subject) {
64
65
  return true
65
66
  }
66
67
 
68
+ // TODO: this adds stack traces relative to packages/
69
+ // shouldn't paths be relative to the root of dd-trace?
67
70
  function calculateDDBasePath (dirname) {
68
71
  const dirSteps = dirname.split(path.sep)
69
72
  const packagesIndex = dirSteps.lastIndexOf('packages')
@@ -74,25 +77,11 @@ function hasOwn (object, prop) {
74
77
  return Object.prototype.hasOwnProperty.call(object, prop)
75
78
  }
76
79
 
77
- /**
78
- * Generates a unique hash from an array of strings by joining them with | before hashing.
79
- * Used to uniquely identify AWS requests for span pointers.
80
- * @param {string[]} components - Array of strings to hash
81
- * @returns {string} A 32-character hash uniquely identifying the components
82
- */
83
- function generatePointerHash (components) {
84
- // If passing S3's ETag as a component, make sure any quotes have already been removed!
85
- const dataToHash = components.join('|')
86
- const hash = crypto.createHash('sha256').update(dataToHash).digest('hex')
87
- return hash.substring(0, 32)
88
- }
89
-
90
80
  module.exports = {
91
81
  isTrue,
92
82
  isFalse,
93
83
  isError,
94
84
  globMatch,
95
85
  calculateDDBasePath,
96
- hasOwn,
97
- generatePointerHash
86
+ hasOwn
98
87
  }
@@ -1,24 +0,0 @@
1
- 'use strict'
2
-
3
- const { addHook, channel } = require('./helpers/instrument')
4
- const shimmer = require('../../datadog-shimmer')
5
-
6
- const qsParseCh = channel('datadog:qs:parse:finish')
7
-
8
- function wrapParse (originalParse) {
9
- return function () {
10
- const qsParsedObj = originalParse.apply(this, arguments)
11
- if (qsParseCh.hasSubscribers && qsParsedObj) {
12
- qsParseCh.publish({ qs: qsParsedObj })
13
- }
14
- return qsParsedObj
15
- }
16
- }
17
-
18
- addHook({
19
- name: 'qs',
20
- versions: ['>=1']
21
- }, qs => {
22
- shimmer.wrap(qs, 'parse', wrapParse)
23
- return qs
24
- })