dd-trace 5.30.0 → 5.32.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 (74) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/README.md +9 -7
  3. package/package.json +7 -6
  4. package/packages/datadog-core/src/storage.js +11 -2
  5. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  6. package/packages/datadog-instrumentations/src/aws-sdk.js +2 -1
  7. package/packages/datadog-instrumentations/src/cucumber.js +14 -5
  8. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  9. package/packages/datadog-instrumentations/src/jest.js +70 -36
  10. package/packages/datadog-instrumentations/src/mocha/utils.js +23 -7
  11. package/packages/datadog-instrumentations/src/node-serialize.js +22 -0
  12. package/packages/datadog-instrumentations/src/openai.js +2 -0
  13. package/packages/datadog-instrumentations/src/vitest.js +107 -59
  14. package/packages/datadog-instrumentations/src/vm.js +49 -0
  15. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime.js +295 -0
  16. package/packages/datadog-plugin-aws-sdk/src/services/index.js +1 -0
  17. package/packages/datadog-plugin-cucumber/src/index.js +30 -32
  18. package/packages/datadog-plugin-jest/src/index.js +34 -37
  19. package/packages/datadog-plugin-langchain/src/index.js +12 -80
  20. package/packages/datadog-plugin-langchain/src/tracing.js +89 -0
  21. package/packages/datadog-plugin-mocha/src/index.js +18 -36
  22. package/packages/datadog-plugin-vitest/src/index.js +20 -34
  23. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  24. package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +2 -0
  25. package/packages/dd-trace/src/appsec/iast/analyzers/untrusted-deserialization-analyzer.js +16 -0
  26. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +9 -8
  27. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  28. package/packages/dd-trace/src/appsec/remote_config/manager.js +11 -1
  29. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +37 -0
  30. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +65 -28
  31. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +57 -17
  32. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +18 -3
  33. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +20 -3
  34. package/packages/dd-trace/src/config.js +39 -3
  35. package/packages/dd-trace/src/crashtracking/crashtracker.js +9 -0
  36. package/packages/dd-trace/src/crashtracking/noop.js +3 -0
  37. package/packages/dd-trace/src/datastreams/fnv.js +1 -1
  38. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -2
  39. package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -1
  40. package/packages/dd-trace/src/debugger/devtools_client/defaults.js +1 -0
  41. package/packages/dd-trace/src/debugger/devtools_client/index.js +32 -14
  42. package/packages/dd-trace/src/debugger/devtools_client/json-buffer.js +36 -0
  43. package/packages/dd-trace/src/debugger/devtools_client/send.js +29 -10
  44. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +35 -1
  45. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +112 -0
  46. package/packages/dd-trace/src/debugger/devtools_client/status.js +20 -11
  47. package/packages/dd-trace/src/debugger/index.js +2 -13
  48. package/packages/dd-trace/src/llmobs/plugins/base.js +40 -11
  49. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chain.js +24 -0
  50. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +111 -0
  51. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/embedding.js +42 -0
  52. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +102 -0
  53. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/llm.js +32 -0
  54. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +131 -0
  55. package/packages/dd-trace/src/llmobs/plugins/openai.js +1 -1
  56. package/packages/dd-trace/src/llmobs/sdk.js +90 -26
  57. package/packages/dd-trace/src/llmobs/tagger.js +11 -3
  58. package/packages/dd-trace/src/llmobs/util.js +7 -1
  59. package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +3 -3
  60. package/packages/dd-trace/src/log/index.js +8 -9
  61. package/packages/dd-trace/src/noop/proxy.js +2 -2
  62. package/packages/dd-trace/src/noop/span.js +1 -1
  63. package/packages/dd-trace/src/opentelemetry/context_manager.js +43 -3
  64. package/packages/dd-trace/src/opentracing/span.js +11 -1
  65. package/packages/dd-trace/src/opentracing/span_context.js +12 -0
  66. package/packages/dd-trace/src/plugins/ci_plugin.js +57 -27
  67. package/packages/dd-trace/src/plugins/util/test.js +42 -12
  68. package/packages/dd-trace/src/priority_sampler.js +7 -2
  69. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +21 -0
  70. package/packages/dd-trace/src/profiling/profiler.js +11 -8
  71. package/packages/dd-trace/src/profiling/profilers/events.js +17 -1
  72. package/packages/dd-trace/src/proxy.js +6 -3
  73. package/packages/dd-trace/src/scope.js +1 -1
  74. package/packages/dd-trace/src/telemetry/index.js +2 -0
@@ -10,7 +10,7 @@ const noopAppsec = new NoopAppsecSdk()
10
10
  const noopDogStatsDClient = new NoopDogStatsDClient()
11
11
  const noopLLMObs = new NoopLLMObsSDK(noop)
12
12
 
13
- class Tracer {
13
+ class NoopProxy {
14
14
  constructor () {
15
15
  this._tracer = noop
16
16
  this.appsec = noopAppsec
@@ -91,4 +91,4 @@ class Tracer {
91
91
  }
92
92
  }
93
93
 
94
- module.exports = Tracer
94
+ module.exports = NoopProxy
@@ -6,7 +6,7 @@ const { storage } = require('../../../datadog-core') // TODO: noop storage?
6
6
 
7
7
  class NoopSpan {
8
8
  constructor (tracer, parent) {
9
- this._store = storage.getStore()
9
+ this._store = storage.getHandle()
10
10
  this._noopTracer = tracer
11
11
  this._noopContext = this._createContext(parent)
12
12
  }
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { storage } = require('../../../datadog-core')
4
- const { trace, ROOT_CONTEXT } = require('@opentelemetry/api')
4
+ const { trace, ROOT_CONTEXT, propagation } = require('@opentelemetry/api')
5
5
  const DataDogSpanContext = require('../opentracing/span_context')
6
6
 
7
7
  const SpanContext = require('./span_context')
@@ -18,17 +18,40 @@ class ContextManager {
18
18
  const context = (activeSpan && activeSpan.context()) || store || ROOT_CONTEXT
19
19
 
20
20
  if (!(context instanceof DataDogSpanContext)) {
21
+ const span = trace.getSpan(context)
22
+ // span instanceof NonRecordingSpan
23
+ if (span && span._spanContext && span._spanContext._ddContext && span._spanContext._ddContext._baggageItems) {
24
+ const baggages = span._spanContext._ddContext._baggageItems
25
+ const entries = {}
26
+ for (const [key, value] of Object.entries(baggages)) {
27
+ entries[key] = { value }
28
+ }
29
+ const otelBaggages = propagation.createBaggage(entries)
30
+ return propagation.setBaggage(context, otelBaggages)
31
+ }
21
32
  return context
22
33
  }
23
34
 
35
+ const baggages = JSON.parse(activeSpan.getAllBaggageItems())
36
+ const entries = {}
37
+ for (const [key, value] of Object.entries(baggages)) {
38
+ entries[key] = { value }
39
+ }
40
+ const otelBaggages = propagation.createBaggage(entries)
41
+
24
42
  if (!context._otelSpanContext) {
25
43
  const newSpanContext = new SpanContext(context)
26
44
  context._otelSpanContext = newSpanContext
27
45
  }
28
46
  if (store && trace.getSpanContext(store) === context._otelSpanContext) {
29
- return store
47
+ return otelBaggages
48
+ ? propagation.setBaggage(store, otelBaggages)
49
+ : store
30
50
  }
31
- return trace.setSpanContext(store || ROOT_CONTEXT, context._otelSpanContext)
51
+ const wrappedContext = trace.setSpanContext(store || ROOT_CONTEXT, context._otelSpanContext)
52
+ return otelBaggages
53
+ ? propagation.setBaggage(wrappedContext, otelBaggages)
54
+ : wrappedContext
32
55
  }
33
56
 
34
57
  with (context, fn, thisArg, ...args) {
@@ -38,9 +61,26 @@ class ContextManager {
38
61
  const cb = thisArg == null ? fn : fn.bind(thisArg)
39
62
  return this._store.run(context, cb, ...args)
40
63
  }
64
+ const baggages = propagation.getBaggage(context)
65
+ let baggageItems = []
66
+ if (baggages) {
67
+ baggageItems = baggages.getAllEntries()
68
+ }
41
69
  if (span && span._ddSpan) {
70
+ // does otel always override datadog?
71
+ span._ddSpan.removeAllBaggageItems()
72
+ for (const baggage of baggageItems) {
73
+ span._ddSpan.setBaggageItem(baggage[0], baggage[1].value)
74
+ }
42
75
  return ddScope.activate(span._ddSpan, run)
43
76
  }
77
+ // span instanceof NonRecordingSpan
78
+ if (span && span._spanContext && span._spanContext._ddContext && span._spanContext._ddContext._baggageItems) {
79
+ span._spanContext._ddContext._baggageItems = {}
80
+ for (const baggage of baggageItems) {
81
+ span._spanContext._ddContext._baggageItems[baggage[0]] = baggage[1].value
82
+ }
83
+ }
44
84
  return run()
45
85
  }
46
86
 
@@ -14,6 +14,7 @@ const { storage } = require('../../../datadog-core')
14
14
  const telemetryMetrics = require('../telemetry/metrics')
15
15
  const { channel } = require('dc-polyfill')
16
16
  const spanleak = require('../spanleak')
17
+ const util = require('util')
17
18
 
18
19
  const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
19
20
 
@@ -64,7 +65,7 @@ class DatadogSpan {
64
65
  this._debug = debug
65
66
  this._processor = processor
66
67
  this._prioritySampler = prioritySampler
67
- this._store = storage.getStore()
68
+ this._store = storage.getHandle()
68
69
  this._duration = undefined
69
70
 
70
71
  this._events = []
@@ -105,6 +106,15 @@ class DatadogSpan {
105
106
  }
106
107
  }
107
108
 
109
+ [util.inspect.custom] () {
110
+ return {
111
+ ...this,
112
+ _parentTracer: `[${this._parentTracer.constructor.name}]`,
113
+ _prioritySampler: `[${this._prioritySampler.constructor.name}]`,
114
+ _processor: `[${this._processor.constructor.name}]`
115
+ }
116
+ }
117
+
108
118
  toString () {
109
119
  const spanContext = this.context()
110
120
  const resourceName = spanContext._tags['resource.name'] || ''
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const util = require('util')
3
4
  const { AUTO_KEEP } = require('../../../../ext/priority')
4
5
 
5
6
  // the lowercase, hex encoded upper 64 bits of a 128-bit trace id, if present
@@ -31,6 +32,17 @@ class DatadogSpanContext {
31
32
  this._otelSpanContext = undefined
32
33
  }
33
34
 
35
+ [util.inspect.custom] () {
36
+ return {
37
+ ...this,
38
+ _trace: {
39
+ ...this._trace,
40
+ started: '[Array]',
41
+ finished: '[Array]'
42
+ }
43
+ }
44
+ }
45
+
34
46
  toTraceId (get128bitId = false) {
35
47
  if (get128bitId) {
36
48
  return this._traceId.toBuffer().length <= 8 && this._trace.tags[TRACE_ID_128]
@@ -23,7 +23,11 @@ const {
23
23
  TEST_LEVEL_EVENT_TYPES,
24
24
  TEST_SUITE,
25
25
  getFileAndLineNumberFromError,
26
- getTestSuitePath
26
+ DI_ERROR_DEBUG_INFO_CAPTURED,
27
+ DI_DEBUG_ERROR_PREFIX,
28
+ DI_DEBUG_ERROR_SNAPSHOT_ID_SUFFIX,
29
+ DI_DEBUG_ERROR_FILE_SUFFIX,
30
+ DI_DEBUG_ERROR_LINE_SUFFIX
27
31
  } = require('./util/test')
28
32
  const Plugin = require('./plugin')
29
33
  const { COMPONENT } = require('../constants')
@@ -180,14 +184,18 @@ module.exports = class CiPlugin extends Plugin {
180
184
  }
181
185
  }
182
186
 
183
- configure (config) {
187
+ configure (config, shouldGetEnvironmentData = true) {
184
188
  super.configure(config)
185
189
 
186
- if (config.isTestDynamicInstrumentationEnabled) {
190
+ if (config.isTestDynamicInstrumentationEnabled && !this.di) {
187
191
  const testVisibilityDynamicInstrumentation = require('../ci-visibility/dynamic-instrumentation')
188
192
  this.di = testVisibilityDynamicInstrumentation
189
193
  }
190
194
 
195
+ if (!shouldGetEnvironmentData) {
196
+ return
197
+ }
198
+
191
199
  this.testEnvironmentMetadata = getTestEnvironmentMetadata(this.constructor.id, this.config)
192
200
 
193
201
  const {
@@ -292,37 +300,59 @@ module.exports = class CiPlugin extends Plugin {
292
300
  return testSpan
293
301
  }
294
302
 
295
- // TODO: If the test finishes and the probe is not hit, we should remove the breakpoint
296
- addDiProbe (err, probe) {
297
- const [file, line] = getFileAndLineNumberFromError(err)
303
+ onDiBreakpointHit ({ snapshot }) {
304
+ if (!this.activeTestSpan || this.activeTestSpan.context()._isFinished) {
305
+ // This is unexpected and is caused by a race condition.
306
+ log.warn('Breakpoint snapshot could not be attached to the active test span')
307
+ return
308
+ }
298
309
 
299
- const relativePath = getTestSuitePath(file, this.repositoryRoot)
310
+ const stackIndex = this.testErrorStackIndex
311
+
312
+ this.activeTestSpan.setTag(DI_ERROR_DEBUG_INFO_CAPTURED, 'true')
313
+ this.activeTestSpan.setTag(
314
+ `${DI_DEBUG_ERROR_PREFIX}.${stackIndex}.${DI_DEBUG_ERROR_SNAPSHOT_ID_SUFFIX}`,
315
+ snapshot.id
316
+ )
317
+ this.activeTestSpan.setTag(
318
+ `${DI_DEBUG_ERROR_PREFIX}.${stackIndex}.${DI_DEBUG_ERROR_FILE_SUFFIX}`,
319
+ snapshot.probe.location.file
320
+ )
321
+ this.activeTestSpan.setTag(
322
+ `${DI_DEBUG_ERROR_PREFIX}.${stackIndex}.${DI_DEBUG_ERROR_LINE_SUFFIX}`,
323
+ Number(snapshot.probe.location.lines[0])
324
+ )
325
+
326
+ const activeTestSpanContext = this.activeTestSpan.context()
327
+
328
+ this.tracer._exporter.exportDiLogs(this.testEnvironmentMetadata, {
329
+ debugger: { snapshot },
330
+ dd: {
331
+ trace_id: activeTestSpanContext.toTraceId(),
332
+ span_id: activeTestSpanContext.toSpanId()
333
+ }
334
+ })
335
+ }
300
336
 
301
- const [
302
- snapshotId,
303
- setProbePromise,
304
- hitProbePromise
305
- ] = this.di.addLineProbe({ file: relativePath, line })
337
+ removeDiProbe (probeId) {
338
+ return this.di.removeProbe(probeId)
339
+ }
340
+
341
+ addDiProbe (err) {
342
+ const [file, line, stackIndex] = getFileAndLineNumberFromError(err, this.repositoryRoot)
306
343
 
307
- if (probe) { // not all frameworks may sync with the set probe promise
308
- probe.setProbePromise = setProbePromise
344
+ if (!file || !Number.isInteger(line)) {
345
+ log.warn('Could not add breakpoint for dynamic instrumentation')
346
+ return
309
347
  }
310
348
 
311
- hitProbePromise.then(({ snapshot }) => {
312
- // TODO: handle race conditions for this.retriedTestIds
313
- const { traceId, spanId } = this.retriedTestIds
314
- this.tracer._exporter.exportDiLogs(this.testEnvironmentMetadata, {
315
- debugger: { snapshot },
316
- dd: {
317
- trace_id: traceId,
318
- span_id: spanId
319
- }
320
- })
321
- })
349
+ const [probeId, setProbePromise] = this.di.addLineProbe({ file, line }, this.onDiBreakpointHit.bind(this))
322
350
 
323
351
  return {
324
- snapshotId,
325
- file: relativePath,
352
+ probeId,
353
+ setProbePromise,
354
+ stackIndex,
355
+ file,
326
356
  line
327
357
  }
328
358
  }
@@ -88,6 +88,7 @@ const TEST_BROWSER_VERSION = 'test.browser.version'
88
88
  // jest worker variables
89
89
  const JEST_WORKER_TRACE_PAYLOAD_CODE = 60
90
90
  const JEST_WORKER_COVERAGE_PAYLOAD_CODE = 61
91
+ const JEST_WORKER_LOGS_PAYLOAD_CODE = 62
91
92
 
92
93
  // cucumber worker variables
93
94
  const CUCUMBER_WORKER_TRACE_PAYLOAD_CODE = 70
@@ -108,10 +109,10 @@ const TEST_LEVEL_EVENT_TYPES = [
108
109
 
109
110
  // Dynamic instrumentation - Test optimization integration tags
110
111
  const DI_ERROR_DEBUG_INFO_CAPTURED = 'error.debug_info_captured'
111
- // TODO: for the moment we'll only use a single snapshot id, so `0` is hardcoded
112
- const DI_DEBUG_ERROR_SNAPSHOT_ID = '_dd.debug.error.0.snapshot_id'
113
- const DI_DEBUG_ERROR_FILE = '_dd.debug.error.0.file'
114
- const DI_DEBUG_ERROR_LINE = '_dd.debug.error.0.line'
112
+ const DI_DEBUG_ERROR_PREFIX = '_dd.debug.error'
113
+ const DI_DEBUG_ERROR_SNAPSHOT_ID_SUFFIX = 'snapshot_id'
114
+ const DI_DEBUG_ERROR_FILE_SUFFIX = 'file'
115
+ const DI_DEBUG_ERROR_LINE_SUFFIX = 'line'
115
116
 
116
117
  module.exports = {
117
118
  TEST_CODE_OWNERS,
@@ -134,6 +135,7 @@ module.exports = {
134
135
  LIBRARY_VERSION,
135
136
  JEST_WORKER_TRACE_PAYLOAD_CODE,
136
137
  JEST_WORKER_COVERAGE_PAYLOAD_CODE,
138
+ JEST_WORKER_LOGS_PAYLOAD_CODE,
137
139
  CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
138
140
  MOCHA_WORKER_TRACE_PAYLOAD_CODE,
139
141
  TEST_SOURCE_START,
@@ -191,9 +193,11 @@ module.exports = {
191
193
  getNumFromKnownTests,
192
194
  getFileAndLineNumberFromError,
193
195
  DI_ERROR_DEBUG_INFO_CAPTURED,
194
- DI_DEBUG_ERROR_SNAPSHOT_ID,
195
- DI_DEBUG_ERROR_FILE,
196
- DI_DEBUG_ERROR_LINE
196
+ DI_DEBUG_ERROR_PREFIX,
197
+ DI_DEBUG_ERROR_SNAPSHOT_ID_SUFFIX,
198
+ DI_DEBUG_ERROR_FILE_SUFFIX,
199
+ DI_DEBUG_ERROR_LINE_SUFFIX,
200
+ getFormattedError
197
201
  }
198
202
 
199
203
  // Returns pkg manager and its version, separated by '-', e.g. npm-8.15.0 or yarn-1.22.19
@@ -650,13 +654,30 @@ function getNumFromKnownTests (knownTests) {
650
654
  return totalNumTests
651
655
  }
652
656
 
653
- function getFileAndLineNumberFromError (error) {
657
+ const DEPENDENCY_FOLDERS = [
658
+ 'node_modules',
659
+ 'node:',
660
+ '.pnpm',
661
+ '.yarn',
662
+ '.pnp'
663
+ ]
664
+
665
+ function getFileAndLineNumberFromError (error, repositoryRoot) {
654
666
  // Split the stack trace into individual lines
655
667
  const stackLines = error.stack.split('\n')
656
668
 
657
- // The top frame is usually the second line
658
- const topFrame = stackLines[1]
669
+ // Remove potential messages on top of the stack that are not frames
670
+ const frames = stackLines.filter(line => line.includes('at ') && line.includes(repositoryRoot))
671
+
672
+ const topRelevantFrameIndex = frames.findIndex(line =>
673
+ line.includes(repositoryRoot) && !DEPENDENCY_FOLDERS.some(pattern => line.includes(pattern))
674
+ )
675
+
676
+ if (topRelevantFrameIndex === -1) {
677
+ return []
678
+ }
659
679
 
680
+ const topFrame = frames[topRelevantFrameIndex]
660
681
  // Regular expression to match the file path, line number, and column number
661
682
  const regex = /\s*at\s+(?:.*\()?(.+):(\d+):(\d+)\)?/
662
683
  const match = topFrame.match(regex)
@@ -664,9 +685,18 @@ function getFileAndLineNumberFromError (error) {
664
685
  if (match) {
665
686
  const filePath = match[1]
666
687
  const lineNumber = Number(match[2])
667
- const columnNumber = Number(match[3])
668
688
 
669
- return [filePath, lineNumber, columnNumber]
689
+ return [filePath, lineNumber, topRelevantFrameIndex]
670
690
  }
671
691
  return []
672
692
  }
693
+
694
+ function getFormattedError (error, repositoryRoot) {
695
+ const newError = new Error(error.message)
696
+ if (error.stack) {
697
+ newError.stack = error.stack.split('\n').filter(line => line.includes(repositoryRoot)).join('\n')
698
+ }
699
+ newError.name = error.name
700
+
701
+ return newError
702
+ }
@@ -120,13 +120,18 @@ class PrioritySampler {
120
120
  if (!span || !this.validate(samplingPriority)) return
121
121
 
122
122
  const context = this._getContext(span)
123
+ const root = context._trace.started[0]
124
+
125
+ if (!root) {
126
+ log.error('Skipping the setPriority on noop span')
127
+ return // noop span
128
+ }
123
129
 
124
130
  context._sampling.priority = samplingPriority
125
131
  context._sampling.mechanism = mechanism
126
132
 
127
- const root = context._trace.started[0]
128
-
129
133
  log.trace(span, samplingPriority, mechanism)
134
+
130
135
  this._addDecisionMaker(root)
131
136
  }
132
137
 
@@ -2,6 +2,22 @@ const os = require('os')
2
2
  const perf = require('perf_hooks').performance
3
3
  const version = require('../../../../../package.json').version
4
4
 
5
+ const libuvThreadPoolSize = (() => {
6
+ const ss = process.env.UV_THREADPOOL_SIZE
7
+ if (ss === undefined) {
8
+ // Backend will apply the default size based on Node version.
9
+ return undefined
10
+ }
11
+ // libuv uses atoi to parse the value, which is almost the same as parseInt, except that parseInt
12
+ // will return NaN on invalid input, while atoi will return 0. This is handled at return.
13
+ const s = parseInt(ss)
14
+ // We dont' interpret the value further here in the library. Backend will interpret the number
15
+ // based on Node version. In all currently known Node versions, 0 results in 1 worker thread,
16
+ // negative values (because they're assigned to an unsigned int) become very high positive values,
17
+ // and the value is finally capped at 1024.
18
+ return isNaN(s) ? 0 : s
19
+ })()
20
+
5
21
  class EventSerializer {
6
22
  constructor ({ env, host, service, version, libraryInjected, activation } = {}) {
7
23
  this._env = env
@@ -56,11 +72,16 @@ class EventSerializer {
56
72
  version
57
73
  },
58
74
  runtime: {
75
+ // os.availableParallelism only available in node 18.14.0/19.4.0 and above
76
+ available_processors: typeof os.availableParallelism === 'function'
77
+ ? os.availableParallelism()
78
+ : os.cpus().length,
59
79
  // Using `nodejs` for consistency with the existing `runtime` tag.
60
80
  // Note that the event `family` property uses `node`, as that's what's
61
81
  // proscribed by the Intake API, but that's an internal enum and is
62
82
  // not customer visible.
63
83
  engine: 'nodejs',
84
+ libuv_threadpool_size: libuvThreadPoolSize,
64
85
  // strip off leading 'v'. This makes the format consistent with other
65
86
  // runtimes (e.g. Ruby) but not with the existing `runtime_version` tag.
66
87
  // We'll keep it like this as we want cross-engine consistency. We
@@ -6,6 +6,7 @@ const { snapshotKinds } = require('./constants')
6
6
  const { threadNamePrefix } = require('./profilers/shared')
7
7
  const { isWebServerSpan, endpointNameFromTags, getStartedSpans } = require('./webspan-utils')
8
8
  const dc = require('dc-polyfill')
9
+ const crashtracker = require('../crashtracking')
9
10
 
10
11
  const profileSubmittedChannel = dc.channel('datadog:profiling:profile-submitted')
11
12
  const spanFinishedChannel = dc.channel('dd-trace:span:finish')
@@ -197,15 +198,17 @@ class Profiler extends EventEmitter {
197
198
  throw new Error('No profile types configured.')
198
199
  }
199
200
 
200
- // collect profiles synchronously so that profilers can be safely stopped asynchronously
201
- for (const profiler of this._config.profilers) {
202
- const profile = profiler.profile(restart, startDate, endDate)
203
- if (!restart) {
204
- this._logger.debug(`Stopped ${profiler.type} profiler in ${threadNamePrefix} thread`)
201
+ crashtracker.withProfilerSerializing(() => {
202
+ // collect profiles synchronously so that profilers can be safely stopped asynchronously
203
+ for (const profiler of this._config.profilers) {
204
+ const profile = profiler.profile(restart, startDate, endDate)
205
+ if (!restart) {
206
+ this._logger.debug(`Stopped ${profiler.type} profiler in ${threadNamePrefix} thread`)
207
+ }
208
+ if (!profile) continue
209
+ profiles.push({ profiler, profile })
205
210
  }
206
- if (!profile) continue
207
- profiles.push({ profiler, profile })
208
- }
211
+ })
209
212
 
210
213
  if (restart) {
211
214
  this._capture(this._timeoutInterval, endDate)
@@ -14,7 +14,23 @@ const pprofValueUnit = 'nanoseconds'
14
14
  const dateOffset = BigInt(Math.round(performance.timeOrigin * MS_TO_NS))
15
15
 
16
16
  function labelFromStr (stringTable, key, valStr) {
17
- return new Label({ key, str: stringTable.dedup(valStr) })
17
+ return new Label({ key, str: stringTable.dedup(safeToString(valStr)) })
18
+ }
19
+
20
+ // We don't want to invoke toString for objects and functions, rather we'll
21
+ // provide dummy values. These values are not meant to emulate built-in toString
22
+ // behavior.
23
+ function safeToString (val) {
24
+ switch (typeof val) {
25
+ case 'string':
26
+ return val
27
+ case 'object':
28
+ return '[object]'
29
+ case 'function':
30
+ return '[function]'
31
+ default:
32
+ return String(val)
33
+ }
18
34
  }
19
35
 
20
36
  function labelFromStrStr (stringTable, keyStr, valStr) {
@@ -119,7 +119,7 @@ class Tracer extends NoopProxy {
119
119
  this._flare.module.send(conf.args)
120
120
  })
121
121
 
122
- if (config.dynamicInstrumentationEnabled) {
122
+ if (config.dynamicInstrumentation.enabled) {
123
123
  DynamicInstrumentation.start(config, rc)
124
124
  }
125
125
  }
@@ -166,7 +166,10 @@ class Tracer extends NoopProxy {
166
166
  if (config.isManualApiEnabled) {
167
167
  const TestApiManualPlugin = require('./ci-visibility/test-api-manual/test-api-manual-plugin')
168
168
  this._testApiManualPlugin = new TestApiManualPlugin(this)
169
- this._testApiManualPlugin.configure({ ...config, enabled: true })
169
+ // `shouldGetEnvironmentData` is passed as false so that we only lazily calculate it
170
+ // This is the only place where we need to do this because the rest of the plugins
171
+ // are lazily configured when the library is imported.
172
+ this._testApiManualPlugin.configure({ ...config, enabled: true }, false)
170
173
  }
171
174
  }
172
175
  if (config.ciVisAgentlessLogSubmissionEnabled) {
@@ -184,7 +187,7 @@ class Tracer extends NoopProxy {
184
187
 
185
188
  if (config.isTestDynamicInstrumentationEnabled) {
186
189
  const testVisibilityDynamicInstrumentation = require('./ci-visibility/dynamic-instrumentation')
187
- testVisibilityDynamicInstrumentation.start()
190
+ testVisibilityDynamicInstrumentation.start(config)
188
191
  }
189
192
  } catch (e) {
190
193
  log.error('Error initialising tracer', e)
@@ -17,7 +17,7 @@ class Scope {
17
17
  if (typeof callback !== 'function') return callback
18
18
 
19
19
  const oldStore = storage.getStore()
20
- const newStore = span ? span._store : oldStore
20
+ const newStore = span ? storage.getStore(span._store) : oldStore
21
21
 
22
22
  storage.enterWith({ ...newStore, span })
23
23
 
@@ -307,6 +307,8 @@ function updateConfig (changes, config) {
307
307
  if (!config.telemetry.enabled) return
308
308
  if (changes.length === 0) return
309
309
 
310
+ logger.trace(changes)
311
+
310
312
  const application = createAppObject(config)
311
313
  const host = createHostObject()
312
314