dd-trace 6.12.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 (251) 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/cypress.js +9 -11
  8. package/ci/test-optimization-validation/framework-adapters/vitest.js +3 -3
  9. package/ci/test-optimization-validation/package-check.js +1 -1
  10. package/ci/test-optimization-validation/report-writer.js +7 -5
  11. package/ci/test-optimization-validation/scenarios/ci-wiring.js +5 -4
  12. package/ci/vitest-no-worker-init-runner-setup.mjs +8 -0
  13. package/ci/vitest-no-worker-init-setup.mjs +13 -13
  14. package/ci/vitest-no-worker-init-v5-setup.mjs +4 -0
  15. package/index.d.ts +127 -26
  16. package/package.json +22 -19
  17. package/packages/datadog-esbuild/index.js +3 -1
  18. package/packages/datadog-instrumentations/src/bunyan.js +4 -2
  19. package/packages/datadog-instrumentations/src/cypress-config.js +19 -17
  20. package/packages/datadog-instrumentations/src/fs.js +24 -20
  21. package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +2 -2
  22. package/packages/datadog-instrumentations/src/graphql.js +16 -16
  23. package/packages/datadog-instrumentations/src/helpers/bunyan.js +12 -3
  24. package/packages/datadog-instrumentations/src/helpers/graphql-jit-runtime.js +504 -0
  25. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  26. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +266 -0
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +1 -2
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +95 -30
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +0 -25
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +83 -0
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +85 -1
  32. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  33. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +30 -12
  34. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +13 -0
  35. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +279 -0
  36. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +12 -1
  37. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +562 -27
  38. package/packages/datadog-instrumentations/src/jest.js +165 -38
  39. package/packages/datadog-instrumentations/src/knex.js +76 -0
  40. package/packages/datadog-instrumentations/src/mariadb-bundle.js +1597 -0
  41. package/packages/datadog-instrumentations/src/mariadb.js +13 -0
  42. package/packages/datadog-instrumentations/src/mocha/common.js +59 -2
  43. package/packages/datadog-instrumentations/src/mocha/main.js +25 -15
  44. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
  45. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +98 -0
  46. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -8
  47. package/packages/datadog-instrumentations/src/net.js +14 -17
  48. package/packages/datadog-instrumentations/src/next.js +212 -62
  49. package/packages/datadog-instrumentations/src/openai.js +42 -40
  50. package/packages/datadog-instrumentations/src/pg.js +15 -0
  51. package/packages/datadog-instrumentations/src/pino.js +15 -4
  52. package/packages/datadog-instrumentations/src/playwright.js +137 -27
  53. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +40 -0
  54. package/packages/datadog-instrumentations/src/sequelize.js +94 -2
  55. package/packages/datadog-instrumentations/src/undici.js +53 -4
  56. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +44 -7
  57. package/packages/datadog-instrumentations/src/vitest-main.js +92 -9
  58. package/packages/datadog-instrumentations/src/vitest-worker.js +82 -14
  59. package/packages/datadog-instrumentations/src/webdriverio.js +986 -1
  60. package/packages/datadog-instrumentations/src/winston.js +6 -3
  61. package/packages/datadog-plugin-amqp10/src/consumer.js +3 -1
  62. package/packages/datadog-plugin-amqp10/src/producer.js +3 -1
  63. package/packages/datadog-plugin-amqp10/src/util.js +2 -1
  64. package/packages/datadog-plugin-avsc/src/schema_iterator.js +6 -1
  65. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +16 -5
  66. package/packages/datadog-plugin-cassandra-driver/src/index.js +8 -3
  67. package/packages/datadog-plugin-cucumber/src/index.js +0 -2
  68. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +262 -48
  69. package/packages/datadog-plugin-cypress/src/support.js +1 -0
  70. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -1
  71. package/packages/datadog-plugin-graphql/src/execute.js +998 -196
  72. package/packages/datadog-plugin-graphql/src/index.js +27 -7
  73. package/packages/datadog-plugin-graphql/src/jit.js +136 -0
  74. package/packages/datadog-plugin-graphql/src/request.js +18 -27
  75. package/packages/datadog-plugin-graphql/src/resolve-error.js +108 -0
  76. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -2
  77. package/packages/datadog-plugin-graphql/src/tools/transforms.js +45 -3
  78. package/packages/datadog-plugin-graphql/src/utils.js +208 -120
  79. package/packages/datadog-plugin-graphql/src/validate.js +10 -23
  80. package/packages/datadog-plugin-http/src/client.js +2 -15
  81. package/packages/datadog-plugin-http2/src/client.js +2 -15
  82. package/packages/datadog-plugin-jest/src/index.js +0 -6
  83. package/packages/datadog-plugin-langchain/src/tracing.js +7 -0
  84. package/packages/datadog-plugin-mocha/src/index.js +403 -75
  85. package/packages/datadog-plugin-mongodb-core/src/query.js +6 -0
  86. package/packages/datadog-plugin-mysql/src/index.js +41 -15
  87. package/packages/datadog-plugin-net/src/tcp.js +3 -1
  88. package/packages/datadog-plugin-next/src/index.js +63 -29
  89. package/packages/datadog-plugin-openai-agents/src/integration.js +15 -11
  90. package/packages/datadog-plugin-playwright/src/index.js +176 -28
  91. package/packages/datadog-plugin-undici/src/index.js +29 -56
  92. package/packages/datadog-plugin-vitest/src/index.js +6 -12
  93. package/packages/dd-trace/src/aiguard/evaluation.js +96 -21
  94. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -3
  95. package/packages/dd-trace/src/aiguard/messages/anthropic.js +29 -10
  96. package/packages/dd-trace/src/aiguard/messages/openai.js +8 -1
  97. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +20 -6
  98. package/packages/dd-trace/src/aiguard/noop.js +9 -1
  99. package/packages/dd-trace/src/aiguard/redaction.js +183 -0
  100. package/packages/dd-trace/src/aiguard/sdk.js +7 -2
  101. package/packages/dd-trace/src/aiguard/tags.js +2 -0
  102. package/packages/dd-trace/src/appsec/api_security/index.js +23 -6
  103. package/packages/dd-trace/src/appsec/api_security/sampler.js +99 -40
  104. package/packages/dd-trace/src/appsec/graphql.js +6 -2
  105. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +3 -3
  106. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -1
  107. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +8 -2
  108. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +11 -36
  109. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +1 -1
  110. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +10 -9
  111. package/packages/dd-trace/src/appsec/lambda.js +44 -26
  112. package/packages/dd-trace/src/appsec/rule_manager.js +2 -0
  113. package/packages/dd-trace/src/ci-visibility/encode/json-encoder.js +32 -2
  114. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +8 -8
  115. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +6 -4
  116. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +20 -4
  117. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +7 -150
  118. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +7 -6
  119. package/packages/dd-trace/src/ci-visibility/exporters/agents.js +54 -0
  120. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +1 -2
  121. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +397 -234
  122. package/packages/dd-trace/src/ci-visibility/exporters/limits.js +9 -0
  123. package/packages/dd-trace/src/ci-visibility/exporters/request.js +323 -31
  124. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +42 -0
  125. package/packages/dd-trace/src/ci-visibility/final-flush.js +21 -0
  126. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +57 -18
  127. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +0 -3
  128. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +164 -71
  129. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +176 -0
  130. package/packages/dd-trace/src/ci-visibility/telemetry.js +10 -7
  131. package/packages/dd-trace/src/ci-visibility/test-video.js +61 -0
  132. package/packages/dd-trace/src/config/generated-config-types.d.ts +10 -0
  133. package/packages/dd-trace/src/config/index.js +75 -14
  134. package/packages/dd-trace/src/config/parsers.js +11 -1
  135. package/packages/dd-trace/src/config/stable.js +3 -3
  136. package/packages/dd-trace/src/config/supported-configurations.json +47 -2
  137. package/packages/dd-trace/src/crashtracking/crashtracker.js +82 -18
  138. package/packages/dd-trace/src/debugger/config.js +12 -1
  139. package/packages/dd-trace/src/debugger/constants.js +1 -0
  140. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -1
  141. package/packages/dd-trace/src/debugger/devtools_client/request-options.js +33 -0
  142. package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -39
  143. package/packages/dd-trace/src/debugger/devtools_client/status.js +11 -6
  144. package/packages/dd-trace/src/debugger/devtools_client/tags.js +41 -0
  145. package/packages/dd-trace/src/debugger/index.js +23 -3
  146. package/packages/dd-trace/src/dogstatsd.js +532 -71
  147. package/packages/dd-trace/src/encode/0.4-cross-payload.js +170 -0
  148. package/packages/dd-trace/src/encode/0.4.js +5 -5
  149. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -8
  150. package/packages/dd-trace/src/evp_proxy/direct.js +27 -23
  151. package/packages/dd-trace/src/exporters/agent/index.js +24 -3
  152. package/packages/dd-trace/src/exporters/agent/writer.js +28 -11
  153. package/packages/dd-trace/src/exporters/agentless/index.js +7 -9
  154. package/packages/dd-trace/src/exporters/agentless/writer.js +100 -118
  155. package/packages/dd-trace/src/exporters/common/agents.js +28 -6
  156. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -2
  157. package/packages/dd-trace/src/exporters/common/final-flush-request-tracker.js +191 -0
  158. package/packages/dd-trace/src/exporters/common/request.js +31 -11
  159. package/packages/dd-trace/src/exporters/common/url.js +40 -1
  160. package/packages/dd-trace/src/exporters/common/writer.js +31 -7
  161. package/packages/dd-trace/src/exporters/span-stats/index.js +23 -4
  162. package/packages/dd-trace/src/external-logger/src/index.js +4 -3
  163. package/packages/dd-trace/src/flush.js +105 -0
  164. package/packages/dd-trace/src/id.js +14 -0
  165. package/packages/dd-trace/src/index.js +5 -2
  166. package/packages/dd-trace/src/llmobs/experiments/client.js +4 -0
  167. package/packages/dd-trace/src/llmobs/experiments/dataset.js +52 -2
  168. package/packages/dd-trace/src/llmobs/experiments/experiment.js +420 -105
  169. package/packages/dd-trace/src/llmobs/experiments/index.js +54 -42
  170. package/packages/dd-trace/src/llmobs/experiments/noop.js +39 -8
  171. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  172. package/packages/dd-trace/src/llmobs/experiments/util.js +21 -0
  173. package/packages/dd-trace/src/llmobs/index.js +111 -48
  174. package/packages/dd-trace/src/llmobs/noop.js +2 -1
  175. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +22 -71
  176. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +23 -13
  177. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +8 -4
  178. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +6 -3
  179. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +7 -0
  180. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +13 -6
  181. package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +5 -7
  182. package/packages/dd-trace/src/llmobs/util.js +0 -33
  183. package/packages/dd-trace/src/llmobs/writers/base.js +106 -53
  184. package/packages/dd-trace/src/log/index.js +7 -1
  185. package/packages/dd-trace/src/log-submission/log-submission-plugin.js +249 -0
  186. package/packages/dd-trace/src/noop/span.js +5 -0
  187. package/packages/dd-trace/src/openfeature/writers/base.js +14 -12
  188. package/packages/dd-trace/src/openfeature/writers/exposures.js +8 -1
  189. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  190. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +45 -3
  191. package/packages/dd-trace/src/opentelemetry/logs/index.js +9 -25
  192. package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +7 -4
  193. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +14 -4
  194. package/packages/dd-trace/src/opentelemetry/metrics/index.js +18 -23
  195. package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +8 -0
  196. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +18 -6
  197. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +14 -2
  198. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +25 -5
  199. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +61 -25
  200. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +19 -4
  201. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +55 -0
  202. package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
  203. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +10 -0
  204. package/packages/dd-trace/src/opentelemetry/tracer.js +26 -2
  205. package/packages/dd-trace/src/opentracing/propagation/text_map.js +494 -351
  206. package/packages/dd-trace/src/opentracing/span.js +28 -0
  207. package/packages/dd-trace/src/otel-thread-ctx.js +23 -5
  208. package/packages/dd-trace/src/payload-tagging/index.js +2 -1
  209. package/packages/dd-trace/src/plugin_manager.js +2 -0
  210. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -6
  211. package/packages/dd-trace/src/plugins/tracing.js +12 -10
  212. package/packages/dd-trace/src/plugins/util/ci.js +3 -1
  213. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +16 -15
  214. package/packages/dd-trace/src/plugins/util/status-validator.js +98 -0
  215. package/packages/dd-trace/src/plugins/util/test.js +157 -41
  216. package/packages/dd-trace/src/plugins/util/url.js +2 -2
  217. package/packages/dd-trace/src/plugins/util/web.js +6 -74
  218. package/packages/dd-trace/src/profiling/config.js +1 -0
  219. package/packages/dd-trace/src/profiling/oom.js +11 -5
  220. package/packages/dd-trace/src/profiling/profiler.js +30 -13
  221. package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
  222. package/packages/dd-trace/src/profiling/profilers/space.js +38 -11
  223. package/packages/dd-trace/src/profiling/profilers/wall.js +14 -14
  224. package/packages/dd-trace/src/profiling/webspan-utils.js +7 -4
  225. package/packages/dd-trace/src/proxy.js +108 -10
  226. package/packages/dd-trace/src/remote_config/index.js +319 -101
  227. package/packages/dd-trace/src/remote_config/scheduler.js +37 -12
  228. package/packages/dd-trace/src/runtime_metrics/client.js +37 -8
  229. package/packages/dd-trace/src/runtime_metrics/index.js +5 -0
  230. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +10 -1
  231. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +22 -7
  232. package/packages/dd-trace/src/sampling_rule.js +36 -9
  233. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +51 -0
  234. package/packages/dd-trace/src/serverless/vercel.js +155 -0
  235. package/packages/dd-trace/src/serverless.js +45 -21
  236. package/packages/dd-trace/src/service-naming/schemas/util.js +1 -1
  237. package/packages/dd-trace/src/span_stats.js +64 -31
  238. package/packages/dd-trace/src/startup-log.js +3 -2
  239. package/packages/dd-trace/src/telemetry/agentless-url.js +28 -0
  240. package/packages/dd-trace/src/telemetry/send-data.js +18 -16
  241. package/packages/dd-trace/src/telemetry/session-propagation.js +3 -2
  242. package/packages/dd-trace/src/tracer.js +26 -5
  243. package/packages/dd-trace/src/tracer_metadata.js +11 -3
  244. package/packages/dd-trace/src/web-tags-cache.js +67 -13
  245. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  246. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  247. package/vendor/dist/protobufjs/index.js +1 -1
  248. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  249. package/packages/datadog-instrumentations/src/playwright-browser-scripts.js +0 -27
  250. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +0 -53
  251. package/packages/dd-trace/src/encode/agentless-json.js +0 -209
@@ -26,6 +26,15 @@ const finishedRegistry = createRegistry('finished')
26
26
  let OTEL_ENABLED = false
27
27
  const ALLOWED = new Set(['string', 'number', 'boolean'])
28
28
 
29
+ /**
30
+ * @typedef {object} RecordedException
31
+ * @property {string} message
32
+ * @property {string} [name]
33
+ * @property {string} [stack]
34
+ * @typedef {string | number | boolean} SpanEventAttributeScalar
35
+ * @typedef {SpanEventAttributeScalar | string[] | number[] | boolean[]} SpanEventAttributeValue
36
+ */
37
+
29
38
  const integrationCounters = {
30
39
  spans_created: {},
31
40
  spans_finished: {},
@@ -306,6 +315,25 @@ class DatadogSpan {
306
315
  this._events.push(event)
307
316
  }
308
317
 
318
+ /**
319
+ * @param {RecordedException} exception
320
+ * @param {Record<string, SpanEventAttributeValue>} [attributes]
321
+ */
322
+ recordException (exception, attributes) {
323
+ const { message, name, stack } = exception
324
+ const eventAttributes = { ...attributes }
325
+ eventAttributes['exception.message'] ??= message
326
+
327
+ if (typeof name === 'string') {
328
+ eventAttributes['exception.type'] ??= name
329
+ }
330
+ if (typeof stack === 'string') {
331
+ eventAttributes['exception.stacktrace'] ??= stack
332
+ }
333
+
334
+ this.addEvent('exception', eventAttributes)
335
+ }
336
+
309
337
  finish (finishTime) {
310
338
  if (this._duration !== undefined) {
311
339
  return
@@ -105,6 +105,10 @@ const THREAD_ID = String(threadId)
105
105
  // time onEnter activates the span, and cleared on span finish, which
106
106
  // is how a pendingEndpoints waiter recognizes a record it must no
107
107
  // longer append to.
108
+ // webTags: the web-server tag bag the record's endpoint comes from, or is
109
+ // waiting on; undefined while the span has no web-server ancestry.
110
+ // Distinguishes a stale announcement from a live one when a nearer
111
+ // web-server span takes over.
108
112
  const CachedSym = Symbol('OtelThreadCtx.cached')
109
113
 
110
114
  let started = false
@@ -137,6 +141,7 @@ function getOrBuildContext (span) {
137
141
  span[CachedSym] = cached
138
142
  }
139
143
  cached.context = new ThreadContext(traceId, spanId, attrs)
144
+ cached.webTags = webTags
140
145
  if (endpoint === undefined) awaitEndpoint(webTags, cached)
141
146
  return cached.context
142
147
  }
@@ -227,21 +232,34 @@ function onEndpointResolved (span) {
227
232
  if (endpoint === undefined) return
228
233
  pendingEndpoints.delete(webTags)
229
234
  for (const cached of waiting) {
230
- if (cached.context !== undefined) appendEndpoint(cached.context, endpoint)
235
+ // A record whose span has since been attributed to a nearer web-server span
236
+ // enlisted again under that bag, and this one is no longer its endpoint.
237
+ if (cached.context !== undefined && cached.webTags === webTags) appendEndpoint(cached.context, endpoint)
231
238
  }
232
239
  }
233
240
 
234
- // A span whose record was built before it looked like a web-server span at all
235
- // has just been recognized as one, so it has no endpoint and never enlisted for
236
- // this request. Its ancestry can't have changed, only its own tags, so this is
237
- // only ever about the announced span's own record.
241
+ // webTagsCache has changed which request it attributes this span to: the span
242
+ // itself or an ancestor was recognized as a web-server span, giving a record
243
+ // built without any web-server ancestry its first endpoint, or a nearer
244
+ // web-server span superseded the one this record is showing. Give the record
245
+ // the new request's endpoint if it has settled, or wait for its announcement.
238
246
  function onWebTagsResolved (span) {
239
247
  if (!started) return
240
248
  const cached = span[CachedSym]
241
249
  if (cached === undefined || cached.context === undefined) return
242
250
  const webTags = webTagsCache.getCachedWebTags(span)
251
+ if (webTags === cached.webTags) return
252
+ // Recorded so that an announcement for the bag this record was previously
253
+ // waiting on no longer applies to it.
254
+ cached.webTags = webTags
243
255
  const endpoint = finalEndpoint(webTags)
244
256
  if (endpoint === undefined) {
257
+ // A record that already shows the outer request's endpoint goes on showing
258
+ // it until the nearer one settles: the record buffer is append-only, so
259
+ // there is no way to take the attribute back, and rebuilding the
260
+ // ThreadContext would strand every async-context frame holding this one.
261
+ // The work is still nested in the outer request, which makes that the least
262
+ // wrong of the values available.
245
263
  awaitEndpoint(webTags, cached)
246
264
  } else {
247
265
  appendEndpoint(cached.context, endpoint)
@@ -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,
@@ -165,6 +165,7 @@ module.exports = class PluginManager {
165
165
  codeOriginForSpans,
166
166
  dbmPropagationMode,
167
167
  dsmEnabled,
168
+ DD_TRACE_HTTP_CLIENT_ERROR_STATUSES,
168
169
  DD_TRACE_HTTP_SERVER_ERROR_STATUSES,
169
170
  clientIpEnabled,
170
171
  clientIpHeader,
@@ -195,6 +196,7 @@ module.exports = class PluginManager {
195
196
  site,
196
197
  url,
197
198
  headers: headerTags || [],
199
+ DD_TRACE_HTTP_CLIENT_ERROR_STATUSES,
198
200
  DD_TRACE_HTTP_SERVER_ERROR_STATUSES,
199
201
  clientIpHeader,
200
202
  DD_TEST_SESSION_NAME,
@@ -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
@@ -0,0 +1,98 @@
1
+ 'use strict'
2
+
3
+ const log = require('../../log')
4
+
5
+ const statusCodeRangesPattern = /^[1-5]\d{2}(?:-[1-5]\d{2})?(?:,[1-5]\d{2}(?:-[1-5]\d{2})?)*$/
6
+ const whitespacePattern = /\s/g
7
+ const MAX_HTTP_STATUS_CODE = 599
8
+
9
+ const SERVER_ERROR_STATUSES = 'DD_TRACE_HTTP_SERVER_ERROR_STATUSES'
10
+ const CLIENT_ERROR_STATUSES = 'DD_TRACE_HTTP_CLIENT_ERROR_STATUSES'
11
+
12
+ /** @typedef {(code: number) => boolean} StatusValidator */
13
+
14
+ /** @type {StatusValidator} */
15
+ function isNotServerErrorCode (code) {
16
+ return code < 500
17
+ }
18
+
19
+ /** @type {StatusValidator} */
20
+ function isNotClientErrorCode (code) {
21
+ return code < 400 || code >= 500
22
+ }
23
+
24
+ /**
25
+ * @param {{ validateStatus?: unknown, DD_TRACE_HTTP_SERVER_ERROR_STATUSES?: unknown }} config
26
+ * @returns {StatusValidator}
27
+ */
28
+ function getServerStatusValidator (config) {
29
+ return getStatusValidator(config, SERVER_ERROR_STATUSES, '500-599', isNotServerErrorCode)
30
+ }
31
+
32
+ /**
33
+ * @param {{ validateStatus?: unknown, DD_TRACE_HTTP_CLIENT_ERROR_STATUSES?: unknown }} config
34
+ * @returns {StatusValidator}
35
+ */
36
+ function getClientStatusValidator (config) {
37
+ return getStatusValidator(config, CLIENT_ERROR_STATUSES, '400-499', isNotClientErrorCode)
38
+ }
39
+
40
+ /**
41
+ * @param {Record<string, unknown>} config
42
+ * @param {string} optionName - Name of the configuration holding the error status ranges.
43
+ * @param {string} defaultRanges - Ranges covered by `defaultValidator`
44
+ * @param {StatusValidator} defaultValidator
45
+ * @returns {StatusValidator}
46
+ */
47
+ function getStatusValidator (config, optionName, defaultRanges, defaultValidator) {
48
+ if (typeof config.validateStatus === 'function') {
49
+ return /** @type {StatusValidator} */ (config.validateStatus)
50
+ } else if (Object.hasOwn(config, 'validateStatus')) {
51
+ log.error('Expected `validateStatus` to be a function.')
52
+ }
53
+
54
+ const ranges = config[optionName]
55
+ if (ranges === undefined) return defaultValidator
56
+ if (typeof ranges !== 'string') {
57
+ log.error('Expected `%s` to be a string.', optionName)
58
+ return defaultValidator
59
+ }
60
+ if (ranges === defaultRanges) return defaultValidator
61
+
62
+ const normalized = ranges.replaceAll(whitespacePattern, '')
63
+ if (normalized === defaultRanges) return defaultValidator
64
+ if (!statusCodeRangesPattern.test(normalized)) {
65
+ log.error('`%s` must contain comma-separated status codes or ranges from 100 to 599.', optionName)
66
+ return defaultValidator
67
+ }
68
+
69
+ const errorStatusCodes = new Uint8Array(MAX_HTTP_STATUS_CODE + 1)
70
+ for (const range of normalized.split(',')) {
71
+ const separator = range.indexOf('-')
72
+ if (separator === -1) {
73
+ errorStatusCodes[Number(range)] = 1
74
+ continue
75
+ }
76
+
77
+ const first = Number(range.slice(0, separator))
78
+ const second = Number(range.slice(separator + 1))
79
+ const start = Math.min(first, second)
80
+ const end = Math.max(first, second)
81
+ errorStatusCodes.fill(1, start, end + 1)
82
+ }
83
+
84
+ /**
85
+ * @param {number} code
86
+ * @returns {boolean}
87
+ */
88
+ function isValidStatusCode (code) {
89
+ return errorStatusCodes[code] !== 1
90
+ }
91
+
92
+ return isValidStatusCode
93
+ }
94
+
95
+ module.exports = {
96
+ getClientStatusValidator,
97
+ getServerStatusValidator,
98
+ }
@@ -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,
@@ -541,6 +547,7 @@ module.exports = {
541
547
  getRelativeCoverageFiles,
542
548
  getLineCoverageBitmap,
543
549
  applySkippedCoverageToCoverage,
550
+ getTestCoverageLinesData,
544
551
  getTestCoverageLinesPercentage,
545
552
  resetCoverage,
546
553
  mergeCoverage,
@@ -1359,6 +1366,34 @@ function getLineCoverageBitmap (lineCoverage, onlyCoveredLines = false) {
1359
1366
  return bitmap
1360
1367
  }
1361
1368
 
1369
+ function getLineCoverageBitmaps (lineCoverage) {
1370
+ let maxLine = 0
1371
+ const entries = Object.entries(lineCoverage)
1372
+
1373
+ for (const [line] of entries) {
1374
+ const lineNumber = Number(line)
1375
+ if (Number.isSafeInteger(lineNumber) && lineNumber > maxLine) {
1376
+ maxLine = lineNumber
1377
+ }
1378
+ }
1379
+ if (maxLine === 0) return {}
1380
+
1381
+ const length = Math.ceil((maxLine + 1) / 8)
1382
+ const coveredBitmap = Buffer.alloc(length)
1383
+ const executableBitmap = Buffer.alloc(length)
1384
+ for (const [line, hits] of entries) {
1385
+ const lineNumber = Number(line)
1386
+ if (!Number.isSafeInteger(lineNumber) || lineNumber <= 0) continue
1387
+
1388
+ const byteIndex = lineNumber >> 3
1389
+ const bit = 1 << (lineNumber % 8)
1390
+ executableBitmap[byteIndex] |= bit
1391
+ if (hits) coveredBitmap[byteIndex] |= bit
1392
+ }
1393
+
1394
+ return { coveredBitmap, executableBitmap }
1395
+ }
1396
+
1362
1397
  function mergeCoverageBitmaps (targetBitmap, bitmap) {
1363
1398
  if (!targetBitmap) {
1364
1399
  return Buffer.from(bitmap)
@@ -1419,16 +1454,6 @@ function getCoverageFileBitmap (bitmap) {
1419
1454
  }
1420
1455
  }
1421
1456
 
1422
- function addCoverageFilesToMap (files, targetMap, rootDir) {
1423
- for (const file of files) {
1424
- const bitmap = getCoverageFileBitmap(file.bitmap)
1425
- if (!bitmap) continue
1426
-
1427
- const filename = rootDir ? getTestSuitePath(file.filename, rootDir) : file.filename
1428
- targetMap.set(filename, mergeCoverageBitmaps(targetMap.get(filename), bitmap))
1429
- }
1430
- }
1431
-
1432
1457
  function addSkippedCoverageToMap (skippedCoverage, targetMap) {
1433
1458
  if (!skippedCoverage) return
1434
1459
 
@@ -1439,23 +1464,57 @@ function addSkippedCoverageToMap (skippedCoverage, targetMap) {
1439
1464
  }
1440
1465
  }
1441
1466
 
1442
- function getTestCoverageLinesPercentage (coverage, skippedCoverage, rootDir) {
1443
- const executableLinesByFile = new Map()
1444
- const coveredLinesByFile = new Map()
1467
+ /**
1468
+ * Calculates line coverage and optionally returns executable-line coverage files in the same traversal.
1469
+ * @param {object} coverage
1470
+ * @param {object} [skippedCoverage]
1471
+ * @param {string} [rootDir]
1472
+ * @param {boolean} [includeExecutableFiles]
1473
+ * @returns {{ percentage: number, executableFiles?: Array<{ filename: string, bitmap: Buffer }> }}
1474
+ */
1475
+ function getTestCoverageLinesData (coverage, skippedCoverage, rootDir, includeExecutableFiles = false) {
1476
+ const coverageMap = getCoverageMap(coverage)
1477
+ const skippedCoverageByFilename = getSkippedCoverageByFilename(skippedCoverage)
1478
+ const coverageByFilename = new Map()
1479
+ const executableFiles = includeExecutableFiles ? [] : undefined
1445
1480
 
1446
- addCoverageFilesToMap(getExecutableFilesFromCoverage(coverage), executableLinesByFile, rootDir)
1447
- addCoverageFilesToMap(getCoveredFilesFromCoverage(coverage), coveredLinesByFile, rootDir)
1448
- addSkippedCoverageToMap(skippedCoverage, coveredLinesByFile)
1481
+ for (const filename of coverageMap.files()) {
1482
+ const fileCoverage = coverageMap.fileCoverageFor(filename)
1483
+ const { coveredBitmap, executableBitmap } = getLineCoverageBitmaps(fileCoverage.getLineCoverage())
1484
+ if (!executableBitmap) continue
1485
+
1486
+ const relativeFilename = rootDir ? getTestSuitePath(filename, rootDir) : filename
1487
+ const existingCoverage = coverageByFilename.get(relativeFilename)
1488
+ if (existingCoverage) {
1489
+ existingCoverage.coveredBitmap = mergeCoverageBitmaps(existingCoverage.coveredBitmap, coveredBitmap)
1490
+ existingCoverage.executableBitmap = mergeCoverageBitmaps(existingCoverage.executableBitmap, executableBitmap)
1491
+ } else {
1492
+ coverageByFilename.set(relativeFilename, { coveredBitmap, executableBitmap })
1493
+ }
1494
+ }
1449
1495
 
1450
1496
  let totalExecutableLines = 0
1451
1497
  let totalCoveredLines = 0
1452
-
1453
- for (const [filename, executableLines] of executableLinesByFile) {
1454
- totalExecutableLines += countBitmapBits(executableLines)
1455
- totalCoveredLines += countCoveredExecutableBits(coveredLinesByFile.get(filename), executableLines)
1498
+ for (const [filename, { coveredBitmap, executableBitmap }] of coverageByFilename) {
1499
+ const skippedBitmap = skippedCoverageByFilename.get(filename)
1500
+ const combinedCoveredBitmap = skippedBitmap
1501
+ ? mergeCoverageBitmaps(coveredBitmap, skippedBitmap)
1502
+ : coveredBitmap
1503
+ totalExecutableLines += countBitmapBits(executableBitmap)
1504
+ totalCoveredLines += countCoveredExecutableBits(combinedCoveredBitmap, executableBitmap)
1505
+ if (executableFiles) {
1506
+ executableFiles.push({ filename, bitmap: executableBitmap })
1507
+ }
1456
1508
  }
1457
1509
 
1458
- return totalExecutableLines === 0 ? 0 : Math.floor((totalCoveredLines / totalExecutableLines) * 10_000) / 100
1510
+ const percentage = totalExecutableLines === 0
1511
+ ? 0
1512
+ : Math.floor((totalCoveredLines / totalExecutableLines) * 10_000) / 100
1513
+ return { percentage, executableFiles }
1514
+ }
1515
+
1516
+ function getTestCoverageLinesPercentage (coverage, skippedCoverage, rootDir) {
1517
+ return getTestCoverageLinesData(coverage, skippedCoverage, rootDir).percentage
1459
1518
  }
1460
1519
 
1461
1520
  function isLineCoveredByBitmap (bitmap, line) {
@@ -1552,19 +1611,77 @@ function fromCoverageMapToCoverage (coverageMap) {
1552
1611
  }, {})
1553
1612
  }
1554
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
+
1555
1675
  // Get the start line of a test by inspecting a given error's stack trace
1556
1676
  function getTestLineStart (err, testSuitePath) {
1557
1677
  if (!err.stack) {
1558
1678
  return null
1559
1679
  }
1560
- // From https://github.com/felixge/node-stack-trace/blob/ba06dcdb50d465cd440d84a563836e293b360427/index.js#L40
1561
1680
  const testFileLine = err.stack.split('\n').find(line => line.includes(testSuitePath))
1562
- try {
1563
- const testFileLineMatch = testFileLine.match(/at (?:(.+?)\s+\()?(?:(.+?):(\d+)(?::(\d+))?|([^)]+))\)?/)
1564
- return Number.parseInt(testFileLineMatch[3], 10) || null
1565
- } catch {
1681
+ if (!testFileLine) {
1566
1682
  return null
1567
1683
  }
1684
+ return parseStackFrameLocation(testFileLine, testSuitePath)?.line || null
1568
1685
  }
1569
1686
 
1570
1687
  // Get the end line of a test by inspecting a given function's source code
@@ -1587,7 +1704,7 @@ function parseAnnotations (annotations) {
1587
1704
  }
1588
1705
  const { type, description } = annotation
1589
1706
  if (type.startsWith('DD_TAGS')) {
1590
- const regex = /\[(.*?)\]/
1707
+ const regex = /^DD_TAGS\[([^\]]*)\]/
1591
1708
  const match = regex.exec(type)
1592
1709
  let tagValue = ''
1593
1710
  if (match) {
@@ -1691,15 +1808,9 @@ function getFileAndLineNumberFromError (error, repositoryRoot) {
1691
1808
  }
1692
1809
 
1693
1810
  const topFrame = frames[topRelevantFrameIndex]
1694
- // Regular expression to match the file path, line number, and column number
1695
- const regex = /\s*at\s+(?:.*\()?(.+):(\d+):(\d+)\)?/
1696
- const match = topFrame.match(regex)
1697
-
1698
- if (match) {
1699
- const filePath = match[1]
1700
- const lineNumber = Number(match[2])
1701
-
1702
- return [filePath, lineNumber, topRelevantFrameIndex]
1811
+ const location = parseStackFrameLocation(topFrame, repositoryRoot)
1812
+ if (location) {
1813
+ return [location.file, location.line, topRelevantFrameIndex]
1703
1814
  }
1704
1815
  return []
1705
1816
  }
@@ -2029,12 +2140,17 @@ function formatTestOptimizationList (items) {
2029
2140
  const more = items.length - shown.length
2030
2141
  const moreSuffix = more > 0 ? `\n ... and ${more} more` : ''
2031
2142
 
2032
- return shown
2033
- .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(
2034
2148
  sanitizeTestOptimizationName(text),
2035
2149
  MAX_TEST_OPTIMIZATION_NAME_LENGTH
2036
- )}${suffix ? ` (${suffix})` : ''}`)
2037
- .join('\n') + moreSuffix
2150
+ )}${suffix ? ` (${suffix})` : ''}`
2151
+ isFirstItem = false
2152
+ }
2153
+ return formattedItems + moreSuffix
2038
2154
  }
2039
2155
 
2040
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