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
@@ -10,16 +10,14 @@ const kinds = require('../../../../../ext/kinds')
10
10
  const { ERROR_MESSAGE } = require('../../constants')
11
11
  const TracingPlugin = require('../tracing')
12
12
  const { storage } = require('../../../../datadog-core')
13
+ const legacyStorage = storage('legacy')
13
14
  const urlFilter = require('./urlfilter')
14
15
  const { createInferredProxySpan, finishInferredProxySpan } = require('./inferred_proxy')
15
16
  const { extractURL, obfuscateQs, calculateHttpEndpoint } = require('./url')
16
17
 
17
- let extractIp
18
-
19
18
  const WEB = types.WEB
20
19
  const SERVER = kinds.SERVER
21
20
  const RESOURCE_NAME = tags.RESOURCE_NAME
22
- const SERVICE_NAME = tags.SERVICE_NAME
23
21
  const SPAN_TYPE = tags.SPAN_TYPE
24
22
  const SPAN_KIND = tags.SPAN_KIND
25
23
  const ERROR = tags.ERROR
@@ -35,7 +33,6 @@ const HTTP_CLIENT_IP = tags.HTTP_CLIENT_IP
35
33
  const MANUAL_DROP = tags.MANUAL_DROP
36
34
 
37
35
  const contexts = new WeakMap()
38
- const ends = new WeakMap()
39
36
 
40
37
  // TODO: change this to no longer rely on creating a dummy plugin to be able to access startSpan
41
38
  function createWebPlugin (tracer, config = {}) {
@@ -67,7 +64,9 @@ const web = {
67
64
  const middleware = getMiddlewareSetting(config)
68
65
  const queryStringObfuscation = getQsObfuscator(config)
69
66
 
70
- extractIp = config.clientIpEnabled && require('./ip_extractor').extractIp
67
+ const extractIp = config.clientIpEnabled
68
+ ? require('./ip_extractor').extractIp
69
+ : undefined
71
70
 
72
71
  return {
73
72
  ...config,
@@ -77,6 +76,7 @@ const web = {
77
76
  filter,
78
77
  middleware,
79
78
  queryStringObfuscation,
79
+ extractIp,
80
80
  }
81
81
  },
82
82
 
@@ -87,7 +87,7 @@ const web = {
87
87
  if (!span) return
88
88
 
89
89
  span.context()._name = `${name}.request`
90
- span.context()._tags.component = name
90
+ span.context().setTag('component', name)
91
91
  span._integrationName = name
92
92
 
93
93
  web.setConfig(req, config)
@@ -105,7 +105,7 @@ const web = {
105
105
  }
106
106
 
107
107
  if (config.service) {
108
- span.setTag(SERVICE_NAME, config.service)
108
+ web.plugin.setServiceName(span, config.service)
109
109
  }
110
110
 
111
111
  analyticsSampler.sample(span, config.measured, true)
@@ -126,7 +126,6 @@ const web = {
126
126
  context.tracer = tracer
127
127
  context.span = span
128
128
  context.res = res
129
- context.store = storage('legacy').getStore()
130
129
 
131
130
  this.setConfig(req, config)
132
131
  addRequestTags(context, this.TYPE)
@@ -204,7 +203,7 @@ const web = {
204
203
  startServerlessSpanWithInferredProxy (tracer, config, name, req, traceCtx) {
205
204
  const headers = req.headers
206
205
  const reqCtx = contexts.get(req)
207
- const store = storage('legacy').getStore()
206
+ const store = legacyStorage.getStore()
208
207
  const pubsubSpan = store?.span?._name === 'pubsub.push.receive' ? store.span : null
209
208
 
210
209
  let childOf = pubsubSpan || tracer.extract(FORMAT_HTTP_HEADERS, headers)
@@ -225,9 +224,11 @@ const web = {
225
224
  const context = contexts.get(req)
226
225
  const { span, inferredProxySpan, error } = context
227
226
 
228
- const spanHasExistingError = span.context()._tags.error || span.context()._tags[ERROR_MESSAGE]
227
+ const spanContext = span.context()
228
+ const spanHasExistingError = spanContext.getTag('error') || spanContext.getTag(ERROR_MESSAGE)
229
229
  const inferredSpanContext = inferredProxySpan?.context()
230
- const inferredSpanHasExistingError = inferredSpanContext?._tags.error || inferredSpanContext?._tags[ERROR_MESSAGE]
230
+ const inferredSpanHasExistingError = inferredSpanContext?.getTag('error') ||
231
+ inferredSpanContext?.getTag(ERROR_MESSAGE)
231
232
 
232
233
  const isValidStatusCode = context.config.validateStatus(statusCode)
233
234
 
@@ -266,7 +267,16 @@ const web = {
266
267
 
267
268
  if (context.finished && !req.stream) return
268
269
 
270
+ // `addRequestTags` is idempotent: in the normal HTTP path it ran during
271
+ // `web.startSpan`. Serverless callers (e.g. Azure Functions) skip
272
+ // `web.startSpan` and rely on this call to do the request-side work.
269
273
  addRequestTags(context, spanType)
274
+ // Configured-header tagging runs at finish time. Framework plugins
275
+ // (connect, express, ...) install their own config via `setFramework`
276
+ // after `web.startSpan` has already locked the http-plugin config in;
277
+ // tagging earlier would use the http-plugin's `headers` list and drop
278
+ // the framework's.
279
+ addRequestHeaders(context)
270
280
  addResponseTags(context)
271
281
 
272
282
  context.config.hooks.request(context.span, req, res)
@@ -293,11 +303,18 @@ const web = {
293
303
  const writeHead = res.writeHead
294
304
 
295
305
  return function (statusCode, statusMessage, headers) {
296
- headers = typeof statusMessage === 'string' ? headers : statusMessage
297
- headers = { ...res.getHeaders(), ...headers }
298
-
299
- if (req.method.toLowerCase() === 'options' && isOriginAllowed(req, headers)) {
300
- addAllowHeaders(req, res, headers)
306
+ // CORS preflight tagging only matters for OPTIONS requests. Skip the
307
+ // getHeaders() spread + isOriginAllowed work entirely for the common
308
+ // GET / POST / etc. case. Node's http module passes `req.method`
309
+ // through unchanged, so all standard methods are uppercase; the
310
+ // `toLowerCase` fallback covers any non-standard caller.
311
+ if (req.method === 'OPTIONS' || req.method.toLowerCase() === 'options') {
312
+ headers = typeof statusMessage === 'string' ? headers : statusMessage
313
+ headers = { ...res.getHeaders(), ...headers }
314
+
315
+ if (isOriginAllowed(req, headers)) {
316
+ addAllowHeaders(req, res, headers)
317
+ }
301
318
  }
302
319
 
303
320
  return writeHead.apply(this, arguments)
@@ -306,34 +323,6 @@ const web = {
306
323
  getContext (req) {
307
324
  return contexts.get(req)
308
325
  },
309
- wrapRes (context, req, res, end) {
310
- return function (...args) {
311
- web.finishAll(context)
312
-
313
- return end.apply(res, args)
314
- }
315
- },
316
- wrapEnd (context) {
317
- const req = context.req
318
- const res = context.res
319
- const end = res.end
320
-
321
- res.writeHead = web.wrapWriteHead(context)
322
-
323
- ends.set(res, this.wrapRes(context, req, res, end))
324
-
325
- Object.defineProperty(res, 'end', {
326
- configurable: true,
327
- get () {
328
- return ends.get(this)
329
- },
330
- set (value) {
331
- ends.set(this, function (...args) {
332
- return storage('legacy').run(context.store, value, ...args)
333
- })
334
- },
335
- })
336
- },
337
326
  setRouteOrEndpointTag (req) {
338
327
  const context = contexts.get(req)
339
328
 
@@ -379,6 +368,16 @@ function splitHeader (str) {
379
368
 
380
369
  function addRequestTags (context, spanType) {
381
370
  const { req, span, inferredProxySpan, config } = context
371
+ const spanContext = span.context()
372
+
373
+ // Idempotency guard. `addRequestTags` runs in `web.startSpan` for the
374
+ // normal HTTP path and again in `web.finishSpan`; without this guard the
375
+ // second call would re-extract the URL, re-obfuscate the query string,
376
+ // and re-publish five `tagsUpdateCh` events with the same values. The
377
+ // serverless path skips `startSpan` and lands here first, in which case
378
+ // HTTP_URL is unset and the work runs normally.
379
+ if (spanContext.hasTag(HTTP_URL)) return
380
+
382
381
  const url = extractURL(req)
383
382
  const type = spanType ?? WEB
384
383
 
@@ -391,8 +390,8 @@ function addRequestTags (context, spanType) {
391
390
  })
392
391
 
393
392
  // if client ip has already been set by appsec, no need to run it again
394
- if (extractIp && !span.context()._tags.hasOwnProperty(HTTP_CLIENT_IP)) {
395
- const clientIp = extractIp(config, req)
393
+ if (config.extractIp && !spanContext.hasTag(HTTP_CLIENT_IP)) {
394
+ const clientIp = config.extractIp(config, req)
396
395
 
397
396
  if (clientIp) {
398
397
  span.setTag(HTTP_CLIENT_IP, clientIp)
@@ -410,8 +409,6 @@ function addRequestTags (context, spanType) {
410
409
  if (securityTest !== undefined) {
411
410
  span.setTag(`${HTTP_REQUEST_HEADERS}.x-datadog-security-test`, securityTest)
412
411
  }
413
-
414
- addHeaders(context)
415
412
  }
416
413
 
417
414
  function addResponseTags (context) {
@@ -426,14 +423,26 @@ function addResponseTags (context) {
426
423
  [HTTP_STATUS_CODE]: res.statusCode,
427
424
  })
428
425
 
426
+ addResponseHeaders(context)
427
+
429
428
  web.addStatusError(req, res.statusCode)
430
429
  }
431
430
 
432
431
  function applyRouteOrEndpointTag (context) {
433
432
  const { paths, span, config } = context
434
433
  if (!span) return
435
- const tags = span.context()._tags
436
- const route = paths.join('')
434
+ const spanContext = span.context()
435
+
436
+ // AppSec calls `web.setRouteOrEndpointTag` from a pre-finish hook so the
437
+ // route/endpoint tags are available for API Security sampling, and the
438
+ // normal finish-time path runs this again. Either tag being present
439
+ // means the work has already been done; paths are stable between the
440
+ // two calls, so the second pass has nothing to add.
441
+ if (spanContext.hasTag(HTTP_ROUTE) || spanContext.hasTag(HTTP_ENDPOINT)) return
442
+
443
+ // Skip the `Array.prototype.join` builtin in the empty / single-segment
444
+ // cases; `paths[0]` covers both (`undefined` is falsy for the empty case).
445
+ const route = paths.length > 1 ? paths.join('') : paths[0]
437
446
 
438
447
  if (route) {
439
448
  // Use http.route from trusted framework instrumentation.
@@ -441,44 +450,49 @@ function applyRouteOrEndpointTag (context) {
441
450
  return
442
451
  }
443
452
 
444
- if (!config.resourceRenamingEnabled || tags[HTTP_ENDPOINT]) {
445
- return
446
- }
453
+ if (!config.resourceRenamingEnabled) return
447
454
 
448
455
  // Route is unavailable, compute http.endpoint once.
449
- const url = tags[HTTP_URL]
456
+ const url = spanContext.getTag(HTTP_URL)
450
457
  const endpoint = url ? calculateHttpEndpoint(url) : '/'
451
458
  span.setTag(HTTP_ENDPOINT, endpoint)
452
459
  }
453
460
 
454
461
  function addResourceTag (context) {
455
462
  const { req, span } = context
456
- const tags = span.context()._tags
463
+ const spanContext = span.context()
457
464
 
458
- if (tags[RESOURCE_NAME]) return
465
+ if (spanContext.getTag(RESOURCE_NAME)) return
459
466
 
460
- const resource = [req.method, tags[HTTP_ROUTE]]
467
+ const resource = [req.method, spanContext.getTag(HTTP_ROUTE)]
461
468
  .filter(Boolean)
462
469
  .join(' ')
463
470
 
464
471
  span.setTag(RESOURCE_NAME, resource)
465
472
  }
466
473
 
467
- function addHeaders (context) {
468
- const { req, res, config, span, inferredProxySpan } = context
474
+ function addRequestHeaders (context) {
475
+ const { req, config, span, inferredProxySpan } = context
469
476
 
470
477
  for (const [key, tag] of config.headers) {
471
478
  const reqHeader = req.headers[key]
472
- const resHeader = res.getHeader(key)
473
-
474
479
  if (reqHeader) {
475
- span.setTag(tag || `${HTTP_REQUEST_HEADERS}.${key}`, reqHeader)
476
- inferredProxySpan?.setTag(tag || `${HTTP_REQUEST_HEADERS}.${key}`, reqHeader)
480
+ const tagName = tag || `${HTTP_REQUEST_HEADERS}.${key}`
481
+ span.setTag(tagName, reqHeader)
482
+ inferredProxySpan?.setTag(tagName, reqHeader)
477
483
  }
484
+ }
485
+ }
486
+
487
+ function addResponseHeaders (context) {
488
+ const { res, config, span, inferredProxySpan } = context
478
489
 
490
+ for (const [key, tag] of config.headers) {
491
+ const resHeader = res.getHeader(key)
479
492
  if (resHeader) {
480
- span.setTag(tag || `${HTTP_RESPONSE_HEADERS}.${key}`, resHeader)
481
- inferredProxySpan?.setTag(tag || `${HTTP_RESPONSE_HEADERS}.${key}`, resHeader)
493
+ const tagName = tag || `${HTTP_RESPONSE_HEADERS}.${key}`
494
+ span.setTag(tagName, resHeader)
495
+ inferredProxySpan?.setTag(tagName, resHeader)
482
496
  }
483
497
  }
484
498
  }
@@ -125,7 +125,7 @@ class PrioritySampler {
125
125
 
126
126
  log.trace(span, auto)
127
127
 
128
- const tag = this._getPriorityFromTags(context._tags, context)
128
+ const tag = this._getPriorityFromTags(context.getTags(), context)
129
129
 
130
130
  if (this.validate(tag)) {
131
131
  context._sampling.priority = tag
@@ -300,7 +300,7 @@ class PrioritySampler {
300
300
  * @returns {SamplingPriority}
301
301
  */
302
302
  #getPriorityByAgent (context) {
303
- const key = `service:${context._tags[SERVICE_NAME]},env:${this._env}`
303
+ const key = `service:${context.getTag(SERVICE_NAME)},env:${this._env}`
304
304
  // TODO: Change underscored properties to private ones.
305
305
  const sampler = this._samplers[key] || this._samplers[DEFAULT_KEY]
306
306
 
@@ -5,6 +5,7 @@ const { pathToFileURL } = require('url')
5
5
 
6
6
  const satisfies = require('../../../../vendor/dist/semifies')
7
7
  const getGitMetadata = require('../git_metadata')
8
+ const log = require('../log')
8
9
  const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags')
9
10
  const { getIsAzureFunction } = require('../serverless')
10
11
  const { getAzureTagsFromMetadata, getAzureAppMetadata, getAzureFunctionMetadata } = require('../azure_metadata')
@@ -14,7 +15,6 @@ const { isACFActive } = require('../../../datadog-core/src/storage')
14
15
 
15
16
  const { AgentExporter } = require('./exporters/agent')
16
17
  const { FileExporter } = require('./exporters/file')
17
- const { ConsoleLogger } = require('./loggers/console')
18
18
  const WallProfiler = require('./profilers/wall')
19
19
  const SpaceProfiler = require('./profilers/space')
20
20
  const EventsProfiler = require('./profilers/events')
@@ -54,7 +54,6 @@ class Config {
54
54
  this.pprofPrefix = options.DD_PROFILING_PPROF_PREFIX
55
55
  this.v8ProfilerBugWorkaroundEnabled = options.DD_PROFILING_V8_PROFILER_BUG_WORKAROUND
56
56
 
57
- this.logger = ensureLogger(options.logger)
58
57
  this.url = getAgentUrl(options)
59
58
 
60
59
  this.libraryInjected = !!options.DD_INJECTION_ENABLED
@@ -73,7 +72,7 @@ class Config {
73
72
  const heapLimitExtensionSize = options.DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE
74
73
  const maxHeapExtensionCount = options.DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT
75
74
  const exportStrategies = oomMonitoringEnabled
76
- ? ensureOOMExportStrategies(options.DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES, this)
75
+ ? ensureOOMExportStrategies(options.DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES)
77
76
  : []
78
77
  const exportCommand = oomMonitoringEnabled ? buildExportCommand(this) : undefined
79
78
  this.oomMonitoring = {
@@ -102,7 +101,7 @@ class Config {
102
101
  if (level !== undefined) {
103
102
  const maxLevel = { gzip: 9, zstd: 22 }[uploadCompression]
104
103
  if (level > maxLevel) {
105
- this.logger.warn(`Invalid compression level ${level}. Will use ${maxLevel}.`)
104
+ log.warn('Invalid compression level %d. Will use %d.', level, maxLevel)
106
105
  level = maxLevel
107
106
  }
108
107
  }
@@ -119,8 +118,7 @@ class Config {
119
118
 
120
119
  const that = this
121
120
  function turnOffAsyncContextFrame (msg) {
122
- that.logger.warn(
123
- `DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED was set ${msg}, it will have no effect.`)
121
+ log.warn('DD_PROFILING_ASYNC_CONTEXT_FRAME_ENABLED was set %s, it will have no effect.', msg)
124
122
  that.asyncContextFrameEnabled = false
125
123
  }
126
124
 
@@ -215,18 +213,18 @@ function getProfilers ({
215
213
  return profilersArray
216
214
  }
217
215
 
218
- function getExportStrategy (name, options) {
216
+ function getExportStrategy (name) {
219
217
  const strategy = Object.values(oomExportStrategies).find(value => value === name)
220
218
  if (strategy === undefined) {
221
- options.logger.error(`Unknown oom export strategy "${name}"`)
219
+ log.error('Unknown oom export strategy "%s"', name)
222
220
  }
223
221
  return strategy
224
222
  }
225
223
 
226
- function ensureOOMExportStrategies (strategies, options) {
224
+ function ensureOOMExportStrategies (strategies) {
227
225
  const set = new Set()
228
226
  for (const strategy of strategies) {
229
- set.add(getExportStrategy(strategy, options))
227
+ set.add(getExportStrategy(strategy))
230
228
  }
231
229
 
232
230
  return [...set]
@@ -239,7 +237,7 @@ function getExporter (name, options) {
239
237
  case 'file':
240
238
  return new FileExporter(options)
241
239
  default:
242
- options.logger.error(`Unknown exporter "${name}"`)
240
+ log.error('Unknown exporter "%s"', name)
243
241
  }
244
242
  }
245
243
 
@@ -255,7 +253,7 @@ function getProfiler (name, options) {
255
253
  case 'space':
256
254
  return new SpaceProfiler(options)
257
255
  default:
258
- options.logger.error(`Unknown profiler "${name}"`)
256
+ log.error('Unknown profiler "%s"', name)
259
257
  }
260
258
  }
261
259
 
@@ -278,17 +276,6 @@ function ensureProfilers (profilers, options) {
278
276
  return filteredProfilers
279
277
  }
280
278
 
281
- function ensureLogger (logger) {
282
- if (typeof logger?.debug !== 'function' ||
283
- typeof logger.info !== 'function' ||
284
- typeof logger.warn !== 'function' ||
285
- typeof logger.error !== 'function') {
286
- return new ConsoleLogger()
287
- }
288
-
289
- return logger
290
- }
291
-
292
279
  function buildExportCommand (options) {
293
280
  const tags = [...Object.entries(options.tags),
294
281
  ['snapshot', snapshotKinds.ON_OUT_OF_MEMORY]].map(([key, value]) => `${key}:${value}`).join(',')
@@ -9,6 +9,7 @@ const retry = require('../../../../../vendor/dist/retry')
9
9
  // TODO: avoid using dd-trace internals. Make this a separate module?
10
10
  const docker = require('../../exporters/common/docker')
11
11
  const FormData = require('../../exporters/common/form-data')
12
+ const log = require('../../log')
12
13
  const { storage } = require('../../../../datadog-core')
13
14
  const version = require('../../../../../package.json').version
14
15
  const telemetryMetrics = require('../../telemetry/metrics')
@@ -89,9 +90,8 @@ function computeRetries (uploadTimeout) {
89
90
  class AgentExporter extends EventSerializer {
90
91
  constructor (config = {}) {
91
92
  super(config)
92
- const { url, logger, uploadTimeout } = config
93
+ const { url, uploadTimeout } = config
93
94
  this._url = url
94
- this._logger = logger
95
95
 
96
96
  const [backoffTries, backoffTime] = computeRetries(uploadTimeout)
97
97
 
@@ -109,12 +109,11 @@ class AgentExporter extends EventSerializer {
109
109
  contentType: 'application/json',
110
110
  }])
111
111
 
112
- this._logger.debug(() => {
113
- return `Building agent export report:\n${event}`
114
- })
112
+ log.debug('Building agent export report:\n%s', event)
115
113
 
116
114
  for (const [type, buffer] of Object.entries(profiles)) {
117
- this._logger.debug(() => {
115
+ // eslint-disable-next-line eslint-rules/eslint-log-printf-style
116
+ log.debug(() => {
118
117
  const bytes = buffer.toString('hex').match(/../g).join(' ')
119
118
  return `Adding ${type} profile to agent export: ` + bytes
120
119
  })
@@ -163,7 +162,8 @@ class AgentExporter extends EventSerializer {
163
162
  options.port = httpOptions.port
164
163
  }
165
164
 
166
- this._logger.debug(() => {
165
+ // eslint-disable-next-line eslint-rules/eslint-log-printf-style
166
+ log.debug(() => {
167
167
  return `Submitting profiler agent report attempt #${attempt} to: ${JSON.stringify(options)}`
168
168
  })
169
169
 
@@ -171,7 +171,7 @@ class AgentExporter extends EventSerializer {
171
171
  if (err) {
172
172
  const { status } = err
173
173
  if ((typeof status !== 'number' || status >= 500 || status === 429) && operation.retry(err)) {
174
- this._logger.warn(`Error from the agent: ${err.message}`)
174
+ log.warn('Error from the agent: %s', err.message)
175
175
  } else {
176
176
  reject(err)
177
177
  }
@@ -180,9 +180,10 @@ class AgentExporter extends EventSerializer {
180
180
 
181
181
  getBody(response, (err, body) => {
182
182
  if (err) {
183
- this._logger.warn(`Error reading agent response: ${err.message}`)
183
+ log.warn('Error reading agent response: %s', err.message)
184
184
  } else {
185
- this._logger.debug(() => {
185
+ // eslint-disable-next-line eslint-rules/eslint-log-printf-style
186
+ log.debug(() => {
186
187
  const bytes = (body.toString('hex').match(/../g) || []).join(' ')
187
188
  return `Agent export response: ${bytes}`
188
189
  })
@@ -17,7 +17,7 @@ function findWebSpan (startedSpans, spanId) {
17
17
  const ispan = startedSpans[i]
18
18
  const context = ispan.context()
19
19
  if (context._spanId === spanId) {
20
- if (isWebServerSpan(context._tags)) {
20
+ if (isWebServerSpan(context.getTags())) {
21
21
  return true
22
22
  }
23
23
  spanId = context._parentId
@@ -40,6 +40,16 @@ function processInfo (infos, info, type) {
40
40
  }
41
41
  }
42
42
 
43
+ // Route pprof through the central log module so logLevel applies.
44
+ const pprofLogger = {
45
+ trace: (...args) => log.trace(...args),
46
+ debug: (...args) => log.debug(...args),
47
+ info: (...args) => log.info(...args),
48
+ warn: (...args) => log.warn(...args),
49
+ error: (...args) => log.error(...args),
50
+ fatal: (...args) => log.error(...args),
51
+ }
52
+
43
53
  class Profiler extends EventEmitter {
44
54
  #compressionFn
45
55
  #compressionFnInitialized = false
@@ -49,7 +59,6 @@ class Profiler extends EventEmitter {
49
59
  #enabled = false
50
60
  #endpointCounts = new Map()
51
61
  #lastStart
52
- #logger
53
62
  #profileSeq = 0
54
63
  #spanFinishListener
55
64
  #timer
@@ -159,14 +168,13 @@ class Profiler extends EventEmitter {
159
168
  this.#enabled = true
160
169
 
161
170
  const config = this.#config = new Config(options)
162
- this.#logger = config.logger
163
171
 
164
172
  this._setInterval()
165
173
  // Log errors if the source map finder fails, but don't prevent the rest
166
174
  // of the profiler from running without source maps.
167
175
  let mapper
168
176
  const { setLogger, SourceMapper } = require('@datadog/pprof')
169
- setLogger(config.logger)
177
+ setLogger(pprofLogger)
170
178
 
171
179
  if (config.sourceMap) {
172
180
  mapper = new SourceMapper(config.debugSourceMaps)
@@ -174,7 +182,8 @@ class Profiler extends EventEmitter {
174
182
  .then(() => {
175
183
  if (config.debugSourceMaps) {
176
184
  const count = mapper.infoMap.size
177
- this.#logger.debug(() => {
185
+ // eslint-disable-next-line eslint-rules/eslint-log-printf-style
186
+ log.debug(() => {
178
187
  return count === 0
179
188
  ? 'Found no source maps'
180
189
  : `Found source maps for following files: [${[...mapper.infoMap.keys()].join(', ')}]`
@@ -195,7 +204,7 @@ class Profiler extends EventEmitter {
195
204
  mapper,
196
205
  nearOOMCallback,
197
206
  })
198
- this.#logger.debug(`Started ${profiler.type} profiler in ${threadNamePrefix} thread`)
207
+ log.debug('Started %s profiler in %s thread', profiler.type, threadNamePrefix)
199
208
  }
200
209
 
201
210
  if (config.endpointCollectionEnabled) {
@@ -248,7 +257,7 @@ class Profiler extends EventEmitter {
248
257
 
249
258
  for (const profiler of this.#config.profilers) {
250
259
  profiler.stop()
251
- this.#logger.debug(`Stopped ${profiler.type} profiler in ${threadNamePrefix} thread`)
260
+ log.debug('Stopped %s profiler in %s thread', profiler.type, threadNamePrefix)
252
261
  }
253
262
 
254
263
  clearTimeout(this.#timer)
@@ -268,7 +277,7 @@ class Profiler extends EventEmitter {
268
277
 
269
278
  #onSpanFinish (span) {
270
279
  const context = span.context()
271
- const tags = context._tags
280
+ const tags = context.getTags()
272
281
  if (!isWebServerSpan(tags)) return
273
282
 
274
283
  const endpointName = endpointNameFromTags(tags)
@@ -312,7 +321,7 @@ class Profiler extends EventEmitter {
312
321
  const info = profiler.getInfo()
313
322
  const profile = profiler.profile(restart, startDate, endDate)
314
323
  if (!restart) {
315
- this.#logger.debug(`Stopped ${profiler.type} profiler in ${threadNamePrefix} thread`)
324
+ log.debug('Stopped %s profiler in %s thread', profiler.type, threadNamePrefix)
316
325
  }
317
326
  if (!profile) continue
318
327
  profiles.push({ profiler, profile, info })
@@ -341,7 +350,8 @@ class Profiler extends EventEmitter {
341
350
  infos.hasMissingSourceMaps = true
342
351
  }
343
352
  processInfo(infos, info, profiler.type)
344
- this.#logger.debug(() => {
353
+ // eslint-disable-next-line eslint-rules/eslint-log-printf-style
354
+ log.debug(() => {
345
355
  const profileJson = JSON.stringify(profile, (_, value) => {
346
356
  return typeof value === 'bigint' ? value.toString() : value
347
357
  })
@@ -358,7 +368,7 @@ class Profiler extends EventEmitter {
358
368
  if (hasEncoded) {
359
369
  await this.#submit(encodedProfiles, infos, startDate, endDate, snapshotKind)
360
370
  profileSubmittedChannel.publish()
361
- this.#logger.debug('Submitted profiles')
371
+ log.debug('Submitted profiles')
362
372
  }
363
373
  } catch (error) {
364
374
  log.error(error)
@@ -3,6 +3,7 @@
3
3
  const dc = require('dc-polyfill')
4
4
 
5
5
  const { storage } = require('../../../../datadog-core')
6
+ const log = require('../../log')
6
7
  const runtimeMetrics = require('../../runtime_metrics')
7
8
  const telemetryMetrics = require('../../telemetry/metrics')
8
9
  const { isWebServerSpan, endpointNameFromTags, getStartedSpans } = require('../webspan-utils')
@@ -112,7 +113,6 @@ class NativeWallProfiler {
112
113
  #customLabelKeys
113
114
  #endpointCollectionEnabled = false
114
115
  #flushIntervalMillis = 0
115
- #logger
116
116
  #mapper
117
117
  #pprof
118
118
  #samplingIntervalMicros = 0
@@ -136,7 +136,6 @@ class NativeWallProfiler {
136
136
  this.#cpuProfilingEnabled = !!options.cpuProfilingEnabled
137
137
  this.#endpointCollectionEnabled = !!options.endpointCollectionEnabled
138
138
  this.#flushIntervalMillis = options.flushInterval || 60 * 1e3 // 60 seconds
139
- this.#logger = options.logger
140
139
  // TODO: Remove default value. It is only used in testing.
141
140
  this.#samplingIntervalMicros = (options.samplingInterval || 1e3 / 99) * 1000
142
141
  this.#telemetryHeartbeatIntervalMillis = options.heartbeatInterval || 60 * 1e3 // 60 seconds
@@ -247,6 +246,7 @@ class NativeWallProfiler {
247
246
  // context -- we simply can't tell which one it might've been across all
248
247
  // possible async context frames.
249
248
  if (this.#asyncContextFrameEnabled) {
249
+ const current = this.#pprof.time.getContext()
250
250
  if (this.#customLabelsActive) {
251
251
  // Custom labels may be active in this async context. The current CPED
252
252
  // context could be a 2-element array [profilingContext, customLabels].
@@ -254,7 +254,6 @@ class NativeWallProfiler {
254
254
  // This flag is monotonic (once set, stays true) because async
255
255
  // continuations from runWithLabels can fire at any time after the
256
256
  // synchronous runWithLabels call has returned.
257
- const current = this.#pprof.time.getContext()
258
257
  if (Array.isArray(current)) {
259
258
  if (current[0] !== sampleContext) {
260
259
  this.#pprof.time.setContext([sampleContext, current[1]])
@@ -262,7 +261,13 @@ class NativeWallProfiler {
262
261
  } else if (current !== sampleContext) {
263
262
  this.#pprof.time.setContext(sampleContext)
264
263
  }
265
- } else {
264
+ // Every setContext() call in ACF mode allocates a fresh contextHolder
265
+ // (a node::ObjectWrap with its own v8::Global<v8::Value>) in the native
266
+ // profiler. Skip the call if the CPED already holds this sampleContext,
267
+ // which is the common case when the same span is repeatedly activated:
268
+ // #getProfilingContext caches profilingContext on span[ProfilingContext],
269
+ // so identity comparison short-circuits.
270
+ } else if (current !== sampleContext) {
266
271
  this.#pprof.time.setContext(sampleContext)
267
272
  }
268
273
  } else {
@@ -294,7 +299,7 @@ class NativeWallProfiler {
294
299
 
295
300
  let webTags
296
301
  if (this.#endpointCollectionEnabled) {
297
- const tags = context._tags
302
+ const tags = context.getTags()
298
303
  if (isWebServerSpan(tags)) {
299
304
  webTags = tags
300
305
  } else {
@@ -333,7 +338,7 @@ class NativeWallProfiler {
333
338
  if (!this.#started) return
334
339
  const profilingContext = span[ProfilingContext]
335
340
  if (profilingContext === undefined || profilingContext.webTags !== undefined) return
336
- const tags = span.context()._tags
341
+ const tags = span.context().getTags()
337
342
  if (isWebServerSpan(tags)) {
338
343
  profilingContext.webTags = tags
339
344
  }
@@ -342,7 +347,7 @@ class NativeWallProfiler {
342
347
  #reportV8bug (maybeBug) {
343
348
  const tag = `v8_profiler_bug_workaround_enabled:${this.#v8ProfilerBugWorkaroundEnabled}`
344
349
  const metric = `v8_cpu_profiler${maybeBug ? '_maybe' : ''}_stuck_event_loop`
345
- this.#logger?.warn(`Wall profiler: ${maybeBug ? 'possible ' : ''}v8 profiler stuck event loop detected.`)
350
+ log.warn('Wall profiler: %sv8 profiler stuck event loop detected.', maybeBug ? 'possible ' : '')
346
351
  // report as runtime metric (can be removed in the future when telemetry is mature)
347
352
  runtimeMetrics.increment(`runtime.node.profiler.${metric}`, tag, true)
348
353
  // report as telemetry metric