dd-trace 5.66.0 → 5.68.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 (107) hide show
  1. package/LICENSE-3rdparty.csv +0 -3
  2. package/README.md +0 -2
  3. package/ci/init.js +52 -54
  4. package/ext/exporters.d.ts +2 -1
  5. package/ext/exporters.js +2 -1
  6. package/index.d.ts +85 -2
  7. package/initialize.mjs +1 -1
  8. package/package.json +8 -11
  9. package/packages/datadog-esbuild/index.js +56 -0
  10. package/packages/datadog-instrumentations/src/aws-sdk.js +42 -4
  11. package/packages/datadog-instrumentations/src/azure-functions.js +1 -1
  12. package/packages/datadog-instrumentations/src/azure-service-bus.js +1 -1
  13. package/packages/datadog-instrumentations/src/cassandra-driver.js +2 -2
  14. package/packages/datadog-instrumentations/src/connect.js +6 -2
  15. package/packages/datadog-instrumentations/src/cucumber.js +31 -6
  16. package/packages/datadog-instrumentations/src/express.js +5 -6
  17. package/packages/datadog-instrumentations/src/fastify.js +3 -3
  18. package/packages/datadog-instrumentations/src/helpers/hook.js +28 -15
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  20. package/packages/datadog-instrumentations/src/helpers/instrument.js +11 -2
  21. package/packages/datadog-instrumentations/src/helpers/register.js +10 -3
  22. package/packages/datadog-instrumentations/src/http2/client.js +1 -0
  23. package/packages/datadog-instrumentations/src/http2/server.js +0 -1
  24. package/packages/datadog-instrumentations/src/ioredis.js +12 -1
  25. package/packages/datadog-instrumentations/src/jest.js +48 -36
  26. package/packages/datadog-instrumentations/src/limitd-client.js +2 -1
  27. package/packages/datadog-instrumentations/src/mocha/main.js +15 -7
  28. package/packages/datadog-instrumentations/src/mocha/utils.js +3 -0
  29. package/packages/datadog-instrumentations/src/mongoose.js +2 -1
  30. package/packages/datadog-instrumentations/src/oracledb.js +19 -13
  31. package/packages/datadog-instrumentations/src/pg.js +9 -5
  32. package/packages/datadog-instrumentations/src/pino.js +18 -6
  33. package/packages/datadog-instrumentations/src/playwright.js +15 -1
  34. package/packages/datadog-instrumentations/src/sequelize.js +1 -1
  35. package/packages/datadog-instrumentations/src/vitest.js +155 -62
  36. package/packages/datadog-plugin-ai/src/tracing.js +3 -3
  37. package/packages/datadog-plugin-aws-sdk/src/base.js +23 -8
  38. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +2 -2
  39. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +101 -2
  40. package/packages/datadog-plugin-aws-sdk/src/util.js +1 -1
  41. package/packages/datadog-plugin-cucumber/src/index.js +4 -56
  42. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +6 -2
  43. package/packages/datadog-plugin-cypress/src/support.js +4 -0
  44. package/packages/datadog-plugin-express/src/code_origin.js +2 -2
  45. package/packages/datadog-plugin-fastify/src/code_origin.js +1 -2
  46. package/packages/datadog-plugin-jest/src/index.js +0 -21
  47. package/packages/datadog-plugin-mocha/src/index.js +3 -57
  48. package/packages/datadog-plugin-mongodb-core/src/index.js +20 -7
  49. package/packages/datadog-plugin-playwright/src/index.js +11 -5
  50. package/packages/datadog-plugin-vitest/src/index.js +5 -1
  51. package/packages/datadog-plugin-ws/src/close.js +1 -1
  52. package/packages/datadog-plugin-ws/src/producer.js +6 -1
  53. package/packages/datadog-plugin-ws/src/receiver.js +6 -1
  54. package/packages/dd-trace/src/appsec/iast/security-controls/parser.js +1 -1
  55. package/packages/dd-trace/src/appsec/telemetry/waf.js +2 -2
  56. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -4
  57. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +11 -3
  58. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +10 -1
  59. package/packages/dd-trace/src/config.js +69 -304
  60. package/packages/dd-trace/src/config_defaults.js +186 -0
  61. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -1
  62. package/packages/dd-trace/src/datastreams/fnv.js +2 -2
  63. package/packages/dd-trace/src/datastreams/writer.js +3 -2
  64. package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -1
  65. package/packages/dd-trace/src/dogstatsd.js +4 -3
  66. package/packages/dd-trace/src/encode/0.4.js +1 -5
  67. package/packages/dd-trace/src/exporter.js +1 -0
  68. package/packages/dd-trace/src/exporters/agent/index.js +3 -2
  69. package/packages/dd-trace/src/exporters/agent/writer.js +1 -1
  70. package/packages/dd-trace/src/exporters/common/agent-info-exporter.js +3 -2
  71. package/packages/dd-trace/src/exporters/common/request.js +2 -1
  72. package/packages/dd-trace/src/exporters/span-stats/index.js +3 -2
  73. package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
  74. package/packages/dd-trace/src/llmobs/index.js +7 -0
  75. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +4 -3
  76. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -1
  77. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +40 -13
  78. package/packages/dd-trace/src/llmobs/plugins/openai.js +7 -1
  79. package/packages/dd-trace/src/llmobs/sdk.js +28 -0
  80. package/packages/dd-trace/src/llmobs/span_processor.js +124 -28
  81. package/packages/dd-trace/src/llmobs/tagger.js +8 -0
  82. package/packages/dd-trace/src/llmobs/telemetry.js +9 -2
  83. package/packages/dd-trace/src/log/index.js +28 -17
  84. package/packages/dd-trace/src/log/log.js +29 -5
  85. package/packages/dd-trace/src/log/writer.js +5 -5
  86. package/packages/dd-trace/src/noop/span.js +1 -0
  87. package/packages/dd-trace/src/opentelemetry/span.js +14 -3
  88. package/packages/dd-trace/src/opentracing/span.js +18 -4
  89. package/packages/dd-trace/src/plugin_manager.js +20 -2
  90. package/packages/dd-trace/src/plugins/ci_plugin.js +97 -3
  91. package/packages/dd-trace/src/plugins/index.js +2 -0
  92. package/packages/dd-trace/src/plugins/util/git-cache.js +129 -0
  93. package/packages/dd-trace/src/plugins/util/git.js +40 -26
  94. package/packages/dd-trace/src/plugins/util/test.js +37 -27
  95. package/packages/dd-trace/src/plugins/util/web.js +1 -1
  96. package/packages/dd-trace/src/profiler.js +4 -1
  97. package/packages/dd-trace/src/profiling/config.js +73 -42
  98. package/packages/dd-trace/src/profiling/profiler.js +3 -1
  99. package/packages/dd-trace/src/profiling/profilers/events.js +3 -8
  100. package/packages/dd-trace/src/profiling/profilers/space.js +1 -0
  101. package/packages/dd-trace/src/profiling/profilers/wall.js +196 -117
  102. package/packages/dd-trace/src/remote_config/capabilities.js +5 -0
  103. package/packages/dd-trace/src/remote_config/manager.js +3 -2
  104. package/packages/dd-trace/src/startup-log.js +2 -1
  105. package/packages/dd-trace/src/supported-configurations.json +3 -0
  106. package/packages/dd-trace/src/telemetry/logs/index.js +2 -2
  107. package/register.js +1 -1
@@ -23,9 +23,16 @@ const LLMObsTagger = require('./tagger')
23
23
  const { channel } = require('dc-polyfill')
24
24
  const evalMetricAppendCh = channel('llmobs:eval-metric:append')
25
25
  const flushCh = channel('llmobs:writers:flush')
26
+ const registerUserSpanProcessorCh = channel('llmobs:register-processor')
26
27
  const NoopLLMObs = require('./noop')
27
28
 
28
29
  class LLMObs extends NoopLLMObs {
30
+ /**
31
+ * flag representing if a user span processor has been registered
32
+ * @type {boolean}
33
+ */
34
+ #hasUserSpanProcessor = false
35
+
29
36
  constructor (tracer, llmobsModule, config) {
30
37
  super(tracer)
31
38
 
@@ -309,6 +316,27 @@ class LLMObs extends NoopLLMObs {
309
316
  }
310
317
  }
311
318
 
319
+ registerProcessor (processor) {
320
+ if (!this.enabled) return
321
+
322
+ if (this.#hasUserSpanProcessor) {
323
+ throw new Error(
324
+ '[LLMObs] Only one user span processor can be registered. ' +
325
+ 'To register a new processor, deregister the existing processor first using `llmobs.deregisterProcessor()`.'
326
+ )
327
+ }
328
+
329
+ this.#hasUserSpanProcessor = true
330
+ registerUserSpanProcessorCh.publish(processor)
331
+ }
332
+
333
+ deregisterProcessor () {
334
+ if (!this.enabled) return
335
+
336
+ this.#hasUserSpanProcessor = false
337
+ registerUserSpanProcessorCh.publish(null)
338
+ }
339
+
312
340
  submitEvaluation (llmobsSpanContext, options = {}) {
313
341
  if (!this.enabled) return
314
342
 
@@ -34,25 +34,55 @@ const LLMObsTagger = require('./tagger')
34
34
  const tracerVersion = require('../../../../package.json').version
35
35
  const logger = require('../log')
36
36
 
37
+ const util = require('node:util')
38
+
39
+ class LLMObservabilitySpan {
40
+ constructor () {
41
+ this.input = []
42
+ this.output = []
43
+
44
+ this._tags = {}
45
+ }
46
+
47
+ getTag (key) {
48
+ return this._tags[key]
49
+ }
50
+ }
51
+
37
52
  class LLMObsSpanProcessor {
53
+ /** @type {import('../config')} */
54
+ #config
55
+
56
+ /** @type {((span: LLMObservabilitySpan) => LLMObservabilitySpan | null) | null} */
57
+ #userSpanProcessor
58
+
59
+ /** @type {import('./writers/spans')} */
60
+ #writer
61
+
38
62
  constructor (config) {
39
- this._config = config
63
+ this.#config = config
64
+ }
65
+
66
+ setUserSpanProcessor (userSpanProcessor) {
67
+ this.#userSpanProcessor = userSpanProcessor
40
68
  }
41
69
 
42
70
  setWriter (writer) {
43
- this._writer = writer
71
+ this.#writer = writer
44
72
  }
45
73
 
46
74
  // TODO: instead of relying on the tagger's weakmap registry, can we use some namespaced storage correlation?
47
75
  process ({ span }) {
48
- if (!this._config.llmobs.enabled) return
76
+ if (!this.#config.llmobs.enabled) return
49
77
  // if the span is not in our private tagger map, it is not an llmobs span
50
78
  if (!LLMObsTagger.tagMap.has(span)) return
51
79
 
52
80
  try {
53
81
  const formattedEvent = this.format(span)
54
82
  telemetry.incrementLLMObsSpanFinishedCount(span)
55
- this._writer.append(formattedEvent)
83
+ if (formattedEvent == null) return
84
+
85
+ this.#writer.append(formattedEvent)
56
86
  } catch (e) {
57
87
  // this should be a rare case
58
88
  // we protect against unserializable properties in the format function, and in
@@ -65,6 +95,9 @@ class LLMObsSpanProcessor {
65
95
  }
66
96
 
67
97
  format (span) {
98
+ const llmObsSpan = new LLMObservabilitySpan()
99
+ let inputType, outputType
100
+
68
101
  const spanTags = span.context()._tags
69
102
  const mlObsTags = LLMObsTagger.tagMap.get(span)
70
103
 
@@ -78,26 +111,29 @@ class LLMObsSpanProcessor {
78
111
  meta.model_name = mlObsTags[MODEL_NAME] || 'custom'
79
112
  meta.model_provider = (mlObsTags[MODEL_PROVIDER] || 'custom').toLowerCase()
80
113
  }
114
+
81
115
  if (mlObsTags[METADATA]) {
82
- this._addObject(mlObsTags[METADATA], meta.metadata = {})
116
+ this.#addObject(mlObsTags[METADATA], meta.metadata = {})
83
117
  }
118
+
84
119
  if (spanKind === 'llm' && mlObsTags[INPUT_MESSAGES]) {
85
- input.messages = mlObsTags[INPUT_MESSAGES]
86
- }
87
- if (mlObsTags[INPUT_VALUE]) {
88
- input.value = mlObsTags[INPUT_VALUE]
89
- }
90
- if (spanKind === 'llm' && mlObsTags[OUTPUT_MESSAGES]) {
91
- output.messages = mlObsTags[OUTPUT_MESSAGES]
92
- }
93
- if (spanKind === 'embedding' && mlObsTags[INPUT_DOCUMENTS]) {
120
+ llmObsSpan.input = mlObsTags[INPUT_MESSAGES]
121
+ inputType = 'messages'
122
+ } else if (spanKind === 'embedding' && mlObsTags[INPUT_DOCUMENTS]) {
94
123
  input.documents = mlObsTags[INPUT_DOCUMENTS]
124
+ } else if (mlObsTags[INPUT_VALUE]) {
125
+ llmObsSpan.input = [{ role: '', content: mlObsTags[INPUT_VALUE] }]
126
+ inputType = 'value'
95
127
  }
96
- if (mlObsTags[OUTPUT_VALUE]) {
97
- output.value = mlObsTags[OUTPUT_VALUE]
98
- }
99
- if (spanKind === 'retrieval' && mlObsTags[OUTPUT_DOCUMENTS]) {
128
+
129
+ if (spanKind === 'llm' && mlObsTags[OUTPUT_MESSAGES]) {
130
+ llmObsSpan.output = mlObsTags[OUTPUT_MESSAGES]
131
+ outputType = 'messages'
132
+ } else if (spanKind === 'retrieval' && mlObsTags[OUTPUT_DOCUMENTS]) {
100
133
  output.documents = mlObsTags[OUTPUT_DOCUMENTS]
134
+ } else if (mlObsTags[OUTPUT_VALUE]) {
135
+ llmObsSpan.output = [{ role: '', content: mlObsTags[OUTPUT_VALUE] }]
136
+ outputType = 'value'
101
137
  }
102
138
 
103
139
  const error = spanTags.error || spanTags[ERROR_TYPE]
@@ -107,9 +143,6 @@ class LLMObsSpanProcessor {
107
143
  meta[ERROR_STACK] = spanTags[ERROR_STACK] || error.stack
108
144
  }
109
145
 
110
- if (input) meta.input = input
111
- if (output) meta.output = output
112
-
113
146
  const metrics = mlObsTags[METRICS] || {}
114
147
 
115
148
  const mlApp = mlObsTags[ML_APP]
@@ -118,12 +151,37 @@ class LLMObsSpanProcessor {
118
151
 
119
152
  const name = mlObsTags[NAME] || span._name
120
153
 
154
+ const tags = this.#getTags(span, mlApp, sessionId, error)
155
+ llmObsSpan._tags = tags
156
+
157
+ const processedSpan = this.#runProcessor(llmObsSpan)
158
+ if (processedSpan === null) return null
159
+
160
+ if (processedSpan.input) {
161
+ if (inputType === 'messages') {
162
+ input.messages = processedSpan.input
163
+ } else if (inputType === 'value') {
164
+ input.value = processedSpan.input[0].content
165
+ }
166
+ }
167
+
168
+ if (processedSpan.output) {
169
+ if (outputType === 'messages') {
170
+ output.messages = processedSpan.output
171
+ } else if (outputType === 'value') {
172
+ output.value = processedSpan.output[0].content
173
+ }
174
+ }
175
+
176
+ if (input) meta.input = input
177
+ if (output) meta.output = output
178
+
121
179
  const llmObsSpanEvent = {
122
180
  trace_id: span.context().toTraceId(true),
123
181
  span_id: span.context().toSpanId(),
124
182
  parent_id: parentId,
125
183
  name,
126
- tags: this._processTags(span, mlApp, sessionId, error),
184
+ tags: this.#objectTagsToStringArrayTags(tags),
127
185
  start_ns: Math.round(span._startTime * 1e6),
128
186
  duration: Math.round(span._duration * 1e6),
129
187
  status: error ? 'error' : 'ok',
@@ -144,7 +202,7 @@ class LLMObsSpanProcessor {
144
202
  // However, we want to protect against circular references or BigInts (unserializable)
145
203
  // This function can be reused for other fields if needed
146
204
  // Messages, Documents, and Metrics are safeguarded in `llmobs/tagger.js`
147
- _addObject (obj, carrier) {
205
+ #addObject (obj, carrier) {
148
206
  const seenObjects = new WeakSet()
149
207
  seenObjects.add(obj) // capture root object
150
208
 
@@ -176,12 +234,12 @@ class LLMObsSpanProcessor {
176
234
  add(obj, carrier)
177
235
  }
178
236
 
179
- _processTags (span, mlApp, sessionId, error) {
237
+ #getTags (span, mlApp, sessionId, error) {
180
238
  let tags = {
181
- ...this._config.parsedDdTags,
182
- version: this._config.version,
183
- env: this._config.env,
184
- service: this._config.service,
239
+ ...this.#config.parsedDdTags,
240
+ version: this.#config.version,
241
+ env: this.#config.env,
242
+ service: this.#config.service,
185
243
  source: 'integration',
186
244
  ml_app: mlApp,
187
245
  'ddtrace.version': tracerVersion,
@@ -191,13 +249,51 @@ class LLMObsSpanProcessor {
191
249
 
192
250
  const errType = span.context()._tags[ERROR_TYPE] || error?.name
193
251
  if (errType) tags.error_type = errType
252
+
194
253
  if (sessionId) tags.session_id = sessionId
254
+
195
255
  const integration = LLMObsTagger.tagMap.get(span)?.[INTEGRATION]
196
256
  if (integration) tags.integration = integration
257
+
197
258
  const existingTags = LLMObsTagger.tagMap.get(span)?.[TAGS] || {}
198
259
  if (existingTags) tags = { ...tags, ...existingTags }
260
+
261
+ return tags
262
+ }
263
+
264
+ #objectTagsToStringArrayTags (tags) {
199
265
  return Object.entries(tags).map(([key, value]) => `${key}:${value ?? ''}`)
200
266
  }
267
+
268
+ /**
269
+ * Runs the user span processor, emitting telemetry and adding some guardrails against invalid return types
270
+ * @param {LLMObservabilitySpan} span
271
+ * @returns {LLMObservabilitySpan | null}
272
+ */
273
+ #runProcessor (span) {
274
+ const processor = this.#userSpanProcessor
275
+ if (!processor) return span
276
+
277
+ let error = false
278
+
279
+ try {
280
+ const processedLLMObsSpan = processor(span)
281
+ if (processedLLMObsSpan === null) return null
282
+
283
+ if (!(processedLLMObsSpan instanceof LLMObservabilitySpan)) {
284
+ error = true
285
+ logger.warn('User span processor must return an instance of an LLMObservabilitySpan or null, dropping span.')
286
+ return null
287
+ }
288
+
289
+ return processedLLMObsSpan
290
+ } catch (e) {
291
+ logger.error(`[LLMObs] Error in LLMObs span processor (${util.inspect(processor)}): ${util.inspect(e)}`)
292
+ error = true
293
+ } finally {
294
+ telemetry.recordLLMObsUserProcessorCalled(error)
295
+ }
296
+ }
201
297
  }
202
298
 
203
299
  module.exports = LLMObsSpanProcessor
@@ -20,6 +20,8 @@ const {
20
20
  NAME,
21
21
  PROPAGATED_PARENT_ID_KEY,
22
22
  ROOT_PARENT_ID,
23
+ CACHE_READ_INPUT_TOKENS_METRIC_KEY,
24
+ CACHE_WRITE_INPUT_TOKENS_METRIC_KEY,
23
25
  INPUT_TOKENS_METRIC_KEY,
24
26
  OUTPUT_TOKENS_METRIC_KEY,
25
27
  TOTAL_TOKENS_METRIC_KEY,
@@ -144,6 +146,12 @@ class LLMObsTagger {
144
146
  case 'totalTokens':
145
147
  processedKey = TOTAL_TOKENS_METRIC_KEY
146
148
  break
149
+ case 'cacheReadTokens':
150
+ processedKey = CACHE_READ_INPUT_TOKENS_METRIC_KEY
151
+ break
152
+ case 'cacheWriteTokens':
153
+ processedKey = CACHE_WRITE_INPUT_TOKENS_METRIC_KEY
154
+ break
147
155
  }
148
156
 
149
157
  if (typeof value === 'number') {
@@ -85,7 +85,8 @@ function recordLLMObsEnabled (startTime, config, value = 1) {
85
85
  error: 0,
86
86
  agentless: Number(config.llmobs.agentlessEnabled),
87
87
  site: config.site,
88
- auto: Number(autoEnabled)
88
+ auto: Number(autoEnabled),
89
+ ml_app: config.llmobs.mlApp
89
90
  }
90
91
  llmobsMetrics.count('product_enabled', tags).inc(value)
91
92
  llmobsMetrics.distribution('init_time', tags).track(initTimeMs)
@@ -154,6 +155,11 @@ function recordSubmitEvaluation (options, err, value = 1) {
154
155
  llmobsMetrics.count('evals_submitted', tags).inc(value)
155
156
  }
156
157
 
158
+ function recordLLMObsUserProcessorCalled (error, value = 1) {
159
+ const tags = { error: error ? 1 : 0 }
160
+ llmobsMetrics.count('user_processor_called', tags).inc(value)
161
+ }
162
+
157
163
  module.exports = {
158
164
  recordLLMObsEnabled,
159
165
  incrementLLMObsSpanStartCount,
@@ -164,5 +170,6 @@ module.exports = {
164
170
  recordLLMObsAnnotate,
165
171
  recordUserFlush,
166
172
  recordExportSpan,
167
- recordSubmitEvaluation
173
+ recordSubmitEvaluation,
174
+ recordLLMObsUserProcessorCalled
168
175
  }
@@ -1,11 +1,9 @@
1
1
  'use strict'
2
-
3
- const coalesce = require('koalas')
4
2
  const { inspect } = require('util')
5
3
  const { isTrue } = require('../util')
6
4
  const { traceChannel, debugChannel, infoChannel, warnChannel, errorChannel } = require('./channels')
7
5
  const logWriter = require('./writer')
8
- const { Log } = require('./log')
6
+ const { Log, LogConfig, NoTransmitError } = require('./log')
9
7
  const { memoize } = require('./utils')
10
8
  const { getEnvironmentVariable } = require('../config-helper')
11
9
 
@@ -15,7 +13,14 @@ const config = {
15
13
  logLevel: 'debug'
16
14
  }
17
15
 
16
+ // in most places where we know we want to mute a log we use log.error() directly
17
+ const NO_TRANSMIT = new LogConfig(false)
18
+
18
19
  const log = {
20
+ LogConfig,
21
+ NO_TRANSMIT,
22
+ NoTransmitError,
23
+
19
24
  /**
20
25
  * @returns Read-only version of logging config. To modify config, call `log.use` and `log.toggle`
21
26
  */
@@ -92,18 +97,26 @@ const log = {
92
97
  return this
93
98
  },
94
99
 
100
+ errorWithoutTelemetry (...args) {
101
+ args.push(NO_TRANSMIT)
102
+ if (errorChannel.hasSubscribers) {
103
+ errorChannel.publish(Log.parse(...args))
104
+ }
105
+ return this
106
+ },
107
+
95
108
  deprecate (code, message) {
96
109
  return this._deprecate(code, message)
97
110
  },
98
111
 
99
112
  isEnabled (fleetStableConfigValue, localStableConfigValue) {
100
- return isTrue(coalesce(
101
- fleetStableConfigValue,
102
- getEnvironmentVariable('DD_TRACE_DEBUG'),
103
- getEnvironmentVariable('OTEL_LOG_LEVEL') === 'debug' || undefined,
104
- localStableConfigValue,
105
- config.enabled
106
- ))
113
+ return isTrue(
114
+ (fleetStableConfigValue ??
115
+ getEnvironmentVariable('DD_TRACE_DEBUG') ??
116
+ getEnvironmentVariable('OTEL_LOG_LEVEL') === 'debug') ||
117
+ (localStableConfigValue ??
118
+ config.enabled)
119
+ )
107
120
  },
108
121
 
109
122
  getLogLevel (
@@ -111,14 +124,12 @@ const log = {
111
124
  fleetStableConfigValue,
112
125
  localStableConfigValue
113
126
  ) {
114
- return coalesce(
115
- optionsValue,
116
- fleetStableConfigValue,
117
- getEnvironmentVariable('DD_TRACE_LOG_LEVEL'),
118
- getEnvironmentVariable('OTEL_LOG_LEVEL'),
119
- localStableConfigValue,
127
+ return optionsValue ??
128
+ fleetStableConfigValue ??
129
+ getEnvironmentVariable('DD_TRACE_LOG_LEVEL') ??
130
+ getEnvironmentVariable('OTEL_LOG_LEVEL') ??
131
+ localStableConfigValue ??
120
132
  config.logLevel
121
- )
122
133
  }
123
134
  }
124
135
 
@@ -2,12 +2,16 @@
2
2
 
3
3
  const { format } = require('util')
4
4
 
5
+ // other times we produce an Error in a central location and log it several other places
6
+ class NoTransmitError extends Error {}
7
+
5
8
  class Log {
6
- constructor (message, args, cause, delegate) {
9
+ constructor (message, args, cause, delegate, sendViaTelemetry = true) {
7
10
  this.message = message
8
11
  this.args = args
9
12
  this.cause = cause
10
13
  this.delegate = delegate
14
+ this.sendViaTelemetry = sendViaTelemetry
11
15
  }
12
16
 
13
17
  get formatted () {
@@ -22,10 +26,18 @@ class Log {
22
26
 
23
27
  static parse (...args) {
24
28
  let message, cause, delegate
29
+ let sendViaTelemetry = true
30
+
31
+ const maybeLogConfig = args.at(-1)
32
+ if (maybeLogConfig instanceof LogConfig) {
33
+ args.pop()
34
+ sendViaTelemetry = maybeLogConfig.transmit
35
+ }
25
36
 
26
- const lastArg = args.at(-1)
27
- if (lastArg && typeof lastArg === 'object' && lastArg.stack) { // lastArg instanceof Error?
37
+ const maybeError = args.at(-1)
38
+ if (maybeError && typeof maybeError === 'object' && maybeError.stack) { // maybeError instanceof Error?
28
39
  cause = args.pop()
40
+ if (cause instanceof NoTransmitError) sendViaTelemetry = false
29
41
  }
30
42
 
31
43
  const firstArg = args.shift()
@@ -43,10 +55,22 @@ class Log {
43
55
  message = String(firstArg)
44
56
  }
45
57
 
46
- return new Log(message, args, cause, delegate)
58
+ return new Log(message, args, cause, delegate, sendViaTelemetry)
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Pass instances of this class to logger methods when fine-grain control is needed
64
+ * @property {boolean} transmit - Whether to send the log via telemetry.
65
+ */
66
+ class LogConfig {
67
+ constructor (transmit = true) {
68
+ this.transmit = transmit
47
69
  }
48
70
  }
49
71
 
50
72
  module.exports = {
51
- Log
73
+ Log,
74
+ LogConfig,
75
+ NoTransmitError,
52
76
  }
@@ -75,12 +75,12 @@ function onError (err) {
75
75
  // TODO: replace it with Error(message, { cause }) when cause has broad support
76
76
  if (formatted) {
77
77
  withNoop(() => {
78
- const l = Error.stackTraceLimit
78
+ const stackTraceLimitBackup = Error.stackTraceLimit
79
79
  Error.stackTraceLimit = 0
80
- const e = new Error(formatted)
81
- Error.stackTraceLimit = l
82
- Error.captureStackTrace(e, stackTraceLimitFunction)
83
- logger.error(e)
80
+ const newError = new Error(formatted)
81
+ Error.stackTraceLimit = stackTraceLimitBackup
82
+ Error.captureStackTrace(newError, stackTraceLimitFunction)
83
+ logger.error(newError)
84
84
  })
85
85
  }
86
86
  if (cause) withNoop(() => logger.error(cause))
@@ -22,6 +22,7 @@ class NoopSpan {
22
22
  setTag (key, value) { return this }
23
23
  addTags (keyValueMap) { return this }
24
24
  addLink (link) { return this }
25
+ addLinks (links) { return this }
25
26
  addSpanPointer (ptrKind, ptrDir, ptrHash) { return this }
26
27
  log () { return this }
27
28
  logEvent () {}
@@ -211,10 +211,21 @@ class Span {
211
211
  return this
212
212
  }
213
213
 
214
- addLink (context, attributes) {
215
- // extract dd context
214
+ addLink (link, attrs) {
215
+ // TODO: Remove this once we remove addLink(context, attrs) in v6.0.0
216
+ if (link instanceof SpanContext) {
217
+ link = { context: link, attributes: attrs ?? {} }
218
+ }
219
+
220
+ const { context, attributes } = link
221
+ // Extract dd context
216
222
  const ddSpanContext = context._ddContext
217
- this._ddSpan.addLink(ddSpanContext, attributes)
223
+ this._ddSpan.addLink({ context: ddSpanContext, attributes })
224
+ return this
225
+ }
226
+
227
+ addLinks (links) {
228
+ links.forEach(link => this.addLink(link))
218
229
  return this
219
230
  }
220
231
 
@@ -86,8 +86,10 @@ class DatadogSpan {
86
86
 
87
87
  this._startTime = fields.startTime || this._getTime()
88
88
 
89
- this._links = []
90
- fields.links && fields.links.forEach(link => this.addLink(link.context, link.attributes))
89
+ this._links = fields.links?.map(link => ({
90
+ context: link.context._ddContext ?? link.context,
91
+ attributes: this._sanitizeAttributes(link.attributes)
92
+ })) ?? []
91
93
 
92
94
  if (DD_TRACE_EXPERIMENTAL_SPAN_COUNTS && finishedRegistry) {
93
95
  runtimeMetrics.increment('runtime.node.spans.unfinished')
@@ -196,13 +198,25 @@ class DatadogSpan {
196
198
 
197
199
  logEvent () {}
198
200
 
199
- addLink (context, attributes) {
201
+ addLink (link, attrs) {
202
+ // TODO: Remove this once we remove addLink(context, attrs) in v6.0.0
203
+ if (link instanceof SpanContext) {
204
+ link = { context: link, attributes: attrs ?? {} }
205
+ }
206
+
207
+ const { context, attributes } = link
208
+
200
209
  this._links.push({
201
210
  context: context._ddContext ?? context,
202
211
  attributes: this._sanitizeAttributes(attributes)
203
212
  })
204
213
  }
205
214
 
215
+ addLinks (links) {
216
+ links.forEach(link => this.addLink(link))
217
+ return this
218
+ }
219
+
206
220
  addSpanPointer (ptrKind, ptrDir, ptrHash) {
207
221
  const zeroContext = new SpanContext({
208
222
  traceId: id('0'),
@@ -214,7 +228,7 @@ class DatadogSpan {
214
228
  'ptr.hash': ptrHash,
215
229
  'link.kind': 'span-pointer'
216
230
  }
217
- this.addLink(zeroContext, attributes)
231
+ this.addLink({ context: zeroContext, attributes })
218
232
  }
219
233
 
220
234
  addEvent (name, attributesOrStartTime, startTime) {
@@ -6,6 +6,15 @@ const plugins = require('./plugins')
6
6
  const log = require('./log')
7
7
  const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper')
8
8
 
9
+ // Test optimization plugins that should only be enabled when isCiVisibility is true
10
+ const TEST_OPTIMIZATION_PLUGINS = new Set([
11
+ 'jest',
12
+ 'vitest',
13
+ 'cucumber',
14
+ 'mocha',
15
+ 'playwright'
16
+ ])
17
+
9
18
  const loadChannel = channel('dd-trace:instrumentation:load')
10
19
 
11
20
  // instrument everything that needs Plugin System V2 instrumentation
@@ -74,6 +83,13 @@ module.exports = class PluginManager {
74
83
 
75
84
  if (!Plugin) return
76
85
  if (!this._tracerConfig) return // TODO: don't wait for tracer to be initialized
86
+
87
+ // Check if this is a Test Optimization plugin and Test Optimization is not enabled
88
+ if (TEST_OPTIMIZATION_PLUGINS.has(name) && !this._tracerConfig.isCiVisibility) {
89
+ log.debug('Plugin "%s" is not initialized because Test Optimization mode is not enabled.', name)
90
+ return
91
+ }
92
+
77
93
  if (!this._pluginsByName[name]) {
78
94
  this._pluginsByName[name] = new Plugin(this._tracer, this._tracerConfig)
79
95
  }
@@ -148,7 +164,8 @@ module.exports = class PluginManager {
148
164
  middlewareTracingEnabled,
149
165
  traceWebsocketMessagesEnabled,
150
166
  traceWebsocketMessagesInheritSampling,
151
- traceWebsocketMessagesSeparateTraces
167
+ traceWebsocketMessagesSeparateTraces,
168
+ experimental
152
169
  } = this._tracerConfig
153
170
 
154
171
  const sharedConfig = {
@@ -166,7 +183,8 @@ module.exports = class PluginManager {
166
183
  isServiceUserProvided,
167
184
  traceWebsocketMessagesEnabled,
168
185
  traceWebsocketMessagesInheritSampling,
169
- traceWebsocketMessagesSeparateTraces
186
+ traceWebsocketMessagesSeparateTraces,
187
+ experimental
170
188
  }
171
189
 
172
190
  if (logInjection !== undefined) {