dd-trace 5.99.1 → 5.101.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 (101) hide show
  1. package/LICENSE-3rdparty.csv +0 -1
  2. package/index.d.ts +14 -0
  3. package/package.json +8 -8
  4. package/packages/datadog-instrumentations/src/cucumber.js +69 -5
  5. package/packages/datadog-instrumentations/src/cypress.js +5 -3
  6. package/packages/datadog-instrumentations/src/express.js +3 -2
  7. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  8. package/packages/datadog-instrumentations/src/hono.js +15 -4
  9. package/packages/datadog-instrumentations/src/http/client.js +20 -3
  10. package/packages/datadog-instrumentations/src/jest.js +146 -90
  11. package/packages/datadog-instrumentations/src/mocha/common.js +4 -1
  12. package/packages/datadog-instrumentations/src/mocha/main.js +43 -26
  13. package/packages/datadog-instrumentations/src/mocha/utils.js +114 -96
  14. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -4
  15. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +11 -6
  16. package/packages/datadog-instrumentations/src/path-to-regexp.js +44 -0
  17. package/packages/datadog-instrumentations/src/playwright.js +108 -18
  18. package/packages/datadog-instrumentations/src/router.js +53 -33
  19. package/packages/datadog-instrumentations/src/vitest.js +76 -30
  20. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -1
  21. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -1
  22. package/packages/datadog-plugin-bullmq/src/consumer.js +5 -4
  23. package/packages/datadog-plugin-bullmq/src/producer.js +37 -29
  24. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +49 -9
  25. package/packages/datadog-plugin-cypress/src/plugin.js +5 -14
  26. package/packages/datadog-plugin-cypress/src/support.js +22 -21
  27. package/packages/datadog-plugin-grpc/src/client.js +1 -1
  28. package/packages/datadog-plugin-grpc/src/server.js +1 -1
  29. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -9
  30. package/packages/datadog-plugin-kafkajs/src/producer.js +2 -8
  31. package/packages/datadog-plugin-mongodb-core/src/index.js +2 -3
  32. package/packages/datadog-plugin-playwright/src/index.js +6 -0
  33. package/packages/datadog-plugin-router/src/index.js +13 -0
  34. package/packages/dd-trace/index.js +4 -3
  35. package/packages/dd-trace/src/aiguard/sdk.js +2 -2
  36. package/packages/dd-trace/src/appsec/reporter.js +4 -1
  37. package/packages/dd-trace/src/baggage.js +10 -0
  38. package/packages/dd-trace/src/ci-visibility/lage.js +2 -1
  39. package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -33
  40. package/packages/dd-trace/src/config/config-types.d.ts +0 -2
  41. package/packages/dd-trace/src/config/generated-config-types.d.ts +17 -41
  42. package/packages/dd-trace/src/config/index.js +7 -60
  43. package/packages/dd-trace/src/config/normalize-service.js +31 -0
  44. package/packages/dd-trace/src/config/supported-configurations.json +15 -32
  45. package/packages/dd-trace/src/datastreams/checkpointer.js +4 -10
  46. package/packages/dd-trace/src/datastreams/encoding.js +39 -28
  47. package/packages/dd-trace/src/datastreams/pathway.js +29 -26
  48. package/packages/dd-trace/src/datastreams/processor.js +17 -15
  49. package/packages/dd-trace/src/datastreams/size.js +6 -2
  50. package/packages/dd-trace/src/debugger/config.js +6 -3
  51. package/packages/dd-trace/src/debugger/devtools_client/index.js +2 -5
  52. package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -1
  53. package/packages/dd-trace/src/dogstatsd.js +10 -7
  54. package/packages/dd-trace/src/encode/0.4.js +3 -3
  55. package/packages/dd-trace/src/encode/0.5.js +2 -2
  56. package/packages/dd-trace/src/encode/agentless-json.js +2 -2
  57. package/packages/dd-trace/src/encode/tags-processors.js +2 -27
  58. package/packages/dd-trace/src/exporters/common/request.js +22 -11
  59. package/packages/dd-trace/src/exporters/common/retry.js +104 -0
  60. package/packages/dd-trace/src/git_metadata.js +66 -0
  61. package/packages/dd-trace/src/git_metadata_tagger.js +13 -5
  62. package/packages/dd-trace/src/heap_snapshots.js +4 -4
  63. package/packages/dd-trace/src/id.js +15 -26
  64. package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
  65. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +27 -16
  66. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +3 -0
  67. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +30 -13
  68. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +20 -50
  69. package/packages/dd-trace/src/llmobs/sdk.js +5 -1
  70. package/packages/dd-trace/src/llmobs/span_processor.js +28 -2
  71. package/packages/dd-trace/src/llmobs/tagger.js +42 -0
  72. package/packages/dd-trace/src/llmobs/telemetry.js +29 -0
  73. package/packages/dd-trace/src/llmobs/util.js +80 -5
  74. package/packages/dd-trace/src/openfeature/eval-metrics-hook.js +2 -2
  75. package/packages/dd-trace/src/opentelemetry/active-span-proxy.js +42 -0
  76. package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +106 -0
  77. package/packages/dd-trace/src/opentelemetry/context_manager.js +22 -10
  78. package/packages/dd-trace/src/opentelemetry/span-helpers.js +308 -0
  79. package/packages/dd-trace/src/opentelemetry/span.js +42 -108
  80. package/packages/dd-trace/src/opentelemetry/tracer.js +11 -36
  81. package/packages/dd-trace/src/opentracing/propagation/text_map.js +95 -36
  82. package/packages/dd-trace/src/opentracing/propagation/tracestate.js +98 -32
  83. package/packages/dd-trace/src/opentracing/span.js +58 -49
  84. package/packages/dd-trace/src/opentracing/span_context.js +1 -0
  85. package/packages/dd-trace/src/plugins/util/ci.js +119 -32
  86. package/packages/dd-trace/src/plugins/util/test.js +293 -27
  87. package/packages/dd-trace/src/priority_sampler.js +6 -4
  88. package/packages/dd-trace/src/profiling/config.js +5 -4
  89. package/packages/dd-trace/src/profiling/ssi-heuristics.js +2 -2
  90. package/packages/dd-trace/src/propagation-hash/index.js +1 -1
  91. package/packages/dd-trace/src/proxy.js +3 -3
  92. package/packages/dd-trace/src/remote_config/index.js +5 -3
  93. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +1 -1
  94. package/packages/dd-trace/src/span_format.js +52 -5
  95. package/packages/dd-trace/src/span_processor.js +1 -5
  96. package/packages/dd-trace/src/spanleak.js +0 -1
  97. package/packages/dd-trace/src/telemetry/telemetry.js +7 -5
  98. package/packages/dd-trace/src/tracer_metadata.js +1 -1
  99. package/packages/dd-trace/src/util.js +17 -0
  100. package/vendor/dist/path-to-regexp/LICENSE +0 -21
  101. package/vendor/dist/path-to-regexp/index.js +0 -1
@@ -85,7 +85,6 @@
85
85
  "node-gyp-build","https://github.com/prebuild/node-gyp-build","['MIT']","['Mathias Buus']"
86
86
  "opentracing","https://github.com/opentracing/opentracing-javascript","['Apache-2.0']","['opentracing']"
87
87
  "oxc-parser","https://github.com/oxc-project/oxc","['MIT']","['Boshen and oxc contributors']"
88
- "path-to-regexp","https://github.com/pillarjs/path-to-regexp","['MIT']","['pillarjs']"
89
88
  "pprof-format","https://github.com/DataDog/pprof-format","['MIT']","['Datadog Inc.']"
90
89
  "protobufjs","https://github.com/protobufjs/protobuf.js","['BSD-3-Clause']","['Daniel Wirtz']"
91
90
  "queue-tick","https://github.com/mafintosh/queue-tick","['MIT']","['Mathias Buus']"
package/index.d.ts CHANGED
@@ -3934,6 +3934,13 @@ declare namespace tracer {
3934
3934
  */
3935
3935
  tags?: { [key: string]: any },
3936
3936
 
3937
+ /**
3938
+ * List of tag keys to propagate to LLM Observability cost and token metrics emitted from this span.
3939
+ * Each key must already be present in `tags` from this call or from a previous annotation on the
3940
+ * same span.
3941
+ */
3942
+ costTags?: string[],
3943
+
3937
3944
  /**
3938
3945
  * A Prompt object that represents the prompt used for an LLM call. Only used on `llm` spans.
3939
3946
  */
@@ -3946,6 +3953,13 @@ declare namespace tracer {
3946
3953
  */
3947
3954
  tags?: { [key: string]: any },
3948
3955
 
3956
+ /**
3957
+ * List of tag keys to propagate to LLM Observability cost and token metrics emitted from each span
3958
+ * in the context.
3959
+ * Each key must already be present in `tags` on the span when it starts.
3960
+ */
3961
+ costTags?: string[],
3962
+
3949
3963
  /**
3950
3964
  * Set to override the span name for any spans annotated within the returned context.
3951
3965
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.99.1",
3
+ "version": "5.101.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -18,11 +18,11 @@
18
18
  "type:check": "tsc --noEmit -p tsconfig.dev.json",
19
19
  "type:doc:build": "cd docs && yarn && yarn build",
20
20
  "type:doc:test": "cd docs && yarn && yarn test",
21
- "lint": "node scripts/check_licenses.js && node scripts/check-no-coverage-artifacts.js && eslint . --concurrency=auto --max-warnings 0",
22
- "lint:fix": "node scripts/check_licenses.js && node scripts/check-no-coverage-artifacts.js && eslint . --concurrency=auto --max-warnings 0 --fix",
21
+ "lint": "node scripts/check_licenses.js && node scripts/check-no-coverage-artifacts.js && node scripts/check-no-mcr-images.js && eslint . --concurrency=auto --max-warnings 0",
22
+ "lint:fix": "node scripts/check_licenses.js && node scripts/check-no-coverage-artifacts.js && node scripts/check-no-mcr-images.js && eslint . --concurrency=auto --max-warnings 0 --fix",
23
23
  "lint:inspect": "npx @eslint/config-inspector@latest",
24
24
  "lint:codeowners": "codeowners-audit",
25
- "lint:codeowners:ci": "codeowners-audit --glob='**/*.spec.js'",
25
+ "lint:codeowners:ci": "codeowners-audit --glob='**/*.spec.js' --glob='benchmark/sirun/**'",
26
26
  "release:proposal": "node scripts/release/proposal",
27
27
  "services": "node ./scripts/install_plugin_modules && node packages/dd-trace/test/setup/services",
28
28
  "test": "echo '\nError: The root \"npm test\" command is intentionally disabled.\n\nInstead, run specific test suites:\n - npm run test:trace:core\n - npm run test:appsec\n - etc.\n\nOr run individual test files:\n npx mocha path/to/test.spec.js\n\nSee CONTRIBUTING.md (Testing section) for more details.\n' && exit 1",
@@ -166,12 +166,12 @@
166
166
  "@datadog/native-appsec": "11.0.1",
167
167
  "@datadog/native-iast-taint-tracking": "4.1.0",
168
168
  "@datadog/native-metrics": "3.1.1",
169
- "@datadog/openfeature-node-server": "^1.1.1",
169
+ "@datadog/openfeature-node-server": "^1.1.2",
170
170
  "@datadog/pprof": "5.14.1",
171
171
  "@datadog/wasm-js-rewriter": "5.0.1",
172
172
  "@opentelemetry/api": ">=1.0.0 <1.10.0",
173
173
  "@opentelemetry/api-logs": "<1.0.0",
174
- "oxc-parser": "^0.127.0"
174
+ "oxc-parser": "^0.128.0"
175
175
  },
176
176
  "devDependencies": {
177
177
  "@actions/core": "^3.0.1",
@@ -192,12 +192,12 @@
192
192
  "bun": "1.3.13",
193
193
  "codeowners-audit": "^2.9.0",
194
194
  "eslint": "^9.39.2",
195
- "eslint-plugin-cypress": "^6.3.1",
195
+ "eslint-plugin-cypress": "^6.4.0",
196
196
  "eslint-plugin-import": "^2.32.0",
197
197
  "eslint-plugin-jsdoc": "^62.9.0",
198
198
  "eslint-plugin-mocha": "^11.2.0",
199
199
  "eslint-plugin-n": "^17.23.2",
200
- "eslint-plugin-promise": "^7.2.1",
200
+ "eslint-plugin-promise": "^7.3.0",
201
201
  "eslint-plugin-unicorn": "^64.0.0",
202
202
  "express": "^5.1.0",
203
203
  "glob": "^10.4.5",
@@ -12,6 +12,10 @@ const {
12
12
  getTestSuitePath,
13
13
  CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
14
14
  getIsFaultyEarlyFlakeDetection,
15
+ recordAttemptToFixExecution,
16
+ collectAttemptToFixExecutionsFromTraces,
17
+ logAttemptToFixTestExecution,
18
+ logTestOptimizationSummary,
15
19
  } = require('../../dd-trace/src/plugins/util/test')
16
20
  const satisfies = require('../../../vendor/dist/semifies')
17
21
  const { addHook, channel } = require('./helpers/instrument')
@@ -60,9 +64,12 @@ const atrStatusesByScenarioKey = new Map()
60
64
  const numRetriesByPickleId = new Map()
61
65
  const numAttemptToCtx = new Map()
62
66
  const newTestsByTestFullname = new Map()
67
+ const attemptToFixTestsByTestFullname = new Map()
63
68
  const modifiedTestsByPickleId = new Map()
64
69
  // Pickle IDs for tests that are genuinely new (not in known tests list).
65
70
  const newTestPickleIds = new Set()
71
+ const attemptToFixExecutions = new Map()
72
+ const loggedAttemptToFixTests = new Set()
66
73
 
67
74
  let eventDataCollector = null
68
75
  let pickleByFile = {}
@@ -154,6 +161,16 @@ function getTestStatusFromRetries (testStatuses) {
154
161
  return 'pass'
155
162
  }
156
163
 
164
+ function getTestStatusFromAttemptToFixExecutions (testStatuses) {
165
+ if (testStatuses.every(status => status === 'pass')) {
166
+ return 'pass'
167
+ }
168
+ if (testStatuses.every(status => status === 'skip')) {
169
+ return 'skip'
170
+ }
171
+ return 'fail'
172
+ }
173
+
157
174
  function getErrorFromCucumberResult (cucumberResult) {
158
175
  if (!cucumberResult.message) {
159
176
  return
@@ -247,9 +264,8 @@ function getFinalStatus ({
247
264
  }) {
248
265
  // Note that intermediate executions DO NOT report a final status tag
249
266
 
250
- // If the test is quarantined or disabled, regardless of its actual execution result or active retry features,
251
- // the final status of its last execution should be reported as 'skip'.
252
- if (isQuarantined || isDisabled || status === 'skip') {
267
+ // If the test is quarantined or disabled, its final status is skip unless attempt-to-fix takes precedence.
268
+ if (status === 'skip' || (!isLastAttemptToFix && (isQuarantined || isDisabled))) {
253
269
  return 'skip'
254
270
  }
255
271
 
@@ -282,6 +298,7 @@ function wrapRun (pl, isLatestVersion, version) {
282
298
  let numAttempt = 0
283
299
 
284
300
  const testFileAbsolutePath = this.pickle.uri
301
+ const testSuitePath = getTestSuitePath(testFileAbsolutePath, process.cwd())
285
302
 
286
303
  const testSourceLine = this.gherkinDocument?.feature?.location?.line
287
304
 
@@ -293,6 +310,9 @@ function wrapRun (pl, isLatestVersion, version) {
293
310
  }
294
311
  const ctx = testStartPayload
295
312
  numAttemptToCtx.set(numAttempt, ctx)
313
+ if (isTestManagementTestsEnabled && getTestProperties(testSuitePath, this.pickle.name).attemptToFix) {
314
+ logAttemptToFixTestExecution(testSuitePath, this.pickle.name, loggedAttemptToFixTests)
315
+ }
296
316
  testStartCh.runStores(ctx, () => {})
297
317
  const promises = {}
298
318
  try {
@@ -441,6 +461,16 @@ function wrapRun (pl, isLatestVersion, version) {
441
461
 
442
462
  const error = getErrorFromCucumberResult(result)
443
463
 
464
+ if (isAttemptToFix) {
465
+ recordAttemptToFixExecution(attemptToFixExecutions, {
466
+ testSuite: testSuitePath,
467
+ testName: this.pickle.name,
468
+ status,
469
+ isDisabled,
470
+ isQuarantined,
471
+ })
472
+ }
473
+
444
474
  if (promises.hitBreakpointPromise) {
445
475
  await promises.hitBreakpointPromise
446
476
  }
@@ -661,6 +691,7 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
661
691
  }
662
692
 
663
693
  atrStatusesByScenarioKey.clear()
694
+ attemptToFixTestsByTestFullname.clear()
664
695
  sessionStartCh.publish({ command, frameworkVersion })
665
696
 
666
697
  if (!errorSkippableRequest && skippedSuites.length) {
@@ -701,6 +732,8 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
701
732
  isTestManagementTestsEnabled,
702
733
  isParallel,
703
734
  })
735
+ logTestOptimizationSummary({ attemptToFixExecutions })
736
+ loggedAttemptToFixTests.clear()
704
737
  eventDataCollector = null
705
738
  return success
706
739
  }
@@ -811,6 +844,7 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
811
844
  let testStatus = lastTestStatus
812
845
  let shouldBePassedByEFD = false
813
846
  let shouldBePassedByTestManagement = false
847
+ let shouldBeFailedByAttemptToFix = false
814
848
  if ((isNew || isModified) && isEarlyFlakeDetectionEnabled) {
815
849
  /**
816
850
  * If Early Flake Detection (EFD) is enabled the logic is as follows:
@@ -827,7 +861,15 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
827
861
  }
828
862
  }
829
863
 
830
- if (isTestManagementTestsEnabled && (isDisabled || isQuarantined)) {
864
+ if (isAttemptToFix && testStatuses.length === testManagementAttemptToFixRetries + 1) {
865
+ testStatus = getTestStatusFromAttemptToFixExecutions(testStatuses)
866
+ if (testStatus === 'fail') {
867
+ this.success = false
868
+ shouldBeFailedByAttemptToFix = true
869
+ }
870
+ }
871
+
872
+ if (isTestManagementTestsEnabled && !isAttemptToFix && (isDisabled || isQuarantined)) {
831
873
  this.success = true
832
874
  shouldBePassedByTestManagement = true
833
875
  }
@@ -863,10 +905,14 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
863
905
  return shouldBePassedByEFD
864
906
  }
865
907
 
866
- if (isNewerCucumberVersion && isTestManagementTestsEnabled && (isQuarantined || isDisabled)) {
908
+ if (isNewerCucumberVersion && isTestManagementTestsEnabled && !isAttemptToFix && (isQuarantined || isDisabled)) {
867
909
  return shouldBePassedByTestManagement
868
910
  }
869
911
 
912
+ if (isNewerCucumberVersion && isAttemptToFix && shouldBeFailedByAttemptToFix) {
913
+ return false
914
+ }
915
+
870
916
  return runTestCaseResult
871
917
  }
872
918
  }
@@ -882,6 +928,7 @@ function getWrappedParseWorkerMessage (parseWorkerMessageFunction, isNewVersion)
882
928
  if (Array.isArray(message)) {
883
929
  const [messageCode, payload] = message
884
930
  if (messageCode === CUCUMBER_WORKER_TRACE_PAYLOAD_CODE) {
931
+ collectAttemptToFixExecutionsFromTraces(payload, attemptToFixExecutions)
885
932
  workerReportTraceCh.publish(payload)
886
933
  return
887
934
  }
@@ -962,6 +1009,23 @@ function getWrappedParseWorkerMessage (parseWorkerMessageFunction, isNewVersion)
962
1009
  // we only push to `finished` if the retries have finished
963
1010
  finished.push(newTestFinalStatus)
964
1011
  }
1012
+ } else if (
1013
+ isTestManagementTestsEnabled &&
1014
+ getTestProperties(getTestSuitePath(testFileAbsolutePath, process.cwd()), pickle.name).attemptToFix
1015
+ ) {
1016
+ const testFullname = `${pickle.uri}:${pickle.name}`
1017
+ let testStatuses = attemptToFixTestsByTestFullname.get(testFullname)
1018
+ if (testStatuses) {
1019
+ testStatuses.push(status)
1020
+ } else {
1021
+ testStatuses = [status]
1022
+ attemptToFixTestsByTestFullname.set(testFullname, testStatuses)
1023
+ }
1024
+
1025
+ if (status === 'skip' || testStatuses.length === testManagementAttemptToFixRetries + 1) {
1026
+ finished.push(getTestStatusFromAttemptToFixExecutions(testStatuses))
1027
+ attemptToFixTestsByTestFullname.delete(testFullname)
1028
+ }
965
1029
  } else {
966
1030
  // TODO: can we get error message?
967
1031
  const finished = pickleResultByFile[testFileAbsolutePath]
@@ -8,11 +8,13 @@ const {
8
8
  wrapConfig,
9
9
  } = require('./cypress-config')
10
10
 
11
+ const MINIMUM_CYPRESS_VERSION = DD_MAJOR >= 6 ? '>=12.0.0' : '>=10.2.0'
12
+
11
13
  // Wrap defineConfig() so configs are instrumented when loaded in Cypress's
12
14
  // config child process. This covers both CLI and programmatic usage with CJS configs.
13
15
  addHook({
14
16
  name: 'cypress',
15
- versions: ['>=10.2.0'],
17
+ versions: [MINIMUM_CYPRESS_VERSION],
16
18
  }, (cypress) => {
17
19
  if (typeof cypress.defineConfig === 'function') {
18
20
  shimmer.wrap(cypress, 'defineConfig', (defineConfig) => function (config) {
@@ -61,7 +63,7 @@ function wrapStartOnModule (mod) {
61
63
  for (const file of ['lib/exec/run.js', 'lib/exec/open.js', 'dist/exec/run.js', 'dist/exec/open.js']) {
62
64
  addHook({
63
65
  name: 'cypress',
64
- versions: ['>=10.2.0'],
66
+ versions: [MINIMUM_CYPRESS_VERSION],
65
67
  file,
66
68
  }, wrapStartOnModule)
67
69
  }
@@ -70,7 +72,7 @@ for (const file of ['lib/exec/run.js', 'lib/exec/open.js', 'dist/exec/run.js', '
70
72
  // The chunk exports runModule and openModule, each with a start() method.
71
73
  addHook({
72
74
  name: 'cypress',
73
- versions: ['>=10.2.0'],
75
+ versions: [MINIMUM_CYPRESS_VERSION],
74
76
  filePattern: 'dist/cli.*',
75
77
  }, (cliChunk) => {
76
78
  if (cliChunk.runModule?.start) {
@@ -3,6 +3,7 @@
3
3
  const shimmer = require('../../datadog-shimmer')
4
4
  const { createWrapRouterMethod } = require('./router')
5
5
  const { addHook, channel, tracingChannel } = require('./helpers/instrument')
6
+ const { getCompileToRegexp } = require('./path-to-regexp')
6
7
  const {
7
8
  setRouterMountPath,
8
9
  markAppMounted,
@@ -26,8 +27,6 @@ function wrapHandle (handle) {
26
27
  }
27
28
  }
28
29
 
29
- const wrapRouterMethod = createWrapRouterMethod('express')
30
-
31
30
  const responseJsonChannel = channel('datadog:express:response:json:start')
32
31
 
33
32
  function wrapResponseJson (json) {
@@ -163,6 +162,8 @@ addHook({ name: 'express', versions: ['>=4'], file: 'lib/express.js' }, express
163
162
  // It would otherwise produce spans for router and express, and so duplicating them.
164
163
  // We now fall back to router instrumentation
165
164
  addHook({ name: 'express', versions: ['4'], file: 'lib/express.js' }, express => {
165
+ const wrapRouterMethod = createWrapRouterMethod('express', getCompileToRegexp())
166
+
166
167
  shimmer.wrap(express.Router, 'use', wrapRouterMethod)
167
168
  shimmer.wrap(express.Router, 'route', wrapRouterMethod)
168
169
 
@@ -118,6 +118,7 @@ module.exports = {
118
118
  passport: () => require('../passport'),
119
119
  'passport-http': () => require('../passport-http'),
120
120
  'passport-local': () => require('../passport-local'),
121
+ 'path-to-regexp': () => require('../path-to-regexp'),
121
122
  pg: () => require('../pg'),
122
123
  pino: () => require('../pino'),
123
124
  'pino-pretty': () => require('../pino'),
@@ -14,9 +14,15 @@ const enterChannel = channel('apm:hono:middleware:enter')
14
14
  const exitChannel = channel('apm:hono:middleware:exit')
15
15
  const finishChannel = channel('apm:hono:middleware:finish')
16
16
 
17
+ // `app.request()` and non-node adapters call `app.fetch` without an `incoming`
18
+ // IncomingMessage; the APM `web` helpers depend on one, so the wrappers below
19
+ // skip publishing whenever it is missing.
17
20
  function wrapFetch (fetch) {
18
21
  return function (request, env, executionCtx) {
19
- handleChannel.publish({ req: env.incoming })
22
+ const req = env?.incoming
23
+ if (req) {
24
+ handleChannel.publish({ req })
25
+ }
20
26
  return fetch.apply(this, arguments)
21
27
  }
22
28
  }
@@ -31,8 +37,10 @@ function wrapCompose (compose) {
31
37
 
32
38
  const instrumentedOnError = (...args) => {
33
39
  const [error, context] = args
34
- const req = context.env.incoming
35
- errorChannel.publish({ req, error })
40
+ const req = context.env?.incoming
41
+ if (req) {
42
+ errorChannel.publish({ req, error })
43
+ }
36
44
  return onError(...args)
37
45
  }
38
46
 
@@ -62,7 +70,10 @@ function wrapMiddleware (middleware, route) {
62
70
  middleware,
63
71
  (middleware) =>
64
72
  function (context, next) {
65
- const req = context.env.incoming
73
+ const req = context.env?.incoming
74
+ if (!req) {
75
+ return middleware.apply(this, arguments)
76
+ }
66
77
  routeChannel.publish({ req, route })
67
78
  enterChannel.publish({ req, name, route })
68
79
  if (typeof next === 'function') {
@@ -26,10 +26,27 @@ function hookFn (http) {
26
26
  return http
27
27
  }
28
28
 
29
+ // `inputURL` may be the user's options object (for the `http.request(options)`
30
+ // shape); never write directly into it. The result is later mutated by
31
+ // `normalizeHeaders` and read by `url.format`, so the merged object must be
32
+ // owned by the tracer. `undefined` means "no URL supplied" — Node merges
33
+ // with the options object or its defaults, so build a tracer-owned
34
+ // options-only shape and let tracing proceed. `null`/primitive first args
35
+ // are returned as-is so `normalizeHeaders` throws and the surrounding
36
+ // try/catch in `instrumentRequest` falls through to the native request;
37
+ // spreading a primitive yields `{}`, which would silently turn an invalid
38
+ // `http.request(123)` into a synthesized localhost request.
29
39
  function combineOptions (inputURL, inputOptions) {
30
- return inputOptions !== null && typeof inputOptions === 'object'
31
- ? Object.assign(inputURL || {}, inputOptions)
32
- : inputURL
40
+ if (inputURL === undefined) {
41
+ return inputOptions !== null && typeof inputOptions === 'object' ? { ...inputOptions } : {}
42
+ }
43
+ if (inputURL === null || (typeof inputURL !== 'object' && typeof inputURL !== 'function')) {
44
+ return inputURL
45
+ }
46
+ if (inputOptions !== null && typeof inputOptions === 'object') {
47
+ return { ...inputURL, ...inputOptions }
48
+ }
49
+ return { ...inputURL }
33
50
  }
34
51
  function normalizeHeaders (options) {
35
52
  options.headers ??= {}