dd-trace 5.103.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 (213) hide show
  1. package/LICENSE-3rdparty.csv +90 -102
  2. package/index.d.ts +107 -6
  3. package/package.json +18 -17
  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 +15 -2
  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/cassandra-driver.js +5 -2
  11. package/packages/datadog-instrumentations/src/cucumber.js +181 -35
  12. package/packages/datadog-instrumentations/src/dns.js +54 -18
  13. package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
  14. package/packages/datadog-instrumentations/src/fastify.js +142 -82
  15. package/packages/datadog-instrumentations/src/graphql.js +188 -67
  16. package/packages/datadog-instrumentations/src/grpc/client.js +48 -32
  17. package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
  18. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +1 -1
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  20. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
  21. package/packages/datadog-instrumentations/src/helpers/kafka.js +17 -0
  22. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
  23. package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
  24. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +3 -2
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +19 -6
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +31 -229
  32. package/packages/datadog-instrumentations/src/hono.js +54 -3
  33. package/packages/datadog-instrumentations/src/http/client.js +2 -2
  34. package/packages/datadog-instrumentations/src/http/server.js +9 -4
  35. package/packages/datadog-instrumentations/src/ioredis.js +3 -3
  36. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
  37. package/packages/datadog-instrumentations/src/jest.js +390 -183
  38. package/packages/datadog-instrumentations/src/kafkajs.js +140 -17
  39. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  40. package/packages/datadog-instrumentations/src/memcached.js +2 -1
  41. package/packages/datadog-instrumentations/src/mocha/main.js +399 -107
  42. package/packages/datadog-instrumentations/src/mocha/utils.js +48 -8
  43. package/packages/datadog-instrumentations/src/mongodb-core.js +1 -1
  44. package/packages/datadog-instrumentations/src/mongoose.js +10 -12
  45. package/packages/datadog-instrumentations/src/mysql.js +2 -2
  46. package/packages/datadog-instrumentations/src/mysql2.js +1 -1
  47. package/packages/datadog-instrumentations/src/nats.js +182 -0
  48. package/packages/datadog-instrumentations/src/nyc.js +38 -1
  49. package/packages/datadog-instrumentations/src/openai.js +33 -18
  50. package/packages/datadog-instrumentations/src/oracledb.js +6 -1
  51. package/packages/datadog-instrumentations/src/pg.js +1 -1
  52. package/packages/datadog-instrumentations/src/pino.js +17 -5
  53. package/packages/datadog-instrumentations/src/playwright.js +537 -297
  54. package/packages/datadog-instrumentations/src/router.js +80 -34
  55. package/packages/datadog-instrumentations/src/stripe.js +1 -1
  56. package/packages/datadog-instrumentations/src/vitest.js +246 -149
  57. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
  58. package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
  59. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
  60. package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
  61. package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
  62. package/packages/datadog-plugin-bunyan/src/index.js +28 -0
  63. package/packages/datadog-plugin-cucumber/src/index.js +17 -3
  64. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +223 -45
  65. package/packages/datadog-plugin-cypress/src/support.js +69 -1
  66. package/packages/datadog-plugin-dns/src/lookup.js +8 -6
  67. package/packages/datadog-plugin-elasticsearch/src/index.js +28 -8
  68. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
  69. package/packages/datadog-plugin-graphql/src/execute.js +2 -0
  70. package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
  71. package/packages/datadog-plugin-graphql/src/utils.js +4 -1
  72. package/packages/datadog-plugin-http/src/server.js +40 -15
  73. package/packages/datadog-plugin-jest/src/index.js +11 -3
  74. package/packages/datadog-plugin-jest/src/util.js +15 -8
  75. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
  76. package/packages/datadog-plugin-kafkajs/src/producer.js +35 -0
  77. package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
  78. package/packages/datadog-plugin-mocha/src/index.js +19 -4
  79. package/packages/datadog-plugin-mongodb-core/src/index.js +311 -35
  80. package/packages/datadog-plugin-nats/src/consumer.js +43 -0
  81. package/packages/datadog-plugin-nats/src/index.js +20 -0
  82. package/packages/datadog-plugin-nats/src/producer.js +62 -0
  83. package/packages/datadog-plugin-nats/src/util.js +33 -0
  84. package/packages/datadog-plugin-next/src/index.js +5 -3
  85. package/packages/datadog-plugin-openai/src/tracing.js +15 -2
  86. package/packages/datadog-plugin-oracledb/src/index.js +13 -2
  87. package/packages/datadog-plugin-pino/src/index.js +42 -0
  88. package/packages/datadog-plugin-playwright/src/index.js +4 -4
  89. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
  90. package/packages/datadog-plugin-redis/src/index.js +37 -2
  91. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  92. package/packages/datadog-plugin-router/src/index.js +33 -44
  93. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  94. package/packages/datadog-plugin-undici/src/index.js +19 -0
  95. package/packages/datadog-plugin-vitest/src/index.js +24 -20
  96. package/packages/datadog-plugin-winston/src/index.js +30 -0
  97. package/packages/datadog-shimmer/src/shimmer.js +49 -21
  98. package/packages/dd-trace/src/aiguard/index.js +1 -1
  99. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  100. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  101. package/packages/dd-trace/src/appsec/blocking.js +2 -2
  102. package/packages/dd-trace/src/appsec/index.js +11 -4
  103. package/packages/dd-trace/src/appsec/reporter.js +24 -11
  104. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  105. package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
  106. package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
  107. package/packages/dd-trace/src/baggage.js +7 -1
  108. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
  109. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
  110. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -1
  111. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +5 -3
  112. package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
  113. package/packages/dd-trace/src/config/generated-config-types.d.ts +7 -2
  114. package/packages/dd-trace/src/config/supported-configurations.json +36 -8
  115. package/packages/dd-trace/src/crashtracking/crashtracker.js +15 -3
  116. package/packages/dd-trace/src/datastreams/context.js +4 -2
  117. package/packages/dd-trace/src/datastreams/writer.js +2 -4
  118. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
  119. package/packages/dd-trace/src/encode/0.4.js +124 -108
  120. package/packages/dd-trace/src/encode/0.5.js +114 -26
  121. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +57 -42
  122. package/packages/dd-trace/src/encode/agentless-json.js +4 -2
  123. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
  124. package/packages/dd-trace/src/encode/span-stats.js +16 -16
  125. package/packages/dd-trace/src/encode/tags-processors.js +16 -0
  126. package/packages/dd-trace/src/exporters/common/agents.js +3 -1
  127. package/packages/dd-trace/src/exporters/common/request.js +3 -1
  128. package/packages/dd-trace/src/id.js +17 -4
  129. package/packages/dd-trace/src/lambda/handler.js +2 -4
  130. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  131. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  132. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  133. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
  134. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  135. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
  136. package/packages/dd-trace/src/llmobs/sdk.js +10 -16
  137. package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
  138. package/packages/dd-trace/src/llmobs/tagger.js +9 -1
  139. package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
  140. package/packages/dd-trace/src/llmobs/util.js +66 -3
  141. package/packages/dd-trace/src/log/index.js +1 -1
  142. package/packages/dd-trace/src/log/writer.js +3 -1
  143. package/packages/dd-trace/src/msgpack/chunk.js +394 -10
  144. package/packages/dd-trace/src/msgpack/index.js +96 -2
  145. package/packages/dd-trace/src/noop/span.js +3 -1
  146. package/packages/dd-trace/src/openfeature/encoding.js +70 -0
  147. package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
  148. package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
  149. package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
  150. package/packages/dd-trace/src/openfeature/writers/exposures.js +51 -20
  151. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
  152. package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
  153. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  154. package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
  155. package/packages/dd-trace/src/opentracing/propagation/text_map.js +62 -67
  156. package/packages/dd-trace/src/opentracing/span.js +59 -19
  157. package/packages/dd-trace/src/opentracing/span_context.js +49 -0
  158. package/packages/dd-trace/src/plugins/apollo.js +3 -1
  159. package/packages/dd-trace/src/plugins/ci_plugin.js +23 -33
  160. package/packages/dd-trace/src/plugins/database.js +7 -6
  161. package/packages/dd-trace/src/plugins/index.js +4 -0
  162. package/packages/dd-trace/src/plugins/log_injection.js +56 -0
  163. package/packages/dd-trace/src/plugins/log_plugin.js +3 -46
  164. package/packages/dd-trace/src/plugins/outbound.js +1 -1
  165. package/packages/dd-trace/src/plugins/plugin.js +15 -17
  166. package/packages/dd-trace/src/plugins/tracing.js +48 -8
  167. package/packages/dd-trace/src/plugins/util/git.js +3 -1
  168. package/packages/dd-trace/src/plugins/util/test.js +318 -13
  169. package/packages/dd-trace/src/plugins/util/web.js +89 -64
  170. package/packages/dd-trace/src/priority_sampler.js +2 -2
  171. package/packages/dd-trace/src/profiling/profiler.js +2 -2
  172. package/packages/dd-trace/src/profiling/profilers/wall.js +10 -4
  173. package/packages/dd-trace/src/sampling_rule.js +7 -7
  174. package/packages/dd-trace/src/scope.js +7 -5
  175. package/packages/dd-trace/src/service-naming/extra-services.js +14 -0
  176. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
  177. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  178. package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
  179. package/packages/dd-trace/src/span_format.js +190 -58
  180. package/packages/dd-trace/src/spanleak.js +1 -1
  181. package/packages/dd-trace/src/standalone/index.js +3 -3
  182. package/packages/dd-trace/src/tagger.js +0 -2
  183. package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
  184. package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
  185. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  186. package/vendor/dist/protobufjs/index.js +1 -1
  187. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  188. package/packages/dd-trace/src/msgpack/encoder.js +0 -308
  189. package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
  190. package/vendor/dist/opentracing/LICENSE +0 -201
  191. package/vendor/dist/opentracing/binary_carrier.d.ts +0 -11
  192. package/vendor/dist/opentracing/constants.d.ts +0 -61
  193. package/vendor/dist/opentracing/examples/demo/demo.d.ts +0 -2
  194. package/vendor/dist/opentracing/ext/tags.d.ts +0 -90
  195. package/vendor/dist/opentracing/functions.d.ts +0 -20
  196. package/vendor/dist/opentracing/global_tracer.d.ts +0 -14
  197. package/vendor/dist/opentracing/index.d.ts +0 -12
  198. package/vendor/dist/opentracing/index.js +0 -1
  199. package/vendor/dist/opentracing/mock_tracer/index.d.ts +0 -5
  200. package/vendor/dist/opentracing/mock_tracer/mock_context.d.ts +0 -13
  201. package/vendor/dist/opentracing/mock_tracer/mock_report.d.ts +0 -16
  202. package/vendor/dist/opentracing/mock_tracer/mock_span.d.ts +0 -50
  203. package/vendor/dist/opentracing/mock_tracer/mock_tracer.d.ts +0 -26
  204. package/vendor/dist/opentracing/noop.d.ts +0 -8
  205. package/vendor/dist/opentracing/reference.d.ts +0 -33
  206. package/vendor/dist/opentracing/span.d.ts +0 -147
  207. package/vendor/dist/opentracing/span_context.d.ts +0 -26
  208. package/vendor/dist/opentracing/test/api_compatibility.d.ts +0 -16
  209. package/vendor/dist/opentracing/test/mocktracer_implemenation.d.ts +0 -3
  210. package/vendor/dist/opentracing/test/noop_implementation.d.ts +0 -4
  211. package/vendor/dist/opentracing/test/opentracing_api.d.ts +0 -3
  212. package/vendor/dist/opentracing/test/unittest.d.ts +0 -2
  213. package/vendor/dist/opentracing/tracer.d.ts +0 -127
@@ -1,245 +1,47 @@
1
1
  'use strict'
2
2
 
3
- // TODO: Move traceIterator to Orchestrion.
3
+ // Custom transforms registered via InstrumentationMatcher.addTransform().
4
+ //
5
+ // Use this file for transforms that are not yet supported upstream in
6
+ // @apm-js-collab/code-transformer (Orchestrion) or that cannot land there
7
+ // for dd-trace-specific reasons. Once a transform is available natively in
8
+ // the library, replace the custom registration with the built-in option and
9
+ // remove the entry here.
4
10
 
5
- const { parse, query, traverse } = require('./compiler')
11
+ const { parse, query } = require('./compiler')
6
12
 
7
- const tracingChannelPredicate = (node) => (
8
- node.specifiers?.[0]?.local?.name === 'tr_ch_apm_tracingChannel' ||
9
- node.declarations?.[0]?.id?.properties?.[0]?.value?.name === 'tr_ch_apm_tracingChannel'
10
- )
11
-
12
- const transforms = module.exports = {
13
- tracingChannelImport ({ dcModule, sourceType }, node) {
14
- if (node.body.some(tracingChannelPredicate)) return
15
-
16
- const index = node.body.findIndex(child => child.directive === 'use strict')
17
- const code = isModuleSourceType(sourceType)
18
- ? `import { tracingChannel as tr_ch_apm_tracingChannel } from "${dcModule}"`
19
- : `const {tracingChannel: tr_ch_apm_tracingChannel} = require("${dcModule}")`
20
-
21
- node.body.splice(index + 1, 0, parse(code, {
22
- isModule: isModuleSourceType(sourceType),
23
- }).body[0])
24
- },
25
-
26
- tracingChannelDeclaration (state, node) {
27
- const { channelName, module: { name } } = state
28
- const channelVariable = 'tr_ch_apm$' + channelName.replaceAll(':', '_')
29
-
30
- if (node.body.some(child => child.declarations?.[0]?.id?.name === channelVariable)) return
31
-
32
- transforms.tracingChannelImport(state, node)
33
-
34
- const index = node.body.findIndex(tracingChannelPredicate)
35
- const code = `
36
- const ${channelVariable} = tr_ch_apm_tracingChannel("orchestrion:${name}:${channelName}")
37
- `
38
-
39
- node.body.splice(index + 1, 0, parse(code).body[0])
40
- },
41
-
42
- traceAsyncIterator: traceAny,
43
- traceIterator: traceAny,
44
- }
45
-
46
- function traceAny (state, node, _parent, ancestry) {
47
- const program = ancestry[ancestry.length - 1]
48
-
49
- if (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') {
50
- traceInstanceMethod(state, node, program)
51
- } else {
52
- traceFunction(state, node, program)
53
- }
54
- }
13
+ module.exports = { waitForAsyncEnd }
55
14
 
56
15
  /**
57
- * @param {string} sourceType
16
+ * Injects a wait for `ctx.asyncEndPromise` into a generated `tracePromise`
17
+ * wrapper's native-Promise fulfillment handler.
18
+ *
19
+ * @param {object} _state
20
+ * @param {import('estree').CallExpression} node
21
+ * @returns {void}
58
22
  */
59
- function isModuleSourceType (sourceType) {
60
- return sourceType === 'module' || sourceType === 'esm'
61
- }
62
-
63
- function traceFunction (state, node, program) {
64
- transforms.tracingChannelDeclaration(state, program)
23
+ function waitForAsyncEnd (_state, node) {
24
+ const onFulfilled = node.arguments[0]
25
+ const statements = onFulfilled?.body?.body
65
26
 
66
- node.body = wrap(state, {
67
- type: 'FunctionExpression',
68
- params: node.params,
69
- body: node.body,
70
- async: node.async,
71
- expression: false,
72
- generator: node.generator,
73
- }, program)
74
-
75
- // The original function no longer contains any calls to `await` or `yield` as
76
- // the function body is copied to the internal wrapped function, so we set
77
- // these to false to avoid altering the return value of the wrapper. The old
78
- // values are instead copied to the new AST node above.
79
- node.generator = false
80
- node.async = false
81
-
82
- wrapSuper(state, node)
83
- }
84
-
85
- function traceInstanceMethod (state, node, program) {
86
- const { functionQuery, operator } = state
87
- const { methodName } = functionQuery
88
-
89
- const classBody = node.body
90
-
91
- // If the method exists on the class, we return as it will be patched later
92
- // while traversing child nodes later on.
93
- if (classBody.body.some(({ key }) => key.name === methodName)) return
94
-
95
- // Method doesn't exist on the class so we assume an instance method and
96
- // wrap it in the constructor instead.
97
- let ctor = classBody.body.find(({ kind }) => kind === 'constructor')
98
-
99
- transforms.tracingChannelDeclaration(state, program)
100
-
101
- if (!ctor) {
102
- ctor = parse(
103
- node.superClass
104
- ? 'class A { constructor (...args) { super(...args) } }'
105
- : 'class A { constructor () {} }'
106
- ).body[0].body.body[0] // Extract constructor from dummy class body.
107
-
108
- classBody.body.unshift(ctor)
27
+ if (!statements || query(onFulfilled.body, '[id.name=__apm$asyncEndPromise]').length > 0) {
28
+ return
109
29
  }
110
30
 
111
- const ctorBody = parse(`
112
- const __apm$${methodName} = this["${methodName}"]
113
- this["${methodName}"] = function () {}
114
- `).body
115
-
116
- // Extract only right-hand side function of line 2.
117
- const fn = ctorBody[1].expression.right
118
-
119
- fn.async = operator === 'tracePromise'
120
- fn.body = wrap(state, { type: 'Identifier', name: `__apm$${methodName}` }, program)
31
+ const returnIndex = statements.findIndex(statement => (
32
+ statement.type === 'ReturnStatement' && statement.argument?.name === 'result'
33
+ ))
121
34
 
122
- wrapSuper(state, fn)
35
+ if (returnIndex === -1) return
123
36
 
124
- ctor.value.body.body.push(...ctorBody)
125
- }
126
-
127
- function wrap (state, node, program) {
128
- const { operator } = state
129
-
130
- if (operator === 'traceAsyncIterator') return wrapIterator(state, node, program)
131
- if (operator === 'traceIterator') return wrapIterator(state, node, program)
132
- }
133
-
134
- function wrapSuper (_state, node) {
135
- const members = new Set()
136
-
137
- traverse(
138
- node.body,
139
- '[object.type=Super]',
140
- (node, parent) => {
141
- const { name } = node.property
142
-
143
- let child
144
-
145
- if (parent.callee) {
146
- // This is needed because for generator functions we have to move the
147
- // original function to a nested wrapped function, but we can't use an
148
- // arrow function because arrow function cannot be generator functions,
149
- // and `super` cannot be called from a nested function, so we have to
150
- // rewrite any `super` call to not use the keyword.
151
- const { expression } = parse(`__apm$super['${name}'].call(this)`).body[0]
152
-
153
- parent.callee = child = expression.callee
154
- parent.arguments.unshift(...expression.arguments)
155
- } else {
156
- parent.expression = child = parse(`__apm$super['${name}']`).body[0]
157
- }
158
-
159
- child.computed = parent.callee.computed
160
- child.optional = parent.callee.optional
161
-
162
- members.add(name)
163
- }
164
- )
165
-
166
- for (const name of members) {
167
- const member = parse(`
168
- class Wrapper {
169
- wrapper () {
170
- __apm$super['${name}'] = super['${name}']
171
- }
172
- }
173
- `).body[0].body.body[0].value.body.body[0]
174
-
175
- node.body.body.unshift(member)
176
- }
177
-
178
- if (members.size > 0) {
179
- node.body.body.unshift(parse('const __apm$super = {}').body[0])
180
- }
181
- }
182
-
183
- function wrapIterator (state, node, program) {
184
- const { channelName, operator } = state
185
- const baseChannel = channelName.replaceAll(':', '_')
186
- const channelVariable = 'tr_ch_apm$' + baseChannel
187
- const nextChannel = baseChannel + '_next'
188
- const traceMethod = operator === 'traceAsyncIterator' ? 'tracePromise' : 'traceSync'
189
- const traceNext = `tr_ch_apm$${nextChannel}.${traceMethod}`
190
-
191
- transforms.tracingChannelDeclaration({ ...state, channelName: nextChannel }, program)
192
-
193
- const wrapper = parse(`
37
+ const waitStatements = parse(`
194
38
  function wrapper () {
195
- const __apm$traced = () => {
196
- const __apm$wrapped = () => {};
197
- return __apm$wrapped.apply(this, arguments);
198
- };
199
-
200
- if (!${channelVariable}.start.hasSubscribers) return __apm$traced();
201
-
202
- {
203
- const wrap = iter => {
204
- const { next: iterNext, return: iterReturn, throw: iterThrow } = iter;
205
-
206
- iter.next = (...args) => ${traceNext}(iterNext, ctx, iter, ...args);
207
- iter.return = (...args) => ${traceNext}(iterReturn, ctx, iter, ...args);
208
- iter.throw = (...args) => ${traceNext}(iterThrow, ctx, iter, ...args);
209
-
210
- return iter;
211
- };
212
- const ctx = {
213
- arguments,
214
- self: this,
215
- moduleVersion: "1.0.0"
216
- };
217
- const iter = ${channelVariable}.traceSync(__apm$traced, ctx);
218
-
219
- if (typeof iter.then !== 'function') return wrap(iter);
220
-
221
- return iter.then(result => {
222
- ctx.result = result;
223
-
224
- ${channelVariable}.asyncStart.publish(ctx);
225
- ${channelVariable}.asyncEnd.publish(ctx);
226
-
227
- return wrap(result);
228
- }, err => {
229
- ctx.error = err;
230
-
231
- ${channelVariable}.error.publish(ctx);
232
- ${channelVariable}.asyncStart.publish(ctx);
233
- ${channelVariable}.asyncEnd.publish(ctx);
234
-
235
- return Promise.reject(err);
236
- });
237
- };
39
+ const __apm$asyncEndPromise = __apm$ctx.asyncEndPromise;
40
+ if (__apm$asyncEndPromise && typeof __apm$asyncEndPromise.then === 'function') {
41
+ return __apm$asyncEndPromise.then(() => result, () => result);
42
+ }
238
43
  }
239
- `).body[0].body // Extract only block statement of function body.
240
-
241
- // Replace the right-hand side assignment of `const __apm$wrapped = () => {}`.
242
- query(wrapper, '[id.name=__apm$wrapped]')[0].init = node
44
+ `).body[0].body.body
243
45
 
244
- return wrapper
46
+ statements.splice(returnIndex, 0, ...waitStatements)
245
47
  }
@@ -14,6 +14,11 @@ 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
+ // Tracks handlers registered via `app.use()` so route-publishing wrappers
18
+ // installed by `wrapRouterAdd` can skip middleware-only matches (a request
19
+ // matching only middleware should keep the bare HTTP-method resource name).
20
+ const middlewareHandlers = new WeakSet()
21
+
17
22
  // `app.request()` and non-node adapters call `app.fetch` without an `incoming`
18
23
  // IncomingMessage; the APM `web` helpers depend on one, so the wrappers below
19
24
  // skip publishing whenever it is missing.
@@ -27,6 +32,53 @@ function wrapFetch (fetch) {
27
32
  }
28
33
  }
29
34
 
35
+ function wrapUse (originalUse) {
36
+ return function (arg1, ...handlers) {
37
+ if (typeof arg1 === 'function') middlewareHandlers.add(arg1)
38
+ for (const h of handlers) middlewareHandlers.add(h)
39
+ return originalUse.call(this, arg1, ...handlers)
40
+ }
41
+ }
42
+
43
+ // `app.basePath()` returns a clone Hono instance built via the library's
44
+ // internal class binding, so it never hits our instrumented constructor. The
45
+ // clone shares the parent router (so `router.add` stays wrapped), but its
46
+ // `use` is a fresh per-instance method that must be wrapped too, otherwise
47
+ // middleware registered on the sub-app never lands in `middlewareHandlers`.
48
+ function wrapBasePath (originalBasePath) {
49
+ return function (path) {
50
+ const clone = originalBasePath.apply(this, arguments)
51
+ shimmer.wrap(clone, 'use', wrapUse)
52
+ shimmer.wrap(clone, 'basePath', wrapBasePath)
53
+ return clone
54
+ }
55
+ }
56
+
57
+ function wrapRouterAdd (originalAdd) {
58
+ return function (method, path, handlerData) {
59
+ const handler = handlerData?.[0]
60
+ if (typeof handler === 'function' && !middlewareHandlers.has(handler)) {
61
+ const meta = handlerData[1]
62
+ const wrappedHandler = function (context, next) {
63
+ const req = context.env?.incoming
64
+ if (req && routeChannel.hasSubscribers) {
65
+ routeChannel.publish({ req, route: meta?.path })
66
+ }
67
+ return handler.apply(this, arguments)
68
+ }
69
+ handlerData = [wrappedHandler, meta]
70
+ }
71
+ return originalAdd.call(this, method, path, handlerData)
72
+ }
73
+ }
74
+
75
+ function instrumentHonoInstance (instance) {
76
+ shimmer.wrap(instance, 'fetch', wrapFetch)
77
+ shimmer.wrap(instance, 'use', wrapUse)
78
+ shimmer.wrap(instance, 'basePath', wrapBasePath)
79
+ shimmer.wrap(instance.router, 'add', wrapRouterAdd)
80
+ }
81
+
30
82
  function onErrorFn (error, _context_) {
31
83
  throw error
32
84
  }
@@ -74,7 +126,6 @@ function wrapMiddleware (middleware, route) {
74
126
  if (!req) {
75
127
  return middleware.apply(this, arguments)
76
128
  }
77
- routeChannel.publish({ req, route })
78
129
  enterChannel.publish({ req, name, route })
79
130
  if (typeof next === 'function') {
80
131
  arguments[1] = wrapNext(req, route, next)
@@ -113,7 +164,7 @@ addHook({
113
164
  class Hono extends hono.Hono {
114
165
  constructor (...args) {
115
166
  super(...args)
116
- shimmer.wrap(this, 'fetch', wrapFetch)
167
+ instrumentHonoInstance(this)
117
168
  }
118
169
  }
119
170
 
@@ -130,7 +181,7 @@ addHook({
130
181
  class Hono extends hono.Hono {
131
182
  constructor (...args) {
132
183
  super(...args)
133
- shimmer.wrap(this, 'fetch', wrapFetch)
184
+ instrumentHonoInstance(this)
134
185
  }
135
186
  }
136
187
 
@@ -188,8 +188,8 @@ function patch (http, methodName) {
188
188
  let finished = false
189
189
  let callback = args.callback
190
190
 
191
- if (callback) {
192
- callback = shimmer.wrapFunction(args.callback, cb => function (...args) {
191
+ if (typeof callback === 'function') {
192
+ callback = shimmer.wrapCallback(args.callback, cb => function (...args) {
193
193
  return asyncStartChannel.runStores(ctx, () => {
194
194
  return cb.apply(this, args)
195
195
  })
@@ -43,7 +43,7 @@ function wrapResponseEmit (emit) {
43
43
  return emit.apply(this, arguments)
44
44
  }
45
45
 
46
- if (['finish', 'close'].includes(eventName) && !requestFinishedSet.has(this)) {
46
+ if ((eventName === 'finish' || eventName === 'close') && !requestFinishedSet.has(this)) {
47
47
  finishServerCh.publish({ req: this.req })
48
48
  requestFinishedSet.add(this)
49
49
  }
@@ -51,6 +51,7 @@ function wrapResponseEmit (emit) {
51
51
  return emit.apply(this, arguments)
52
52
  }
53
53
  }
54
+
54
55
  function wrapEmit (emit) {
55
56
  return function (eventName, req, res) {
56
57
  if (!startServerCh.hasSubscribers) {
@@ -61,8 +62,12 @@ function wrapEmit (emit) {
61
62
  res.req = req
62
63
 
63
64
  const abortController = new AbortController()
65
+ // Single ctx shared with `exitServerCh` below and forwarded by the
66
+ // server plugin to `incomingHttpRequestStart`; existing subscribers
67
+ // only read the message, so the reuse is safe.
68
+ const ctx = { req, res, abortController }
64
69
 
65
- startServerCh.publish({ req, res, abortController })
70
+ startServerCh.publish(ctx)
66
71
 
67
72
  try {
68
73
  if (abortController.signal.aborted) {
@@ -76,7 +81,7 @@ function wrapEmit (emit) {
76
81
 
77
82
  throw err
78
83
  } finally {
79
- exitServerCh.publish({ req })
84
+ exitServerCh.publish(ctx)
80
85
  }
81
86
  }
82
87
  return emit.apply(this, arguments)
@@ -107,7 +112,7 @@ function wrapWriteHead (writeHead) {
107
112
  }
108
113
 
109
114
  // this doesn't support explicit duplicate headers, but it's an edge case
110
- const responseHeaders = Object.assign(this.getHeaders(), obj)
115
+ const responseHeaders = obj === undefined ? this.getHeaders() : Object.assign(this.getHeaders(), obj)
111
116
 
112
117
  startWriteHeadCh.publish({
113
118
  req: this.req,
@@ -14,9 +14,9 @@ const connectionOptionsCache = new WeakMap()
14
14
 
15
15
  function wrapRedis (Redis) {
16
16
  shimmer.wrap(Redis.prototype, 'sendCommand', sendCommand => function (command, stream) {
17
- if (!startCh.hasSubscribers) return sendCommand.apply(this, arguments)
17
+ if (!startCh.hasSubscribers) return sendCommand.call(this, command, stream)
18
18
 
19
- if (!command || !command.promise) return sendCommand.apply(this, arguments)
19
+ if (!command?.promise) return sendCommand.call(this, command, stream)
20
20
 
21
21
  const options = this.options || {}
22
22
  let connectionOptions = connectionOptionsCache.get(this)
@@ -35,7 +35,7 @@ function wrapRedis (Redis) {
35
35
  return startCh.runStores(ctx, () => {
36
36
  command.promise.then(() => finish(finishCh, errorCh, ctx), err => finish(finishCh, errorCh, ctx, err))
37
37
 
38
- return sendCommand.apply(this, arguments)
38
+ return sendCommand.call(this, command, stream)
39
39
  })
40
40
  })
41
41
  return Redis
@@ -0,0 +1,163 @@
1
+ 'use strict'
2
+
3
+ const { readFileSync } = require('node:fs')
4
+ const path = require('node:path')
5
+
6
+ const COVERAGE_BACKFILL_CACHE_DIRECTORY = 'dd-trace-coverage-backfill'
7
+ const TRANSFORM_OPTIONS = {
8
+ instrument: true,
9
+ supportsDynamicImport: true,
10
+ supportsExportNamespaceFrom: true,
11
+ supportsStaticESM: true,
12
+ supportsTopLevelAwait: true,
13
+ }
14
+
15
+ function getCoverageBackfillFiles (skippableSuitesCoverage, rootDir, getTestSuitePath) {
16
+ const files = []
17
+ for (const filename of Object.keys(skippableSuitesCoverage || {})) {
18
+ const relativeFilename = path.isAbsolute(filename)
19
+ ? getTestSuitePath(filename, rootDir)
20
+ : filename
21
+ files.push(relativeFilename)
22
+ }
23
+ return files
24
+ }
25
+
26
+ // Use a separate Jest cache namespace for synthetic backfill transforms so they cannot reuse or overwrite normal
27
+ // Jest transform cache entries produced during the user's test run.
28
+ function getCoverageBackfillConfig (config) {
29
+ if (!config?.cacheDirectory) return config
30
+
31
+ return {
32
+ ...config,
33
+ cacheDirectory: path.join(config.cacheDirectory, COVERAGE_BACKFILL_CACHE_DIRECTORY),
34
+ }
35
+ }
36
+
37
+ function getCoverageBackfillDependencies (CoverageReporter, getCoverageBackfillRequire) {
38
+ const coverageWorkerRequire = getCoverageBackfillRequire(CoverageReporter)
39
+
40
+ return {
41
+ createFileCoverage: coverageWorkerRequire('istanbul-lib-coverage').createFileCoverage,
42
+ createScriptTransformer: coverageWorkerRequire('@jest/transform').createScriptTransformer,
43
+ readInitialCoverage: coverageWorkerRequire('istanbul-lib-instrument').readInitialCoverage,
44
+ }
45
+ }
46
+
47
+ // Some transformers expose Istanbul coverage as a literal that readInitialCoverage does not parse.
48
+ function extractCoverageDataObject (code) {
49
+ const marker = 'var coverageData = '
50
+ const start = code.indexOf(marker)
51
+ if (start === -1) return
52
+
53
+ let depth = 0
54
+ let quote
55
+ let escaped = false
56
+ let index = start + marker.length
57
+ for (; index < code.length; index++) {
58
+ const char = code[index]
59
+ if (quote) {
60
+ if (escaped) {
61
+ escaped = false
62
+ } else if (char === '\\') {
63
+ escaped = true
64
+ } else if (char === quote) {
65
+ quote = undefined
66
+ }
67
+ continue
68
+ }
69
+ if (char === '"' || char === "'" || char === '`') {
70
+ quote = char
71
+ } else if (char === '{') {
72
+ depth++
73
+ } else if (char === '}') {
74
+ depth--
75
+ if (depth === 0) {
76
+ index++
77
+ break
78
+ }
79
+ }
80
+ }
81
+ if (depth !== 0) return
82
+
83
+ try {
84
+ // eslint-disable-next-line no-new-func
85
+ return new Function(`return (${code.slice(start + marker.length, index)})`)()
86
+ } catch {
87
+ // Ignore transformer output that does not contain parseable Istanbul metadata.
88
+ }
89
+ }
90
+
91
+ // Read the Istanbul file metadata emitted by Jest's transformer.
92
+ function getCoverageDataFromCode (code, readInitialCoverage) {
93
+ return readInitialCoverage(code)?.coverageData || extractCoverageDataObject(code)
94
+ }
95
+
96
+ function transformFileWithTransformers (absoluteFile, sourceText, transformers, readInitialCoverage) {
97
+ return Promise.all(transformers.map(transformer => {
98
+ return transformer.transformSourceAsync(absoluteFile, sourceText, TRANSFORM_OPTIONS)
99
+ .then(({ code }) => getCoverageDataFromCode(code, readInitialCoverage))
100
+ .catch(() => {})
101
+ })).then(coverageDataByContext => coverageDataByContext.find(Boolean))
102
+ }
103
+
104
+ function getBackfillCoverageDataForFile (file, rootDir, transformers, coverageMap, readInitialCoverage) {
105
+ const absoluteFile = path.isAbsolute(file) ? file : path.join(rootDir, file)
106
+ if (coverageMap.data[absoluteFile]) return Promise.resolve()
107
+
108
+ let sourceText
109
+ try {
110
+ sourceText = readFileSync(absoluteFile, 'utf8')
111
+ } catch {
112
+ return Promise.resolve()
113
+ }
114
+
115
+ return transformFileWithTransformers(absoluteFile, sourceText, transformers, readInitialCoverage)
116
+ }
117
+
118
+ // Seed Jest's coverage map with files that did not run locally but are covered by backend meta.coverage.
119
+ function addCoverageBackfillUntestedFiles ({
120
+ coverageMap,
121
+ testContexts,
122
+ rootDir,
123
+ CoverageReporter,
124
+ coverageBackfillFiles,
125
+ getCoverageBackfillRequire,
126
+ }) {
127
+ if (!coverageBackfillFiles?.length || !coverageMap || !rootDir) return Promise.resolve()
128
+
129
+ let createFileCoverage, createScriptTransformer, readInitialCoverage
130
+ try {
131
+ ({
132
+ createFileCoverage,
133
+ createScriptTransformer,
134
+ readInitialCoverage,
135
+ } = getCoverageBackfillDependencies(CoverageReporter, getCoverageBackfillRequire))
136
+ } catch {
137
+ return Promise.resolve()
138
+ }
139
+
140
+ const contexts = [...(testContexts || [])]
141
+ return Promise.all(contexts.map(context => {
142
+ return createScriptTransformer(getCoverageBackfillConfig(context.config)).catch(() => {})
143
+ }))
144
+ .then(transformers => transformers.filter(Boolean))
145
+ .then(transformers => {
146
+ if (!transformers.length) return []
147
+ return Promise.all(coverageBackfillFiles.map(file => {
148
+ return getBackfillCoverageDataForFile(file, rootDir, transformers, coverageMap, readInitialCoverage)
149
+ }))
150
+ })
151
+ .then(coverageDataByFile => {
152
+ for (const coverageData of coverageDataByFile) {
153
+ if (coverageData && !coverageMap.data[coverageData.path]) {
154
+ coverageMap.addFileCoverage(createFileCoverage(coverageData))
155
+ }
156
+ }
157
+ })
158
+ }
159
+
160
+ module.exports = {
161
+ addCoverageBackfillUntestedFiles,
162
+ getCoverageBackfillFiles,
163
+ }