dd-trace 5.24.0 → 5.25.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 (98) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/index.d.ts +335 -0
  3. package/package.json +13 -7
  4. package/packages/datadog-code-origin/index.js +4 -4
  5. package/packages/datadog-core/src/utils/src/parse-tags.js +33 -0
  6. package/packages/datadog-esbuild/index.js +4 -2
  7. package/packages/datadog-instrumentations/src/amqplib.js +65 -5
  8. package/packages/datadog-instrumentations/src/child_process.js +135 -27
  9. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  10. package/packages/datadog-instrumentations/src/helpers/register.js +9 -0
  11. package/packages/datadog-instrumentations/src/kafkajs.js +123 -63
  12. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -2
  13. package/packages/datadog-instrumentations/src/multer.js +37 -0
  14. package/packages/datadog-instrumentations/src/openai.js +2 -2
  15. package/packages/datadog-instrumentations/src/url.js +84 -0
  16. package/packages/datadog-instrumentations/src/utils/src/extract-package-and-module-path.js +7 -4
  17. package/packages/datadog-plugin-amqplib/src/consumer.js +4 -4
  18. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -0
  19. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -0
  20. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +1 -0
  21. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -0
  22. package/packages/datadog-plugin-fastify/src/code_origin.js +2 -2
  23. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +8 -1
  24. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +8 -0
  25. package/packages/datadog-plugin-grpc/src/client.js +3 -0
  26. package/packages/datadog-plugin-grpc/src/server.js +3 -0
  27. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +6 -3
  28. package/packages/datadog-plugin-kafkajs/src/consumer.js +8 -4
  29. package/packages/datadog-plugin-kafkajs/src/producer.js +10 -4
  30. package/packages/datadog-plugin-mocha/src/index.js +4 -1
  31. package/packages/datadog-plugin-openai/src/index.js +9 -1015
  32. package/packages/datadog-plugin-openai/src/tracing.js +1023 -0
  33. package/packages/dd-trace/src/appsec/addresses.js +2 -0
  34. package/packages/dd-trace/src/appsec/channels.js +3 -1
  35. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +55 -7
  36. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -2
  37. package/packages/dd-trace/src/appsec/index.js +3 -0
  38. package/packages/dd-trace/src/appsec/rasp/command_injection.js +49 -0
  39. package/packages/dd-trace/src/appsec/rasp/index.js +3 -0
  40. package/packages/dd-trace/src/appsec/rasp/ssrf.js +4 -3
  41. package/packages/dd-trace/src/appsec/rasp/utils.js +3 -2
  42. package/packages/dd-trace/src/appsec/recommended.json +2 -4
  43. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +1 -0
  44. package/packages/dd-trace/src/appsec/remote_config/index.js +2 -0
  45. package/packages/dd-trace/src/appsec/reporter.js +5 -4
  46. package/packages/dd-trace/src/appsec/sdk/track_event.js +5 -3
  47. package/packages/dd-trace/src/appsec/waf/waf_manager.js +4 -0
  48. package/packages/dd-trace/src/azure_metadata.js +120 -0
  49. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +97 -0
  50. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +90 -0
  51. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +19 -1
  52. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +53 -0
  53. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +8 -1
  54. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +43 -0
  55. package/packages/dd-trace/src/config.js +75 -6
  56. package/packages/dd-trace/src/constants.js +3 -1
  57. package/packages/dd-trace/src/datastreams/pathway.js +1 -0
  58. package/packages/dd-trace/src/debugger/devtools_client/index.js +9 -13
  59. package/packages/dd-trace/src/debugger/devtools_client/send.js +15 -1
  60. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +57 -23
  61. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +12 -2
  62. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +31 -20
  63. package/packages/dd-trace/src/debugger/devtools_client/snapshot/symbols.js +6 -0
  64. package/packages/dd-trace/src/debugger/devtools_client/state.js +11 -2
  65. package/packages/dd-trace/src/debugger/index.js +10 -3
  66. package/packages/dd-trace/src/llmobs/constants/tags.js +34 -0
  67. package/packages/dd-trace/src/llmobs/constants/text.js +6 -0
  68. package/packages/dd-trace/src/llmobs/constants/writers.js +13 -0
  69. package/packages/dd-trace/src/llmobs/index.js +103 -0
  70. package/packages/dd-trace/src/llmobs/noop.js +82 -0
  71. package/packages/dd-trace/src/llmobs/plugins/base.js +65 -0
  72. package/packages/dd-trace/src/llmobs/plugins/openai.js +205 -0
  73. package/packages/dd-trace/src/llmobs/sdk.js +377 -0
  74. package/packages/dd-trace/src/llmobs/span_processor.js +195 -0
  75. package/packages/dd-trace/src/llmobs/storage.js +7 -0
  76. package/packages/dd-trace/src/llmobs/tagger.js +322 -0
  77. package/packages/dd-trace/src/llmobs/util.js +176 -0
  78. package/packages/dd-trace/src/llmobs/writers/base.js +111 -0
  79. package/packages/dd-trace/src/llmobs/writers/evaluations.js +29 -0
  80. package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +23 -0
  81. package/packages/dd-trace/src/llmobs/writers/spans/agentless.js +17 -0
  82. package/packages/dd-trace/src/llmobs/writers/spans/base.js +49 -0
  83. package/packages/dd-trace/src/noop/proxy.js +3 -0
  84. package/packages/dd-trace/src/noop/span.js +3 -0
  85. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  86. package/packages/dd-trace/src/opentelemetry/tracer.js +1 -0
  87. package/packages/dd-trace/src/opentracing/propagation/text_map.js +73 -12
  88. package/packages/dd-trace/src/opentracing/span.js +12 -0
  89. package/packages/dd-trace/src/opentracing/tracer.js +8 -1
  90. package/packages/dd-trace/src/payload-tagging/config/aws.json +71 -3
  91. package/packages/dd-trace/src/plugins/outbound.js +9 -0
  92. package/packages/dd-trace/src/priority_sampler.js +16 -0
  93. package/packages/dd-trace/src/profiling/config.js +3 -1
  94. package/packages/dd-trace/src/profiling/exporters/agent.js +7 -5
  95. package/packages/dd-trace/src/profiling/profilers/wall.js +2 -1
  96. package/packages/dd-trace/src/proxy.js +8 -1
  97. package/packages/dd-trace/src/span_processor.js +5 -0
  98. package/packages/dd-trace/src/telemetry/index.js +11 -1
@@ -13,19 +13,38 @@ const childProcessChannel = dc.tracingChannel('datadog:child_process:execution')
13
13
 
14
14
  // ignored exec method because it calls to execFile directly
15
15
  const execAsyncMethods = ['execFile', 'spawn']
16
- const execSyncMethods = ['execFileSync', 'spawnSync']
17
16
 
18
17
  const names = ['child_process', 'node:child_process']
19
18
 
20
19
  // child_process and node:child_process returns the same object instance, we only want to add hooks once
21
20
  let patched = false
21
+
22
+ function throwSyncError (error) {
23
+ throw error
24
+ }
25
+
26
+ function returnSpawnSyncError (error, context) {
27
+ context.result = {
28
+ error,
29
+ status: null,
30
+ signal: null,
31
+ output: null,
32
+ stdout: null,
33
+ stderr: null,
34
+ pid: 0
35
+ }
36
+
37
+ return context.result
38
+ }
39
+
22
40
  names.forEach(name => {
23
41
  addHook({ name }, childProcess => {
24
42
  if (!patched) {
25
43
  patched = true
26
- shimmer.massWrap(childProcess, execAsyncMethods, wrapChildProcessAsyncMethod())
27
- shimmer.massWrap(childProcess, execSyncMethods, wrapChildProcessSyncMethod())
28
- shimmer.wrap(childProcess, 'execSync', wrapChildProcessSyncMethod(true))
44
+ shimmer.massWrap(childProcess, execAsyncMethods, wrapChildProcessAsyncMethod(childProcess.ChildProcess))
45
+ shimmer.wrap(childProcess, 'execSync', wrapChildProcessSyncMethod(throwSyncError, true))
46
+ shimmer.wrap(childProcess, 'execFileSync', wrapChildProcessSyncMethod(throwSyncError))
47
+ shimmer.wrap(childProcess, 'spawnSync', wrapChildProcessSyncMethod(returnSpawnSyncError))
29
48
  }
30
49
 
31
50
  return childProcess
@@ -34,17 +53,21 @@ names.forEach(name => {
34
53
 
35
54
  function normalizeArgs (args, shell) {
36
55
  const childProcessInfo = {
37
- command: args[0]
56
+ command: args[0],
57
+ file: args[0]
38
58
  }
39
59
 
40
60
  if (Array.isArray(args[1])) {
41
61
  childProcessInfo.command = childProcessInfo.command + ' ' + args[1].join(' ')
62
+ childProcessInfo.fileArgs = args[1]
63
+
42
64
  if (args[2] !== null && typeof args[2] === 'object') {
43
65
  childProcessInfo.options = args[2]
44
66
  }
45
67
  } else if (args[1] !== null && typeof args[1] === 'object') {
46
68
  childProcessInfo.options = args[1]
47
69
  }
70
+
48
71
  childProcessInfo.shell = shell ||
49
72
  childProcessInfo.options?.shell === true ||
50
73
  typeof childProcessInfo.options?.shell === 'string'
@@ -52,7 +75,21 @@ function normalizeArgs (args, shell) {
52
75
  return childProcessInfo
53
76
  }
54
77
 
55
- function wrapChildProcessSyncMethod (shell = false) {
78
+ function createContextFromChildProcessInfo (childProcessInfo) {
79
+ const context = {
80
+ command: childProcessInfo.command,
81
+ file: childProcessInfo.file,
82
+ shell: childProcessInfo.shell
83
+ }
84
+
85
+ if (childProcessInfo.fileArgs) {
86
+ context.fileArgs = childProcessInfo.fileArgs
87
+ }
88
+
89
+ return context
90
+ }
91
+
92
+ function wrapChildProcessSyncMethod (returnError, shell = false) {
56
93
  return function wrapMethod (childProcessMethod) {
57
94
  return function () {
58
95
  if (!childProcessChannel.start.hasSubscribers || arguments.length === 0) {
@@ -63,14 +100,30 @@ function wrapChildProcessSyncMethod (shell = false) {
63
100
 
64
101
  const innerResource = new AsyncResource('bound-anonymous-fn')
65
102
  return innerResource.runInAsyncScope(() => {
66
- return childProcessChannel.traceSync(
67
- childProcessMethod,
68
- {
69
- command: childProcessInfo.command,
70
- shell: childProcessInfo.shell
71
- },
72
- this,
73
- ...arguments)
103
+ const context = createContextFromChildProcessInfo(childProcessInfo)
104
+ const abortController = new AbortController()
105
+
106
+ childProcessChannel.start.publish({ ...context, abortController })
107
+
108
+ try {
109
+ if (abortController.signal.aborted) {
110
+ const error = abortController.signal.reason || new Error('Aborted')
111
+ // expected behaviors on error are different
112
+ return returnError(error, context)
113
+ }
114
+
115
+ const result = childProcessMethod.apply(this, arguments)
116
+ context.result = result
117
+
118
+ return result
119
+ } catch (err) {
120
+ context.error = err
121
+ childProcessChannel.error.publish(context)
122
+
123
+ throw err
124
+ } finally {
125
+ childProcessChannel.end.publish(context)
126
+ }
74
127
  })
75
128
  }
76
129
  }
@@ -84,18 +137,52 @@ function wrapChildProcessCustomPromisifyMethod (customPromisifyMethod, shell) {
84
137
 
85
138
  const childProcessInfo = normalizeArgs(arguments, shell)
86
139
 
87
- return childProcessChannel.tracePromise(
88
- customPromisifyMethod,
89
- {
90
- command: childProcessInfo.command,
91
- shell: childProcessInfo.shell
92
- },
93
- this,
94
- ...arguments)
140
+ const context = createContextFromChildProcessInfo(childProcessInfo)
141
+
142
+ const { start, end, asyncStart, asyncEnd, error } = childProcessChannel
143
+ const abortController = new AbortController()
144
+
145
+ start.publish({
146
+ ...context,
147
+ abortController
148
+ })
149
+
150
+ let result
151
+ if (abortController.signal.aborted) {
152
+ result = Promise.reject(abortController.signal.reason || new Error('Aborted'))
153
+ } else {
154
+ try {
155
+ result = customPromisifyMethod.apply(this, arguments)
156
+ } catch (error) {
157
+ error.publish({ ...context, error })
158
+ throw error
159
+ } finally {
160
+ end.publish(context)
161
+ }
162
+ }
163
+
164
+ function reject (err) {
165
+ context.error = err
166
+ error.publish(context)
167
+ asyncStart.publish(context)
168
+
169
+ asyncEnd.publish(context)
170
+ return Promise.reject(err)
171
+ }
172
+
173
+ function resolve (result) {
174
+ context.result = result
175
+ asyncStart.publish(context)
176
+
177
+ asyncEnd.publish(context)
178
+ return result
179
+ }
180
+
181
+ return Promise.prototype.then.call(result, resolve, reject)
95
182
  }
96
183
  }
97
184
 
98
- function wrapChildProcessAsyncMethod (shell = false) {
185
+ function wrapChildProcessAsyncMethod (ChildProcess, shell = false) {
99
186
  return function wrapMethod (childProcessMethod) {
100
187
  function wrappedChildProcessMethod () {
101
188
  if (!childProcessChannel.start.hasSubscribers || arguments.length === 0) {
@@ -112,9 +199,31 @@ function wrapChildProcessAsyncMethod (shell = false) {
112
199
 
113
200
  const innerResource = new AsyncResource('bound-anonymous-fn')
114
201
  return innerResource.runInAsyncScope(() => {
115
- childProcessChannel.start.publish({ command: childProcessInfo.command, shell: childProcessInfo.shell })
202
+ const context = createContextFromChildProcessInfo(childProcessInfo)
203
+ const abortController = new AbortController()
204
+
205
+ childProcessChannel.start.publish({ ...context, abortController })
206
+
207
+ let childProcess
208
+ if (abortController.signal.aborted) {
209
+ childProcess = new ChildProcess()
210
+ childProcess.on('error', () => {}) // Original method does not crash when non subscribers
211
+
212
+ process.nextTick(() => {
213
+ const error = abortController.signal.reason || new Error('Aborted')
214
+ childProcess.emit('error', error)
215
+
216
+ const cb = arguments[arguments.length - 1]
217
+ if (typeof cb === 'function') {
218
+ cb(error)
219
+ }
220
+
221
+ childProcess.emit('close')
222
+ })
223
+ } else {
224
+ childProcess = childProcessMethod.apply(this, arguments)
225
+ }
116
226
 
117
- const childProcess = childProcessMethod.apply(this, arguments)
118
227
  if (childProcess) {
119
228
  let errorExecuted = false
120
229
 
@@ -129,8 +238,7 @@ function wrapChildProcessAsyncMethod (shell = false) {
129
238
  childProcessChannel.error.publish()
130
239
  }
131
240
  childProcessChannel.asyncEnd.publish({
132
- command: childProcessInfo.command,
133
- shell: childProcessInfo.shell,
241
+ ...context,
134
242
  result: code
135
243
  })
136
244
  })
@@ -79,6 +79,7 @@ module.exports = {
79
79
  'mongodb-core': () => require('../mongodb-core'),
80
80
  mongoose: () => require('../mongoose'),
81
81
  mquery: () => require('../mquery'),
82
+ multer: () => require('../multer'),
82
83
  mysql: () => require('../mysql'),
83
84
  mysql2: () => require('../mysql2'),
84
85
  net: () => require('../net'),
@@ -90,6 +91,7 @@ module.exports = {
90
91
  'node:http2': () => require('../http2'),
91
92
  'node:https': () => require('../http'),
92
93
  'node:net': () => require('../net'),
94
+ 'node:url': () => require('../url'),
93
95
  nyc: () => require('../nyc'),
94
96
  oracledb: () => require('../oracledb'),
95
97
  openai: () => require('../openai'),
@@ -114,6 +116,7 @@ module.exports = {
114
116
  sharedb: () => require('../sharedb'),
115
117
  tedious: () => require('../tedious'),
116
118
  undici: () => require('../undici'),
119
+ url: () => require('../url'),
117
120
  vitest: { esmFirst: true, fn: () => require('../vitest') },
118
121
  when: () => require('../when'),
119
122
  winston: () => require('../winston'),
@@ -22,6 +22,15 @@ const disabledInstrumentations = new Set(
22
22
  DD_TRACE_DISABLED_INSTRUMENTATIONS ? DD_TRACE_DISABLED_INSTRUMENTATIONS.split(',') : []
23
23
  )
24
24
 
25
+ // Check for DD_TRACE_<INTEGRATION>_ENABLED environment variables
26
+ for (const [key, value] of Object.entries(process.env)) {
27
+ const match = key.match(/^DD_TRACE_(.+)_ENABLED$/)
28
+ if (match && (value.toLowerCase() === 'false' || value === '0')) {
29
+ const integration = match[1].toLowerCase()
30
+ disabledInstrumentations.add(integration)
31
+ }
32
+ }
33
+
25
34
  const loadChannel = channel('dd-trace:instrumentation:load')
26
35
 
27
36
  // Globals
@@ -52,45 +52,59 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
52
52
  const send = producer.send
53
53
  const bootstrapServers = this._brokers
54
54
 
55
- producer.send = function () {
56
- const innerAsyncResource = new AsyncResource('bound-anonymous-fn')
55
+ const kafkaClusterIdPromise = getKafkaClusterId(this)
57
56
 
58
- return innerAsyncResource.runInAsyncScope(() => {
59
- if (!producerStartCh.hasSubscribers) {
60
- return send.apply(this, arguments)
61
- }
57
+ producer.send = function () {
58
+ const wrappedSend = (clusterId) => {
59
+ const innerAsyncResource = new AsyncResource('bound-anonymous-fn')
62
60
 
63
- try {
64
- const { topic, messages = [] } = arguments[0]
65
- for (const message of messages) {
66
- if (message !== null && typeof message === 'object') {
67
- message.headers = message.headers || {}
68
- }
61
+ return innerAsyncResource.runInAsyncScope(() => {
62
+ if (!producerStartCh.hasSubscribers) {
63
+ return send.apply(this, arguments)
69
64
  }
70
- producerStartCh.publish({ topic, messages, bootstrapServers })
71
-
72
- const result = send.apply(this, arguments)
73
-
74
- result.then(
75
- innerAsyncResource.bind(res => {
76
- producerFinishCh.publish(undefined)
77
- producerCommitCh.publish(res)
78
- }),
79
- innerAsyncResource.bind(err => {
80
- if (err) {
81
- producerErrorCh.publish(err)
65
+
66
+ try {
67
+ const { topic, messages = [] } = arguments[0]
68
+ for (const message of messages) {
69
+ if (message !== null && typeof message === 'object') {
70
+ message.headers = message.headers || {}
82
71
  }
83
- producerFinishCh.publish(undefined)
84
- })
85
- )
72
+ }
73
+ producerStartCh.publish({ topic, messages, bootstrapServers, clusterId })
86
74
 
87
- return result
88
- } catch (e) {
89
- producerErrorCh.publish(e)
90
- producerFinishCh.publish(undefined)
91
- throw e
92
- }
93
- })
75
+ const result = send.apply(this, arguments)
76
+
77
+ result.then(
78
+ innerAsyncResource.bind(res => {
79
+ producerFinishCh.publish(undefined)
80
+ producerCommitCh.publish(res)
81
+ }),
82
+ innerAsyncResource.bind(err => {
83
+ if (err) {
84
+ producerErrorCh.publish(err)
85
+ }
86
+ producerFinishCh.publish(undefined)
87
+ })
88
+ )
89
+
90
+ return result
91
+ } catch (e) {
92
+ producerErrorCh.publish(e)
93
+ producerFinishCh.publish(undefined)
94
+ throw e
95
+ }
96
+ })
97
+ }
98
+
99
+ if (!isPromise(kafkaClusterIdPromise)) {
100
+ // promise is already resolved
101
+ return wrappedSend(kafkaClusterIdPromise)
102
+ } else {
103
+ // promise is not resolved
104
+ return kafkaClusterIdPromise.then((clusterId) => {
105
+ return wrappedSend(clusterId)
106
+ })
107
+ }
94
108
  }
95
109
  return producer
96
110
  })
@@ -100,15 +114,17 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
100
114
  return createConsumer.apply(this, arguments)
101
115
  }
102
116
 
103
- const eachMessageExtractor = (args) => {
117
+ const kafkaClusterIdPromise = getKafkaClusterId(this)
118
+
119
+ const eachMessageExtractor = (args, clusterId) => {
104
120
  const { topic, partition, message } = args[0]
105
- return { topic, partition, message, groupId }
121
+ return { topic, partition, message, groupId, clusterId }
106
122
  }
107
123
 
108
- const eachBatchExtractor = (args) => {
124
+ const eachBatchExtractor = (args, clusterId) => {
109
125
  const { batch } = args[0]
110
126
  const { topic, partition, messages } = batch
111
- return { topic, partition, messages, groupId }
127
+ return { topic, partition, messages, groupId, clusterId }
112
128
  }
113
129
 
114
130
  const consumer = createConsumer.apply(this, arguments)
@@ -116,43 +132,53 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
116
132
  consumer.on(consumer.events.COMMIT_OFFSETS, commitsFromEvent)
117
133
 
118
134
  const run = consumer.run
119
-
120
135
  const groupId = arguments[0].groupId
136
+
121
137
  consumer.run = function ({ eachMessage, eachBatch, ...runArgs }) {
122
- eachMessage = wrapFunction(
123
- eachMessage,
124
- consumerStartCh,
125
- consumerFinishCh,
126
- consumerErrorCh,
127
- eachMessageExtractor
128
- )
129
-
130
- eachBatch = wrapFunction(
131
- eachBatch,
132
- batchConsumerStartCh,
133
- batchConsumerFinishCh,
134
- batchConsumerErrorCh,
135
- eachBatchExtractor
136
- )
137
-
138
- return run({
139
- eachMessage,
140
- eachBatch,
141
- ...runArgs
142
- })
138
+ const wrapConsume = (clusterId) => {
139
+ return run({
140
+ eachMessage: wrappedCallback(
141
+ eachMessage,
142
+ consumerStartCh,
143
+ consumerFinishCh,
144
+ consumerErrorCh,
145
+ eachMessageExtractor,
146
+ clusterId
147
+ ),
148
+ eachBatch: wrappedCallback(
149
+ eachBatch,
150
+ batchConsumerStartCh,
151
+ batchConsumerFinishCh,
152
+ batchConsumerErrorCh,
153
+ eachBatchExtractor,
154
+ clusterId
155
+ ),
156
+ ...runArgs
157
+ })
158
+ }
159
+
160
+ if (!isPromise(kafkaClusterIdPromise)) {
161
+ // promise is already resolved
162
+ return wrapConsume(kafkaClusterIdPromise)
163
+ } else {
164
+ // promise is not resolved
165
+ return kafkaClusterIdPromise.then((clusterId) => {
166
+ return wrapConsume(clusterId)
167
+ })
168
+ }
143
169
  }
144
-
145
170
  return consumer
146
171
  })
147
172
  return Kafka
148
173
  })
149
174
 
150
- const wrapFunction = (fn, startCh, finishCh, errorCh, extractArgs) => {
175
+ const wrappedCallback = (fn, startCh, finishCh, errorCh, extractArgs, clusterId) => {
151
176
  return typeof fn === 'function'
152
177
  ? function (...args) {
153
178
  const innerAsyncResource = new AsyncResource('bound-anonymous-fn')
154
179
  return innerAsyncResource.runInAsyncScope(() => {
155
- const extractedArgs = extractArgs(args)
180
+ const extractedArgs = extractArgs(args, clusterId)
181
+
156
182
  startCh.publish(extractedArgs)
157
183
  try {
158
184
  const result = fn.apply(this, args)
@@ -179,3 +205,37 @@ const wrapFunction = (fn, startCh, finishCh, errorCh, extractArgs) => {
179
205
  }
180
206
  : fn
181
207
  }
208
+
209
+ const getKafkaClusterId = (kafka) => {
210
+ if (kafka._ddKafkaClusterId) {
211
+ return kafka._ddKafkaClusterId
212
+ }
213
+
214
+ if (!kafka.admin) {
215
+ return null
216
+ }
217
+
218
+ const admin = kafka.admin()
219
+
220
+ if (!admin.describeCluster) {
221
+ return null
222
+ }
223
+
224
+ return admin.connect()
225
+ .then(() => {
226
+ return admin.describeCluster()
227
+ })
228
+ .then((clusterInfo) => {
229
+ const clusterId = clusterInfo?.clusterId
230
+ kafka._ddKafkaClusterId = clusterId
231
+ admin.disconnect()
232
+ return clusterId
233
+ })
234
+ .catch((error) => {
235
+ throw error
236
+ })
237
+ }
238
+
239
+ function isPromise (obj) {
240
+ return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function'
241
+ }
@@ -280,12 +280,12 @@ function getOnFailHandler (isMain) {
280
280
  }
281
281
 
282
282
  function getOnTestRetryHandler () {
283
- return function (test) {
283
+ return function (test, err) {
284
284
  const asyncResource = getTestAsyncResource(test)
285
285
  if (asyncResource) {
286
286
  const isFirstAttempt = test._currentRetry === 0
287
287
  asyncResource.runInAsyncScope(() => {
288
- testRetryCh.publish(isFirstAttempt)
288
+ testRetryCh.publish({ isFirstAttempt, err })
289
289
  })
290
290
  }
291
291
  const key = getTestToArKey(test)
@@ -0,0 +1,37 @@
1
+ 'use strict'
2
+
3
+ const shimmer = require('../../datadog-shimmer')
4
+ const { channel, addHook, AsyncResource } = require('./helpers/instrument')
5
+
6
+ const multerReadCh = channel('datadog:multer:read:finish')
7
+
8
+ function publishRequestBodyAndNext (req, res, next) {
9
+ return shimmer.wrapFunction(next, next => function () {
10
+ if (multerReadCh.hasSubscribers && req) {
11
+ const abortController = new AbortController()
12
+ const body = req.body
13
+
14
+ multerReadCh.publish({ req, res, body, abortController })
15
+
16
+ if (abortController.signal.aborted) return
17
+ }
18
+
19
+ return next.apply(this, arguments)
20
+ })
21
+ }
22
+
23
+ addHook({
24
+ name: 'multer',
25
+ file: 'lib/make-middleware.js',
26
+ versions: ['^1.4.4-lts.1']
27
+ }, makeMiddleware => {
28
+ return shimmer.wrapFunction(makeMiddleware, makeMiddleware => function () {
29
+ const middleware = makeMiddleware.apply(this, arguments)
30
+
31
+ return shimmer.wrapFunction(middleware, middleware => function wrapMulterMiddleware (req, res, next) {
32
+ const nextResource = new AsyncResource('bound-anonymous-fn')
33
+ arguments[2] = nextResource.bind(publishRequestBodyAndNext(req, res, next))
34
+ return middleware.apply(this, arguments)
35
+ })
36
+ })
37
+ })
@@ -3,8 +3,8 @@
3
3
  const { addHook } = require('./helpers/instrument')
4
4
  const shimmer = require('../../datadog-shimmer')
5
5
 
6
- const tracingChannel = require('dc-polyfill').tracingChannel
7
- const ch = tracingChannel('apm:openai:request')
6
+ const dc = require('dc-polyfill')
7
+ const ch = dc.tracingChannel('apm:openai:request')
8
8
 
9
9
  const V4_PACKAGE_SHIMS = [
10
10
  {
@@ -0,0 +1,84 @@
1
+ 'use strict'
2
+
3
+ const { addHook, channel } = require('./helpers/instrument')
4
+ const shimmer = require('../../datadog-shimmer')
5
+ const names = ['url', 'node:url']
6
+
7
+ const parseFinishedChannel = channel('datadog:url:parse:finish')
8
+ const urlGetterChannel = channel('datadog:url:getter:finish')
9
+ const instrumentedGetters = ['host', 'origin', 'hostname']
10
+
11
+ addHook({ name: names }, function (url) {
12
+ shimmer.wrap(url, 'parse', (parse) => {
13
+ return function wrappedParse (input) {
14
+ const parsedValue = parse.apply(this, arguments)
15
+ if (!parseFinishedChannel.hasSubscribers) return parsedValue
16
+
17
+ parseFinishedChannel.publish({
18
+ input,
19
+ parsed: parsedValue,
20
+ isURL: false
21
+ })
22
+
23
+ return parsedValue
24
+ }
25
+ })
26
+
27
+ const URLPrototype = url.URL.prototype.constructor.prototype
28
+ instrumentedGetters.forEach(property => {
29
+ const originalDescriptor = Object.getOwnPropertyDescriptor(URLPrototype, property)
30
+
31
+ if (originalDescriptor?.get) {
32
+ const newDescriptor = shimmer.wrap(originalDescriptor, 'get', function (originalGet) {
33
+ return function get () {
34
+ const result = originalGet.apply(this, arguments)
35
+ if (!urlGetterChannel.hasSubscribers) return result
36
+
37
+ const context = { urlObject: this, result, property }
38
+ urlGetterChannel.publish(context)
39
+
40
+ return context.result
41
+ }
42
+ })
43
+
44
+ Object.defineProperty(URLPrototype, property, newDescriptor)
45
+ }
46
+ })
47
+
48
+ shimmer.wrap(url, 'URL', (URL) => {
49
+ return class extends URL {
50
+ constructor (input, base) {
51
+ super(...arguments)
52
+
53
+ if (!parseFinishedChannel.hasSubscribers) return
54
+
55
+ parseFinishedChannel.publish({
56
+ input,
57
+ base,
58
+ parsed: this,
59
+ isURL: true
60
+ })
61
+ }
62
+ }
63
+ })
64
+
65
+ if (url.URL.parse) {
66
+ shimmer.wrap(url.URL, 'parse', (parse) => {
67
+ return function wrappedParse (input, base) {
68
+ const parsedValue = parse.apply(this, arguments)
69
+ if (!parseFinishedChannel.hasSubscribers) return parsedValue
70
+
71
+ parseFinishedChannel.publish({
72
+ input,
73
+ base,
74
+ parsed: parsedValue,
75
+ isURL: true
76
+ })
77
+
78
+ return parsedValue
79
+ }
80
+ })
81
+ }
82
+
83
+ return url
84
+ })
@@ -6,7 +6,7 @@ const NM = 'node_modules/'
6
6
  * For a given full path to a module,
7
7
  * return the package name it belongs to and the local path to the module
8
8
  * input: '/foo/node_modules/@co/stuff/foo/bar/baz.js'
9
- * output: { pkg: '@co/stuff', path: 'foo/bar/baz.js' }
9
+ * output: { pkg: '@co/stuff', path: 'foo/bar/baz.js', pkgJson: '/foo/node_modules/@co/stuff/package.json' }
10
10
  */
11
11
  module.exports = function extractPackageAndModulePath (fullPath) {
12
12
  const nm = fullPath.lastIndexOf(NM)
@@ -17,17 +17,20 @@ module.exports = function extractPackageAndModulePath (fullPath) {
17
17
  const subPath = fullPath.substring(nm + NM.length)
18
18
  const firstSlash = subPath.indexOf('/')
19
19
 
20
+ const firstPath = fullPath.substring(fullPath[0], nm + NM.length)
21
+
20
22
  if (subPath[0] === '@') {
21
23
  const secondSlash = subPath.substring(firstSlash + 1).indexOf('/')
22
-
23
24
  return {
24
25
  pkg: subPath.substring(0, firstSlash + 1 + secondSlash),
25
- path: subPath.substring(firstSlash + 1 + secondSlash + 1)
26
+ path: subPath.substring(firstSlash + 1 + secondSlash + 1),
27
+ pkgJson: firstPath + subPath.substring(0, firstSlash + 1 + secondSlash) + '/package.json'
26
28
  }
27
29
  }
28
30
 
29
31
  return {
30
32
  pkg: subPath.substring(0, firstSlash),
31
- path: subPath.substring(firstSlash + 1)
33
+ path: subPath.substring(firstSlash + 1),
34
+ pkgJson: firstPath + subPath.substring(0, firstSlash) + '/package.json'
32
35
  }
33
36
  }