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
@@ -0,0 +1,42 @@
1
+ 'use strict'
2
+
3
+ const dc = require('dc-polyfill')
4
+
5
+ const { channel } = require('./instrument')
6
+
7
+ /**
8
+ * Shimmer-compatible instrumentor for promise-returning APIs (e.g. `dns.promises.lookup`).
9
+ * Mirrors `createCallbackInstrumentor`'s channel triplet (`<prefix>:start`, `:finish`, `:error`)
10
+ * so a plugin subscribing to those channels for the callback variant works for the promise
11
+ * variant unchanged. `:finish` is the `tracingChannel` `asyncEnd` slot, so it fires after the
12
+ * promise settles with `ctx.result` set to the resolved value.
13
+ *
14
+ * @param {string} prefix
15
+ * @returns {(buildContext: (thisArg: unknown, args: unknown[]) => object | undefined) =>
16
+ * (fn: Function) => Function}
17
+ */
18
+ function createPromiseInstrumentor (prefix) {
19
+ const start = channel(prefix + ':start')
20
+ const finish = channel(prefix + ':finish')
21
+ const error = channel(prefix + ':error')
22
+ const tracing = dc.tracingChannel({
23
+ start,
24
+ end: channel(prefix + ':end'),
25
+ asyncStart: channel(prefix + ':asyncStart'),
26
+ asyncEnd: finish,
27
+ error,
28
+ })
29
+
30
+ return function instrument (buildContext) {
31
+ return function wrap (fn) {
32
+ return function (...args) {
33
+ if (!start.hasSubscribers) return fn.apply(this, args)
34
+ const ctx = buildContext(this, args)
35
+ if (ctx === undefined) return fn.apply(this, args)
36
+ return tracing.tracePromise(fn, ctx, this, ...args)
37
+ }
38
+ }
39
+ }
40
+ }
41
+
42
+ module.exports = { createPromiseInstrumentor }
@@ -133,7 +133,7 @@ for (const name of names) {
133
133
  try {
134
134
  loadChannel.publish({ name })
135
135
 
136
- moduleExports = hook(moduleExports, moduleVersion, isIitm) ?? moduleExports
136
+ moduleExports = hook(moduleExports, moduleVersion, isIitm, { moduleBaseDir, moduleName }) ?? moduleExports
137
137
  } catch (error) {
138
138
  log.info('Error during ddtrace instrumentation of application, aborting.', error)
139
139
  telemetry('error', [
@@ -5,7 +5,7 @@ const { join } = require('path')
5
5
  const { pathToFileURL } = require('url')
6
6
  const log = require('../../../../dd-trace/src/log')
7
7
  const { create } = require('../../../../../vendor/dist/@apm-js-collab/code-transformer')
8
- const { traceAsyncIterator, traceIterator } = require('./transforms')
8
+ const { waitForAsyncEnd } = require('./transforms')
9
9
  const instrumentations = require('./instrumentations')
10
10
 
11
11
  // `dc-polyfill` is referenced from injected `require()` (CJS) and `import`
@@ -34,8 +34,7 @@ const matcherCjs = create(instrumentations, dcPolyfillCjs)
34
34
  const matcherEsm = create(instrumentations, dcPolyfillEsm)
35
35
 
36
36
  for (const matcher of [matcherCjs, matcherEsm]) {
37
- matcher.addTransform('traceIterator', traceIterator)
38
- matcher.addTransform('traceAsyncIterator', traceAsyncIterator)
37
+ matcher.addTransform('waitForAsyncEnd', waitForAsyncEnd)
39
38
  }
40
39
 
41
40
  function rewrite (content, filename, format) {
@@ -0,0 +1,50 @@
1
+ 'use strict'
2
+
3
+ module.exports = [{
4
+ module: {
5
+ name: '@azure/cosmos',
6
+ versionRange: '>=4.4.1',
7
+ filePath: 'dist/browser/plugins/Plugin.js',
8
+ },
9
+ functionQuery: {
10
+ functionName: 'executePlugins',
11
+ kind: 'Async',
12
+ },
13
+ channelName: 'executePlugins',
14
+ },
15
+ {
16
+ module: {
17
+ name: '@azure/cosmos',
18
+ versionRange: '>=4.4.1',
19
+ filePath: 'dist/commonjs/plugins/Plugin.js',
20
+ },
21
+ functionQuery: {
22
+ functionName: 'executePlugins',
23
+ kind: 'Async',
24
+ },
25
+ channelName: 'executePlugins',
26
+ },
27
+ {
28
+ module: {
29
+ name: '@azure/cosmos',
30
+ versionRange: '>=4.4.1',
31
+ filePath: 'dist/esm/plugins/Plugin.js',
32
+ },
33
+ functionQuery: {
34
+ functionName: 'executePlugins',
35
+ kind: 'Async',
36
+ },
37
+ channelName: 'executePlugins',
38
+ },
39
+ {
40
+ module: {
41
+ name: '@azure/cosmos',
42
+ versionRange: '>=4.4.1',
43
+ filePath: 'dist/react-native/plugins/Plugin.js',
44
+ },
45
+ functionQuery: {
46
+ functionName: 'executePlugins',
47
+ kind: 'Async',
48
+ },
49
+ channelName: 'executePlugins',
50
+ }]
@@ -2,8 +2,10 @@
2
2
 
3
3
  module.exports = [
4
4
  ...require('./ai'),
5
+ ...require('./azure-cosmos'),
5
6
  ...require('./bullmq'),
6
7
  ...require('./langchain'),
7
8
  ...require('./langgraph'),
8
9
  ...require('./modelcontextprotocol-sdk'),
10
+ ...require('./playwright'),
9
11
  ]
@@ -10,9 +10,10 @@ module.exports = [
10
10
  functionQuery: {
11
11
  methodName: 'stream',
12
12
  className: 'Pregel',
13
+ kind: 'Async',
14
+ returnKind: 'AsyncIterator',
13
15
  },
14
16
  channelName: 'Pregel_stream',
15
- transform: 'traceAsyncIterator',
16
17
  },
17
18
  {
18
19
  module: {
@@ -23,8 +24,9 @@ module.exports = [
23
24
  functionQuery: {
24
25
  methodName: 'stream',
25
26
  className: 'Pregel',
27
+ kind: 'Async',
28
+ returnKind: 'AsyncIterator',
26
29
  },
27
30
  channelName: 'Pregel_stream',
28
- transform: 'traceAsyncIterator',
29
31
  },
30
32
  ]
@@ -0,0 +1,85 @@
1
+ 'use strict'
2
+
3
+ // Playwright 1.60 bundles several former hook targets into local classes/functions.
4
+ // Keep these rewrites limited to private bundled internals that addHook cannot wrap.
5
+ module.exports = [
6
+ {
7
+ module: {
8
+ name: 'playwright',
9
+ versionRange: '>=1.60.0',
10
+ filePath: 'lib/runner/index.js',
11
+ },
12
+ functionQuery: {
13
+ className: 'Dispatcher',
14
+ methodName: 'run',
15
+ kind: 'Async',
16
+ },
17
+ channelName: 'Dispatcher_run',
18
+ },
19
+ {
20
+ module: {
21
+ name: 'playwright',
22
+ versionRange: '>=1.60.0',
23
+ filePath: 'lib/runner/index.js',
24
+ },
25
+ functionQuery: {
26
+ className: 'Dispatcher',
27
+ methodName: '_createWorker',
28
+ kind: 'Sync',
29
+ },
30
+ channelName: 'Dispatcher_createWorker',
31
+ },
32
+ {
33
+ module: {
34
+ name: 'playwright',
35
+ versionRange: '>=1.60.0',
36
+ filePath: 'lib/runner/index.js',
37
+ },
38
+ functionQuery: {
39
+ className: 'ProcessHost',
40
+ methodName: 'startRunner',
41
+ kind: 'Async',
42
+ },
43
+ channelName: 'ProcessHost_startRunner',
44
+ },
45
+ {
46
+ module: {
47
+ name: 'playwright',
48
+ versionRange: '>=1.60.0',
49
+ filePath: 'lib/runner/index.js',
50
+ },
51
+ functionQuery: {
52
+ functionName: 'createRootSuite',
53
+ kind: 'Async',
54
+ },
55
+ channelName: 'createRootSuite',
56
+ },
57
+ {
58
+ module: {
59
+ name: 'playwright-core',
60
+ versionRange: '>=1.60.0',
61
+ filePath: 'lib/coreBundle.js',
62
+ },
63
+ astQuery: 'AssignmentExpression[left.name="Page2"] > ClassExpression > ClassBody > ' +
64
+ 'MethodDefinition[kind="method"][key.name="goto"] > FunctionExpression[async], ' +
65
+ 'VariableDeclarator[id.name="Page2"] > ClassExpression > ClassBody > ' +
66
+ 'MethodDefinition[kind="method"][key.name="goto"] > FunctionExpression[async], ' +
67
+ 'ClassDeclaration[id.name="Page2"] > ClassBody > ' +
68
+ 'MethodDefinition[kind="method"][key.name="goto"] > FunctionExpression[async]',
69
+ functionQuery: {
70
+ methodName: 'goto',
71
+ kind: 'Async',
72
+ },
73
+ channelName: 'Page_goto',
74
+ },
75
+ {
76
+ module: {
77
+ name: 'playwright-core',
78
+ versionRange: '>=1.60.0',
79
+ filePath: 'lib/coreBundle.js',
80
+ },
81
+ astQuery: 'ReturnStatement > CallExpression[callee.object.name="promise"][callee.property.name="then"]',
82
+ channelName: 'Page_goto',
83
+ transform: 'waitForAsyncEnd',
84
+ },
85
+ ]
@@ -1,246 +1,47 @@
1
1
  'use strict'
2
2
 
3
- // TODO: Move traceIterator to Orchestrion.
4
-
5
- const { parse, query, traverse } = require('./compiler')
6
-
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
- /**
14
- * @param {{ dcModule: string, moduleType: 'esm' | 'cjs' }} state
15
- * @param {import('estree').Program} node
16
- */
17
- tracingChannelImport ({ dcModule, moduleType }, node) {
18
- if (node.body.some(tracingChannelPredicate)) return
19
-
20
- // The vendored matcher state exposes `moduleType` (`esm` / `cjs`), so we
21
- // read that field directly. Naming it `sourceType` here used to silently
22
- // pick the CJS branch for every ESM file, leaving `require()` baked into
23
- // pure ESM modules like `@langchain/langgraph/dist/pregel/index.js`.
24
- const isModule = moduleType === 'esm'
25
-
26
- const index = node.body.findIndex(child => child.directive === 'use strict')
27
- const code = isModule
28
- ? `import tr_ch_apm_dc from "${dcModule}"; const {tracingChannel: tr_ch_apm_tracingChannel} = tr_ch_apm_dc`
29
- : `const {tracingChannel: tr_ch_apm_tracingChannel} = require("${dcModule}")`
30
-
31
- node.body.splice(index + 1, 0, ...parse(code, { isModule }).body)
32
- },
33
-
34
- tracingChannelDeclaration (state, node) {
35
- const { channelName, module: { name } } = state
36
- const channelVariable = 'tr_ch_apm$' + channelName.replaceAll(':', '_')
37
-
38
- if (node.body.some(child => child.declarations?.[0]?.id?.name === channelVariable)) return
39
-
40
- transforms.tracingChannelImport(state, node)
41
-
42
- const index = node.body.findIndex(tracingChannelPredicate)
43
- const code = `
44
- const ${channelVariable} = tr_ch_apm_tracingChannel("orchestrion:${name}:${channelName}")
45
- `
46
-
47
- node.body.splice(index + 1, 0, parse(code).body[0])
48
- },
49
-
50
- traceAsyncIterator: traceAny,
51
- traceIterator: traceAny,
52
- }
53
-
54
- function traceAny (state, node, _parent, ancestry) {
55
- const program = ancestry[ancestry.length - 1]
56
-
57
- if (node.type === 'ClassDeclaration' || node.type === 'ClassExpression') {
58
- traceInstanceMethod(state, node, program)
59
- } else {
60
- traceFunction(state, node, program)
61
- }
62
- }
63
-
64
- function traceFunction (state, node, program) {
65
- transforms.tracingChannelDeclaration(state, program)
66
-
67
- node.body = wrap(state, {
68
- type: 'FunctionExpression',
69
- params: node.params,
70
- body: node.body,
71
- async: node.async,
72
- expression: false,
73
- generator: node.generator,
74
- }, program)
75
-
76
- // The original function no longer contains any calls to `await` or `yield` as
77
- // the function body is copied to the internal wrapped function, so we set
78
- // these to false to avoid altering the return value of the wrapper. The old
79
- // values are instead copied to the new AST node above.
80
- node.generator = false
81
- node.async = false
82
-
83
- wrapSuper(state, node)
84
- }
85
-
86
- function traceInstanceMethod (state, node, program) {
87
- const { functionQuery, operator } = state
88
- const { methodName } = functionQuery
89
-
90
- const classBody = node.body
91
-
92
- // If the method exists on the class, we return as it will be patched later
93
- // while traversing child nodes later on.
94
- if (classBody.body.some(({ key }) => key.name === methodName)) return
95
-
96
- // Method doesn't exist on the class so we assume an instance method and
97
- // wrap it in the constructor instead.
98
- let ctor = classBody.body.find(({ kind }) => kind === 'constructor')
99
-
100
- transforms.tracingChannelDeclaration(state, program)
101
-
102
- if (!ctor) {
103
- ctor = parse(
104
- node.superClass
105
- ? 'class A { constructor (...args) { super(...args) } }'
106
- : 'class A { constructor () {} }'
107
- ).body[0].body.body[0] // Extract constructor from dummy class body.
108
-
109
- classBody.body.unshift(ctor)
110
- }
111
-
112
- const ctorBody = parse(`
113
- const __apm$${methodName} = this["${methodName}"]
114
- this["${methodName}"] = function () {}
115
- `).body
116
-
117
- // Extract only right-hand side function of line 2.
118
- const fn = ctorBody[1].expression.right
119
-
120
- fn.async = operator === 'tracePromise'
121
- fn.body = wrap(state, { type: 'Identifier', name: `__apm$${methodName}` }, program)
122
-
123
- wrapSuper(state, fn)
124
-
125
- ctor.value.body.body.push(...ctorBody)
126
- }
127
-
128
- function wrap (state, node, program) {
129
- const { operator } = state
130
-
131
- if (operator === 'traceAsyncIterator') return wrapIterator(state, node, program)
132
- if (operator === 'traceIterator') return wrapIterator(state, node, program)
133
- }
134
-
135
- function wrapSuper (_state, node) {
136
- const members = new Set()
137
-
138
- traverse(
139
- node.body,
140
- '[object.type=Super]',
141
- (node, parent) => {
142
- const { name } = node.property
143
-
144
- let child
145
-
146
- if (parent.callee) {
147
- // This is needed because for generator functions we have to move the
148
- // original function to a nested wrapped function, but we can't use an
149
- // arrow function because arrow function cannot be generator functions,
150
- // and `super` cannot be called from a nested function, so we have to
151
- // rewrite any `super` call to not use the keyword.
152
- const { expression } = parse(`__apm$super['${name}'].call(this)`).body[0]
153
-
154
- parent.callee = child = expression.callee
155
- parent.arguments.unshift(...expression.arguments)
156
- } else {
157
- parent.expression = child = parse(`__apm$super['${name}']`).body[0]
158
- }
159
-
160
- child.computed = parent.callee.computed
161
- child.optional = parent.callee.optional
162
-
163
- members.add(name)
164
- }
165
- )
166
-
167
- for (const name of members) {
168
- const member = parse(`
169
- class Wrapper {
170
- wrapper () {
171
- __apm$super['${name}'] = super['${name}']
172
- }
173
- }
174
- `).body[0].body.body[0].value.body.body[0]
175
-
176
- node.body.body.unshift(member)
177
- }
178
-
179
- if (members.size > 0) {
180
- node.body.body.unshift(parse('const __apm$super = {}').body[0])
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.
10
+
11
+ const { parse, query } = require('./compiler')
12
+
13
+ module.exports = { waitForAsyncEnd }
14
+
15
+ /**
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}
22
+ */
23
+ function waitForAsyncEnd (_state, node) {
24
+ const onFulfilled = node.arguments[0]
25
+ const statements = onFulfilled?.body?.body
26
+
27
+ if (!statements || query(onFulfilled.body, '[id.name=__apm$asyncEndPromise]').length > 0) {
28
+ return
181
29
  }
182
- }
183
30
 
184
- function wrapIterator (state, node, program) {
185
- const { channelName, operator } = state
186
- const baseChannel = channelName.replaceAll(':', '_')
187
- const channelVariable = 'tr_ch_apm$' + baseChannel
188
- const nextChannel = baseChannel + '_next'
189
- const traceMethod = operator === 'traceAsyncIterator' ? 'tracePromise' : 'traceSync'
190
- const traceNext = `tr_ch_apm$${nextChannel}.${traceMethod}`
31
+ const returnIndex = statements.findIndex(statement => (
32
+ statement.type === 'ReturnStatement' && statement.argument?.name === 'result'
33
+ ))
191
34
 
192
- transforms.tracingChannelDeclaration({ ...state, channelName: nextChannel }, program)
35
+ if (returnIndex === -1) return
193
36
 
194
- const wrapper = parse(`
37
+ const waitStatements = parse(`
195
38
  function wrapper () {
196
- const __apm$traced = () => {
197
- const __apm$wrapped = () => {};
198
- return __apm$wrapped.apply(this, arguments);
199
- };
200
-
201
- if (!${channelVariable}.start.hasSubscribers) return __apm$traced();
202
-
203
- {
204
- const wrap = iter => {
205
- const { next: iterNext, return: iterReturn, throw: iterThrow } = iter;
206
-
207
- iter.next = (...args) => ${traceNext}(iterNext, ctx, iter, ...args);
208
- iter.return = (...args) => ${traceNext}(iterReturn, ctx, iter, ...args);
209
- iter.throw = (...args) => ${traceNext}(iterThrow, ctx, iter, ...args);
210
-
211
- return iter;
212
- };
213
- const ctx = {
214
- arguments,
215
- self: this,
216
- moduleVersion: "1.0.0"
217
- };
218
- const iter = ${channelVariable}.traceSync(__apm$traced, ctx);
219
-
220
- if (typeof iter.then !== 'function') return wrap(iter);
221
-
222
- return iter.then(result => {
223
- ctx.result = result;
224
-
225
- ${channelVariable}.asyncStart.publish(ctx);
226
- ${channelVariable}.asyncEnd.publish(ctx);
227
-
228
- return wrap(result);
229
- }, err => {
230
- ctx.error = err;
231
-
232
- ${channelVariable}.error.publish(ctx);
233
- ${channelVariable}.asyncStart.publish(ctx);
234
- ${channelVariable}.asyncEnd.publish(ctx);
235
-
236
- return Promise.reject(err);
237
- });
238
- };
39
+ const __apm$asyncEndPromise = __apm$ctx.asyncEndPromise;
40
+ if (__apm$asyncEndPromise && typeof __apm$asyncEndPromise.then === 'function') {
41
+ return __apm$asyncEndPromise.then(() => result, () => result);
42
+ }
239
43
  }
240
- `).body[0].body // Extract only block statement of function body.
241
-
242
- // Replace the right-hand side assignment of `const __apm$wrapped = () => {}`.
243
- query(wrapper, '[id.name=__apm$wrapped]')[0].init = node
44
+ `).body[0].body.body
244
45
 
245
- return wrapper
46
+ statements.splice(returnIndex, 0, ...waitStatements)
246
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
 
@@ -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,