dd-trace 5.104.0 → 5.106.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 (159) 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 +16 -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/cucumber-worker-threads.js +19 -0
  11. package/packages/datadog-instrumentations/src/cucumber.js +390 -157
  12. package/packages/datadog-instrumentations/src/dns.js +54 -18
  13. package/packages/datadog-instrumentations/src/fastify.js +142 -82
  14. package/packages/datadog-instrumentations/src/graphql.js +188 -62
  15. package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
  16. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  17. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
  18. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
  19. package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
  20. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -3
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +37 -236
  27. package/packages/datadog-instrumentations/src/hono.js +54 -3
  28. package/packages/datadog-instrumentations/src/http/server.js +9 -4
  29. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
  30. package/packages/datadog-instrumentations/src/jest.js +360 -150
  31. package/packages/datadog-instrumentations/src/kafkajs.js +120 -16
  32. package/packages/datadog-instrumentations/src/mocha/main.js +128 -17
  33. package/packages/datadog-instrumentations/src/nats.js +182 -0
  34. package/packages/datadog-instrumentations/src/nyc.js +38 -1
  35. package/packages/datadog-instrumentations/src/openai.js +33 -18
  36. package/packages/datadog-instrumentations/src/oracledb.js +6 -1
  37. package/packages/datadog-instrumentations/src/pino.js +17 -5
  38. package/packages/datadog-instrumentations/src/playwright.js +515 -292
  39. package/packages/datadog-instrumentations/src/router.js +76 -32
  40. package/packages/datadog-instrumentations/src/stripe.js +1 -1
  41. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
  42. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +1 -1
  43. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +218 -4
  44. package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
  45. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
  46. package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
  47. package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
  48. package/packages/datadog-plugin-bunyan/src/index.js +28 -0
  49. package/packages/datadog-plugin-cucumber/src/index.js +17 -3
  50. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +199 -28
  51. package/packages/datadog-plugin-cypress/src/support.js +69 -1
  52. package/packages/datadog-plugin-dns/src/lookup.js +8 -6
  53. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
  54. package/packages/datadog-plugin-graphql/src/execute.js +2 -0
  55. package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
  56. package/packages/datadog-plugin-http/src/server.js +40 -15
  57. package/packages/datadog-plugin-jest/src/index.js +11 -3
  58. package/packages/datadog-plugin-jest/src/util.js +15 -8
  59. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
  60. package/packages/datadog-plugin-kafkajs/src/producer.js +3 -0
  61. package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
  62. package/packages/datadog-plugin-mocha/src/index.js +19 -4
  63. package/packages/datadog-plugin-mongodb-core/src/index.js +281 -40
  64. package/packages/datadog-plugin-nats/src/consumer.js +43 -0
  65. package/packages/datadog-plugin-nats/src/index.js +20 -0
  66. package/packages/datadog-plugin-nats/src/producer.js +62 -0
  67. package/packages/datadog-plugin-nats/src/util.js +33 -0
  68. package/packages/datadog-plugin-next/src/index.js +5 -3
  69. package/packages/datadog-plugin-openai/src/tracing.js +15 -2
  70. package/packages/datadog-plugin-oracledb/src/index.js +13 -2
  71. package/packages/datadog-plugin-pino/src/index.js +42 -0
  72. package/packages/datadog-plugin-playwright/src/index.js +4 -4
  73. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
  74. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  75. package/packages/datadog-plugin-router/src/index.js +33 -44
  76. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  77. package/packages/datadog-plugin-vitest/src/index.js +5 -13
  78. package/packages/datadog-plugin-winston/src/index.js +30 -0
  79. package/packages/datadog-shimmer/src/shimmer.js +33 -40
  80. package/packages/dd-trace/src/aiguard/index.js +1 -1
  81. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  82. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  83. package/packages/dd-trace/src/appsec/index.js +1 -1
  84. package/packages/dd-trace/src/appsec/reporter.js +5 -6
  85. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  86. package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
  87. package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
  88. package/packages/dd-trace/src/baggage.js +7 -1
  89. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
  90. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
  91. package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
  92. package/packages/dd-trace/src/config/generated-config-types.d.ts +6 -2
  93. package/packages/dd-trace/src/config/supported-configurations.json +27 -8
  94. package/packages/dd-trace/src/datastreams/writer.js +2 -4
  95. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
  96. package/packages/dd-trace/src/encode/0.4.js +124 -108
  97. package/packages/dd-trace/src/encode/0.5.js +114 -26
  98. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +31 -23
  99. package/packages/dd-trace/src/encode/agentless-json.js +4 -2
  100. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
  101. package/packages/dd-trace/src/encode/span-stats.js +16 -16
  102. package/packages/dd-trace/src/encode/tags-processors.js +16 -0
  103. package/packages/dd-trace/src/id.js +15 -0
  104. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +92 -6
  105. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +43 -21
  106. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  107. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  108. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
  109. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  110. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
  111. package/packages/dd-trace/src/llmobs/sdk.js +0 -16
  112. package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
  113. package/packages/dd-trace/src/llmobs/tagger.js +9 -1
  114. package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
  115. package/packages/dd-trace/src/llmobs/util.js +66 -3
  116. package/packages/dd-trace/src/log/index.js +1 -1
  117. package/packages/dd-trace/src/msgpack/chunk.js +394 -10
  118. package/packages/dd-trace/src/msgpack/index.js +96 -2
  119. package/packages/dd-trace/src/openfeature/encoding.js +70 -0
  120. package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
  121. package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
  122. package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
  123. package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
  124. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  125. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +22 -3
  126. package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
  127. package/packages/dd-trace/src/opentracing/propagation/text_map.js +64 -77
  128. package/packages/dd-trace/src/opentracing/span.js +59 -19
  129. package/packages/dd-trace/src/opentracing/span_context.js +50 -3
  130. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -20
  131. package/packages/dd-trace/src/plugins/database.js +7 -6
  132. package/packages/dd-trace/src/plugins/index.js +4 -0
  133. package/packages/dd-trace/src/plugins/log_injection.js +56 -0
  134. package/packages/dd-trace/src/plugins/log_plugin.js +3 -48
  135. package/packages/dd-trace/src/plugins/outbound.js +1 -1
  136. package/packages/dd-trace/src/plugins/plugin.js +15 -17
  137. package/packages/dd-trace/src/plugins/tracing.js +43 -5
  138. package/packages/dd-trace/src/plugins/util/test.js +236 -13
  139. package/packages/dd-trace/src/plugins/util/web.js +79 -65
  140. package/packages/dd-trace/src/priority_sampler.js +2 -2
  141. package/packages/dd-trace/src/profiling/config.js +10 -23
  142. package/packages/dd-trace/src/profiling/exporters/agent.js +11 -10
  143. package/packages/dd-trace/src/profiling/profiler.js +21 -11
  144. package/packages/dd-trace/src/profiling/profilers/wall.js +12 -7
  145. package/packages/dd-trace/src/sampling_rule.js +7 -7
  146. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
  147. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  148. package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
  149. package/packages/dd-trace/src/span_format.js +190 -58
  150. package/packages/dd-trace/src/spanleak.js +1 -1
  151. package/packages/dd-trace/src/standalone/index.js +3 -3
  152. package/packages/dd-trace/src/tagger.js +0 -2
  153. package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
  154. package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
  155. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  156. package/vendor/dist/protobufjs/index.js +1 -1
  157. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  158. package/packages/dd-trace/src/msgpack/encoder.js +0 -308
  159. package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
@@ -4,6 +4,7 @@ const plugins = {
4
4
  get '@anthropic-ai/sdk' () { return require('../../../datadog-plugin-anthropic/src') },
5
5
  get '@apollo/gateway' () { return require('../../../datadog-plugin-apollo/src') },
6
6
  get '@aws-sdk/smithy-client' () { return require('../../../datadog-plugin-aws-sdk/src') },
7
+ get '@azure/cosmos' () { return require('../../../datadog-plugin-azure-cosmos/src') },
7
8
  get '@azure/event-hubs' () { return require('../../../datadog-plugin-azure-event-hubs/src') },
8
9
  get '@azure/functions' () { return require('../../../datadog-plugin-azure-functions/src') },
9
10
  get '@modelcontextprotocol/sdk' () { return require('../../../datadog-plugin-modelcontextprotocol-sdk/src') },
@@ -30,6 +31,7 @@ const plugins = {
30
31
  get '@prisma/client' () { return require('../../../datadog-plugin-prisma/src') },
31
32
  get './runtime/library.js' () { return require('../../../datadog-plugin-prisma/src') },
32
33
  get '@redis/client' () { return require('../../../datadog-plugin-redis/src') },
34
+ get '@smithy/core' () { return require('../../../datadog-plugin-aws-sdk/src') },
33
35
  get '@smithy/smithy-client' () { return require('../../../datadog-plugin-aws-sdk/src') },
34
36
  get '@vitest/runner' () { return require('../../../datadog-plugin-vitest/src') },
35
37
  get '@langchain/langgraph' () { return require('../../../datadog-plugin-langgraph/src') },
@@ -90,6 +92,8 @@ const plugins = {
90
92
  get mongoose () { return require('../../../datadog-plugin-mongoose/src') },
91
93
  get mysql () { return require('../../../datadog-plugin-mysql/src') },
92
94
  get mysql2 () { return require('../../../datadog-plugin-mysql2/src') },
95
+ get '@nats-io/nats-core' () { return require('../../../datadog-plugin-nats/src') },
96
+ get '@nats-io/transport-node' () { return require('../../../datadog-plugin-nats/src') },
93
97
  get net () { return require('../../../datadog-plugin-net/src') },
94
98
  get next () { return require('../../../datadog-plugin-next/src') },
95
99
  get 'node:dns' () { return require('../../../datadog-plugin-dns/src') },
@@ -0,0 +1,56 @@
1
+ 'use strict'
2
+
3
+ const { LOG } = require('../../../../ext/formats')
4
+ const { storage } = require('../../../datadog-core')
5
+
6
+ const legacyStorage = storage('legacy')
7
+
8
+ /**
9
+ * Runs the tracer's log injector and returns the populated log holder, or
10
+ * `undefined` when the propagator emitted no `dd` field (no span, no
11
+ * service / version / env). Hot-path callers gate on the return.
12
+ *
13
+ * @param {object} tracer
14
+ * @returns {{ dd: object } | undefined}
15
+ */
16
+ function buildLogHolder (tracer) {
17
+ const logHolder = {}
18
+ tracer.inject(legacyStorage.getStore()?.span, LOG, logHolder)
19
+ return logHolder.dd ? logHolder : undefined
20
+ }
21
+
22
+ /**
23
+ * @param {object} message Caller-owned log record; never mutated.
24
+ * @param {{ dd: object }} logHolder Holds the dd fields injected by the tracer.
25
+ */
26
+ function messageProxy (message, logHolder) {
27
+ return new Proxy(message, {
28
+ get (target, key) {
29
+ if (shouldOverride(target, key)) return logHolder.dd
30
+ return target[key]
31
+ },
32
+ set (target, key, value) {
33
+ return Reflect.set(target, key, value)
34
+ },
35
+ ownKeys (target) {
36
+ const ownKeys = Reflect.ownKeys(target)
37
+ if (!Object.hasOwn(target, 'dd') && Reflect.isExtensible(target)) {
38
+ ownKeys.push('dd')
39
+ }
40
+ return ownKeys
41
+ },
42
+ getOwnPropertyDescriptor (target, p) {
43
+ return Reflect.getOwnPropertyDescriptor(shouldOverride(target, p) ? logHolder : target, p)
44
+ },
45
+ })
46
+ }
47
+
48
+ /**
49
+ * @param {object} target
50
+ * @param {string | symbol} p
51
+ */
52
+ function shouldOverride (target, p) {
53
+ return p === 'dd' && !Object.hasOwn(target, p) && Reflect.isExtensible(target)
54
+ }
55
+
56
+ module.exports = { buildLogHolder, messageProxy }
@@ -1,55 +1,8 @@
1
1
  'use strict'
2
2
 
3
- const { LOG } = require('../../../../ext/formats')
4
- const { storage } = require('../../../datadog-core')
5
3
  const Plugin = require('./plugin')
6
4
 
7
- const legacyStorage = storage('legacy')
8
-
9
- function messageProxy (message, holder) {
10
- return new Proxy(message, {
11
- get (target, key) {
12
- if (shouldOverride(target, key)) {
13
- return holder.dd
14
- }
15
-
16
- return target[key]
17
- },
18
- set (target, key, value) {
19
- return Reflect.set(target, key, value)
20
- },
21
- ownKeys (target) {
22
- const ownKeys = Reflect.ownKeys(target)
23
- if (!Object.hasOwn(target, 'dd') && Reflect.isExtensible(target)) {
24
- ownKeys.push('dd')
25
- }
26
- return ownKeys
27
- },
28
- getOwnPropertyDescriptor (target, p) {
29
- return Reflect.getOwnPropertyDescriptor(shouldOverride(target, p) ? holder : target, p)
30
- },
31
- })
32
- }
33
-
34
- function shouldOverride (target, p) {
35
- return p === 'dd' && !Object.hasOwn(target, p) && Reflect.isExtensible(target)
36
- }
37
-
38
- module.exports = class LogPlugin extends Plugin {
39
- constructor (...args) {
40
- super(...args)
41
-
42
- this.addSub(`apm:${this.constructor.id}:log`, (arg) => {
43
- const span = legacyStorage.getStore()?.span
44
-
45
- // NOTE: This needs to run whether or not there is a span
46
- // so service, version, and env will always get injected.
47
- const holder = {}
48
- this.tracer.inject(span, LOG, holder)
49
- arg.message = messageProxy(arg.message, holder)
50
- })
51
- }
52
-
5
+ class LogPlugin extends Plugin {
53
6
  configure (config) {
54
7
  return super.configure({
55
8
  ...config,
@@ -57,3 +10,5 @@ module.exports = class LogPlugin extends Plugin {
57
10
  })
58
11
  }
59
12
  }
13
+
14
+ module.exports = LogPlugin
@@ -125,7 +125,7 @@ class OutboundPlugin extends TracingPlugin {
125
125
  */
126
126
  tagPeerService (span) {
127
127
  if (this._tracerConfig.spanComputePeerService) {
128
- const peerData = this.getPeerService(span.context()._tags)
128
+ const peerData = this.getPeerService(span.context().getTags())
129
129
  if (peerData !== undefined) {
130
130
  span.addTags(this.getPeerServiceRemap(peerData))
131
131
  }
@@ -6,6 +6,8 @@ const dc = require('dc-polyfill')
6
6
  const logger = require('../log')
7
7
  const { storage } = require('../../../datadog-core')
8
8
 
9
+ const legacyStorage = storage('legacy')
10
+
9
11
  /**
10
12
  * Base class for all Datadog plugins.
11
13
  *
@@ -28,8 +30,7 @@ class Subscription {
28
30
  constructor (event, handler) {
29
31
  this._channel = dc.channel(event)
30
32
  this._handler = (message, name) => {
31
- const store = storage('legacy').getStore()
32
- if (!store || !store.noop) {
33
+ if (!legacyStorage.getHandle()?.noop) {
33
34
  handler(message, name)
34
35
  }
35
36
  }
@@ -50,20 +51,20 @@ class StoreBinding {
50
51
  constructor (event, transform) {
51
52
  this._channel = dc.channel(event)
52
53
  this._transform = data => {
53
- const store = storage('legacy').getStore()
54
+ const handle = legacyStorage.getHandle()
54
55
 
55
- return !store || !store.noop || (data && Object.hasOwn(data, 'currentStore'))
56
+ return !handle?.noop || (data && Object.hasOwn(data, 'currentStore'))
56
57
  ? transform(data)
57
- : store
58
+ : legacyStorage.getStore()
58
59
  }
59
60
  }
60
61
 
61
62
  enable () {
62
- this._channel.bindStore(storage('legacy'), this._transform)
63
+ this._channel.bindStore(legacyStorage, this._transform)
63
64
  }
64
65
 
65
66
  disable () {
66
- this._channel.unbindStore(storage('legacy'))
67
+ this._channel.unbindStore(legacyStorage)
67
68
  }
68
69
  }
69
70
 
@@ -102,24 +103,21 @@ module.exports = class Plugin {
102
103
  * @returns {void}
103
104
  */
104
105
  enter (span, store) {
105
- store = store || storage('legacy').getStore()
106
- storage('legacy').enterWith({ ...store, span })
106
+ store = store || legacyStorage.getStore()
107
+ legacyStorage.enterWith({ ...store, span })
107
108
  }
108
109
 
109
110
  /**
110
111
  * Subscribe to a diagnostic channel with automatic error handling and enable/disable lifecycle.
111
112
  *
112
113
  * @param {string} channelName Diagnostic channel name.
113
- * @param {(...args: unknown[]) => unknown} handler Handler invoked on messages.
114
+ * @param {(message: unknown, name: string) => unknown} handler Handler invoked on messages.
114
115
  * @returns {void}
115
116
  */
116
117
  addSub (channelName, handler) {
117
- /**
118
- * @type {typeof handler}
119
- */
120
- const wrappedHandler = (...args) => {
118
+ const wrappedHandler = (message, name) => {
121
119
  try {
122
- return handler.apply(this, args)
120
+ return handler.call(this, message, name)
123
121
  } catch (error) {
124
122
  logger.error('Error in plugin handler:', error)
125
123
  logger.info('Disabling plugin: %s', this.constructor.name)
@@ -147,12 +145,12 @@ module.exports = class Plugin {
147
145
  * @returns {void}
148
146
  */
149
147
  addError (error) {
150
- const store = storage('legacy').getStore()
148
+ const store = legacyStorage.getStore()
151
149
 
152
150
  if (!store || !store.span) return
153
151
 
154
152
  const span = /** @type {import('../opentracing/span')} */ (store.span)
155
- if (!span._spanContext._tags.error) {
153
+ if (!span.context().getTag('error')) {
156
154
  span.setTag('error', error || 1)
157
155
  }
158
156
  }
@@ -3,6 +3,7 @@
3
3
  const { storage } = require('../../../datadog-core')
4
4
  const analyticsSampler = require('../analytics_sampler')
5
5
  const { COMPONENT, SVC_SRC_KEY } = require('../constants')
6
+ const { INTEGRATION_SERVICE } = require('../service-naming/source-resolver')
6
7
  const Plugin = require('./plugin')
7
8
 
8
9
  const legacyStorage = storage('legacy')
@@ -99,13 +100,11 @@ class TracingPlugin extends Plugin {
99
100
  const bindName = `bind${event.charAt(0).toUpperCase()}${event.slice(1)}`
100
101
 
101
102
  if (this[event]) {
102
- this.addTraceSub(event, message => {
103
- this[event](message)
104
- })
103
+ this.addTraceSub(event, this[event].bind(this))
105
104
  }
106
105
 
107
106
  if (this[bindName]) {
108
- this.addTraceBind(event, message => this[bindName](message))
107
+ this.addTraceBind(event, this[bindName].bind(this))
109
108
  }
110
109
  }
111
110
  }
@@ -128,12 +127,49 @@ class TracingPlugin extends Plugin {
128
127
  this.addBind(`${prefix}:${eventName}`, transform)
129
128
  }
130
129
 
130
+ /**
131
+ * Record the integration's intended `service.name` on a span without writing the tag.
132
+ *
133
+ * Use this when the plugin has already set `service.name` directly on the span (e.g. via
134
+ * the `tracer.startSpan` tags object) and only needs to stamp the marker so
135
+ * `Span#finish` can later detect user overrides and re-attribute the source.
136
+ *
137
+ * Prefer {@link TracingPlugin#setServiceName} when the tag itself also needs to be written.
138
+ *
139
+ * No-op when there is nothing meaningful to record
140
+ *
141
+ * @param {import('../opentracing/span')} span Internal DatadogSpan instance.
142
+ * @param {string|undefined} name Service name the integration is claiming.
143
+ */
144
+ stampIntegrationService (span, name) {
145
+ if (name === undefined) return
146
+ span[INTEGRATION_SERVICE] = name
147
+ }
148
+
149
+ /**
150
+ * Set `service.name` on a span on behalf of this integration and stamp the marker.
151
+ *
152
+ * Use this for late-binding cases where the service is not known at startSpan time
153
+ * (e.g. web framework config applied after the span is already open).
154
+ *
155
+ * For spans started via {@link TracingPlugin#startSpan}, pass `service` as an option
156
+ * instead — it sets the tag and stamps the marker in one step.
157
+ *
158
+ * @param {import('../opentracing/span')} span Internal DatadogSpan instance.
159
+ * @param {string} name Service name the integration is claiming.
160
+ */
161
+ setServiceName (span, name) {
162
+ // eslint-disable-next-line eslint-rules/eslint-prefer-set-service-name -- this is the implementation
163
+ span._spanContext.setTag('service.name', name)
164
+ this.stampIntegrationService(span, name)
165
+ }
166
+
131
167
  /**
132
168
  * @param {unknown} error
133
169
  * @param {import('../../../..').Span} [span]
134
170
  */
135
171
  addError (error, span = this.activeSpan) {
136
- if (span && !span._spanContext._tags.error) {
172
+ if (span && !span.context().getTag('error')) {
137
173
  // Errors may be wrapped in a context.
138
174
  span.setTag('error', error?.error || error || 1)
139
175
  }
@@ -224,6 +260,8 @@ class TracingPlugin extends Plugin {
224
260
  links: childOf?._links,
225
261
  })
226
262
 
263
+ this.stampIntegrationService(span, serviceName)
264
+
227
265
  analyticsSampler.sample(span, config.measured)
228
266
 
229
267
  // TODO: Remove this after migration to TracingChannel is done.
@@ -229,11 +229,11 @@ const BASE_LIKE_BRANCH_FILTER = /^(main|master|preprod|prod|dev|development|trun
229
229
 
230
230
  /**
231
231
  * Returns request error tags from a test session span for propagation to child events.
232
- * @param {{ context: () => { _tags?: Record<string, string> } } | undefined} sessionSpan
232
+ * @param {{ context: () => { getTag?: (key: string) => string } } | undefined} sessionSpan
233
233
  * @returns {Record<string, string>}
234
234
  */
235
235
  function getSessionRequestErrorTags (sessionSpan) {
236
- const tags = sessionSpan?.context()._tags
236
+ const tags = sessionSpan?.context()?.getTags?.()
237
237
  const sessionRequestErrorTags = {}
238
238
  if (!tags || typeof tags !== 'object') return {}
239
239
  if (tags[DD_CI_LIBRARY_CONFIGURATION_ERROR_SETTINGS] === 'true') {
@@ -253,11 +253,11 @@ function getSessionRequestErrorTags (sessionSpan) {
253
253
 
254
254
  /**
255
255
  * Returns ITR skipping-enabled tags from a test session span for propagation to child events.
256
- * @param {{ context: () => { _tags?: Record<string, string> } } | undefined} sessionSpan
256
+ * @param {{ context: () => { getTags?: () => Record<string, string> } } | undefined} sessionSpan
257
257
  * @returns {Record<string, string>}
258
258
  */
259
259
  function getSessionItrSkippingEnabledTags (sessionSpan) {
260
- const tags = sessionSpan?.context()._tags
260
+ const tags = sessionSpan?.context()?.getTags?.()
261
261
  if (!tags || typeof tags !== 'object') return {}
262
262
  if (tags[TEST_ITR_SKIPPING_ENABLED] !== undefined) {
263
263
  return {
@@ -418,6 +418,12 @@ module.exports = {
418
418
  ITR_CORRELATION_ID,
419
419
  addIntelligentTestRunnerSpanTags,
420
420
  getCoveredFilenamesFromCoverage,
421
+ getCoveredFilesFromCoverage,
422
+ getExecutableFilesFromCoverage,
423
+ getRelativeCoverageFiles,
424
+ getLineCoverageBitmap,
425
+ applySkippedCoverageToCoverage,
426
+ getTestCoverageLinesPercentage,
421
427
  resetCoverage,
422
428
  mergeCoverage,
423
429
  fromCoverageMapToCoverage,
@@ -952,7 +958,6 @@ function getTestLevelCommonTags (command, testFrameworkVersion, testFramework) {
952
958
  return {
953
959
  [TEST_FRAMEWORK_VERSION]: testFrameworkVersion,
954
960
  [LIBRARY_VERSION]: ddTraceVersion,
955
- [TEST_COMMAND]: command,
956
961
  [TEST_TYPE]: getTestTypeFromFramework(testFramework),
957
962
  }
958
963
  }
@@ -1030,15 +1035,233 @@ function addIntelligentTestRunnerSpanTags (
1030
1035
  }
1031
1036
 
1032
1037
  function getCoveredFilenamesFromCoverage (coverage) {
1033
- const coverageMap = istanbul.createCoverageMap(coverage)
1038
+ return getCoveredFilesFromCoverage(coverage).map(({ filename }) => filename)
1039
+ }
1034
1040
 
1035
- return coverageMap
1036
- .files()
1037
- .filter(filename => {
1038
- const fileCoverage = coverageMap.fileCoverageFor(filename)
1039
- const lineCoverage = fileCoverage.getLineCoverage()
1040
- return Object.entries(lineCoverage).some(([, numExecutions]) => !!numExecutions)
1041
- })
1041
+ function getCoverageMap (coverage) {
1042
+ if (coverage?.files && coverage?.fileCoverageFor) {
1043
+ return coverage
1044
+ }
1045
+ return istanbul.createCoverageMap(coverage)
1046
+ }
1047
+
1048
+ function getCoveredFilesFromCoverage (coverage) {
1049
+ const coverageMap = getCoverageMap(coverage)
1050
+ const coverageFiles = []
1051
+
1052
+ for (const filename of coverageMap.files()) {
1053
+ const fileCoverage = coverageMap.fileCoverageFor(filename)
1054
+ const bitmap = getLineCoverageBitmap(fileCoverage.getLineCoverage(), true)
1055
+ if (bitmap) {
1056
+ coverageFiles.push({ filename, bitmap })
1057
+ }
1058
+ }
1059
+
1060
+ return coverageFiles
1061
+ }
1062
+
1063
+ function getExecutableFilesFromCoverage (coverage) {
1064
+ const coverageMap = getCoverageMap(coverage)
1065
+ const coverageFiles = []
1066
+
1067
+ for (const filename of coverageMap.files()) {
1068
+ const fileCoverage = coverageMap.fileCoverageFor(filename)
1069
+ const bitmap = getLineCoverageBitmap(fileCoverage.getLineCoverage())
1070
+ if (bitmap) {
1071
+ coverageFiles.push({ filename, bitmap })
1072
+ }
1073
+ }
1074
+
1075
+ return coverageFiles
1076
+ }
1077
+
1078
+ function getRelativeCoverageFiles (coverageFiles, rootDir) {
1079
+ return coverageFiles.map(({ filename, bitmap }) => ({
1080
+ filename: getTestSuitePath(filename, rootDir),
1081
+ bitmap,
1082
+ }))
1083
+ }
1084
+
1085
+ function getLineCoverageBitmap (lineCoverage, onlyCoveredLines = false) {
1086
+ let maxLine = 0
1087
+ const lines = []
1088
+
1089
+ for (const [line, hits] of Object.entries(lineCoverage)) {
1090
+ if (onlyCoveredLines && !hits) continue
1091
+
1092
+ const lineNumber = Number(line)
1093
+ if (!Number.isSafeInteger(lineNumber) || lineNumber <= 0) continue
1094
+
1095
+ lines.push(lineNumber)
1096
+ if (lineNumber > maxLine) {
1097
+ maxLine = lineNumber
1098
+ }
1099
+ }
1100
+
1101
+ if (maxLine === 0) return
1102
+
1103
+ const bitmap = Buffer.alloc(Math.ceil((maxLine + 1) / 8))
1104
+ for (const lineNumber of lines) {
1105
+ bitmap[lineNumber >> 3] |= 1 << (lineNumber % 8)
1106
+ }
1107
+
1108
+ return bitmap
1109
+ }
1110
+
1111
+ function mergeCoverageBitmaps (targetBitmap, bitmap) {
1112
+ if (!targetBitmap) {
1113
+ return Buffer.from(bitmap)
1114
+ }
1115
+
1116
+ if (targetBitmap.length < bitmap.length) {
1117
+ const biggerBitmap = Buffer.alloc(bitmap.length)
1118
+ targetBitmap.copy(biggerBitmap)
1119
+ targetBitmap = biggerBitmap
1120
+ }
1121
+
1122
+ for (let i = 0; i < bitmap.length; i++) {
1123
+ targetBitmap[i] |= bitmap[i]
1124
+ }
1125
+
1126
+ return targetBitmap
1127
+ }
1128
+
1129
+ function countBitmapBits (bitmap) {
1130
+ let count = 0
1131
+
1132
+ for (const byte of bitmap) {
1133
+ let value = byte
1134
+ while (value) {
1135
+ value &= value - 1
1136
+ count++
1137
+ }
1138
+ }
1139
+
1140
+ return count
1141
+ }
1142
+
1143
+ function countCoveredExecutableBits (coveredBitmap, executableBitmap) {
1144
+ if (!coveredBitmap) return 0
1145
+
1146
+ let count = 0
1147
+ const length = Math.min(coveredBitmap.length, executableBitmap.length)
1148
+
1149
+ for (let i = 0; i < length; i++) {
1150
+ let value = coveredBitmap[i] & executableBitmap[i]
1151
+ while (value) {
1152
+ value &= value - 1
1153
+ count++
1154
+ }
1155
+ }
1156
+
1157
+ return count
1158
+ }
1159
+
1160
+ function getCoverageFileBitmap (bitmap) {
1161
+ if (!bitmap) return
1162
+ if (Buffer.isBuffer(bitmap)) return bitmap
1163
+ if (ArrayBuffer.isView(bitmap)) {
1164
+ return Buffer.from(bitmap.buffer, bitmap.byteOffset, bitmap.byteLength)
1165
+ }
1166
+ if (typeof bitmap === 'string') {
1167
+ return Buffer.from(bitmap, 'base64')
1168
+ }
1169
+ }
1170
+
1171
+ function addCoverageFilesToMap (files, targetMap, rootDir) {
1172
+ for (const file of files) {
1173
+ const bitmap = getCoverageFileBitmap(file.bitmap)
1174
+ if (!bitmap) continue
1175
+
1176
+ const filename = rootDir ? getTestSuitePath(file.filename, rootDir) : file.filename
1177
+ targetMap.set(filename, mergeCoverageBitmaps(targetMap.get(filename), bitmap))
1178
+ }
1179
+ }
1180
+
1181
+ function addSkippedCoverageToMap (skippedCoverage, targetMap) {
1182
+ if (!skippedCoverage) return
1183
+
1184
+ for (const [filename, bitmap] of Object.entries(skippedCoverage)) {
1185
+ const coverageBitmap = getCoverageFileBitmap(bitmap)
1186
+ if (!coverageBitmap) continue
1187
+ targetMap.set(filename, mergeCoverageBitmaps(targetMap.get(filename), coverageBitmap))
1188
+ }
1189
+ }
1190
+
1191
+ function hasSkippedCoverage (skippedCoverage) {
1192
+ return skippedCoverage && typeof skippedCoverage === 'object' && Object.keys(skippedCoverage).length > 0
1193
+ }
1194
+
1195
+ function getTestCoverageLinesPercentage (coverage, skippedCoverage, rootDir) {
1196
+ const executableLinesByFile = new Map()
1197
+ const coveredLinesByFile = new Map()
1198
+
1199
+ addCoverageFilesToMap(getExecutableFilesFromCoverage(coverage), executableLinesByFile, rootDir)
1200
+ addCoverageFilesToMap(getCoveredFilesFromCoverage(coverage), coveredLinesByFile, rootDir)
1201
+ addSkippedCoverageToMap(skippedCoverage, coveredLinesByFile)
1202
+
1203
+ let totalExecutableLines = 0
1204
+ let totalCoveredLines = 0
1205
+
1206
+ for (const [filename, executableLines] of executableLinesByFile) {
1207
+ totalExecutableLines += countBitmapBits(executableLines)
1208
+ totalCoveredLines += countCoveredExecutableBits(coveredLinesByFile.get(filename), executableLines)
1209
+ }
1210
+
1211
+ return totalExecutableLines === 0 ? 0 : Math.floor((totalCoveredLines / totalExecutableLines) * 10_000) / 100
1212
+ }
1213
+
1214
+ function isLineCoveredByBitmap (bitmap, line) {
1215
+ if (!Number.isSafeInteger(line) || line <= 0) return false
1216
+
1217
+ const byteIndex = line >> 3
1218
+ return byteIndex < bitmap.length && !!(bitmap[byteIndex] & (1 << (line % 8)))
1219
+ }
1220
+
1221
+ function getSkippedCoverageByFilename (skippedCoverage) {
1222
+ const skippedCoverageByFilename = new Map()
1223
+ addSkippedCoverageToMap(skippedCoverage, skippedCoverageByFilename)
1224
+ return skippedCoverageByFilename
1225
+ }
1226
+
1227
+ function applySkippedCoverageToFileCoverage (fileCoverage, skippedBitmap) {
1228
+ let updated = false
1229
+ for (const [statementId, statementLocation] of Object.entries(fileCoverage.data.statementMap)) {
1230
+ const startLine = statementLocation?.start?.line
1231
+ if (!isLineCoveredByBitmap(skippedBitmap, startLine)) continue
1232
+ if (fileCoverage.data.s[statementId] > 0) continue
1233
+
1234
+ fileCoverage.data.s[statementId] = 1
1235
+ updated = true
1236
+ }
1237
+ return updated
1238
+ }
1239
+
1240
+ /**
1241
+ * Applies backend skipped-suite coverage to an Istanbul coverage map.
1242
+ * @param {object} coverage
1243
+ * @param {object} skippedCoverage
1244
+ * @param {string} [rootDir]
1245
+ * @returns {boolean}
1246
+ */
1247
+ function applySkippedCoverageToCoverage (coverage, skippedCoverage, rootDir) {
1248
+ if (!hasSkippedCoverage(skippedCoverage)) return false
1249
+
1250
+ const coverageMap = getCoverageMap(coverage)
1251
+ const skippedCoverageByFilename = getSkippedCoverageByFilename(skippedCoverage)
1252
+ let matched = false
1253
+
1254
+ for (const filename of coverageMap.files()) {
1255
+ const relativeFilename = rootDir ? getTestSuitePath(filename, rootDir) : filename
1256
+ const skippedBitmap = skippedCoverageByFilename.get(relativeFilename)
1257
+ if (!skippedBitmap) continue
1258
+
1259
+ const fileCoverage = coverageMap.fileCoverageFor(filename)
1260
+ applySkippedCoverageToFileCoverage(fileCoverage, skippedBitmap)
1261
+ matched = true
1262
+ }
1263
+
1264
+ return matched
1042
1265
  }
1043
1266
 
1044
1267
  function resetCoverage (coverage) {