dd-trace 6.13.0 → 6.14.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 (201) hide show
  1. package/LICENSE-3rdparty.csv +3 -1
  2. package/ci/init.js +8 -3
  3. package/ci/test-optimization-validation/ci-package-scripts.js +2 -2
  4. package/ci/test-optimization-validation/ci-remediation.js +6 -6
  5. package/ci/test-optimization-validation/command-blocker.js +7 -5
  6. package/ci/test-optimization-validation/framework-adapters/cucumber.js +1 -1
  7. package/ci/test-optimization-validation/framework-adapters/vitest.js +3 -3
  8. package/ci/test-optimization-validation/package-check.js +1 -1
  9. package/ci/test-optimization-validation/report-writer.js +7 -5
  10. package/ci/vitest-no-worker-init-runner-setup.mjs +8 -0
  11. package/ci/vitest-no-worker-init-setup.mjs +13 -13
  12. package/ci/vitest-no-worker-init-v5-setup.mjs +4 -0
  13. package/package.json +13 -10
  14. package/packages/datadog-esbuild/index.js +3 -1
  15. package/packages/datadog-instrumentations/src/cypress-config.js +19 -17
  16. package/packages/datadog-instrumentations/src/fs.js +24 -20
  17. package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +2 -2
  18. package/packages/datadog-instrumentations/src/graphql.js +16 -16
  19. package/packages/datadog-instrumentations/src/helpers/graphql-jit-runtime.js +504 -0
  20. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  21. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +265 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +1 -2
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +95 -30
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +0 -25
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +83 -0
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +85 -1
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +30 -12
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +13 -0
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +251 -0
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +11 -1
  32. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +562 -27
  33. package/packages/datadog-instrumentations/src/jest.js +25 -20
  34. package/packages/datadog-instrumentations/src/knex.js +76 -0
  35. package/packages/datadog-instrumentations/src/mariadb.js +6 -0
  36. package/packages/datadog-instrumentations/src/mocha/common.js +59 -2
  37. package/packages/datadog-instrumentations/src/mocha/main.js +25 -15
  38. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
  39. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +98 -0
  40. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -8
  41. package/packages/datadog-instrumentations/src/net.js +14 -17
  42. package/packages/datadog-instrumentations/src/next.js +212 -62
  43. package/packages/datadog-instrumentations/src/openai.js +42 -40
  44. package/packages/datadog-instrumentations/src/pg.js +15 -0
  45. package/packages/datadog-instrumentations/src/playwright.js +109 -13
  46. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +40 -0
  47. package/packages/datadog-instrumentations/src/sequelize.js +94 -2
  48. package/packages/datadog-instrumentations/src/undici.js +53 -4
  49. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +37 -3
  50. package/packages/datadog-instrumentations/src/vitest-main.js +92 -9
  51. package/packages/datadog-instrumentations/src/vitest-worker.js +71 -14
  52. package/packages/datadog-instrumentations/src/webdriverio.js +965 -6
  53. package/packages/datadog-plugin-amqp10/src/consumer.js +3 -1
  54. package/packages/datadog-plugin-amqp10/src/producer.js +3 -1
  55. package/packages/datadog-plugin-amqp10/src/util.js +2 -1
  56. package/packages/datadog-plugin-avsc/src/schema_iterator.js +6 -1
  57. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +16 -5
  58. package/packages/datadog-plugin-cassandra-driver/src/index.js +8 -3
  59. package/packages/datadog-plugin-cucumber/src/index.js +0 -2
  60. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +262 -48
  61. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -1
  62. package/packages/datadog-plugin-graphql/src/execute.js +998 -196
  63. package/packages/datadog-plugin-graphql/src/index.js +27 -7
  64. package/packages/datadog-plugin-graphql/src/jit.js +136 -0
  65. package/packages/datadog-plugin-graphql/src/request.js +18 -27
  66. package/packages/datadog-plugin-graphql/src/resolve-error.js +108 -0
  67. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -2
  68. package/packages/datadog-plugin-graphql/src/tools/transforms.js +45 -3
  69. package/packages/datadog-plugin-graphql/src/utils.js +208 -120
  70. package/packages/datadog-plugin-graphql/src/validate.js +10 -23
  71. package/packages/datadog-plugin-jest/src/index.js +0 -6
  72. package/packages/datadog-plugin-langchain/src/tracing.js +7 -0
  73. package/packages/datadog-plugin-mocha/src/index.js +403 -75
  74. package/packages/datadog-plugin-mongodb-core/src/query.js +6 -0
  75. package/packages/datadog-plugin-mysql/src/index.js +41 -15
  76. package/packages/datadog-plugin-net/src/tcp.js +3 -1
  77. package/packages/datadog-plugin-next/src/index.js +63 -29
  78. package/packages/datadog-plugin-playwright/src/index.js +167 -27
  79. package/packages/datadog-plugin-undici/src/index.js +27 -41
  80. package/packages/datadog-plugin-vitest/src/index.js +6 -12
  81. package/packages/dd-trace/src/aiguard/evaluation.js +2 -1
  82. package/packages/dd-trace/src/aiguard/messages/anthropic.js +8 -1
  83. package/packages/dd-trace/src/aiguard/messages/openai.js +8 -1
  84. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +20 -6
  85. package/packages/dd-trace/src/appsec/api_security/index.js +23 -6
  86. package/packages/dd-trace/src/appsec/api_security/sampler.js +99 -40
  87. package/packages/dd-trace/src/appsec/graphql.js +6 -2
  88. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +3 -3
  89. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -1
  90. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +8 -2
  91. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +11 -36
  92. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +1 -1
  93. package/packages/dd-trace/src/appsec/lambda.js +44 -26
  94. package/packages/dd-trace/src/ci-visibility/encode/json-encoder.js +32 -2
  95. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +8 -8
  96. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +6 -4
  97. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +20 -4
  98. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +5 -173
  99. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +7 -6
  100. package/packages/dd-trace/src/ci-visibility/exporters/agents.js +54 -0
  101. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +1 -2
  102. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +394 -225
  103. package/packages/dd-trace/src/ci-visibility/exporters/limits.js +9 -0
  104. package/packages/dd-trace/src/ci-visibility/exporters/request.js +323 -31
  105. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +42 -0
  106. package/packages/dd-trace/src/ci-visibility/final-flush.js +2 -1
  107. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +57 -18
  108. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +0 -3
  109. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +164 -71
  110. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +176 -0
  111. package/packages/dd-trace/src/ci-visibility/telemetry.js +10 -7
  112. package/packages/dd-trace/src/ci-visibility/test-video.js +61 -0
  113. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  114. package/packages/dd-trace/src/config/index.js +73 -13
  115. package/packages/dd-trace/src/config/parsers.js +11 -1
  116. package/packages/dd-trace/src/config/stable.js +3 -3
  117. package/packages/dd-trace/src/config/supported-configurations.json +20 -2
  118. package/packages/dd-trace/src/crashtracking/crashtracker.js +82 -18
  119. package/packages/dd-trace/src/debugger/config.js +12 -1
  120. package/packages/dd-trace/src/debugger/constants.js +1 -0
  121. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -1
  122. package/packages/dd-trace/src/debugger/devtools_client/request-options.js +33 -0
  123. package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -40
  124. package/packages/dd-trace/src/debugger/devtools_client/status.js +11 -6
  125. package/packages/dd-trace/src/debugger/devtools_client/tags.js +41 -0
  126. package/packages/dd-trace/src/debugger/index.js +23 -3
  127. package/packages/dd-trace/src/dogstatsd.js +510 -76
  128. package/packages/dd-trace/src/encode/0.4-cross-payload.js +170 -0
  129. package/packages/dd-trace/src/encode/0.4.js +5 -5
  130. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -8
  131. package/packages/dd-trace/src/evp_proxy/direct.js +27 -23
  132. package/packages/dd-trace/src/exporters/agent/index.js +2 -1
  133. package/packages/dd-trace/src/exporters/agent/writer.js +24 -7
  134. package/packages/dd-trace/src/exporters/agentless/index.js +7 -9
  135. package/packages/dd-trace/src/exporters/agentless/writer.js +100 -118
  136. package/packages/dd-trace/src/exporters/common/agents.js +28 -6
  137. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -2
  138. package/packages/dd-trace/src/exporters/common/final-flush-request-tracker.js +191 -0
  139. package/packages/dd-trace/src/exporters/common/request.js +31 -11
  140. package/packages/dd-trace/src/exporters/common/url.js +40 -1
  141. package/packages/dd-trace/src/exporters/common/writer.js +9 -7
  142. package/packages/dd-trace/src/flush.js +6 -3
  143. package/packages/dd-trace/src/id.js +14 -0
  144. package/packages/dd-trace/src/index.js +5 -2
  145. package/packages/dd-trace/src/llmobs/experiments/dataset.js +2 -1
  146. package/packages/dd-trace/src/llmobs/index.js +32 -32
  147. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +22 -71
  148. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +23 -13
  149. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +7 -0
  150. package/packages/dd-trace/src/llmobs/util.js +0 -33
  151. package/packages/dd-trace/src/llmobs/writers/base.js +49 -32
  152. package/packages/dd-trace/src/log/index.js +7 -1
  153. package/packages/dd-trace/src/{ci-visibility/log-submission → log-submission}/log-submission-plugin.js +48 -19
  154. package/packages/dd-trace/src/openfeature/writers/base.js +14 -12
  155. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  156. package/packages/dd-trace/src/opentelemetry/logs/index.js +5 -24
  157. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +14 -4
  158. package/packages/dd-trace/src/opentelemetry/metrics/index.js +15 -22
  159. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +14 -4
  160. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +10 -0
  161. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +19 -4
  162. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +55 -0
  163. package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
  164. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +10 -0
  165. package/packages/dd-trace/src/opentracing/propagation/text_map.js +93 -18
  166. package/packages/dd-trace/src/payload-tagging/index.js +2 -1
  167. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -6
  168. package/packages/dd-trace/src/plugins/tracing.js +12 -10
  169. package/packages/dd-trace/src/plugins/util/ci.js +3 -1
  170. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +16 -15
  171. package/packages/dd-trace/src/plugins/util/test.js +83 -20
  172. package/packages/dd-trace/src/plugins/util/url.js +2 -2
  173. package/packages/dd-trace/src/plugins/util/web.js +4 -9
  174. package/packages/dd-trace/src/profiling/config.js +1 -0
  175. package/packages/dd-trace/src/profiling/oom.js +7 -2
  176. package/packages/dd-trace/src/profiling/profiler.js +23 -13
  177. package/packages/dd-trace/src/profiling/profilers/space.js +38 -11
  178. package/packages/dd-trace/src/profiling/webspan-utils.js +7 -4
  179. package/packages/dd-trace/src/proxy.js +75 -5
  180. package/packages/dd-trace/src/remote_config/index.js +319 -101
  181. package/packages/dd-trace/src/remote_config/scheduler.js +37 -12
  182. package/packages/dd-trace/src/runtime_metrics/client.js +37 -8
  183. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -1
  184. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +5 -1
  185. package/packages/dd-trace/src/sampling_rule.js +36 -9
  186. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +4 -8
  187. package/packages/dd-trace/src/serverless/vercel.js +53 -20
  188. package/packages/dd-trace/src/serverless.js +10 -3
  189. package/packages/dd-trace/src/service-naming/schemas/util.js +1 -1
  190. package/packages/dd-trace/src/span_stats.js +24 -28
  191. package/packages/dd-trace/src/startup-log.js +3 -2
  192. package/packages/dd-trace/src/telemetry/agentless-url.js +28 -0
  193. package/packages/dd-trace/src/telemetry/send-data.js +18 -16
  194. package/packages/dd-trace/src/telemetry/session-propagation.js +3 -2
  195. package/packages/dd-trace/src/tracer.js +4 -5
  196. package/packages/dd-trace/src/tracer_metadata.js +11 -3
  197. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  198. package/vendor/dist/protobufjs/index.js +1 -1
  199. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  200. package/packages/datadog-instrumentations/src/playwright-browser-scripts.js +0 -27
  201. package/packages/dd-trace/src/encode/agentless-json.js +0 -210
@@ -85,6 +85,18 @@ const percentByte = /%([0-9A-Fa-f]{2})/g
85
85
  * @property {string} [traceId]
86
86
  */
87
87
 
88
+ /**
89
+ * @param {Array<string | undefined>} traceTagReplacements
90
+ * @param {string} key
91
+ * @returns {boolean}
92
+ */
93
+ function hasTraceTagReplacement (traceTagReplacements, key) {
94
+ for (let index = 0; index < traceTagReplacements.length; index += 2) {
95
+ if (traceTagReplacements[index] === key) return true
96
+ }
97
+ return false
98
+ }
99
+
88
100
  /**
89
101
  * @param {string | undefined} traceId
90
102
  * @param {string | undefined} spanId
@@ -314,18 +326,31 @@ class TextMapPropagator {
314
326
 
315
327
  const injectTraceContext = this.#config.apmTracingEnabled !== false ||
316
328
  hasTraceSourcePropagationTag(spanContext._trace.tags)
329
+ let traceTagReplacements
330
+ let optionalTraceTagCount = 0
331
+ if (injectTraceContext && injectCh.hasSubscribers && (
332
+ this.#hasPropagationStyle('inject', 'datadog') || this.#hasPropagationStyle('inject', 'tracecontext')
333
+ )) {
334
+ const injection = { spanContext }
335
+ injectCh.publish(injection)
336
+ traceTagReplacements = injection.traceTagReplacements
337
+ optionalTraceTagCount = injection.optionalTraceTagCount ?? 0
338
+ }
317
339
  if (injectTraceContext) {
318
- injectedCarrier = this.#injectDatadog(spanContext, injectedCarrier ?? carrier) ?? injectedCarrier
340
+ injectedCarrier = this.#injectDatadog(
341
+ spanContext, injectedCarrier ?? carrier, traceTagReplacements, optionalTraceTagCount
342
+ ) ?? injectedCarrier
319
343
  injectedCarrier = this.#injectB3MultipleHeaders(spanContext, injectedCarrier ?? carrier) ?? injectedCarrier
320
344
  injectedCarrier = this.#injectB3SingleHeader(spanContext, injectedCarrier ?? carrier) ?? injectedCarrier
321
345
  }
322
346
  injectedCarrier = this
323
- .#injectTraceparent(spanContext, injectedCarrier ?? carrier, injectTraceContext) ?? injectedCarrier
347
+ .#injectTraceparent(
348
+ spanContext, injectedCarrier ?? carrier, injectTraceContext, traceTagReplacements
349
+ ) ?? injectedCarrier
324
350
 
325
351
  if (injectedCarrier === undefined) return
326
352
 
327
353
  carrier = injectedCarrier
328
- if (injectCh.hasSubscribers) injectCh.publish({ spanContext, carrier })
329
354
 
330
355
  // eslint-disable-next-line eslint-rules/eslint-log-printf-style
331
356
  log.debug(() => `Inject into carrier: ${JSON.stringify(pickTextMap(carrier))}.`)
@@ -359,9 +384,11 @@ class TextMapPropagator {
359
384
  /**
360
385
  * @param {DatadogSpanContext} spanContext
361
386
  * @param {Record<string, string>} [carrier]
387
+ * @param {Array<string | undefined>} [traceTagReplacements]
388
+ * @param {number} optionalTraceTagCount
362
389
  * @returns {Record<string, string> | undefined}
363
390
  */
364
- #injectDatadog (spanContext, carrier) {
391
+ #injectDatadog (spanContext, carrier, traceTagReplacements, optionalTraceTagCount) {
365
392
  if (!this.#hasPropagationStyle('inject', 'datadog')) return
366
393
 
367
394
  carrier ??= {}
@@ -374,7 +401,7 @@ class TextMapPropagator {
374
401
  const priority = spanContext._sampling.priority
375
402
  if (Number.isInteger(priority)) writeDatadogSamplingPriority(carrier, priority.toString())
376
403
 
377
- this.#injectTags(spanContext, carrier)
404
+ this.#injectTags(carrier, spanContext._trace.tags, traceTagReplacements, optionalTraceTagCount)
378
405
 
379
406
  return carrier
380
407
  }
@@ -449,23 +476,27 @@ class TextMapPropagator {
449
476
  }
450
477
 
451
478
  /**
452
- * @param {DatadogSpanContext} spanContext
453
479
  * @param {Record<string, string>} carrier
480
+ * @param {Record<string, string>} traceTags
481
+ * @param {Array<string | undefined>} [traceTagReplacements]
482
+ * @param {number} optionalTraceTagCount
454
483
  * @returns {void}
455
484
  */
456
- #injectTags (spanContext, carrier) {
457
- const trace = spanContext._trace
458
-
485
+ #injectTags (carrier, traceTags, traceTagReplacements, optionalTraceTagCount) {
459
486
  if (this.#config.DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH === 0) {
460
487
  log.debug('Trace tag propagation is disabled, skipping injection.')
461
488
  return
462
489
  }
463
490
 
491
+ const maxLength = this.#config.DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH
464
492
  let header = ''
465
493
 
466
- for (const key of Object.keys(trace.tags)) {
467
- const value = trace.tags[key]
468
- if (!value || !key.startsWith('_dd.p.')) continue
494
+ for (const key of Object.keys(traceTags)) {
495
+ const value = traceTags[key]
496
+ if (!value || !key.startsWith('_dd.p.') ||
497
+ traceTagReplacements && hasTraceTagReplacement(traceTagReplacements, key)) {
498
+ continue
499
+ }
469
500
  if (!tagKeyExpr.test(key) || !tagValueExpr.test(value)) {
470
501
  log.error('Trace tags from span are invalid, skipping injection.')
471
502
  return
@@ -475,7 +506,24 @@ class TextMapPropagator {
475
506
  header += `${key}=${value}`
476
507
  }
477
508
 
478
- if (header.length > this.#config.DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH) {
509
+ if (traceTagReplacements) {
510
+ const requiredEntryCount = traceTagReplacements.length - optionalTraceTagCount * 2
511
+ for (let index = 0; index < traceTagReplacements.length; index += 2) {
512
+ const key = traceTagReplacements[index]
513
+ const value = traceTagReplacements[index + 1]
514
+ if (!value || !key.startsWith('_dd.p.')) continue
515
+ if (!tagKeyExpr.test(key) || !tagValueExpr.test(value)) {
516
+ log.error('Trace tags from span are invalid, skipping injection.')
517
+ return
518
+ }
519
+
520
+ const entry = `${header ? ',' : ''}${key}=${value}`
521
+ if (index >= requiredEntryCount && header.length + entry.length > maxLength) break
522
+ header += entry
523
+ }
524
+ }
525
+
526
+ if (header.length > maxLength) {
479
527
  log.error('Trace tags from span are too large, skipping injection.')
480
528
  } else if (header) {
481
529
  writeDatadogTags(carrier, header)
@@ -538,9 +586,10 @@ class TextMapPropagator {
538
586
  * @param {DatadogSpanContext} spanContext
539
587
  * @param {Record<string, string> | undefined} carrier
540
588
  * @param {boolean} injectTraceContext
589
+ * @param {Array<string | undefined>} [traceTagReplacements]
541
590
  * @returns {Record<string, string> | undefined}
542
591
  */
543
- #injectTraceparent (spanContext, carrier, injectTraceContext) {
592
+ #injectTraceparent (spanContext, carrier, injectTraceContext, traceTagReplacements) {
544
593
  if (!this.#hasPropagationStyle('inject', 'tracecontext')) return
545
594
 
546
595
  if (!injectTraceContext) {
@@ -556,9 +605,12 @@ class TextMapPropagator {
556
605
  carrier ??= {}
557
606
  const {
558
607
  _sampling: { priority, mechanism },
559
- _tracestate: ts = new TraceState(),
560
- _trace: { origin, tags: traceTags },
608
+ _tracestate,
609
+ _trace: { origin },
561
610
  } = spanContext
611
+ const ts = traceTagReplacements
612
+ ? TraceState.fromString(_tracestate?.toString())
613
+ : _tracestate ?? new TraceState()
562
614
 
563
615
  writeTraceparent(carrier, spanContext.toTraceparent())
564
616
 
@@ -584,8 +636,9 @@ class TextMapPropagator {
584
636
  state.set('o', originValue)
585
637
  }
586
638
 
587
- for (const key of Object.keys(traceTags)) {
588
- const tagValueRaw = traceTags[key]
639
+ for (const key of Object.keys(spanContext._trace.tags)) {
640
+ if (traceTagReplacements && hasTraceTagReplacement(traceTagReplacements, key)) continue
641
+ const tagValueRaw = spanContext._trace.tags[key]
589
642
  if (!tagValueRaw || !key.startsWith('_dd.p.')) continue
590
643
 
591
644
  const tagKey = 't.' + key.slice(6)
@@ -598,6 +651,28 @@ class TextMapPropagator {
598
651
 
599
652
  state.set(tagKey, tagValue)
600
653
  }
654
+
655
+ if (traceTagReplacements) {
656
+ for (let index = 0; index < traceTagReplacements.length; index += 2) {
657
+ const key = traceTagReplacements[index]
658
+ if (!key.startsWith('_dd.p.')) continue
659
+
660
+ const tagKey = 't.' + key.slice(6)
661
+ .replaceAll(tracestateTagKeyFilter, '_')
662
+ const tagValueRaw = traceTagReplacements[index + 1]
663
+ if (!tagValueRaw) {
664
+ state.delete(tagKey)
665
+ continue
666
+ }
667
+
668
+ const tagValue = tagValueRaw
669
+ .toString()
670
+ .replaceAll(tracestateTagValueFilter, '_')
671
+ .replaceAll('=', '~')
672
+
673
+ state.set(tagKey, tagValue)
674
+ }
675
+ }
601
676
  })
602
677
 
603
678
  writeTracestate(carrier, ts.toString())
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
- const rfdc = require('../../../../vendor/dist/rfdc')({ proto: false, circles: false })
3
+ const createRfdc = require('../../../../vendor/dist/rfdc')
4
+ const rfdc = createRfdc({ proto: false, circles: false })
4
5
 
5
6
  const {
6
7
  PAYLOAD_TAG_REQUEST_PREFIX,
@@ -728,12 +728,7 @@ module.exports = class CiPlugin extends Plugin {
728
728
  this._bufferWorkerTrace(missingTestSuite, trace)
729
729
  return
730
730
  }
731
- const exporter = this.tracer._exporter
732
- if (exporter.exportTraceWithDeferredTestSuite) {
733
- exporter.exportTraceWithDeferredTestSuite(trace)
734
- } else {
735
- exporter.export(trace)
736
- }
731
+ this.tracer._exporter.export(trace)
737
732
  }
738
733
 
739
734
  /**
@@ -243,19 +243,21 @@ class TracingPlugin extends Plugin {
243
243
  serviceSource = undefined
244
244
  }
245
245
 
246
+ const tags = {
247
+ [COMPONENT]: component,
248
+ 'service.name': serviceName || tracer._service,
249
+ 'resource.name': resource,
250
+ 'span.kind': kind,
251
+ 'span.type': type,
252
+ ...meta,
253
+ ...metrics,
254
+ }
255
+ if (serviceSource !== undefined && !Object.hasOwn(tags, SVC_SRC_KEY)) tags[SVC_SRC_KEY] = serviceSource
256
+
246
257
  const span = tracer.startSpan(name, {
247
258
  startTime,
248
259
  childOf,
249
- tags: {
250
- [COMPONENT]: component,
251
- 'service.name': serviceName || tracer._service,
252
- 'resource.name': resource,
253
- 'span.kind': kind,
254
- 'span.type': type,
255
- ...(serviceSource === undefined ? undefined : { [SVC_SRC_KEY]: serviceSource }),
256
- ...meta,
257
- ...metrics,
258
- },
260
+ tags,
259
261
  integrationName: integrationName || component,
260
262
  links: childOf?._links,
261
263
  })
@@ -45,7 +45,9 @@ function parseEmailAndName (emailAndName) {
45
45
  }
46
46
  let name = ''
47
47
  let email = ''
48
- const matchNameAndEmail = emailAndName.match(/(?:"?([^"]*)"?\s)?(?:<?(.+@[^>]+)>?)/)
48
+ // Untrusted git author metadata (a fork PR controls it). The anchor fixes the start position, the
49
+ // email classes cannot overlap around `@`, and the slice bounds the optional name part.
50
+ const matchNameAndEmail = emailAndName.slice(0, 1024).match(/^(?:"?([^"]*)"?\s)?<?([^\s@]+@[^\s>]+)>?/)
49
51
  if (matchNameAndEmail) {
50
52
  name = matchNameAndEmail[1]
51
53
  email = matchNameAndEmail[2]
@@ -69,26 +69,27 @@ function createInferredProxySpan (headers, childOf, tracer, reqCtx, traceCtx, co
69
69
 
70
70
  log.debug('Successfully extracted inferred span info %s for proxy:', proxyContext, proxyContext.proxySystemName)
71
71
 
72
+ const meta = {
73
+ service: proxyContext.domainName || tracer._config.service,
74
+ component: proxySpanInfo.component,
75
+ [SPAN_TYPE]: 'web',
76
+ [SPAN_KIND]: 'server',
77
+ [HTTP_METHOD]: proxyContext.method,
78
+ [HTTP_URL]: 'https://' + proxyContext.domainName + proxyContext.path,
79
+ stage: proxyContext.stage,
80
+ region: proxyContext.region,
81
+ }
82
+ if (proxyContext.resourcePath) meta[HTTP_ROUTE] = proxyContext.resourcePath
83
+ if (proxyContext.accountId) meta.account_id = proxyContext.accountId
84
+ if (proxyContext.apiId) meta.apiid = proxyContext.apiId
85
+ if (proxyContext.awsUser) meta.aws_user = proxyContext.awsUser
86
+
72
87
  const span = startSpanHelper(tracer, proxySpanInfo.spanName, {
73
88
  childOf,
74
89
  type: 'web',
75
90
  startTime: proxyContext.requestTime,
76
91
  integrationName: proxySpanInfo.component,
77
- meta: {
78
- service: proxyContext.domainName || tracer._config.service,
79
- component: proxySpanInfo.component,
80
- [SPAN_TYPE]: 'web',
81
- [SPAN_KIND]: 'server',
82
- [HTTP_METHOD]: proxyContext.method,
83
- [HTTP_URL]: 'https://' + proxyContext.domainName + proxyContext.path,
84
- stage: proxyContext.stage,
85
- region: proxyContext.region,
86
- ...(proxyContext.resourcePath && { [HTTP_ROUTE]: proxyContext.resourcePath }),
87
- ...(proxyContext.accountId && { account_id: proxyContext.accountId }),
88
- ...(proxyContext.apiId && { apiid: proxyContext.apiId }),
89
- ...(proxyContext.region && { region: proxyContext.region }),
90
- ...(proxyContext.awsUser && { aws_user: proxyContext.awsUser }),
91
- },
92
+ meta,
92
93
  }, traceCtx, config)
93
94
 
94
95
  reqCtx.inferredProxySpan = span
@@ -108,6 +108,9 @@ const TEST_SOURCE_FILE = 'test.source.file'
108
108
  const TEST_SOURCE_START = 'test.source.start'
109
109
  const TEST_FAILURE_SCREENSHOT_UPLOADED = 'test.failure_screenshot.uploaded'
110
110
  const TEST_FAILURE_SCREENSHOT_UPLOAD_ERROR = 'test.failure_screenshot.upload_error'
111
+ const TEST_FAILURE_VIDEO_UPLOADED = 'test.failure_video.uploaded'
112
+ const TEST_FAILURE_VIDEO_UPLOAD_ERROR = 'test.failure_video.upload_error'
113
+ const TEST_FAILURE_VIDEO_SCOPE = 'test.failure_video.scope'
111
114
  const LIBRARY_VERSION = 'library_version'
112
115
  const TEST_COMMAND = 'test.command'
113
116
  const TEST_MODULE = 'test.module'
@@ -127,7 +130,7 @@ const TEST_IS_MODIFIED = 'test.is_modified'
127
130
  const TEST_HAS_DYNAMIC_NAME = '_dd.has_dynamic_name'
128
131
  const CI_APP_ORIGIN = 'ciapp-test'
129
132
  // eslint-disable-next-line no-control-regex
130
- const TEST_OPTIMIZATION_NAME_CONTROL_RE = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g
133
+ const TEST_OPTIMIZATION_NAME_CONTROL_RE = /[\u0000-\u0008\v\f\u000E-\u001F\u007F-\u009F]/g
131
134
  const TEST_OPTIMIZATION_NAME_WHITESPACE_RE = /\s+/g
132
135
 
133
136
  // Matches patterns that are almost certainly runtime-generated values in test names:
@@ -474,6 +477,9 @@ module.exports = {
474
477
  TEST_SOURCE_FILE,
475
478
  TEST_FAILURE_SCREENSHOT_UPLOADED,
476
479
  TEST_FAILURE_SCREENSHOT_UPLOAD_ERROR,
480
+ TEST_FAILURE_VIDEO_UPLOADED,
481
+ TEST_FAILURE_VIDEO_UPLOAD_ERROR,
482
+ TEST_FAILURE_VIDEO_SCOPE,
477
483
  CI_APP_ORIGIN,
478
484
  LIBRARY_VERSION,
479
485
  JEST_WORKER_TRACE_PAYLOAD_CODE,
@@ -1605,19 +1611,77 @@ function fromCoverageMapToCoverage (coverageMap) {
1605
1611
  }, {})
1606
1612
  }
1607
1613
 
1614
+ /**
1615
+ * @param {number} code
1616
+ * @returns {boolean}
1617
+ */
1618
+ function isAsciiDigit (code) {
1619
+ return code >= 0x30 && code <= 0x39
1620
+ }
1621
+
1622
+ /**
1623
+ * Parse a V8 stack frame into its file path and 1-based line number.
1624
+ *
1625
+ * Stack text includes caller-controlled function names and can be overwritten. Scan numeric suffixes
1626
+ * right-to-left and discard V8's trailing anonymous location for eval frames.
1627
+ * @param {string} frame
1628
+ * @param {string} expectedPath A known path contained in the frame.
1629
+ * @returns {{ file: string, line: number } | null}
1630
+ */
1631
+ function parseStackFrameLocation (frame, expectedPath) {
1632
+ let pathStart = frame.lastIndexOf(expectedPath)
1633
+ while (frame.charCodeAt(pathStart - 1) !== 0x28 /* ( */ &&
1634
+ !frame.startsWith('at ', pathStart - 3) &&
1635
+ !frame.startsWith('file://', pathStart - 7)) {
1636
+ const previousPathStart = frame.lastIndexOf(expectedPath, pathStart - 1)
1637
+ if (previousPathStart === -1) break
1638
+ pathStart = previousPathStart
1639
+ }
1640
+ const scope = frame.slice(pathStart)
1641
+ let end = scope.length
1642
+ if (scope.charCodeAt(end - 1) === 0x29 /* ) */) end--
1643
+
1644
+ while (true) {
1645
+ let lastNumberStart = end
1646
+ while (lastNumberStart > 0 && isAsciiDigit(scope.charCodeAt(lastNumberStart - 1))) {
1647
+ lastNumberStart--
1648
+ }
1649
+ if (lastNumberStart === end || scope.charCodeAt(lastNumberStart - 1) !== 0x3A /* : */) {
1650
+ return null
1651
+ }
1652
+ const colonBeforeLast = lastNumberStart - 1
1653
+ let lineStart = colonBeforeLast
1654
+ while (lineStart > 0 && isAsciiDigit(scope.charCodeAt(lineStart - 1))) {
1655
+ lineStart--
1656
+ }
1657
+ if (lineStart < colonBeforeLast && scope.charCodeAt(lineStart - 1) === 0x3A /* : */) {
1658
+ const fileEnd = lineStart - 1
1659
+ const anonymousStart = fileEnd - 11
1660
+ if (scope.charCodeAt(fileEnd - 1) === 0x3E /* > */ && anonymousStart >= 3 &&
1661
+ scope.startsWith('<anonymous>', anonymousStart) &&
1662
+ scope.charCodeAt(anonymousStart - 1) === 0x20 /* space */ &&
1663
+ scope.charCodeAt(anonymousStart - 2) === 0x2C /* , */ &&
1664
+ scope.charCodeAt(anonymousStart - 3) === 0x29 /* ) */) {
1665
+ end = anonymousStart - 3
1666
+ while (scope.charCodeAt(end - 1) === 0x29 /* ) */) end--
1667
+ continue
1668
+ }
1669
+ return { file: scope.slice(0, fileEnd), line: Number(scope.slice(lineStart, colonBeforeLast)) }
1670
+ }
1671
+ return { file: scope.slice(0, colonBeforeLast), line: Number(scope.slice(lastNumberStart, end)) }
1672
+ }
1673
+ }
1674
+
1608
1675
  // Get the start line of a test by inspecting a given error's stack trace
1609
1676
  function getTestLineStart (err, testSuitePath) {
1610
1677
  if (!err.stack) {
1611
1678
  return null
1612
1679
  }
1613
- // From https://github.com/felixge/node-stack-trace/blob/ba06dcdb50d465cd440d84a563836e293b360427/index.js#L40
1614
1680
  const testFileLine = err.stack.split('\n').find(line => line.includes(testSuitePath))
1615
- try {
1616
- const testFileLineMatch = testFileLine.match(/at (?:(.+?)\s+\()?(?:(.+?):(\d+)(?::(\d+))?|([^)]+))\)?/)
1617
- return Number.parseInt(testFileLineMatch[3], 10) || null
1618
- } catch {
1681
+ if (!testFileLine) {
1619
1682
  return null
1620
1683
  }
1684
+ return parseStackFrameLocation(testFileLine, testSuitePath)?.line || null
1621
1685
  }
1622
1686
 
1623
1687
  // Get the end line of a test by inspecting a given function's source code
@@ -1640,7 +1704,7 @@ function parseAnnotations (annotations) {
1640
1704
  }
1641
1705
  const { type, description } = annotation
1642
1706
  if (type.startsWith('DD_TAGS')) {
1643
- const regex = /\[(.*?)\]/
1707
+ const regex = /^DD_TAGS\[([^\]]*)\]/
1644
1708
  const match = regex.exec(type)
1645
1709
  let tagValue = ''
1646
1710
  if (match) {
@@ -1744,15 +1808,9 @@ function getFileAndLineNumberFromError (error, repositoryRoot) {
1744
1808
  }
1745
1809
 
1746
1810
  const topFrame = frames[topRelevantFrameIndex]
1747
- // Regular expression to match the file path, line number, and column number
1748
- const regex = /\s*at\s+(?:.*\()?(.+):(\d+):(\d+)\)?/
1749
- const match = topFrame.match(regex)
1750
-
1751
- if (match) {
1752
- const filePath = match[1]
1753
- const lineNumber = Number(match[2])
1754
-
1755
- return [filePath, lineNumber, topRelevantFrameIndex]
1811
+ const location = parseStackFrameLocation(topFrame, repositoryRoot)
1812
+ if (location) {
1813
+ return [location.file, location.line, topRelevantFrameIndex]
1756
1814
  }
1757
1815
  return []
1758
1816
  }
@@ -2082,12 +2140,17 @@ function formatTestOptimizationList (items) {
2082
2140
  const more = items.length - shown.length
2083
2141
  const moreSuffix = more > 0 ? `\n ... and ${more} more` : ''
2084
2142
 
2085
- return shown
2086
- .map(({ text, suffix }) => ` • ${truncateTestOptimizationNameMiddle(
2143
+ let formattedItems = ''
2144
+ let isFirstItem = true
2145
+ for (const { text, suffix } of shown) {
2146
+ if (!isFirstItem) formattedItems += '\n'
2147
+ formattedItems += ` • ${truncateTestOptimizationNameMiddle(
2087
2148
  sanitizeTestOptimizationName(text),
2088
2149
  MAX_TEST_OPTIMIZATION_NAME_LENGTH
2089
- )}${suffix ? ` (${suffix})` : ''}`)
2090
- .join('\n') + moreSuffix
2150
+ )}${suffix ? ` (${suffix})` : ''}`
2151
+ isFirstItem = false
2152
+ }
2153
+ return formattedItems + moreSuffix
2091
2154
  }
2092
2155
 
2093
2156
  /**
@@ -14,7 +14,7 @@ const INT_SEGMENT = /^[1-9][0-9]+$/ // Integer of size at least 2 (>=10)
14
14
  const INT_ID_SEGMENT = /^(?=.*[0-9])[0-9._-]{3,}$/ // Mixed string with digits and delimiters
15
15
  const HEX_SEGMENT = /^(?=.*[0-9])[A-Fa-f0-9]{6,}$/ // Hexadecimal digits of size at least 6 with at least one decimal digit
16
16
  const HEX_ID_SEGMENT = /^(?=.*[0-9])[A-Fa-f0-9._-]{6,}$/ // Mixed string with hex digits and delimiters
17
- const STRING_SEGMENT = /(?:^.{20,}|[%&'()*+,:=@])/ // Long string or a string containing special characters
17
+ const SPECIAL_CHARACTER_SEGMENT = /[%&'()*+,:=@]/
18
18
 
19
19
  /**
20
20
  * Extract full URL from HTTP request
@@ -167,7 +167,7 @@ function calculateHttpEndpoint (url) {
167
167
 
168
168
  if (HEX_ID_SEGMENT.test(element)) return '{param:hex_id}'
169
169
 
170
- if (STRING_SEGMENT.test(element)) return '{param:str}'
170
+ if (element.length >= 20 || SPECIAL_CHARACTER_SEGMENT.test(element)) return '{param:str}'
171
171
 
172
172
  // No match
173
173
  return element
@@ -35,7 +35,6 @@ const HTTP_CLIENT_IP = tags.HTTP_CLIENT_IP
35
35
  const MANUAL_DROP = tags.MANUAL_DROP
36
36
 
37
37
  const contexts = new WeakMap()
38
- const requests = new WeakMap()
39
38
 
40
39
  // TODO: change this to no longer rely on creating a dummy plugin to be able to access startSpan
41
40
  function createWebPlugin (tracer, config = {}) {
@@ -129,7 +128,6 @@ const web = {
129
128
  context.tracer = tracer
130
129
  context.span = span
131
130
  context.res = res
132
- requests.set(span, req)
133
131
 
134
132
  this.setConfig(req, config)
135
133
  addRequestTags(context, this.TYPE)
@@ -314,7 +312,6 @@ const web = {
314
312
  web.finishMiddleware(context)
315
313
 
316
314
  web.finishSpan(context, spanType)
317
- requests.delete(context.span)
318
315
 
319
316
  finishInferredProxySpan(context)
320
317
  },
@@ -344,9 +341,6 @@ const web = {
344
341
  getContext (req) {
345
342
  return contexts.get(req)
346
343
  },
347
- getRequest (span) {
348
- return requests.get(span)
349
- },
350
344
  setRouteOrEndpointTag (req) {
351
345
  const context = contexts.get(req)
352
346
 
@@ -511,9 +505,10 @@ function addResourceTag (context) {
511
505
 
512
506
  if (spanContext.getTag(RESOURCE_NAME)) return
513
507
 
514
- const resource = [req.method, spanContext.getTag(HTTP_ROUTE)]
515
- .filter(Boolean)
516
- .join(' ')
508
+ let resource = req.method
509
+ const route = spanContext.getTag(HTTP_ROUTE)
510
+
511
+ if (route) resource += ` ${route}`
517
512
 
518
513
  span.setTag(RESOURCE_NAME, resource)
519
514
  }
@@ -222,4 +222,5 @@ function buildProfilingRuntime (config) {
222
222
 
223
223
  module.exports = {
224
224
  buildProfilingRuntime,
225
+ getProfilingTags,
225
226
  }
@@ -54,8 +54,13 @@ function strategiesToCallbackMode (strategies, callbackMode) {
54
54
  * @returns {string[]}
55
55
  */
56
56
  function buildExportCommand (exporters, tags) {
57
- const tagString = [...Object.entries(tags),
58
- ['snapshot', snapshotKinds.ON_OUT_OF_MEMORY]].map(([key, value]) => `${key}:${value}`).join(',')
57
+ let tagString = ''
58
+ for (const [key, value] of Object.entries(tags)) {
59
+ if (tagString) tagString += ','
60
+ tagString += `${key}:${value}`
61
+ }
62
+ if (tagString) tagString += ','
63
+ tagString += `snapshot:${snapshotKinds.ON_OUT_OF_MEMORY}`
59
64
  let urls = ''
60
65
  for (const exporter of exporters) {
61
66
  const url = exporter.getExportUrl()