dd-trace 5.104.0 → 5.105.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 (151) hide show
  1. package/LICENSE-3rdparty.csv +90 -102
  2. package/index.d.ts +82 -3
  3. package/package.json +15 -15
  4. package/packages/datadog-core/src/storage.js +1 -1
  5. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  6. package/packages/datadog-instrumentations/src/ai.js +8 -7
  7. package/packages/datadog-instrumentations/src/aws-sdk.js +13 -0
  8. package/packages/datadog-instrumentations/src/azure-cosmos.js +7 -0
  9. package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
  10. package/packages/datadog-instrumentations/src/cucumber.js +78 -5
  11. package/packages/datadog-instrumentations/src/dns.js +54 -18
  12. package/packages/datadog-instrumentations/src/fastify.js +142 -82
  13. package/packages/datadog-instrumentations/src/graphql.js +188 -62
  14. package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
  15. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  16. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
  17. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
  18. package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
  19. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  20. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -3
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +37 -236
  26. package/packages/datadog-instrumentations/src/hono.js +54 -3
  27. package/packages/datadog-instrumentations/src/http/server.js +9 -4
  28. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
  29. package/packages/datadog-instrumentations/src/jest.js +360 -150
  30. package/packages/datadog-instrumentations/src/kafkajs.js +120 -16
  31. package/packages/datadog-instrumentations/src/mocha/main.js +128 -17
  32. package/packages/datadog-instrumentations/src/nats.js +182 -0
  33. package/packages/datadog-instrumentations/src/nyc.js +38 -1
  34. package/packages/datadog-instrumentations/src/openai.js +33 -18
  35. package/packages/datadog-instrumentations/src/oracledb.js +6 -1
  36. package/packages/datadog-instrumentations/src/pino.js +17 -5
  37. package/packages/datadog-instrumentations/src/playwright.js +515 -292
  38. package/packages/datadog-instrumentations/src/router.js +76 -32
  39. package/packages/datadog-instrumentations/src/stripe.js +1 -1
  40. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
  41. package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
  42. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
  43. package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
  44. package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
  45. package/packages/datadog-plugin-bunyan/src/index.js +28 -0
  46. package/packages/datadog-plugin-cucumber/src/index.js +17 -3
  47. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +199 -28
  48. package/packages/datadog-plugin-cypress/src/support.js +69 -1
  49. package/packages/datadog-plugin-dns/src/lookup.js +8 -6
  50. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
  51. package/packages/datadog-plugin-graphql/src/execute.js +2 -0
  52. package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
  53. package/packages/datadog-plugin-http/src/server.js +40 -15
  54. package/packages/datadog-plugin-jest/src/index.js +11 -3
  55. package/packages/datadog-plugin-jest/src/util.js +15 -8
  56. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
  57. package/packages/datadog-plugin-kafkajs/src/producer.js +3 -0
  58. package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
  59. package/packages/datadog-plugin-mocha/src/index.js +19 -4
  60. package/packages/datadog-plugin-mongodb-core/src/index.js +281 -40
  61. package/packages/datadog-plugin-nats/src/consumer.js +43 -0
  62. package/packages/datadog-plugin-nats/src/index.js +20 -0
  63. package/packages/datadog-plugin-nats/src/producer.js +62 -0
  64. package/packages/datadog-plugin-nats/src/util.js +33 -0
  65. package/packages/datadog-plugin-next/src/index.js +5 -3
  66. package/packages/datadog-plugin-openai/src/tracing.js +15 -2
  67. package/packages/datadog-plugin-oracledb/src/index.js +13 -2
  68. package/packages/datadog-plugin-pino/src/index.js +42 -0
  69. package/packages/datadog-plugin-playwright/src/index.js +4 -4
  70. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
  71. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  72. package/packages/datadog-plugin-router/src/index.js +33 -44
  73. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  74. package/packages/datadog-plugin-vitest/src/index.js +5 -13
  75. package/packages/datadog-plugin-winston/src/index.js +30 -0
  76. package/packages/datadog-shimmer/src/shimmer.js +33 -40
  77. package/packages/dd-trace/src/aiguard/index.js +1 -1
  78. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  79. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  80. package/packages/dd-trace/src/appsec/index.js +1 -1
  81. package/packages/dd-trace/src/appsec/reporter.js +5 -6
  82. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  83. package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
  84. package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
  85. package/packages/dd-trace/src/baggage.js +7 -1
  86. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
  87. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
  88. package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
  89. package/packages/dd-trace/src/config/generated-config-types.d.ts +6 -2
  90. package/packages/dd-trace/src/config/supported-configurations.json +27 -8
  91. package/packages/dd-trace/src/datastreams/writer.js +2 -4
  92. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
  93. package/packages/dd-trace/src/encode/0.4.js +124 -108
  94. package/packages/dd-trace/src/encode/0.5.js +114 -26
  95. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +31 -23
  96. package/packages/dd-trace/src/encode/agentless-json.js +4 -2
  97. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
  98. package/packages/dd-trace/src/encode/span-stats.js +16 -16
  99. package/packages/dd-trace/src/encode/tags-processors.js +16 -0
  100. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  101. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  102. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  103. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
  104. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  105. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
  106. package/packages/dd-trace/src/llmobs/sdk.js +0 -16
  107. package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
  108. package/packages/dd-trace/src/llmobs/tagger.js +9 -1
  109. package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
  110. package/packages/dd-trace/src/llmobs/util.js +66 -3
  111. package/packages/dd-trace/src/log/index.js +1 -1
  112. package/packages/dd-trace/src/msgpack/chunk.js +394 -10
  113. package/packages/dd-trace/src/msgpack/index.js +96 -2
  114. package/packages/dd-trace/src/openfeature/encoding.js +70 -0
  115. package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
  116. package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
  117. package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
  118. package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
  119. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  120. package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
  121. package/packages/dd-trace/src/opentracing/propagation/text_map.js +62 -67
  122. package/packages/dd-trace/src/opentracing/span.js +59 -19
  123. package/packages/dd-trace/src/opentracing/span_context.js +49 -0
  124. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -20
  125. package/packages/dd-trace/src/plugins/database.js +7 -6
  126. package/packages/dd-trace/src/plugins/index.js +4 -0
  127. package/packages/dd-trace/src/plugins/log_injection.js +56 -0
  128. package/packages/dd-trace/src/plugins/log_plugin.js +3 -48
  129. package/packages/dd-trace/src/plugins/outbound.js +1 -1
  130. package/packages/dd-trace/src/plugins/plugin.js +15 -17
  131. package/packages/dd-trace/src/plugins/tracing.js +43 -5
  132. package/packages/dd-trace/src/plugins/util/test.js +236 -13
  133. package/packages/dd-trace/src/plugins/util/web.js +79 -65
  134. package/packages/dd-trace/src/priority_sampler.js +2 -2
  135. package/packages/dd-trace/src/profiling/profiler.js +2 -2
  136. package/packages/dd-trace/src/profiling/profilers/wall.js +10 -4
  137. package/packages/dd-trace/src/sampling_rule.js +7 -7
  138. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
  139. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  140. package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
  141. package/packages/dd-trace/src/span_format.js +190 -58
  142. package/packages/dd-trace/src/spanleak.js +1 -1
  143. package/packages/dd-trace/src/standalone/index.js +3 -3
  144. package/packages/dd-trace/src/tagger.js +0 -2
  145. package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
  146. package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
  147. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  148. package/vendor/dist/protobufjs/index.js +1 -1
  149. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  150. package/packages/dd-trace/src/msgpack/encoder.js +0 -308
  151. package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
@@ -7,21 +7,26 @@ const shimmer = require('../../datadog-shimmer')
7
7
  const log = require('../../dd-trace/src/log')
8
8
  const { getEnvironmentVariable } = require('../../dd-trace/src/config/helper')
9
9
  const {
10
- getCoveredFilenamesFromCoverage,
10
+ getCoveredFilesFromCoverage,
11
+ getExecutableFilesFromCoverage,
11
12
  resetCoverage,
12
13
  mergeCoverage,
13
14
  fromCoverageMapToCoverage,
14
15
  getTestSuitePath,
16
+ getRelativeCoverageFiles,
15
17
  CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
16
18
  getIsFaultyEarlyFlakeDetection,
17
19
  getEfdRetryCount,
18
20
  getMaxEfdRetryCount,
21
+ applySkippedCoverageToCoverage,
22
+ getTestCoverageLinesPercentage,
19
23
  recordAttemptToFixExecution,
20
24
  collectAttemptToFixExecutionsFromTraces,
21
25
  logAttemptToFixTestExecution,
22
26
  logTestOptimizationSummary,
23
27
  getTestOptimizationRequestResults,
24
28
  } = require('../../dd-trace/src/plugins/util/test')
29
+ const { writeCoverageBackfillToCache } = require('../../dd-trace/src/ci-visibility/test-optimization-cache')
25
30
  const satisfies = require('../../../vendor/dist/semifies')
26
31
  const { addHook, channel } = require('./helpers/instrument')
27
32
 
@@ -86,10 +91,14 @@ let pickleByFile = {}
86
91
  const pickleResultByFile = {}
87
92
 
88
93
  let skippableSuites = []
94
+ let skippableSuitesCoverage = {}
95
+ let skippedSuitesCoverage = {}
89
96
  let itrCorrelationId = ''
90
97
  let isForcedToRun = false
91
98
  let isUnskippable = false
99
+ let isItrEnabled = false
92
100
  let isSuitesSkippingEnabled = false
101
+ let isCoverageReportUploadEnabled = false
93
102
  let isEarlyFlakeDetectionEnabled = false
94
103
  let earlyFlakeDetectionNumRetries = 0
95
104
  let earlyFlakeDetectionSlowTestRetries = {}
@@ -106,11 +115,55 @@ let numTestRetries = 0
106
115
  let knownTests = {}
107
116
  let skippedSuites = []
108
117
  let isSuitesSkipped = false
118
+ let repositoryRoot
109
119
 
110
120
  function isValidKnownTests (receivedKnownTests) {
111
121
  return !!receivedKnownTests.cucumber
112
122
  }
113
123
 
124
+ function hasSkippableSuitesCoverage () {
125
+ return skippableSuitesCoverage &&
126
+ typeof skippableSuitesCoverage === 'object' &&
127
+ Object.keys(skippableSuitesCoverage).length > 0
128
+ }
129
+
130
+ function isTiaCoverageBackfillEnabled () {
131
+ return isItrEnabled && isCoverageReportUploadEnabled
132
+ }
133
+
134
+ function getCoverageRootDir () {
135
+ return repositoryRoot || process.cwd()
136
+ }
137
+
138
+ function shouldReportCodeCoverageLinesPct (hasBackfilledCoverage) {
139
+ return !isSuitesSkipped || hasBackfilledCoverage
140
+ }
141
+
142
+ function getSkippedSuitesCoverageForRun () {
143
+ return isSuitesSkipped && isTiaCoverageBackfillEnabled() && hasSkippableSuitesCoverage()
144
+ ? skippableSuitesCoverage
145
+ : {}
146
+ }
147
+
148
+ function applySkippedCoverageToCucumberCoverageMap () {
149
+ if (!isTiaCoverageBackfillEnabled()) return false
150
+ return applySkippedCoverageToCoverage(originalCoverageMap, skippedSuitesCoverage, getCoverageRootDir())
151
+ }
152
+
153
+ function getCucumberTestSessionCoverageFiles () {
154
+ return getRelativeCoverageFiles(getExecutableFilesFromCoverage(originalCoverageMap), getCoverageRootDir())
155
+ }
156
+
157
+ function resetSuiteSkippingRunState () {
158
+ skippableSuites = []
159
+ skippableSuitesCoverage = {}
160
+ skippedSuitesCoverage = {}
161
+ skippedSuites = []
162
+ isSuitesSkipped = false
163
+ repositoryRoot = undefined
164
+ writeCoverageBackfillToCache({})
165
+ }
166
+
114
167
  function getSuiteStatusFromTestStatuses (testStatuses) {
115
168
  if (testStatuses.includes('fail')) {
116
169
  return 'fail'
@@ -683,6 +736,7 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
683
736
  if (!libraryConfigurationCh.hasSubscribers) {
684
737
  return start.apply(this, arguments)
685
738
  }
739
+ resetSuiteSkippingRunState()
686
740
  const options = getCucumberOptions(this)
687
741
 
688
742
  if (!isParallel && this.adapter?.options) {
@@ -692,11 +746,14 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
692
746
 
693
747
  const configurationResponse = await getChannelPromise(libraryConfigurationCh, frameworkVersion)
694
748
 
749
+ repositoryRoot = configurationResponse.repositoryRoot
750
+ isItrEnabled = configurationResponse.libraryConfig?.isItrEnabled
695
751
  isEarlyFlakeDetectionEnabled = configurationResponse.libraryConfig?.isEarlyFlakeDetectionEnabled
696
752
  earlyFlakeDetectionNumRetries = configurationResponse.libraryConfig?.earlyFlakeDetectionNumRetries
697
753
  earlyFlakeDetectionSlowTestRetries = configurationResponse.libraryConfig?.earlyFlakeDetectionSlowTestRetries ?? {}
698
754
  earlyFlakeDetectionFaultyThreshold = configurationResponse.libraryConfig?.earlyFlakeDetectionFaultyThreshold
699
- isSuitesSkippingEnabled = configurationResponse.libraryConfig?.isSuitesSkippingEnabled
755
+ isSuitesSkippingEnabled = isItrEnabled && configurationResponse.libraryConfig?.isSuitesSkippingEnabled
756
+ isCoverageReportUploadEnabled = configurationResponse.libraryConfig?.isCoverageReportUploadEnabled
700
757
  isFlakyTestRetriesEnabled = configurationResponse.libraryConfig?.isFlakyTestRetriesEnabled
701
758
  const configRetryCount = configurationResponse.libraryConfig?.flakyTestRetriesCount
702
759
  numTestRetries = (typeof configRetryCount === 'number' && configRetryCount > 0) ? configRetryCount : 0
@@ -733,6 +790,7 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
733
790
 
734
791
  errorSkippableRequest = skippableResponse.err
735
792
  skippableSuites = skippableResponse.skippableSuites ?? []
793
+ skippableSuitesCoverage = skippableResponse.skippableSuitesCoverage ?? {}
736
794
 
737
795
  if (!errorSkippableRequest) {
738
796
  const filteredPickles = isCoordinator
@@ -753,6 +811,8 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
753
811
  }
754
812
 
755
813
  skippedSuites = [...filteredPickles.skippedSuites]
814
+ skippedSuitesCoverage = getSkippedSuitesCoverageForRun()
815
+ writeCoverageBackfillToCache(skippedSuitesCoverage, getCoverageRootDir())
756
816
  itrCorrelationId = skippableResponse.itrCorrelationId
757
817
  }
758
818
  }
@@ -816,13 +876,25 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
816
876
  }
817
877
 
818
878
  let testCodeCoverageLinesTotal
879
+ let testSessionCoverageFiles
819
880
 
820
- if (global.__coverage__) {
881
+ if (global.__coverage__ || untestedCoverage) {
821
882
  try {
883
+ let hasBackfilledCoverage = false
822
884
  if (untestedCoverage) {
823
885
  originalCoverageMap.merge(fromCoverageMapToCoverage(untestedCoverage))
824
886
  }
825
- testCodeCoverageLinesTotal = originalCoverageMap.getCoverageSummary().lines.pct
887
+ hasBackfilledCoverage = applySkippedCoverageToCucumberCoverageMap()
888
+ if (shouldReportCodeCoverageLinesPct(hasBackfilledCoverage)) {
889
+ testCodeCoverageLinesTotal = getTestCoverageLinesPercentage(
890
+ originalCoverageMap,
891
+ undefined,
892
+ getCoverageRootDir()
893
+ )
894
+ }
895
+ if (isTiaCoverageBackfillEnabled()) {
896
+ testSessionCoverageFiles = getCucumberTestSessionCoverageFiles()
897
+ }
826
898
  } catch {
827
899
  // ignore errors
828
900
  }
@@ -834,6 +906,7 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
834
906
  status: success ? 'pass' : 'fail',
835
907
  isSuitesSkipped,
836
908
  testCodeCoverageLinesTotal,
909
+ testSessionCoverageFiles,
837
910
  numSkippedSuites: skippedSuites.length,
838
911
  hasUnskippableSuites: isUnskippable,
839
912
  hasForcedToRunSuites: isForcedToRun,
@@ -1008,7 +1081,7 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
1008
1081
  // last test in suite
1009
1082
  const testSuiteStatus = getSuiteStatusFromTestStatuses(pickleResultByFile[testFileAbsolutePath])
1010
1083
  if (global.__coverage__) {
1011
- const coverageFiles = getCoveredFilenamesFromCoverage(global.__coverage__)
1084
+ const coverageFiles = getCoveredFilesFromCoverage(global.__coverage__)
1012
1085
 
1013
1086
  testSuiteCodeCoverageCh.publish({
1014
1087
  coverageFiles,
@@ -3,6 +3,7 @@
3
3
  const shimmer = require('../../datadog-shimmer')
4
4
  const { addHook } = require('./helpers/instrument')
5
5
  const { createCallbackInstrumentor } = require('./helpers/callback-instrumentor')
6
+ const { createPromiseInstrumentor } = require('./helpers/promise-instrumentor')
6
7
 
7
8
  const rrtypes = {
8
9
  resolveAny: 'ANY',
@@ -18,30 +19,55 @@ const rrtypes = {
18
19
  resolveSoa: 'SOA',
19
20
  }
20
21
 
21
- addHook({ name: 'dns' }, dns => {
22
- const lookup = createCallbackInstrumentor('apm:dns:lookup', { captureResult: true })
23
- const lookupService = createCallbackInstrumentor('apm:dns:lookup_service', { captureResult: true })
24
- const resolve = createCallbackInstrumentor('apm:dns:resolve', { captureResult: true })
25
- const reverse = createCallbackInstrumentor('apm:dns:reverse', { captureResult: true })
26
-
27
- shimmer.wrap(dns, 'lookup', lookup(buildArgsContext()))
28
- shimmer.wrap(dns, 'lookupService', lookupService(buildArgsContext()))
29
- shimmer.wrap(dns, 'resolve', resolve(buildArgsContext()))
30
- shimmer.wrap(dns, 'reverse', reverse(buildArgsContext()))
31
-
32
- patchResolveShorthands(dns, resolve)
22
+ // `dns.promises` and `require('dns/promises')` resolve to the same exports object. Both
23
+ // access paths register a hook, so without a guard the second hook to fire would stack a
24
+ // second wrap layer on top and publish every `apm:dns:*` event twice per call. The WeakSet
25
+ // collapses the two hooks to one wrap regardless of which one runs first.
26
+ const wrappedPromiseApis = new WeakSet()
33
27
 
34
- if (dns.Resolver) {
35
- shimmer.wrap(dns.Resolver.prototype, 'resolve', resolve(buildArgsContext()))
36
- shimmer.wrap(dns.Resolver.prototype, 'reverse', reverse(buildArgsContext()))
28
+ addHook({ name: 'dns' }, dns => {
29
+ patchApi(dns, createCallbackInstrumentor, buildCallbackArgsContext)
37
30
 
38
- patchResolveShorthands(dns.Resolver.prototype, resolve)
31
+ if (dns.promises) {
32
+ patchPromiseApi(dns.promises)
39
33
  }
40
34
 
41
35
  return dns
42
36
  })
43
37
 
44
- function patchResolveShorthands (prototype, resolve) {
38
+ addHook({ name: 'dns/promises' }, dnsPromises => {
39
+ patchPromiseApi(dnsPromises)
40
+ return dnsPromises
41
+ })
42
+
43
+ function patchPromiseApi (api) {
44
+ if (wrappedPromiseApis.has(api)) return
45
+ wrappedPromiseApis.add(api)
46
+ patchApi(api, createPromiseInstrumentor, buildPromiseArgsContext)
47
+ }
48
+
49
+ function patchApi (api, instrumentorFactory, buildArgsContext) {
50
+ const lookup = instrumentorFactory('apm:dns:lookup', { captureResult: true })
51
+ const lookupService = instrumentorFactory('apm:dns:lookup_service', { captureResult: true })
52
+ const resolve = instrumentorFactory('apm:dns:resolve', { captureResult: true })
53
+ const reverse = instrumentorFactory('apm:dns:reverse', { captureResult: true })
54
+
55
+ shimmer.wrap(api, 'lookup', lookup(buildArgsContext()))
56
+ shimmer.wrap(api, 'lookupService', lookupService(buildArgsContext()))
57
+ shimmer.wrap(api, 'resolve', resolve(buildArgsContext()))
58
+ shimmer.wrap(api, 'reverse', reverse(buildArgsContext()))
59
+
60
+ patchResolveShorthands(api, resolve, buildArgsContext)
61
+
62
+ if (api.Resolver) {
63
+ shimmer.wrap(api.Resolver.prototype, 'resolve', resolve(buildArgsContext()))
64
+ shimmer.wrap(api.Resolver.prototype, 'reverse', reverse(buildArgsContext()))
65
+
66
+ patchResolveShorthands(api.Resolver.prototype, resolve, buildArgsContext)
67
+ }
68
+ }
69
+
70
+ function patchResolveShorthands (prototype, resolve, buildArgsContext) {
45
71
  for (const method of Object.keys(rrtypes)) {
46
72
  if (prototype[method]) {
47
73
  shimmer.wrap(prototype, method, resolve(buildArgsContext(rrtypes[method])))
@@ -49,7 +75,7 @@ function patchResolveShorthands (prototype, resolve) {
49
75
  }
50
76
  }
51
77
 
52
- function buildArgsContext (rrtype) {
78
+ function buildCallbackArgsContext (rrtype) {
53
79
  return function (_, args) {
54
80
  if (args.length < 2) return
55
81
  const captured = [...args]
@@ -60,3 +86,13 @@ function buildArgsContext (rrtype) {
60
86
  return { args: captured }
61
87
  }
62
88
  }
89
+
90
+ function buildPromiseArgsContext (rrtype) {
91
+ return function (_, args) {
92
+ const captured = [...args]
93
+ if (rrtype) {
94
+ captured.push(rrtype)
95
+ }
96
+ return { args: captured }
97
+ }
98
+ }
@@ -54,65 +54,119 @@ function wrapAddHook (addHook) {
54
54
 
55
55
  if (typeof fn !== 'function') return addHook.apply(this, arguments)
56
56
 
57
- arguments[arguments.length - 1] = shimmer.wrapFunction(fn, fn => function (request, reply, done) {
58
- const req = getReq(request)
59
- const ctx = { req }
60
-
61
- try {
62
- // done callback is always the last argument
63
- const doneCallback = arguments[arguments.length - 1]
64
-
65
- if (typeof doneCallback === 'function') {
66
- arguments[arguments.length - 1] = function (err) {
67
- ctx.error = err
68
- publishError(ctx)
69
-
70
- const hasCookies = request.cookies && Object.keys(request.cookies).length > 0
71
-
72
- if (cookieParserReadCh.hasSubscribers && hasCookies && !cookiesPublished.has(req)) {
73
- ctx.res = getRes(reply)
74
- ctx.abortController = new AbortController()
75
- ctx.cookies = request.cookies
76
-
77
- cookieParserReadCh.publish(ctx)
78
- cookiesPublished.add(req)
79
-
80
- if (ctx.abortController.signal.aborted) return
81
- }
82
-
83
- if (name === 'onRequest' || name === 'preParsing') {
84
- parsingContexts.set(req, ctx)
85
-
86
- return callbackFinishCh.runStores(ctx, () => {
87
- return doneCallback.apply(this, arguments)
88
- })
89
- }
90
- return doneCallback.apply(this, arguments)
91
- }
92
-
93
- return fn.apply(this, arguments)
94
- }
95
-
96
- const promise = fn.apply(this, arguments)
97
-
98
- if (promise && typeof promise.catch === 'function') {
99
- return promise.catch(err => {
100
- ctx.error = err
101
- return publishError(ctx)
102
- })
103
- }
104
-
105
- return promise
106
- } catch (e) {
107
- ctx.error = e
108
- throw publishError(ctx)
57
+ arguments[arguments.length - 1] = shimmer.wrapFunction(fn, fn => function wrappedHook () {
58
+ // Fast path: every fastify request invokes each addHook'd handler, so the wrap
59
+ // runs in the user's hot path. The only side effects this wrapper carries are
60
+ // the three channels below; when none of them have a subscriber (the default
61
+ // plugin config, and the steady state once appsec / cookie subscribers detach),
62
+ // the wrap has nothing to do, and a `fn.apply(this, arguments)` forward keeps
63
+ // V8's CallApplyArguments fast path intact.
64
+ //
65
+ // The previous shape mutated `arguments[arguments.length - 1]` to swap `done`.
66
+ // That mutation materialises the magical arguments object and disables V8
67
+ // inlining of the enclosing function. The slow path below builds a fresh args
68
+ // array instead so the hot fast path keeps a clean forward.
69
+ if (errorChannel.hasSubscribers || cookieParserReadCh.hasSubscribers || callbackFinishCh.hasSubscribers) {
70
+ return invokeHookWithContext(name, fn, this, arguments)
109
71
  }
72
+ return fn.apply(this, arguments)
110
73
  })
111
74
 
112
75
  return addHook.apply(this, arguments)
113
76
  })
114
77
  }
115
78
 
79
+ /**
80
+ * Slow path of {@link wrapAddHook}; entered only when at least one wrap-fed
81
+ * channel has a subscriber. Allocates the per-request context, rewraps `done`,
82
+ * and forwards to the user-supplied hook.
83
+ *
84
+ * @param {string} name Lifecycle phase the hook was registered against.
85
+ * @param {Function} fn User-supplied hook.
86
+ * @param {unknown} thisArg `this` Fastify passes to the hook.
87
+ * @param {ArrayLike<unknown>} args Fastify's positional args; the dispatcher always
88
+ * places `done` as the trailing positional (see fastify/lib/hooks.js hookIterator,
89
+ * onSendHookRunner, preParsingHookRunner, onRequestAbortHookRunner).
90
+ */
91
+ function invokeHookWithContext (name, fn, thisArg, args) {
92
+ const request = args[0]
93
+ const reply = args[1]
94
+ const req = getReq(request)
95
+ const ctx = { req }
96
+
97
+ try {
98
+ const lastArg = args[args.length - 1]
99
+
100
+ if (typeof lastArg === 'function') {
101
+ // Copy the args so we can swap the trailing `done` without touching the
102
+ // caller's magical arguments object. Fastify hook arities are 2 to 4
103
+ // across lifecycle phases, but `done` is always last.
104
+ const callArgs = [...args]
105
+ callArgs[callArgs.length - 1] = wrapHookDone(ctx, request, reply, req, name, lastArg)
106
+ return fn.apply(thisArg, callArgs)
107
+ }
108
+
109
+ const promise = fn.apply(thisArg, args)
110
+
111
+ if (promise && typeof promise.catch === 'function') {
112
+ return promise.catch(error => {
113
+ ctx.error = error
114
+ return publishError(ctx)
115
+ })
116
+ }
117
+
118
+ return promise
119
+ } catch (error) {
120
+ ctx.error = error
121
+ throw publishError(ctx)
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Per-request closure invoked when fastify resolves the user hook's `done`.
127
+ * Captures `ctx` plus the dispatcher-level fields needed to publish on the
128
+ * cookie / callback channels. The closure cannot be hoisted: fastify invokes
129
+ * `done` with a single `(err)` arg, so request / reply / req / name / doneCallback
130
+ * must close over rather than ride the call signature.
131
+ *
132
+ * @param {{ req: unknown, [key: string]: unknown }} ctx
133
+ * @param {{ cookies?: Record<string, unknown>, [key: string]: unknown }} request
134
+ * @param {object} reply
135
+ * @param {unknown} req
136
+ * @param {string} name
137
+ * @param {Function} doneCallback
138
+ */
139
+ function wrapHookDone (ctx, request, reply, req, name, doneCallback) {
140
+ return function wrappedDone (error) {
141
+ ctx.error = error
142
+ publishError(ctx)
143
+
144
+ const hasCookies = request.cookies && Object.keys(request.cookies).length > 0
145
+
146
+ if (cookieParserReadCh.hasSubscribers && hasCookies && !cookiesPublished.has(req)) {
147
+ ctx.res = getRes(reply)
148
+ ctx.abortController = new AbortController()
149
+ ctx.cookies = request.cookies
150
+
151
+ cookieParserReadCh.publish(ctx)
152
+ cookiesPublished.add(req)
153
+
154
+ if (ctx.abortController.signal.aborted) return
155
+ }
156
+
157
+ if (name === 'onRequest' || name === 'preParsing') {
158
+ parsingContexts.set(req, ctx)
159
+
160
+ if (callbackFinishCh.hasSubscribers) {
161
+ const self = this
162
+ const allArgs = arguments
163
+ return callbackFinishCh.runStores(ctx, () => doneCallback.apply(self, allArgs))
164
+ }
165
+ }
166
+ return doneCallback.apply(this, arguments)
167
+ }
168
+ }
169
+
116
170
  function onRequest (request, reply, done) {
117
171
  if (typeof done !== 'function') return
118
172
 
@@ -157,45 +211,51 @@ function preValidation (request, reply, done) {
157
211
  const ctx = parsingContexts.get(req)
158
212
  ctx.res = res
159
213
 
160
- const processInContext = () => {
161
- let abortController
214
+ if (!ctx) return processInContext(request, ctx, done, req)
162
215
 
163
- if (queryParamsReadCh.hasSubscribers && request.query) {
164
- abortController ??= new AbortController()
165
- ctx.abortController = abortController
166
- ctx.query = request.query
167
- queryParamsReadCh.publish(ctx)
168
-
169
- if (abortController.signal.aborted) return
170
- }
216
+ preValidationCh.runStores(ctx, processInContext, undefined, request, ctx, done, req)
217
+ }
171
218
 
172
- // Analyze body before schema validation
173
- if (bodyParserReadCh.hasSubscribers && request.body && !bodyPublished.has(req)) {
174
- abortController ??= new AbortController()
175
- ctx.abortController = abortController
176
- ctx.body = request.body
177
- bodyParserReadCh.publish(ctx)
219
+ /**
220
+ * @param {{ query?: object, body?: object, params?: object, [key: string]: unknown }} request
221
+ * @param {{ res?: object, abortController?: AbortController, [key: string]: unknown }} ctx
222
+ * @param {Function} done
223
+ * @param {unknown} req
224
+ */
225
+ function processInContext (request, ctx, done, req) {
226
+ let abortController
227
+
228
+ if (queryParamsReadCh.hasSubscribers && request.query) {
229
+ abortController ??= new AbortController()
230
+ ctx.abortController = abortController
231
+ ctx.query = request.query
232
+ queryParamsReadCh.publish(ctx)
233
+
234
+ if (abortController.signal.aborted) return
235
+ }
178
236
 
179
- bodyPublished.add(req)
237
+ // Analyze body before schema validation
238
+ if (bodyParserReadCh.hasSubscribers && request.body && !bodyPublished.has(req)) {
239
+ abortController ??= new AbortController()
240
+ ctx.abortController = abortController
241
+ ctx.body = request.body
242
+ bodyParserReadCh.publish(ctx)
180
243
 
181
- if (abortController.signal.aborted) return
182
- }
244
+ bodyPublished.add(req)
183
245
 
184
- if (pathParamsReadCh.hasSubscribers && request.params) {
185
- abortController ??= new AbortController()
186
- ctx.abortController = abortController
187
- ctx.params = request.params
188
- pathParamsReadCh.publish(ctx)
246
+ if (abortController.signal.aborted) return
247
+ }
189
248
 
190
- if (abortController.signal.aborted) return
191
- }
249
+ if (pathParamsReadCh.hasSubscribers && request.params) {
250
+ abortController ??= new AbortController()
251
+ ctx.abortController = abortController
252
+ ctx.params = request.params
253
+ pathParamsReadCh.publish(ctx)
192
254
 
193
- done()
255
+ if (abortController.signal.aborted) return
194
256
  }
195
257
 
196
- if (!ctx) return processInContext()
197
-
198
- preValidationCh.runStores(ctx, processInContext)
258
+ done()
199
259
  }
200
260
 
201
261
  function preParsing (request, reply, payload, done) {