dd-trace 5.103.0 → 5.105.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 (213) hide show
  1. package/LICENSE-3rdparty.csv +90 -102
  2. package/index.d.ts +107 -6
  3. package/package.json +18 -17
  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 +15 -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/cassandra-driver.js +5 -2
  11. package/packages/datadog-instrumentations/src/cucumber.js +181 -35
  12. package/packages/datadog-instrumentations/src/dns.js +54 -18
  13. package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
  14. package/packages/datadog-instrumentations/src/fastify.js +142 -82
  15. package/packages/datadog-instrumentations/src/graphql.js +188 -67
  16. package/packages/datadog-instrumentations/src/grpc/client.js +48 -32
  17. package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
  18. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +1 -1
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  20. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
  21. package/packages/datadog-instrumentations/src/helpers/kafka.js +17 -0
  22. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
  23. package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
  24. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +3 -2
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +19 -6
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +31 -229
  32. package/packages/datadog-instrumentations/src/hono.js +54 -3
  33. package/packages/datadog-instrumentations/src/http/client.js +2 -2
  34. package/packages/datadog-instrumentations/src/http/server.js +9 -4
  35. package/packages/datadog-instrumentations/src/ioredis.js +3 -3
  36. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
  37. package/packages/datadog-instrumentations/src/jest.js +390 -183
  38. package/packages/datadog-instrumentations/src/kafkajs.js +140 -17
  39. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  40. package/packages/datadog-instrumentations/src/memcached.js +2 -1
  41. package/packages/datadog-instrumentations/src/mocha/main.js +399 -107
  42. package/packages/datadog-instrumentations/src/mocha/utils.js +48 -8
  43. package/packages/datadog-instrumentations/src/mongodb-core.js +1 -1
  44. package/packages/datadog-instrumentations/src/mongoose.js +10 -12
  45. package/packages/datadog-instrumentations/src/mysql.js +2 -2
  46. package/packages/datadog-instrumentations/src/mysql2.js +1 -1
  47. package/packages/datadog-instrumentations/src/nats.js +182 -0
  48. package/packages/datadog-instrumentations/src/nyc.js +38 -1
  49. package/packages/datadog-instrumentations/src/openai.js +33 -18
  50. package/packages/datadog-instrumentations/src/oracledb.js +6 -1
  51. package/packages/datadog-instrumentations/src/pg.js +1 -1
  52. package/packages/datadog-instrumentations/src/pino.js +17 -5
  53. package/packages/datadog-instrumentations/src/playwright.js +537 -297
  54. package/packages/datadog-instrumentations/src/router.js +80 -34
  55. package/packages/datadog-instrumentations/src/stripe.js +1 -1
  56. package/packages/datadog-instrumentations/src/vitest.js +246 -149
  57. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
  58. package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
  59. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
  60. package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
  61. package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
  62. package/packages/datadog-plugin-bunyan/src/index.js +28 -0
  63. package/packages/datadog-plugin-cucumber/src/index.js +17 -3
  64. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +223 -45
  65. package/packages/datadog-plugin-cypress/src/support.js +69 -1
  66. package/packages/datadog-plugin-dns/src/lookup.js +8 -6
  67. package/packages/datadog-plugin-elasticsearch/src/index.js +28 -8
  68. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
  69. package/packages/datadog-plugin-graphql/src/execute.js +2 -0
  70. package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
  71. package/packages/datadog-plugin-graphql/src/utils.js +4 -1
  72. package/packages/datadog-plugin-http/src/server.js +40 -15
  73. package/packages/datadog-plugin-jest/src/index.js +11 -3
  74. package/packages/datadog-plugin-jest/src/util.js +15 -8
  75. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
  76. package/packages/datadog-plugin-kafkajs/src/producer.js +35 -0
  77. package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
  78. package/packages/datadog-plugin-mocha/src/index.js +19 -4
  79. package/packages/datadog-plugin-mongodb-core/src/index.js +311 -35
  80. package/packages/datadog-plugin-nats/src/consumer.js +43 -0
  81. package/packages/datadog-plugin-nats/src/index.js +20 -0
  82. package/packages/datadog-plugin-nats/src/producer.js +62 -0
  83. package/packages/datadog-plugin-nats/src/util.js +33 -0
  84. package/packages/datadog-plugin-next/src/index.js +5 -3
  85. package/packages/datadog-plugin-openai/src/tracing.js +15 -2
  86. package/packages/datadog-plugin-oracledb/src/index.js +13 -2
  87. package/packages/datadog-plugin-pino/src/index.js +42 -0
  88. package/packages/datadog-plugin-playwright/src/index.js +4 -4
  89. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
  90. package/packages/datadog-plugin-redis/src/index.js +37 -2
  91. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  92. package/packages/datadog-plugin-router/src/index.js +33 -44
  93. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  94. package/packages/datadog-plugin-undici/src/index.js +19 -0
  95. package/packages/datadog-plugin-vitest/src/index.js +24 -20
  96. package/packages/datadog-plugin-winston/src/index.js +30 -0
  97. package/packages/datadog-shimmer/src/shimmer.js +49 -21
  98. package/packages/dd-trace/src/aiguard/index.js +1 -1
  99. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  100. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  101. package/packages/dd-trace/src/appsec/blocking.js +2 -2
  102. package/packages/dd-trace/src/appsec/index.js +11 -4
  103. package/packages/dd-trace/src/appsec/reporter.js +24 -11
  104. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  105. package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
  106. package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
  107. package/packages/dd-trace/src/baggage.js +7 -1
  108. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
  109. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
  110. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -1
  111. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +5 -3
  112. package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
  113. package/packages/dd-trace/src/config/generated-config-types.d.ts +7 -2
  114. package/packages/dd-trace/src/config/supported-configurations.json +36 -8
  115. package/packages/dd-trace/src/crashtracking/crashtracker.js +15 -3
  116. package/packages/dd-trace/src/datastreams/context.js +4 -2
  117. package/packages/dd-trace/src/datastreams/writer.js +2 -4
  118. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
  119. package/packages/dd-trace/src/encode/0.4.js +124 -108
  120. package/packages/dd-trace/src/encode/0.5.js +114 -26
  121. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +57 -42
  122. package/packages/dd-trace/src/encode/agentless-json.js +4 -2
  123. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
  124. package/packages/dd-trace/src/encode/span-stats.js +16 -16
  125. package/packages/dd-trace/src/encode/tags-processors.js +16 -0
  126. package/packages/dd-trace/src/exporters/common/agents.js +3 -1
  127. package/packages/dd-trace/src/exporters/common/request.js +3 -1
  128. package/packages/dd-trace/src/id.js +17 -4
  129. package/packages/dd-trace/src/lambda/handler.js +2 -4
  130. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  131. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  132. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  133. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
  134. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  135. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
  136. package/packages/dd-trace/src/llmobs/sdk.js +10 -16
  137. package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
  138. package/packages/dd-trace/src/llmobs/tagger.js +9 -1
  139. package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
  140. package/packages/dd-trace/src/llmobs/util.js +66 -3
  141. package/packages/dd-trace/src/log/index.js +1 -1
  142. package/packages/dd-trace/src/log/writer.js +3 -1
  143. package/packages/dd-trace/src/msgpack/chunk.js +394 -10
  144. package/packages/dd-trace/src/msgpack/index.js +96 -2
  145. package/packages/dd-trace/src/noop/span.js +3 -1
  146. package/packages/dd-trace/src/openfeature/encoding.js +70 -0
  147. package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
  148. package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
  149. package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
  150. package/packages/dd-trace/src/openfeature/writers/exposures.js +51 -20
  151. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
  152. package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
  153. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  154. package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
  155. package/packages/dd-trace/src/opentracing/propagation/text_map.js +62 -67
  156. package/packages/dd-trace/src/opentracing/span.js +59 -19
  157. package/packages/dd-trace/src/opentracing/span_context.js +49 -0
  158. package/packages/dd-trace/src/plugins/apollo.js +3 -1
  159. package/packages/dd-trace/src/plugins/ci_plugin.js +23 -33
  160. package/packages/dd-trace/src/plugins/database.js +7 -6
  161. package/packages/dd-trace/src/plugins/index.js +4 -0
  162. package/packages/dd-trace/src/plugins/log_injection.js +56 -0
  163. package/packages/dd-trace/src/plugins/log_plugin.js +3 -46
  164. package/packages/dd-trace/src/plugins/outbound.js +1 -1
  165. package/packages/dd-trace/src/plugins/plugin.js +15 -17
  166. package/packages/dd-trace/src/plugins/tracing.js +48 -8
  167. package/packages/dd-trace/src/plugins/util/git.js +3 -1
  168. package/packages/dd-trace/src/plugins/util/test.js +318 -13
  169. package/packages/dd-trace/src/plugins/util/web.js +89 -64
  170. package/packages/dd-trace/src/priority_sampler.js +2 -2
  171. package/packages/dd-trace/src/profiling/profiler.js +2 -2
  172. package/packages/dd-trace/src/profiling/profilers/wall.js +10 -4
  173. package/packages/dd-trace/src/sampling_rule.js +7 -7
  174. package/packages/dd-trace/src/scope.js +7 -5
  175. package/packages/dd-trace/src/service-naming/extra-services.js +14 -0
  176. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
  177. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  178. package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
  179. package/packages/dd-trace/src/span_format.js +190 -58
  180. package/packages/dd-trace/src/spanleak.js +1 -1
  181. package/packages/dd-trace/src/standalone/index.js +3 -3
  182. package/packages/dd-trace/src/tagger.js +0 -2
  183. package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
  184. package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
  185. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  186. package/vendor/dist/protobufjs/index.js +1 -1
  187. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  188. package/packages/dd-trace/src/msgpack/encoder.js +0 -308
  189. package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
  190. package/vendor/dist/opentracing/LICENSE +0 -201
  191. package/vendor/dist/opentracing/binary_carrier.d.ts +0 -11
  192. package/vendor/dist/opentracing/constants.d.ts +0 -61
  193. package/vendor/dist/opentracing/examples/demo/demo.d.ts +0 -2
  194. package/vendor/dist/opentracing/ext/tags.d.ts +0 -90
  195. package/vendor/dist/opentracing/functions.d.ts +0 -20
  196. package/vendor/dist/opentracing/global_tracer.d.ts +0 -14
  197. package/vendor/dist/opentracing/index.d.ts +0 -12
  198. package/vendor/dist/opentracing/index.js +0 -1
  199. package/vendor/dist/opentracing/mock_tracer/index.d.ts +0 -5
  200. package/vendor/dist/opentracing/mock_tracer/mock_context.d.ts +0 -13
  201. package/vendor/dist/opentracing/mock_tracer/mock_report.d.ts +0 -16
  202. package/vendor/dist/opentracing/mock_tracer/mock_span.d.ts +0 -50
  203. package/vendor/dist/opentracing/mock_tracer/mock_tracer.d.ts +0 -26
  204. package/vendor/dist/opentracing/noop.d.ts +0 -8
  205. package/vendor/dist/opentracing/reference.d.ts +0 -33
  206. package/vendor/dist/opentracing/span.d.ts +0 -147
  207. package/vendor/dist/opentracing/span_context.d.ts +0 -26
  208. package/vendor/dist/opentracing/test/api_compatibility.d.ts +0 -16
  209. package/vendor/dist/opentracing/test/mocktracer_implemenation.d.ts +0 -3
  210. package/vendor/dist/opentracing/test/noop_implementation.d.ts +0 -4
  211. package/vendor/dist/opentracing/test/opentracing_api.d.ts +0 -3
  212. package/vendor/dist/opentracing/test/unittest.d.ts +0 -2
  213. package/vendor/dist/opentracing/tracer.d.ts +0 -127
@@ -503,13 +503,38 @@ function getTestFinishInfo (test, status, config, error) {
503
503
  }
504
504
  }
505
505
 
506
- function getOnTestEndHandler (config) {
506
+ function getOnTestEndHandler (config, finalAttemptHandlers) {
507
507
  return async function (test) {
508
508
  if (test._ddShouldSkipEfdRetry) {
509
509
  return
510
510
  }
511
511
  const ctx = getTestContext(test)
512
512
  const status = getTestStatus(test)
513
+ const shouldFinishTest = ctx && (!getAfterEachHooks(test).length || (test._ddIsDisabled && !test._ddIsAttemptToFix))
514
+ let testFinishInfo
515
+ let isFinalAttempt = false
516
+
517
+ // If there are afterEach to be run, we don't finish the test yet.
518
+ // Disabled tests (marked pending by us) are finished immediately without waiting for afterEach hooks.
519
+ // In older mocha versions, pending tests don't run afterEach hooks, so we can't rely on
520
+ // getOnHookEndHandler to finish the test. This mirrors Jest's approach where the skip handler
521
+ // directly sets finalStatus without waiting for hooks
522
+ if (!ctx && test.isPending()) {
523
+ test._ddIsFinalAttempt = true
524
+ isFinalAttempt = true
525
+ }
526
+
527
+ if (shouldFinishTest) {
528
+ testFinishInfo = getTestFinishInfo(test, status, config, ctx.err || test.err)
529
+ if (testFinishInfo.finalStatus !== undefined) {
530
+ test._ddIsFinalAttempt = true
531
+ isFinalAttempt = true
532
+ }
533
+ }
534
+
535
+ if (isFinalAttempt) {
536
+ finalAttemptHandlers?.onStart?.(test)
537
+ }
513
538
 
514
539
  // After finishing it might take a bit for the snapshot to be handled.
515
540
  // This means that tests retried with DI are BREAKPOINT_HIT_GRACE_PERIOD_MS slower at least.
@@ -521,13 +546,7 @@ function getOnTestEndHandler (config) {
521
546
  })
522
547
  }
523
548
 
524
- // If there are afterEach to be run, we don't finish the test yet.
525
- // Disabled tests (marked pending by us) are finished immediately without waiting for afterEach hooks.
526
- // In older mocha versions, pending tests don't run afterEach hooks, so we can't rely on
527
- // getOnHookEndHandler to finish the test. This mirrors Jest's approach where the skip handler
528
- // directly sets finalStatus without waiting for hooks
529
- if (ctx && (!getAfterEachHooks(test).length || (test._ddIsDisabled && !test._ddIsAttemptToFix))) {
530
- const testFinishInfo = getTestFinishInfo(test, status, config, ctx.err || test.err)
549
+ if (shouldFinishTest) {
531
550
  testFinishCh.publish({
532
551
  status,
533
552
  hasBeenRetried: isMochaRetry(test),
@@ -536,6 +555,10 @@ function getOnTestEndHandler (config) {
536
555
  ...ctx.currentStore,
537
556
  })
538
557
  }
558
+
559
+ if (isFinalAttempt) {
560
+ finalAttemptHandlers?.onFinish?.(test)
561
+ }
539
562
  }
540
563
  }
541
564
 
@@ -552,6 +575,9 @@ function getOnHookEndHandler (config) {
552
575
  // skip to avoid double-publishing
553
576
  if (ctx && (!test._ddIsDisabled || test._ddIsAttemptToFix)) {
554
577
  const testFinishInfo = getTestFinishInfo(test, status, config, ctx.err || test.err)
578
+ if (testFinishInfo.finalStatus !== undefined) {
579
+ test._ddIsFinalAttempt = true
580
+ }
555
581
  testFinishCh.publish({
556
582
  status,
557
583
  hasBeenRetried: isMochaRetry(test),
@@ -583,6 +609,20 @@ function getOnFailHandler (isMain, config) {
583
609
  testContext.err = err
584
610
  errorCh.runStores(testContext, () => {})
585
611
  const testFinishInfo = getTestFinishInfo(test, 'fail', config, err)
612
+ // ATR never retries hook failures: this.retries(N) is set in runnableWrapper
613
+ // which only runs when the test function executes — hooks bypass that path,
614
+ // so _retries stays at -1 and getIsLastRetry returns false, leaving finalStatus
615
+ // undefined. We must also mark the attempt final when no clone-based retry
616
+ // mechanism (EFD original, EFD clone, ATF) has queued further attempts.
617
+ const noCloneRetries = !test._ddIsEfdRetry &&
618
+ !((test._ddIsNew || test._ddIsModified) && config.isEarlyFlakeDetectionEnabled) &&
619
+ !test._ddIsAttemptToFix
620
+ if (testFinishInfo.finalStatus !== undefined || noCloneRetries) {
621
+ test._ddIsFinalAttempt = true
622
+ }
623
+ // test.state is never set to 'failed' for hook failures (Mocha marks the hook,
624
+ // not the test). Flag it so finishRootSuiteForFile can compute the correct status.
625
+ test._ddHookFailed = true
586
626
  testFinishCh.publish({
587
627
  status: 'fail',
588
628
  hasBeenRetried: isMochaRetry(test),
@@ -174,7 +174,7 @@ function instrument (operation, command, instance, args, server, ns, ops, option
174
174
  name,
175
175
  }
176
176
  return startCh.runStores(ctx, () => {
177
- args[index] = shimmer.wrapFunction(callback, callback => function (err, res) {
177
+ args[index] = shimmer.wrapCallback(callback, callback => function (err, res) {
178
178
  if (err) {
179
179
  ctx.error = err
180
180
  errorCh.publish(ctx)
@@ -125,21 +125,19 @@ addHook({
125
125
  const resolve = args[0]
126
126
  const reject = args[1]
127
127
 
128
- args[0] = shimmer.wrapFunction(resolve, resolve => function wrappedResolve (...args) {
129
- finishCh.publish(ctx)
130
-
131
- if (resolve) {
128
+ if (typeof resolve === 'function') {
129
+ args[0] = shimmer.wrapCallback(resolve, resolve => function wrappedResolve (...args) {
130
+ finishCh.publish(ctx)
132
131
  return resolve.apply(this, args)
133
- }
134
- })
135
-
136
- args[1] = shimmer.wrapFunction(reject, reject => function wrappedReject (...args) {
137
- finishCh.publish(ctx)
132
+ })
133
+ }
138
134
 
139
- if (reject) {
135
+ if (typeof reject === 'function') {
136
+ args[1] = shimmer.wrapCallback(reject, reject => function wrappedReject (...args) {
137
+ finishCh.publish(ctx)
140
138
  return reject.apply(this, args)
141
- }
142
- })
139
+ })
140
+ }
143
141
 
144
142
  return originalThen.apply(this, args)
145
143
  }
@@ -29,7 +29,7 @@ addHook({ name: 'mysql', file: 'lib/Connection.js', versions: ['>=2'] }, Connect
29
29
 
30
30
  if (res._callback) {
31
31
  const cb = res._callback
32
- res._callback = shimmer.wrapFunction(cb, cb => function (error, result) {
32
+ res._callback = shimmer.wrapCallback(cb, cb => function (error, result) {
33
33
  if (error) {
34
34
  ctx.error = error
35
35
  errorCh.publish(ctx)
@@ -86,7 +86,7 @@ addHook({ name: 'mysql', file: 'lib/Pool.js', versions: ['>=2'] }, Pool => {
86
86
  return startPoolQueryCh.runStores(ctx, () => {
87
87
  const cb = args[args.length - 1]
88
88
  if (typeof cb === 'function') {
89
- args[args.length - 1] = shimmer.wrapFunction(cb, cb => function (...args) {
89
+ args[args.length - 1] = shimmer.wrapCallback(cb, cb => function (...args) {
90
90
  return finishPoolQueryCh.runStores(ctx, cb, this, ...args)
91
91
  })
92
92
  }
@@ -176,7 +176,7 @@ function wrapConnection (Connection, version) {
176
176
  if (typeof this.onResult === 'function') {
177
177
  const onResult = this.onResult
178
178
 
179
- this.onResult = shimmer.wrapFunction(onResult, onResult => function (error) {
179
+ this.onResult = shimmer.wrapCallback(onResult, onResult => function (error) {
180
180
  if (error) {
181
181
  ctx.error = error
182
182
  errorCh.publish(ctx)
@@ -0,0 +1,182 @@
1
+ 'use strict'
2
+
3
+ // Shimmer required: NATS consumer paths need argument modification — the user's
4
+ // `opts.callback` is wrapped before being handed to SubscriptionImpl, and the
5
+ // returned subscription's async iterator is wrapped so iterator-style consumers
6
+ // get receive events. Orchestrion can only wrap method calls, not arguments
7
+ // or returned iterables.
8
+
9
+ const shimmer = require('../../datadog-shimmer')
10
+ const { addHook, channel } = require('./helpers/instrument')
11
+
12
+ const publishStartCh = channel('apm:nats:publish:start')
13
+ const publishFinishCh = channel('apm:nats:publish:finish')
14
+ const publishErrorCh = channel('apm:nats:publish:error')
15
+
16
+ const consumeStartCh = channel('apm:nats:consume:start')
17
+ const consumeFinishCh = channel('apm:nats:consume:finish')
18
+ const consumeErrorCh = channel('apm:nats:consume:error')
19
+
20
+ // Tracks connections that are currently inside a `request`/`requestMany` call
21
+ // so the nested `this.publish(...)` they issue short-circuits without creating
22
+ // a second producer span (the outer request wrap already created one and
23
+ // injected headers — the inner publish would double-count it). A WeakSet avoids
24
+ // changing the shape of the user's connection object.
25
+ const requestsInFlight = new WeakSet()
26
+
27
+ // Captured from the `lib/headers.js` hook below. The nats-core package always
28
+ // imports `./headers` from `lib/nats.js`, so by the time we wrap `publish` the
29
+ // reference is set. No defensive checks needed at call sites.
30
+ let createHeaders
31
+
32
+ addHook({ name: '@nats-io/nats-core', versions: ['>=3.0.0'], file: 'lib/headers.js' }, exports => {
33
+ createHeaders = exports.headers
34
+ return exports
35
+ })
36
+
37
+ // transport-node re-exports nats-core internals — the passthrough hook ensures
38
+ // the package name is registered so `withVersions('nats', '@nats-io/transport-node', ...)`
39
+ // can resolve it in plugin tests.
40
+ addHook({ name: '@nats-io/transport-node', versions: ['>=3.0.0'] }, exports => exports)
41
+
42
+ function wrapSyncProducer (original, type) {
43
+ return function (subject, data, options) {
44
+ if (!publishStartCh.hasSubscribers) {
45
+ return original.apply(this, arguments)
46
+ }
47
+ const opts = { ...options }
48
+ const ctx = { type, subject, data, options: opts, connection: this, createHeaders }
49
+ return publishStartCh.runStores(ctx, () => {
50
+ try {
51
+ return original.call(this, subject, data, opts)
52
+ } catch (err) {
53
+ ctx.error = err
54
+ publishErrorCh.publish(ctx)
55
+ throw err
56
+ } finally {
57
+ publishFinishCh.publish(ctx)
58
+ }
59
+ })
60
+ }
61
+ }
62
+
63
+ // publish is also wrapped by `wrapSyncProducer`, but request/requestMany call
64
+ // `this.publish(...)` internally. Set a marker on the connection so the inner
65
+ // publish wrap short-circuits — see `wrapPublish`.
66
+ function wrapAsyncProducer (original, type) {
67
+ return function (subject, data, options) {
68
+ if (!publishStartCh.hasSubscribers) {
69
+ return original.apply(this, arguments)
70
+ }
71
+ const opts = { ...options }
72
+ const ctx = { type, subject, data, options: opts, connection: this, createHeaders }
73
+ return publishStartCh.runStores(ctx, () => {
74
+ requestsInFlight.add(this)
75
+ let promise
76
+ try {
77
+ // `request`/`requestMany` never throw synchronously — they wrap their own
78
+ // input validation in a try/catch that returns `Promise.reject`.
79
+ promise = original.call(this, subject, data, opts)
80
+ } finally {
81
+ // The nested `this.publish(...)` runs during the synchronous body of
82
+ // request/requestMany, so clearing the marker as soon as the call
83
+ // returns is sufficient — the promise resolution happens later.
84
+ requestsInFlight.delete(this)
85
+ }
86
+ return Promise.resolve(promise).then(
87
+ result => {
88
+ ctx.result = result
89
+ publishFinishCh.publish(ctx)
90
+ return result
91
+ },
92
+ err => {
93
+ ctx.error = err
94
+ publishErrorCh.publish(ctx)
95
+ publishFinishCh.publish(ctx)
96
+ throw err
97
+ }
98
+ )
99
+ })
100
+ }
101
+ }
102
+
103
+ function wrapPublish (original) {
104
+ const wrapped = wrapSyncProducer(original, 'publish')
105
+ return function (subject, data, options) {
106
+ // Called from inside request/requestMany — the outer wrap already produced
107
+ // a span and injected headers; running the inner wrap would double-count.
108
+ if (requestsInFlight.has(this)) {
109
+ return original.apply(this, arguments)
110
+ }
111
+ return wrapped.apply(this, arguments)
112
+ }
113
+ }
114
+
115
+ function wrapSubscribeCallback (userCallback, subject, connection) {
116
+ return function (err, message) {
117
+ if (!message || err) {
118
+ return userCallback.call(this, err, message)
119
+ }
120
+ const ctx = { subject, message, connection }
121
+ return consumeStartCh.runStores(ctx, () => {
122
+ try {
123
+ return userCallback.call(this, err, message)
124
+ } catch (e) {
125
+ ctx.error = e
126
+ consumeErrorCh.publish(ctx)
127
+ throw e
128
+ } finally {
129
+ consumeFinishCh.publish(ctx)
130
+ }
131
+ })
132
+ }
133
+ }
134
+
135
+ // Iterator-style consumers don't expose a delivery callback we can wrap, so
136
+ // the consume span represents the moment of receipt only — it starts and
137
+ // finishes before the value is yielded to user code, and the user's loop
138
+ // body is not parented under the span.
139
+ function wrapAsyncIteratorFactory (asyncIterator, subject, connection) {
140
+ return function () {
141
+ const iterator = asyncIterator.apply(this, arguments)
142
+ iterator.next = shimmer.wrapCallback(iterator.next, next => function () {
143
+ return next.apply(this, arguments).then(result => {
144
+ if (result && !result.done && result.value) {
145
+ const ctx = { subject, message: result.value, connection }
146
+ consumeStartCh.runStores(ctx, () => {
147
+ consumeFinishCh.publish(ctx)
148
+ })
149
+ }
150
+ return result
151
+ })
152
+ })
153
+ return iterator
154
+ }
155
+ }
156
+
157
+ addHook({ name: '@nats-io/nats-core', versions: ['>=3.0.0'], file: 'lib/nats.js' }, exports => {
158
+ const proto = exports.NatsConnectionImpl.prototype
159
+
160
+ shimmer.wrap(proto, 'publish', wrapPublish)
161
+ shimmer.wrap(proto, 'request', request => wrapAsyncProducer(request, 'request'))
162
+ shimmer.wrap(proto, 'requestMany', requestMany => wrapAsyncProducer(requestMany, 'requestMany'))
163
+
164
+ shimmer.wrap(proto, 'subscribe', subscribe => function (subject, opts) {
165
+ if (!consumeStartCh.hasSubscribers) {
166
+ return subscribe.apply(this, arguments)
167
+ }
168
+
169
+ const userOpts = opts ?? {}
170
+ if (typeof userOpts.callback === 'function') {
171
+ arguments[1] = { ...userOpts, callback: wrapSubscribeCallback(userOpts.callback, subject, this) }
172
+ return subscribe.apply(this, arguments)
173
+ }
174
+
175
+ const sub = subscribe.apply(this, arguments)
176
+ shimmer.wrap(sub, Symbol.asyncIterator, asyncIterator =>
177
+ wrapAsyncIteratorFactory(asyncIterator, subject, this))
178
+ return sub
179
+ })
180
+
181
+ return exports
182
+ })
@@ -2,7 +2,12 @@
2
2
 
3
3
  const shimmer = require('../../datadog-shimmer')
4
4
  const { getEnvironmentVariable } = require('../../dd-trace/src/config/helper')
5
- const { setupSettingsCachePath } = require('../../dd-trace/src/ci-visibility/test-optimization-cache')
5
+ const {
6
+ readCoverageBackfillFromCache,
7
+ readCoverageBackfillRootDirFromCache,
8
+ setupSettingsCachePath,
9
+ } = require('../../dd-trace/src/ci-visibility/test-optimization-cache')
10
+ const { applySkippedCoverageToCoverage } = require('../../dd-trace/src/plugins/util/test')
6
11
  const { addHook, channel } = require('./helpers/instrument')
7
12
 
8
13
  const codeCoverageWrapCh = channel('ci:nyc:wrap')
@@ -16,6 +21,38 @@ addHook({
16
21
  // when dd-trace fetches library configuration
17
22
  setupSettingsCachePath()
18
23
 
24
+ if (nycPackage.prototype.getCoverageMapFromAllCoverageFiles) {
25
+ // Some test frameworks receive skipped-suite coverage in the test process, but nyc merges reports later in the nyc
26
+ // process. Reuse the settings cache path as the process handoff so nyc can backfill skipped files before reporting.
27
+ shimmer.wrap(
28
+ nycPackage.prototype,
29
+ 'getCoverageMapFromAllCoverageFiles',
30
+ getCoverageMapFromAllCoverageFiles => function (...args) {
31
+ const coverageMap = getCoverageMapFromAllCoverageFiles.apply(this, args)
32
+ const applyCoverageBackfill = (resolvedCoverageMap) => {
33
+ try {
34
+ if (!resolvedCoverageMap) {
35
+ return resolvedCoverageMap
36
+ }
37
+ applySkippedCoverageToCoverage(
38
+ resolvedCoverageMap,
39
+ readCoverageBackfillFromCache(),
40
+ readCoverageBackfillRootDirFromCache() || this.cwd
41
+ )
42
+ } catch {
43
+ // Do not break nyc's report generation if the cached backfill is stale or malformed.
44
+ }
45
+ return resolvedCoverageMap
46
+ }
47
+
48
+ if (coverageMap && typeof coverageMap.then === 'function') {
49
+ return coverageMap.then(applyCoverageBackfill)
50
+ }
51
+ return applyCoverageBackfill(coverageMap)
52
+ }
53
+ )
54
+ }
55
+
19
56
  // `wrap` is an async function
20
57
  shimmer.wrap(nycPackage.prototype, 'wrap', wrap => function (...args) {
21
58
  // Only relevant if the config `all` is set to true (for untested code coverage)
@@ -3,6 +3,7 @@
3
3
  const dc = require('dc-polyfill')
4
4
  const shimmer = require('../../datadog-shimmer')
5
5
  const { addHook } = require('./helpers/instrument')
6
+ const aiGuard = require('./helpers/openai-ai-guard')
6
7
 
7
8
  const ch = dc.tracingChannel('apm:openai:request')
8
9
  const onStreamedChunkCh = dc.channel('apm:openai:request:chunk')
@@ -216,15 +217,20 @@ for (const extension of extensions) {
216
217
 
217
218
  for (const methodName of methods) {
218
219
  shimmer.wrap(targetPrototype, methodName, methodFn => function (...args) {
219
- if (!ch.start.hasSubscribers) {
220
+ if (!ch.start.hasSubscribers && !aiGuard.hasSubscribers()) {
220
221
  return methodFn.apply(this, args)
221
222
  }
222
-
223
223
  // The OpenAI library lets you set `stream: true` on the options arg to any method
224
224
  // However, we only want to handle streamed responses in specific cases
225
225
  // chat.completions and completions
226
226
  const stream = streamedResponse && getOption(args, 'stream', false)
227
227
 
228
+ const guard = aiGuard.createGuard(baseResource, args[0], stream)
229
+
230
+ if (!ch.start.hasSubscribers && !guard) {
231
+ return methodFn.apply(this, args)
232
+ }
233
+
228
234
  const client = this._client || this.client
229
235
 
230
236
  const ctx = {
@@ -249,7 +255,7 @@ for (const extension of extensions) {
249
255
  const parsedPromise = origApiPromParse.apply(this, args)
250
256
  .then(body => Promise.all([this.responsePromise, body]))
251
257
 
252
- return handleUnwrappedAPIPromise(parsedPromise, ctx, stream)
258
+ return handleUnwrappedAPIPromise(parsedPromise, ctx, stream, guard)
253
259
  })
254
260
 
255
261
  return unwrappedPromise
@@ -262,9 +268,11 @@ for (const extension of extensions) {
262
268
  const parsedPromise = origApiPromParse.apply(this, args)
263
269
  .then(body => Promise.all([this.responsePromise, body]))
264
270
 
265
- return handleUnwrappedAPIPromise(parsedPromise, ctx, stream)
271
+ return handleUnwrappedAPIPromise(parsedPromise, ctx, stream, guard)
266
272
  })
267
273
 
274
+ if (guard) aiGuard.wrapAsResponse(apiProm, guard)
275
+
268
276
  ch.end.publish(ctx)
269
277
 
270
278
  return apiProm
@@ -276,8 +284,10 @@ for (const extension of extensions) {
276
284
  }
277
285
  }
278
286
 
279
- function handleUnwrappedAPIPromise (apiProm, ctx, stream) {
280
- return apiProm
287
+ function handleUnwrappedAPIPromise (apiProm, ctx, stream, guard) {
288
+ const guardedApiProm = guard ? aiGuard.gateParse(apiProm, guard) : apiProm
289
+
290
+ return guardedApiProm
281
291
  .then(([{ response, options }, body]) => {
282
292
  if (stream) {
283
293
  if (body.iterator) {
@@ -287,22 +297,27 @@ function handleUnwrappedAPIPromise (apiProm, ctx, stream) {
287
297
  body.response.body, Symbol.asyncIterator, wrapStreamIterator(response, options, ctx)
288
298
  )
289
299
  }
290
- } else {
291
- finish(ctx, {
292
- headers: response.headers,
293
- data: body,
294
- request: {
295
- path: response.url,
296
- method: options.method,
297
- },
298
- })
300
+ return body
299
301
  }
300
302
 
301
- return body
303
+ finish(ctx, {
304
+ headers: response.headers,
305
+ data: body,
306
+ request: {
307
+ path: response.url,
308
+ method: options.method,
309
+ },
310
+ })
311
+
312
+ if (!guard) return body
313
+
314
+ return aiGuard.evaluateOutput(guard, body).then(() => body)
302
315
  })
303
316
  .catch(error => {
304
- finish(ctx, undefined, error)
305
-
317
+ // ctx.result is set inside finish(); if absent, finish never ran (sync throw in
318
+ // success branch, before-model block, or openai error) — record the error now.
319
+ // If finish already ran successfully (after-model block), don't double-publish.
320
+ if (!ctx.result) finish(ctx, undefined, error)
306
321
  throw error
307
322
  })
308
323
  }
@@ -22,7 +22,7 @@ function finish (ctx) {
22
22
 
23
23
  addHook({ name: 'oracledb', versions: ['>=5'], file: 'lib/oracledb.js' }, oracledb => {
24
24
  shimmer.wrap(oracledb.Connection.prototype, 'execute', execute => {
25
- return function wrappedExecute (dbQuery, ...args) {
25
+ return function wrappedExecute (dbQuery) {
26
26
  if (!startChannel.hasSubscribers) {
27
27
  return execute.apply(this, arguments)
28
28
  }
@@ -72,6 +72,11 @@ addHook({ name: 'oracledb', versions: ['>=5'], file: 'lib/oracledb.js' }, oracle
72
72
  }
73
73
 
74
74
  return startChannel.runStores(ctx, () => {
75
+ // bindStart is skipped when tracing is suppressed (legacy store is `noop`),
76
+ // leaving ctx.injected unset — do not overwrite the caller's SQL argument.
77
+ if (ctx.injected !== undefined) {
78
+ arguments[0] = ctx.injected
79
+ }
75
80
  try {
76
81
  let result = execute.apply(this, arguments)
77
82
 
@@ -193,7 +193,7 @@ function wrapPoolQuery (query) {
193
193
  }
194
194
 
195
195
  if (typeof cb === 'function') {
196
- args[args.length - 1] = shimmer.wrapFunction(cb, cb => function (...args) {
196
+ args[args.length - 1] = shimmer.wrapCallback(cb, cb => function (...args) {
197
197
  finish(ctx)
198
198
  return cb.apply(this, args)
199
199
  })
@@ -6,7 +6,16 @@ const {
6
6
  addHook,
7
7
  } = require('./helpers/instrument')
8
8
 
9
+ /**
10
+ * @param {string} symbol
11
+ * @param {(original: Function) => Function} wrapper
12
+ * @param {Function} pino
13
+ */
9
14
  function wrapPino (symbol, wrapper, pino) {
15
+ /**
16
+ * @param {unknown[]} args
17
+ * @returns {unknown}
18
+ */
10
19
  return function pinoWithTrace (...args) {
11
20
  const instance = pino.apply(this, args)
12
21
 
@@ -22,15 +31,18 @@ function wrapPino (symbol, wrapper, pino) {
22
31
  }
23
32
 
24
33
  function wrapAsJson (asJson) {
25
- const ch = channel('apm:pino:log')
34
+ const jsonCh = channel('apm:pino:log:json')
26
35
  return function asJsonWithTrace (obj, msg, num, time) {
27
36
  obj = arguments[0] = obj || {}
28
37
 
29
- const payload = { message: obj }
30
- ch.publish(payload)
31
- arguments[0] = payload.message
38
+ // Caller-provided `dd` wins -- skip the splice so a bespoke `dd` survives.
39
+ if (!jsonCh.hasSubscribers || Object.hasOwn(obj, 'dd')) {
40
+ return asJson.apply(this, arguments)
41
+ }
32
42
 
33
- return asJson.apply(this, arguments)
43
+ const payload = { line: asJson.apply(this, arguments) }
44
+ jsonCh.publish(payload)
45
+ return payload.line
34
46
  }
35
47
  }
36
48