dd-trace 5.52.0 → 5.53.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 (55) hide show
  1. package/README.md +5 -0
  2. package/index.d.ts +54 -6
  3. package/package.json +1 -1
  4. package/packages/datadog-instrumentations/src/amqplib.js +8 -5
  5. package/packages/datadog-instrumentations/src/child_process.js +2 -1
  6. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +16 -1
  7. package/packages/datadog-instrumentations/src/couchbase.js +2 -1
  8. package/packages/datadog-instrumentations/src/cucumber.js +41 -46
  9. package/packages/datadog-instrumentations/src/express.js +2 -6
  10. package/packages/datadog-instrumentations/src/fs.js +6 -5
  11. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  12. package/packages/datadog-instrumentations/src/helpers/register.js +17 -12
  13. package/packages/datadog-instrumentations/src/http/client.js +2 -1
  14. package/packages/datadog-instrumentations/src/iovalkey.js +51 -0
  15. package/packages/datadog-instrumentations/src/jest.js +49 -41
  16. package/packages/datadog-instrumentations/src/kafkajs.js +21 -8
  17. package/packages/datadog-instrumentations/src/mocha/main.js +33 -46
  18. package/packages/datadog-instrumentations/src/mocha/utils.js +72 -75
  19. package/packages/datadog-instrumentations/src/mysql2.js +3 -1
  20. package/packages/datadog-instrumentations/src/net.js +3 -1
  21. package/packages/datadog-instrumentations/src/next.js +6 -14
  22. package/packages/datadog-instrumentations/src/pg.js +5 -11
  23. package/packages/datadog-instrumentations/src/playwright.js +60 -69
  24. package/packages/datadog-instrumentations/src/url.js +9 -17
  25. package/packages/datadog-instrumentations/src/vitest.js +55 -75
  26. package/packages/datadog-plugin-cucumber/src/index.js +29 -18
  27. package/packages/datadog-plugin-iovalkey/src/index.js +18 -0
  28. package/packages/datadog-plugin-jest/src/index.js +14 -8
  29. package/packages/datadog-plugin-kafkajs/src/producer.js +8 -5
  30. package/packages/datadog-plugin-mocha/src/index.js +55 -35
  31. package/packages/datadog-plugin-playwright/src/index.js +26 -20
  32. package/packages/datadog-plugin-redis/src/index.js +8 -3
  33. package/packages/datadog-plugin-vitest/src/index.js +53 -42
  34. package/packages/datadog-shimmer/src/shimmer.js +164 -33
  35. package/packages/dd-trace/src/appsec/graphql.js +2 -2
  36. package/packages/dd-trace/src/appsec/index.js +14 -11
  37. package/packages/dd-trace/src/appsec/rasp/index.js +4 -2
  38. package/packages/dd-trace/src/appsec/rasp/utils.js +11 -6
  39. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +2 -2
  40. package/packages/dd-trace/src/appsec/telemetry/index.js +1 -2
  41. package/packages/dd-trace/src/appsec/telemetry/rasp.js +0 -9
  42. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +6 -6
  43. package/packages/dd-trace/src/config.js +1 -1
  44. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +59 -7
  45. package/packages/dd-trace/src/debugger/devtools_client/index.js +10 -26
  46. package/packages/dd-trace/src/debugger/devtools_client/send.js +8 -7
  47. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +15 -7
  48. package/packages/dd-trace/src/debugger/devtools_client/state.js +21 -1
  49. package/packages/dd-trace/src/dogstatsd.js +2 -0
  50. package/packages/dd-trace/src/llmobs/tagger.js +3 -3
  51. package/packages/dd-trace/src/plugins/index.js +1 -0
  52. package/packages/dd-trace/src/proxy.js +0 -4
  53. package/packages/dd-trace/src/serverless.js +0 -48
  54. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +8 -0
  55. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
package/README.md CHANGED
@@ -22,6 +22,11 @@ Most of the documentation for `dd-trace` is available on these webpages:
22
22
 
23
23
  ## Version Release Lines and Maintenance
24
24
 
25
+ > **Node.js v24 Notice**: We're currently adding compatibility for Node.js v24. To use the tracer with your application either continue to use Node.js v22 (LTS), or do both of the following as a workaround:
26
+ > * Install v5.52.0 (or newer) of the tracer
27
+ > * Set `--no-async-context-frame` either using a CLI argument or via `NODE_OPTIONS`
28
+ > Once support for Node.js v24 is complete this flag will no longer be needed.
29
+
25
30
  | Release Line | Latest Version | Node.js | [SSI](https://docs.datadoghq.com/tracing/trace_collection/automatic_instrumentation/single-step-apm/?tab=linuxhostorvm) | [K8s Injection](https://docs.datadoghq.com/tracing/trace_collection/library_injection_local/?tab=kubernetes) |Status |Initial Release | End of Life |
26
31
  | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
27
32
  | [`v1`](https://github.com/DataDog/dd-trace-js/tree/v1.x) | ![npm v1](https://img.shields.io/npm/v/dd-trace/legacy-v1?color=white&label=%20&style=flat-square) | `>= v12` | NO | NO | **EOL** | 2021-07-13 | 2022-02-25 |
package/index.d.ts CHANGED
@@ -189,6 +189,7 @@ interface Plugins {
189
189
  "http": tracer.plugins.http;
190
190
  "http2": tracer.plugins.http2;
191
191
  "ioredis": tracer.plugins.ioredis;
192
+ "iovalkey": tracer.plugins.iovalkey;
192
193
  "jest": tracer.plugins.jest;
193
194
  "kafkajs": tracer.plugins.kafkajs
194
195
  "knex": tracer.plugins.knex;
@@ -855,7 +856,7 @@ declare namespace tracer {
855
856
  * @param value The amount to increment the stat by.
856
857
  * @param tags Tags to pass along, such as `{ foo: 'bar' }`. Values are combined with config.tags.
857
858
  */
858
- increment(stat: string, value?: number, tags?: Record<string, string|number>): void
859
+ increment(stat: string, value?: number, tags?: Record<string, string|number> | string[]): void
859
860
 
860
861
  /**
861
862
  * Decrements a metric by the specified value, optionally specifying tags.
@@ -863,7 +864,7 @@ declare namespace tracer {
863
864
  * @param value The amount to decrement the stat by.
864
865
  * @param tags Tags to pass along, such as `{ foo: 'bar' }`. Values are combined with config.tags.
865
866
  */
866
- decrement(stat: string, value?: number, tags?: Record<string, string|number>): void
867
+ decrement(stat: string, value?: number, tags?: Record<string, string|number> | string[]): void
867
868
 
868
869
  /**
869
870
  * Sets a distribution value, optionally specifying tags.
@@ -871,7 +872,7 @@ declare namespace tracer {
871
872
  * @param value The amount to increment the stat by.
872
873
  * @param tags Tags to pass along, such as `{ foo: 'bar' }`. Values are combined with config.tags.
873
874
  */
874
- distribution(stat: string, value?: number, tags?: Record<string, string|number>): void
875
+ distribution(stat: string, value?: number, tags?: Record<string, string|number> | string[]): void
875
876
 
876
877
  /**
877
878
  * Sets a gauge value, optionally specifying tags.
@@ -879,7 +880,7 @@ declare namespace tracer {
879
880
  * @param value The amount to increment the stat by.
880
881
  * @param tags Tags to pass along, such as `{ foo: 'bar' }`. Values are combined with config.tags.
881
882
  */
882
- gauge(stat: string, value?: number, tags?: Record<string, string|number>): void
883
+ gauge(stat: string, value?: number, tags?: Record<string, string|number> | string[]): void
883
884
 
884
885
  /**
885
886
  * Sets a histogram value, optionally specifying tags.
@@ -887,7 +888,7 @@ declare namespace tracer {
887
888
  * @param value The amount to increment the stat by.
888
889
  * @param tags Tags to pass along, such as `{ foo: 'bar' }`. Values are combined with config.tags.
889
890
  */
890
- histogram(stat: string, value?: number, tags?: Record<string, string|number>): void
891
+ histogram(stat: string, value?: number, tags?: Record<string, string|number> | string[]): void
891
892
 
892
893
  /**
893
894
  * Forces any unsent metrics to be sent
@@ -1361,7 +1362,7 @@ declare namespace tracer {
1361
1362
  * [child_process](https://nodejs.org/api/child_process.html) module.
1362
1363
  */
1363
1364
  interface child_process extends Instrumentation {}
1364
-
1365
+
1365
1366
  /**
1366
1367
  * This plugin automatically instruments the
1367
1368
  * [confluentinc-kafka-javascript](https://github.com/confluentinc/confluent-kafka-js) module.
@@ -1666,6 +1667,53 @@ declare namespace tracer {
1666
1667
  splitByInstance?: boolean;
1667
1668
  }
1668
1669
 
1670
+ /**
1671
+ * This plugin automatically instruments the
1672
+ * [iovalkey](https://github.com/valkey-io/iovalkey) module.
1673
+ */
1674
+ interface iovalkey extends Instrumentation {
1675
+ /**
1676
+ * List of commands that should be instrumented. Commands must be in
1677
+ * lowercase for example 'xread'.
1678
+ *
1679
+ * @default /^.*$/
1680
+ */
1681
+ allowlist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[];
1682
+
1683
+ /**
1684
+ * Deprecated in favor of `allowlist`.
1685
+ *
1686
+ * @deprecated
1687
+ * @hidden
1688
+ */
1689
+ whitelist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[];
1690
+
1691
+ /**
1692
+ * List of commands that should not be instrumented. Takes precedence over
1693
+ * allowlist if a command matches an entry in both. Commands must be in
1694
+ * lowercase for example 'xread'.
1695
+ *
1696
+ * @default []
1697
+ */
1698
+ blocklist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[];
1699
+
1700
+ /**
1701
+ * Deprecated in favor of `blocklist`.
1702
+ *
1703
+ * @deprecated
1704
+ * @hidden
1705
+ */
1706
+ blacklist?: string | RegExp | ((command: string) => boolean) | (string | RegExp | ((command: string) => boolean))[];
1707
+
1708
+ /**
1709
+ * Whether to use a different service name for each Redis instance based
1710
+ * on the configured connection name of the client.
1711
+ *
1712
+ * @default false
1713
+ */
1714
+ splitByInstance?: boolean;
1715
+ }
1716
+
1669
1717
  /**
1670
1718
  * This plugin automatically instruments the
1671
1719
  * [jest](https://github.com/jestjs/jest) module.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.52.0",
3
+ "version": "5.53.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -15,13 +15,16 @@ const startCh = channel('apm:amqplib:command:start')
15
15
  const finishCh = channel('apm:amqplib:command:finish')
16
16
  const errorCh = channel('apm:amqplib:command:error')
17
17
 
18
- let methods = {}
18
+ const methods = {}
19
19
 
20
20
  addHook({ name: 'amqplib', file: 'lib/defs.js', versions: [MIN_VERSION] }, defs => {
21
- methods = Object.keys(defs)
22
- .filter(key => Number.isInteger(defs[key]))
23
- .filter(key => isCamelCase(key))
24
- .reduce((acc, key) => Object.assign(acc, { [defs[key]]: kebabCase(key).replace('-', '.') }), {})
21
+ for (const [key, value] of Object.entries(defs)) {
22
+ if (Number.isInteger(value) && isCamelCase(key)) {
23
+ // TODO(BridgeAR): It should replace all `-` with `.`, so it has to be replaceAll or use a regex.
24
+ // Example method that is not renamed properly: BasicGetEmpty = 3932232
25
+ methods[value] = kebabCase(key).replace('-', '.')
26
+ }
27
+ }
25
28
  return defs
26
29
  })
27
30
 
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const { errorMonitor } = require('events')
3
4
  const util = require('util')
4
5
 
5
6
  const {
@@ -227,7 +228,7 @@ function wrapChildProcessAsyncMethod (ChildProcess, shell = false) {
227
228
  if (childProcess) {
228
229
  let errorExecuted = false
229
230
 
230
- childProcess.on('error', (e) => {
231
+ childProcess.on(errorMonitor, (e) => {
231
232
  errorExecuted = true
232
233
  childProcessChannel.error.publish(e)
233
234
  })
@@ -7,6 +7,8 @@ const {
7
7
  } = require('./helpers/instrument')
8
8
  const shimmer = require('../../datadog-shimmer')
9
9
 
10
+ const log = require('../../dd-trace/src/log')
11
+
10
12
  // Create channels for Confluent Kafka JavaScript
11
13
  const channels = {
12
14
  producerStart: channel('apm:@confluentinc/kafka-javascript:produce:start'),
@@ -25,6 +27,8 @@ const channels = {
25
27
  batchConsumerCommit: channel('apm:@confluentinc/kafka-javascript:consume-batch:commit')
26
28
  }
27
29
 
30
+ const disabledHeaderWeakSet = new WeakSet()
31
+
28
32
  // we need to store the offset per partition per topic for the consumer to track offsets for DSM
29
33
  const latestConsumerOffsets = new Map()
30
34
 
@@ -206,7 +210,8 @@ function instrumentKafkaJS (kafkaJS) {
206
210
  channels.producerStart.publish({
207
211
  topic: payload?.topic,
208
212
  messages: payload?.messages || [],
209
- bootstrapServers: kafka._ddBrokers
213
+ bootstrapServers: kafka._ddBrokers,
214
+ disableHeaderInjection: disabledHeaderWeakSet.has(producer)
210
215
  })
211
216
 
212
217
  const result = send.apply(this, arguments)
@@ -218,6 +223,16 @@ function instrumentKafkaJS (kafkaJS) {
218
223
  }),
219
224
  asyncResource.bind(err => {
220
225
  if (err) {
226
+ // Fixes bug where we would inject message headers for kafka brokers
227
+ // that don't support headers (version <0.11). On the error, we disable
228
+ // header injection. Tnfortunately the error name / type is not more specific.
229
+ // This approach is implemented by other tracers as well.
230
+ if (err.name === 'KafkaJSError' && err.type === 'ERR_UNKNOWN') {
231
+ disabledHeaderWeakSet.add(producer)
232
+ log.error('Kafka Broker responded with UNKNOWN_SERVER_ERROR (-1). ' +
233
+ 'Please look at broker logs for more information. ' +
234
+ 'Tracer message header injection for Kafka is disabled.')
235
+ }
221
236
  channels.producerError.publish(err)
222
237
  }
223
238
  channels.producerFinish.publish(undefined)
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const { errorMonitor } = require('events')
3
4
  const {
4
5
  channel,
5
6
  addHook,
@@ -186,7 +187,7 @@ addHook({ name: 'couchbase', file: 'lib/bucket.js', versions: ['^2.6.12'] }, Buc
186
187
  finishCh.publish(undefined)
187
188
  }))
188
189
 
189
- emitter.once('error', asyncResource.bind((error) => {
190
+ emitter.once(errorMonitor, asyncResource.bind((error) => {
190
191
  errorCh.publish(error)
191
192
  finishCh.publish(undefined)
192
193
  }))
@@ -8,6 +8,7 @@ const log = require('../../dd-trace/src/log')
8
8
  const testStartCh = channel('ci:cucumber:test:start')
9
9
  const testRetryCh = channel('ci:cucumber:test:retry')
10
10
  const testFinishCh = channel('ci:cucumber:test:finish') // used for test steps too
11
+ const testFnCh = channel('ci:cucumber:test:fn')
11
12
 
12
13
  const testStepStartCh = channel('ci:cucumber:test-step:start')
13
14
 
@@ -52,7 +53,7 @@ const patched = new WeakSet()
52
53
 
53
54
  const lastStatusByPickleId = new Map()
54
55
  const numRetriesByPickleId = new Map()
55
- const numAttemptToAsyncResource = new Map()
56
+ const numAttemptToCtx = new Map()
56
57
  const newTestsByTestFullname = new Map()
57
58
 
58
59
  let eventDataCollector = null
@@ -227,16 +228,12 @@ function wrapRun (pl, isLatestVersion) {
227
228
  patched.add(pl)
228
229
 
229
230
  shimmer.wrap(pl.prototype, 'run', run => function () {
230
- if (!testStartCh.hasSubscribers) {
231
+ if (!testFinishCh.hasSubscribers) {
231
232
  return run.apply(this, arguments)
232
233
  }
233
234
 
234
235
  let numAttempt = 0
235
236
 
236
- const asyncResource = new AsyncResource('bound-anonymous-fn')
237
-
238
- numAttemptToAsyncResource.set(numAttempt, asyncResource)
239
-
240
237
  const testFileAbsolutePath = this.pickle.uri
241
238
 
242
239
  const testSourceLine = this.gherkinDocument?.feature?.location?.line
@@ -247,9 +244,9 @@ function wrapRun (pl, isLatestVersion) {
247
244
  testSourceLine,
248
245
  isParallel: !!process.env.CUCUMBER_WORKER_ID
249
246
  }
250
- asyncResource.runInAsyncScope(() => {
251
- testStartCh.publish(testStartPayload)
252
- })
247
+ const ctx = testStartPayload
248
+ numAttemptToCtx.set(numAttempt, ctx)
249
+ testStartCh.runStores(ctx, () => { })
253
250
  const promises = {}
254
251
  try {
255
252
  this.eventBroadcaster.on('envelope', shimmer.wrapFunction(null, () => async (testCase) => {
@@ -265,7 +262,7 @@ function wrapRun (pl, isLatestVersion) {
265
262
  // ignore error
266
263
  }
267
264
 
268
- const failedAttemptAsyncResource = numAttemptToAsyncResource.get(numAttempt)
265
+ const failedAttemptCtx = numAttemptToCtx.get(numAttempt)
269
266
  const isFirstAttempt = numAttempt++ === 0
270
267
  const isAtrRetry = !isFirstAttempt && isFlakyTestRetriesEnabled
271
268
 
@@ -273,23 +270,19 @@ function wrapRun (pl, isLatestVersion) {
273
270
  await promises.hitBreakpointPromise
274
271
  }
275
272
 
276
- failedAttemptAsyncResource.runInAsyncScope(() => {
277
- // the current span will be finished and a new one will be created
278
- testRetryCh.publish({ isFirstAttempt, error, isAtrRetry })
279
- })
273
+ // the current span will be finished and a new one will be created
274
+ testRetryCh.publish({ isFirstAttempt, error, isAtrRetry, ...failedAttemptCtx.currentStore })
280
275
 
281
- const newAsyncResource = new AsyncResource('bound-anonymous-fn')
282
- numAttemptToAsyncResource.set(numAttempt, newAsyncResource)
276
+ const newCtx = { ...testStartPayload, promises }
277
+ numAttemptToCtx.set(numAttempt, newCtx)
283
278
 
284
- newAsyncResource.runInAsyncScope(() => {
285
- testStartCh.publish({ ...testStartPayload, promises }) // a new span will be created
286
- })
279
+ testStartCh.runStores(newCtx, () => { })
287
280
  }
288
281
  }
289
282
  }))
290
283
  let promise
291
284
 
292
- asyncResource.runInAsyncScope(() => {
285
+ testFnCh.runStores(ctx, () => {
293
286
  promise = run.apply(this, arguments)
294
287
  })
295
288
  promise.finally(async () => {
@@ -343,39 +336,40 @@ function wrapRun (pl, isLatestVersion) {
343
336
  isEfdRetry = numRetries > 0
344
337
  }
345
338
 
346
- const attemptAsyncResource = numAttemptToAsyncResource.get(numAttempt)
339
+ const attemptCtx = numAttemptToCtx.get(numAttempt)
347
340
 
348
341
  const error = getErrorFromCucumberResult(result)
349
342
 
350
343
  if (promises.hitBreakpointPromise) {
351
344
  await promises.hitBreakpointPromise
352
345
  }
353
- attemptAsyncResource.runInAsyncScope(() => {
354
- testFinishCh.publish({
355
- status,
356
- skipReason,
357
- error,
358
- isNew,
359
- isEfdRetry,
360
- isFlakyRetry: numAttempt > 0,
361
- isAttemptToFix,
362
- isAttemptToFixRetry,
363
- hasFailedAllRetries,
364
- hasPassedAllRetries,
365
- hasFailedAttemptToFix,
366
- isDisabled,
367
- isQuarantined
368
- })
346
+ testFinishCh.publish({
347
+ status,
348
+ skipReason,
349
+ error,
350
+ isNew,
351
+ isEfdRetry,
352
+ isFlakyRetry: numAttempt > 0,
353
+ isAttemptToFix,
354
+ isAttemptToFixRetry,
355
+ hasFailedAllRetries,
356
+ hasPassedAllRetries,
357
+ hasFailedAttemptToFix,
358
+ isDisabled,
359
+ isQuarantined,
360
+ ...attemptCtx.currentStore
369
361
  })
370
362
  })
371
363
  return promise
372
364
  } catch (err) {
373
- errorCh.publish(err)
374
- throw err
365
+ ctx.err = err
366
+ errorCh.runStores(ctx, () => {
367
+ throw err
368
+ })
375
369
  }
376
370
  })
377
371
  shimmer.wrap(pl.prototype, 'runStep', runStep => function () {
378
- if (!testStepStartCh.hasSubscribers) {
372
+ if (!testFinishCh.hasSubscribers) {
379
373
  return runStep.apply(this, arguments)
380
374
  }
381
375
  const testStep = arguments[0]
@@ -387,9 +381,8 @@ function wrapRun (pl, isLatestVersion) {
387
381
  resource = testStep.isHook ? 'hook' : testStep.pickleStep.text
388
382
  }
389
383
 
390
- const asyncResource = new AsyncResource('bound-anonymous-fn')
391
- return asyncResource.runInAsyncScope(() => {
392
- testStepStartCh.publish({ resource })
384
+ const ctx = { resource }
385
+ return testStepStartCh.runStores(ctx, () => {
393
386
  try {
394
387
  const promise = runStep.apply(this, arguments)
395
388
 
@@ -398,12 +391,14 @@ function wrapRun (pl, isLatestVersion) {
398
391
  ? getStatusFromResultLatest(result)
399
392
  : getStatusFromResult(result)
400
393
 
401
- testFinishCh.publish({ isStep: true, status, skipReason, errorMessage })
394
+ testFinishCh.publish({ isStep: true, status, skipReason, errorMessage, ...ctx.currentStore })
402
395
  })
403
396
  return promise
404
397
  } catch (err) {
405
- errorCh.publish(err)
406
- throw err
398
+ ctx.err = err
399
+ errorCh.runStores(ctx, () => {
400
+ throw err
401
+ })
407
402
  }
408
403
  })
409
404
  })
@@ -149,11 +149,9 @@ addHook({ name: 'express', versions: ['>=4.3.0 <5.0.0'] }, express => {
149
149
  const queryReadCh = channel('datadog:express:query:finish')
150
150
 
151
151
  addHook({ name: 'express', file: ['lib/request.js'], versions: ['>=5.0.0'] }, request => {
152
- const requestDescriptor = Object.getOwnPropertyDescriptor(request, 'query')
153
-
154
- shimmer.wrap(requestDescriptor, 'get', function (originalGet) {
152
+ shimmer.wrap(request, 'query', function (originalGet) {
155
153
  return function wrappedGet () {
156
- const query = originalGet.apply(this, arguments)
154
+ const query = originalGet.call(this)
157
155
 
158
156
  if (queryReadCh.hasSubscribers && query) {
159
157
  queryReadCh.publish({ query })
@@ -163,7 +161,5 @@ addHook({ name: 'express', file: ['lib/request.js'], versions: ['>=5.0.0'] }, re
163
161
  }
164
162
  })
165
163
 
166
- Object.defineProperty(request, 'query', requestDescriptor)
167
-
168
164
  return request
169
165
  })
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const { errorMonitor } = require('events')
3
4
  const { channel, addHook } = require('./helpers/instrument')
4
5
  const shimmer = require('../../datadog-shimmer')
5
6
 
@@ -199,16 +200,16 @@ function wrapCreateStream (original) {
199
200
  }
200
201
  const onFinish = () => {
201
202
  finishChannel.runStores(ctx, () => {})
202
- stream.off('close', onFinish)
203
- stream.off('end', onFinish)
204
- stream.off('finish', onFinish)
205
- stream.off('error', onError)
203
+ stream.removeListener('close', onFinish)
204
+ stream.removeListener('end', onFinish)
205
+ stream.removeListener('finish', onFinish)
206
+ stream.removeListener(errorMonitor, onError)
206
207
  }
207
208
 
208
209
  stream.once('close', onFinish)
209
210
  stream.once('end', onFinish)
210
211
  stream.once('finish', onFinish)
211
- stream.once('error', onError)
212
+ stream.once(errorMonitor, onError)
212
213
 
213
214
  return stream
214
215
  } catch (error) {
@@ -61,6 +61,7 @@ module.exports = {
61
61
  http2: () => require('../http2'),
62
62
  https: () => require('../http'),
63
63
  ioredis: () => require('../ioredis'),
64
+ iovalkey: () => require('../iovalkey'),
64
65
  'jest-circus': () => require('../jest'),
65
66
  'jest-config': () => require('../jest'),
66
67
  'jest-environment-node': () => require('../jest'),
@@ -43,7 +43,7 @@ if (!disabledInstrumentations.has('process')) {
43
43
  require('../process')
44
44
  }
45
45
 
46
- const HOOK_SYMBOL = Symbol('hookExportsMap')
46
+ const HOOK_SYMBOL = Symbol('hookExportsSet')
47
47
 
48
48
  if (DD_TRACE_DEBUG && DD_TRACE_DEBUG.toLowerCase() !== 'false') {
49
49
  checkRequireCache.checkForRequiredModules()
@@ -89,12 +89,13 @@ for (const packageName of names) {
89
89
  fullFilePattern = filename(name, fullFilePattern)
90
90
  }
91
91
 
92
- // Create a WeakMap associated with the hook function so that patches on the same moduleExport only happens once
92
+ // Create a WeakSet associated with the hook function so that patches on the same moduleExport only happens once
93
93
  // for example by instrumenting both dns and node:dns double the spans would be created
94
- // since they both patch the same moduleExport, this WeakMap is used to mitigate that
95
- if (!hook[HOOK_SYMBOL]) {
96
- hook[HOOK_SYMBOL] = new WeakMap()
97
- }
94
+ // since they both patch the same moduleExport, this WeakSet is used to mitigate that
95
+ // TODO(BridgeAR): Instead of using a WeakSet here, why not just use aliases for the hook in register?
96
+ // That way it would also not be duplicated. The actual name being used has to be identified else wise.
97
+ // Maybe it is also not important to know what name was actually used?
98
+ hook[HOOK_SYMBOL] ??= new WeakSet()
98
99
  let matchesFile = false
99
100
 
100
101
  matchesFile = moduleName === fullFilename
@@ -114,7 +115,7 @@ for (const packageName of names) {
114
115
  log.error('Error getting version for "%s": %s', name, e.message, e)
115
116
  continue
116
117
  }
117
- if (typeof namesAndSuccesses[`${name}@${version}`] === 'undefined') {
118
+ if (namesAndSuccesses[`${name}@${version}`] === undefined) {
118
119
  // TODO If `file` is present, we might elsewhere instrument the result of the module
119
120
  // for a version range that actually matches, so we can't assume that we're _not_
120
121
  // going to instrument that. However, the way the data model around instrumentation
@@ -140,12 +141,16 @@ for (const packageName of names) {
140
141
  loadChannel.publish({ name, version, file })
141
142
  // Send the name and version of the module back to the callback because now addHook
142
143
  // takes in an array of names so by passing the name the callback will know which module name is being used
143
- moduleExports = hook(moduleExports, version, name)
144
- // Set the moduleExports in the hooks weakmap
145
- hook[HOOK_SYMBOL].set(moduleExports, name)
144
+ // TODO(BridgeAR): This is only true in case the name is identical
145
+ // in all loads. If they deviate, the deviating name would not be
146
+ // picked up due to the unification. Check what modules actually use the name.
147
+ // TODO(BridgeAR): Only replace moduleExports if the hook returns a new value.
148
+ // This allows to reduce the instrumentation code (no return needed).
149
+ moduleExports = hook(moduleExports, version, name) ?? moduleExports
150
+ // Set the moduleExports in the hooks WeakSet
151
+ hook[HOOK_SYMBOL].add(moduleExports)
146
152
  } catch (e) {
147
- log.info('Error during ddtrace instrumentation of application, aborting.')
148
- log.info(e)
153
+ log.info('Error during ddtrace instrumentation of application, aborting.', e)
149
154
  telemetry('error', [
150
155
  `error_type:${e.constructor.name}`,
151
156
  `integration:${name}`,
@@ -3,6 +3,7 @@
3
3
  /* eslint-disable no-fallthrough */
4
4
 
5
5
  const url = require('url')
6
+ const { errorMonitor } = require('events')
6
7
  const { channel, addHook } = require('../helpers/instrument')
7
8
  const shimmer = require('../../../datadog-shimmer')
8
9
 
@@ -88,7 +89,7 @@ function patch (http, methodName) {
88
89
  const res = arg
89
90
  ctx.res = res
90
91
  res.on('end', finish)
91
- res.on('error', finish)
92
+ res.on(errorMonitor, finish)
92
93
  break
93
94
  }
94
95
  case 'connect':
@@ -0,0 +1,51 @@
1
+ 'use strict'
2
+
3
+ const {
4
+ channel,
5
+ addHook,
6
+ AsyncResource
7
+ } = require('./helpers/instrument')
8
+ const shimmer = require('../../datadog-shimmer')
9
+
10
+ const startCh = channel('apm:iovalkey:command:start')
11
+ const finishCh = channel('apm:iovalkey:command:finish')
12
+ const errorCh = channel('apm:iovalkey:command:error')
13
+
14
+ addHook({ name: 'iovalkey', versions: ['>=0.0.1'] }, Valkey => {
15
+ shimmer.wrap(Valkey.prototype, 'sendCommand', sendCommand => function (command, stream) {
16
+ if (!startCh.hasSubscribers) return sendCommand.apply(this, arguments)
17
+
18
+ if (!command?.promise) return sendCommand.apply(this, arguments)
19
+
20
+ const options = this.options || {}
21
+ const connectionName = options.connectionName
22
+ const db = options.db
23
+ const connectionOptions = { host: options.host, port: options.port }
24
+
25
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
26
+ return asyncResource.runInAsyncScope(() => {
27
+ startCh.publish({ db, command: command.name, args: command.args, connectionOptions, connectionName })
28
+
29
+ const onResolve = asyncResource.bind(() => finishCh.publish())
30
+ const onReject = asyncResource.bind(err => finish(finishCh, errorCh, err))
31
+
32
+ command.promise.then(onResolve, onReject)
33
+
34
+ try {
35
+ return sendCommand.apply(this, arguments)
36
+ } catch (err) {
37
+ errorCh.publish(err)
38
+
39
+ throw err
40
+ }
41
+ })
42
+ })
43
+ return Valkey
44
+ })
45
+
46
+ function finish (finishCh, errorCh, error) {
47
+ if (error) {
48
+ errorCh.publish(error)
49
+ }
50
+ finishCh.publish()
51
+ }