dd-trace 5.24.0 → 5.26.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 (138) hide show
  1. package/LICENSE-3rdparty.csv +3 -0
  2. package/index.d.ts +345 -8
  3. package/init.js +60 -47
  4. package/package.json +16 -7
  5. package/packages/datadog-code-origin/index.js +4 -4
  6. package/packages/datadog-core/index.js +1 -3
  7. package/packages/datadog-core/src/storage.js +21 -0
  8. package/packages/datadog-core/src/utils/src/parse-tags.js +33 -0
  9. package/packages/datadog-esbuild/index.js +4 -2
  10. package/packages/datadog-instrumentations/src/amqplib.js +65 -5
  11. package/packages/datadog-instrumentations/src/child_process.js +135 -27
  12. package/packages/datadog-instrumentations/src/express.js +1 -1
  13. package/packages/datadog-instrumentations/src/handlebars.js +40 -0
  14. package/packages/datadog-instrumentations/src/helpers/hooks.js +5 -0
  15. package/packages/datadog-instrumentations/src/helpers/register.js +9 -0
  16. package/packages/datadog-instrumentations/src/jest.js +6 -2
  17. package/packages/datadog-instrumentations/src/kafkajs.js +123 -63
  18. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -2
  19. package/packages/datadog-instrumentations/src/multer.js +37 -0
  20. package/packages/datadog-instrumentations/src/openai.js +2 -2
  21. package/packages/datadog-instrumentations/src/pug.js +23 -0
  22. package/packages/datadog-instrumentations/src/router.js +2 -3
  23. package/packages/datadog-instrumentations/src/url.js +84 -0
  24. package/packages/datadog-instrumentations/src/utils/src/extract-package-and-module-path.js +7 -4
  25. package/packages/datadog-plugin-amqplib/src/consumer.js +6 -5
  26. package/packages/datadog-plugin-aws-sdk/src/base.js +5 -0
  27. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -0
  28. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +10 -7
  29. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +35 -0
  30. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +11 -9
  31. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +59 -45
  32. package/packages/datadog-plugin-cypress/src/support.js +1 -0
  33. package/packages/datadog-plugin-fastify/src/code_origin.js +2 -2
  34. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +10 -2
  35. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +8 -0
  36. package/packages/datadog-plugin-grpc/src/client.js +3 -0
  37. package/packages/datadog-plugin-grpc/src/server.js +5 -1
  38. package/packages/datadog-plugin-http/src/client.js +42 -1
  39. package/packages/datadog-plugin-http2/src/client.js +26 -1
  40. package/packages/datadog-plugin-jest/src/index.js +2 -1
  41. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +6 -3
  42. package/packages/datadog-plugin-kafkajs/src/consumer.js +10 -5
  43. package/packages/datadog-plugin-kafkajs/src/producer.js +10 -4
  44. package/packages/datadog-plugin-mocha/src/index.js +5 -2
  45. package/packages/datadog-plugin-moleculer/src/server.js +2 -2
  46. package/packages/datadog-plugin-openai/src/index.js +9 -1015
  47. package/packages/datadog-plugin-openai/src/tracing.js +1023 -0
  48. package/packages/datadog-plugin-rhea/src/consumer.js +2 -1
  49. package/packages/datadog-plugin-vitest/src/index.js +2 -1
  50. package/packages/dd-trace/src/appsec/addresses.js +2 -0
  51. package/packages/dd-trace/src/appsec/api_security_sampler.js +50 -27
  52. package/packages/dd-trace/src/appsec/channels.js +3 -1
  53. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  54. package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +33 -16
  55. package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +18 -0
  56. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +55 -7
  57. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -2
  58. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  59. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -2
  60. package/packages/dd-trace/src/appsec/index.js +9 -6
  61. package/packages/dd-trace/src/appsec/rasp/command_injection.js +49 -0
  62. package/packages/dd-trace/src/appsec/rasp/index.js +3 -0
  63. package/packages/dd-trace/src/appsec/rasp/ssrf.js +4 -3
  64. package/packages/dd-trace/src/appsec/rasp/utils.js +3 -2
  65. package/packages/dd-trace/src/appsec/recommended.json +354 -158
  66. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  67. package/packages/dd-trace/src/appsec/remote_config/index.js +2 -7
  68. package/packages/dd-trace/src/appsec/reporter.js +6 -4
  69. package/packages/dd-trace/src/appsec/sdk/track_event.js +5 -3
  70. package/packages/dd-trace/src/appsec/waf/waf_manager.js +4 -0
  71. package/packages/dd-trace/src/azure_metadata.js +120 -0
  72. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +97 -0
  73. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +90 -0
  74. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +19 -1
  75. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +53 -0
  76. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +8 -1
  77. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +43 -0
  78. package/packages/dd-trace/src/config.js +88 -10
  79. package/packages/dd-trace/src/constants.js +8 -1
  80. package/packages/dd-trace/src/crashtracking/crashtracker.js +98 -0
  81. package/packages/dd-trace/src/crashtracking/index.js +15 -0
  82. package/packages/dd-trace/src/crashtracking/noop.js +8 -0
  83. package/packages/dd-trace/src/datastreams/pathway.js +1 -0
  84. package/packages/dd-trace/src/debugger/devtools_client/index.js +9 -13
  85. package/packages/dd-trace/src/debugger/devtools_client/send.js +15 -1
  86. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +57 -23
  87. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +12 -2
  88. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +31 -20
  89. package/packages/dd-trace/src/debugger/devtools_client/snapshot/symbols.js +6 -0
  90. package/packages/dd-trace/src/debugger/devtools_client/state.js +11 -2
  91. package/packages/dd-trace/src/debugger/index.js +10 -3
  92. package/packages/dd-trace/src/llmobs/constants/tags.js +34 -0
  93. package/packages/dd-trace/src/llmobs/constants/text.js +6 -0
  94. package/packages/dd-trace/src/llmobs/constants/writers.js +13 -0
  95. package/packages/dd-trace/src/llmobs/index.js +103 -0
  96. package/packages/dd-trace/src/llmobs/noop.js +82 -0
  97. package/packages/dd-trace/src/llmobs/plugins/base.js +65 -0
  98. package/packages/dd-trace/src/llmobs/plugins/openai.js +205 -0
  99. package/packages/dd-trace/src/llmobs/sdk.js +377 -0
  100. package/packages/dd-trace/src/llmobs/span_processor.js +195 -0
  101. package/packages/dd-trace/src/llmobs/storage.js +7 -0
  102. package/packages/dd-trace/src/llmobs/tagger.js +322 -0
  103. package/packages/dd-trace/src/llmobs/util.js +176 -0
  104. package/packages/dd-trace/src/llmobs/writers/base.js +111 -0
  105. package/packages/dd-trace/src/llmobs/writers/evaluations.js +29 -0
  106. package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +23 -0
  107. package/packages/dd-trace/src/llmobs/writers/spans/agentless.js +17 -0
  108. package/packages/dd-trace/src/llmobs/writers/spans/base.js +52 -0
  109. package/packages/dd-trace/src/log/index.js +10 -13
  110. package/packages/dd-trace/src/log/log.js +52 -0
  111. package/packages/dd-trace/src/log/writer.js +50 -19
  112. package/packages/dd-trace/src/noop/proxy.js +3 -0
  113. package/packages/dd-trace/src/noop/span.js +4 -0
  114. package/packages/dd-trace/src/opentelemetry/span.js +16 -1
  115. package/packages/dd-trace/src/opentelemetry/tracer.js +1 -0
  116. package/packages/dd-trace/src/opentracing/propagation/text_map.js +106 -32
  117. package/packages/dd-trace/src/opentracing/span.js +26 -0
  118. package/packages/dd-trace/src/opentracing/span_context.js +1 -0
  119. package/packages/dd-trace/src/opentracing/tracer.js +8 -1
  120. package/packages/dd-trace/src/payload-tagging/config/aws.json +71 -3
  121. package/packages/dd-trace/src/plugins/outbound.js +9 -0
  122. package/packages/dd-trace/src/plugins/tracing.js +3 -3
  123. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +121 -0
  124. package/packages/dd-trace/src/plugins/util/ip_extractor.js +0 -1
  125. package/packages/dd-trace/src/plugins/util/web.js +39 -11
  126. package/packages/dd-trace/src/priority_sampler.js +16 -0
  127. package/packages/dd-trace/src/profiling/config.js +3 -1
  128. package/packages/dd-trace/src/profiling/exporters/agent.js +7 -5
  129. package/packages/dd-trace/src/profiling/profilers/wall.js +2 -1
  130. package/packages/dd-trace/src/proxy.js +13 -1
  131. package/packages/dd-trace/src/span_processor.js +5 -0
  132. package/packages/dd-trace/src/telemetry/index.js +11 -1
  133. package/packages/dd-trace/src/telemetry/logs/index.js +16 -11
  134. package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -8
  135. package/packages/dd-trace/src/telemetry/metrics.js +6 -1
  136. package/packages/dd-trace/src/util.js +16 -1
  137. package/version.js +4 -2
  138. /package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/{code-injection-sensitive-analyzer.js → tainted-range-based-sensitive-analyzer.js} +0 -0
@@ -52,45 +52,59 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
52
52
  const send = producer.send
53
53
  const bootstrapServers = this._brokers
54
54
 
55
- producer.send = function () {
56
- const innerAsyncResource = new AsyncResource('bound-anonymous-fn')
55
+ const kafkaClusterIdPromise = getKafkaClusterId(this)
57
56
 
58
- return innerAsyncResource.runInAsyncScope(() => {
59
- if (!producerStartCh.hasSubscribers) {
60
- return send.apply(this, arguments)
61
- }
57
+ producer.send = function () {
58
+ const wrappedSend = (clusterId) => {
59
+ const innerAsyncResource = new AsyncResource('bound-anonymous-fn')
62
60
 
63
- try {
64
- const { topic, messages = [] } = arguments[0]
65
- for (const message of messages) {
66
- if (message !== null && typeof message === 'object') {
67
- message.headers = message.headers || {}
68
- }
61
+ return innerAsyncResource.runInAsyncScope(() => {
62
+ if (!producerStartCh.hasSubscribers) {
63
+ return send.apply(this, arguments)
69
64
  }
70
- producerStartCh.publish({ topic, messages, bootstrapServers })
71
-
72
- const result = send.apply(this, arguments)
73
-
74
- result.then(
75
- innerAsyncResource.bind(res => {
76
- producerFinishCh.publish(undefined)
77
- producerCommitCh.publish(res)
78
- }),
79
- innerAsyncResource.bind(err => {
80
- if (err) {
81
- producerErrorCh.publish(err)
65
+
66
+ try {
67
+ const { topic, messages = [] } = arguments[0]
68
+ for (const message of messages) {
69
+ if (message !== null && typeof message === 'object') {
70
+ message.headers = message.headers || {}
82
71
  }
83
- producerFinishCh.publish(undefined)
84
- })
85
- )
72
+ }
73
+ producerStartCh.publish({ topic, messages, bootstrapServers, clusterId })
86
74
 
87
- return result
88
- } catch (e) {
89
- producerErrorCh.publish(e)
90
- producerFinishCh.publish(undefined)
91
- throw e
92
- }
93
- })
75
+ const result = send.apply(this, arguments)
76
+
77
+ result.then(
78
+ innerAsyncResource.bind(res => {
79
+ producerFinishCh.publish(undefined)
80
+ producerCommitCh.publish(res)
81
+ }),
82
+ innerAsyncResource.bind(err => {
83
+ if (err) {
84
+ producerErrorCh.publish(err)
85
+ }
86
+ producerFinishCh.publish(undefined)
87
+ })
88
+ )
89
+
90
+ return result
91
+ } catch (e) {
92
+ producerErrorCh.publish(e)
93
+ producerFinishCh.publish(undefined)
94
+ throw e
95
+ }
96
+ })
97
+ }
98
+
99
+ if (!isPromise(kafkaClusterIdPromise)) {
100
+ // promise is already resolved
101
+ return wrappedSend(kafkaClusterIdPromise)
102
+ } else {
103
+ // promise is not resolved
104
+ return kafkaClusterIdPromise.then((clusterId) => {
105
+ return wrappedSend(clusterId)
106
+ })
107
+ }
94
108
  }
95
109
  return producer
96
110
  })
@@ -100,15 +114,17 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
100
114
  return createConsumer.apply(this, arguments)
101
115
  }
102
116
 
103
- const eachMessageExtractor = (args) => {
117
+ const kafkaClusterIdPromise = getKafkaClusterId(this)
118
+
119
+ const eachMessageExtractor = (args, clusterId) => {
104
120
  const { topic, partition, message } = args[0]
105
- return { topic, partition, message, groupId }
121
+ return { topic, partition, message, groupId, clusterId }
106
122
  }
107
123
 
108
- const eachBatchExtractor = (args) => {
124
+ const eachBatchExtractor = (args, clusterId) => {
109
125
  const { batch } = args[0]
110
126
  const { topic, partition, messages } = batch
111
- return { topic, partition, messages, groupId }
127
+ return { topic, partition, messages, groupId, clusterId }
112
128
  }
113
129
 
114
130
  const consumer = createConsumer.apply(this, arguments)
@@ -116,43 +132,53 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
116
132
  consumer.on(consumer.events.COMMIT_OFFSETS, commitsFromEvent)
117
133
 
118
134
  const run = consumer.run
119
-
120
135
  const groupId = arguments[0].groupId
136
+
121
137
  consumer.run = function ({ eachMessage, eachBatch, ...runArgs }) {
122
- eachMessage = wrapFunction(
123
- eachMessage,
124
- consumerStartCh,
125
- consumerFinishCh,
126
- consumerErrorCh,
127
- eachMessageExtractor
128
- )
129
-
130
- eachBatch = wrapFunction(
131
- eachBatch,
132
- batchConsumerStartCh,
133
- batchConsumerFinishCh,
134
- batchConsumerErrorCh,
135
- eachBatchExtractor
136
- )
137
-
138
- return run({
139
- eachMessage,
140
- eachBatch,
141
- ...runArgs
142
- })
138
+ const wrapConsume = (clusterId) => {
139
+ return run({
140
+ eachMessage: wrappedCallback(
141
+ eachMessage,
142
+ consumerStartCh,
143
+ consumerFinishCh,
144
+ consumerErrorCh,
145
+ eachMessageExtractor,
146
+ clusterId
147
+ ),
148
+ eachBatch: wrappedCallback(
149
+ eachBatch,
150
+ batchConsumerStartCh,
151
+ batchConsumerFinishCh,
152
+ batchConsumerErrorCh,
153
+ eachBatchExtractor,
154
+ clusterId
155
+ ),
156
+ ...runArgs
157
+ })
158
+ }
159
+
160
+ if (!isPromise(kafkaClusterIdPromise)) {
161
+ // promise is already resolved
162
+ return wrapConsume(kafkaClusterIdPromise)
163
+ } else {
164
+ // promise is not resolved
165
+ return kafkaClusterIdPromise.then((clusterId) => {
166
+ return wrapConsume(clusterId)
167
+ })
168
+ }
143
169
  }
144
-
145
170
  return consumer
146
171
  })
147
172
  return Kafka
148
173
  })
149
174
 
150
- const wrapFunction = (fn, startCh, finishCh, errorCh, extractArgs) => {
175
+ const wrappedCallback = (fn, startCh, finishCh, errorCh, extractArgs, clusterId) => {
151
176
  return typeof fn === 'function'
152
177
  ? function (...args) {
153
178
  const innerAsyncResource = new AsyncResource('bound-anonymous-fn')
154
179
  return innerAsyncResource.runInAsyncScope(() => {
155
- const extractedArgs = extractArgs(args)
180
+ const extractedArgs = extractArgs(args, clusterId)
181
+
156
182
  startCh.publish(extractedArgs)
157
183
  try {
158
184
  const result = fn.apply(this, args)
@@ -179,3 +205,37 @@ const wrapFunction = (fn, startCh, finishCh, errorCh, extractArgs) => {
179
205
  }
180
206
  : fn
181
207
  }
208
+
209
+ const getKafkaClusterId = (kafka) => {
210
+ if (kafka._ddKafkaClusterId) {
211
+ return kafka._ddKafkaClusterId
212
+ }
213
+
214
+ if (!kafka.admin) {
215
+ return null
216
+ }
217
+
218
+ const admin = kafka.admin()
219
+
220
+ if (!admin.describeCluster) {
221
+ return null
222
+ }
223
+
224
+ return admin.connect()
225
+ .then(() => {
226
+ return admin.describeCluster()
227
+ })
228
+ .then((clusterInfo) => {
229
+ const clusterId = clusterInfo?.clusterId
230
+ kafka._ddKafkaClusterId = clusterId
231
+ admin.disconnect()
232
+ return clusterId
233
+ })
234
+ .catch((error) => {
235
+ throw error
236
+ })
237
+ }
238
+
239
+ function isPromise (obj) {
240
+ return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function'
241
+ }
@@ -280,12 +280,12 @@ function getOnFailHandler (isMain) {
280
280
  }
281
281
 
282
282
  function getOnTestRetryHandler () {
283
- return function (test) {
283
+ return function (test, err) {
284
284
  const asyncResource = getTestAsyncResource(test)
285
285
  if (asyncResource) {
286
286
  const isFirstAttempt = test._currentRetry === 0
287
287
  asyncResource.runInAsyncScope(() => {
288
- testRetryCh.publish(isFirstAttempt)
288
+ testRetryCh.publish({ isFirstAttempt, err })
289
289
  })
290
290
  }
291
291
  const key = getTestToArKey(test)
@@ -0,0 +1,37 @@
1
+ 'use strict'
2
+
3
+ const shimmer = require('../../datadog-shimmer')
4
+ const { channel, addHook, AsyncResource } = require('./helpers/instrument')
5
+
6
+ const multerReadCh = channel('datadog:multer:read:finish')
7
+
8
+ function publishRequestBodyAndNext (req, res, next) {
9
+ return shimmer.wrapFunction(next, next => function () {
10
+ if (multerReadCh.hasSubscribers && req) {
11
+ const abortController = new AbortController()
12
+ const body = req.body
13
+
14
+ multerReadCh.publish({ req, res, body, abortController })
15
+
16
+ if (abortController.signal.aborted) return
17
+ }
18
+
19
+ return next.apply(this, arguments)
20
+ })
21
+ }
22
+
23
+ addHook({
24
+ name: 'multer',
25
+ file: 'lib/make-middleware.js',
26
+ versions: ['^1.4.4-lts.1']
27
+ }, makeMiddleware => {
28
+ return shimmer.wrapFunction(makeMiddleware, makeMiddleware => function () {
29
+ const middleware = makeMiddleware.apply(this, arguments)
30
+
31
+ return shimmer.wrapFunction(middleware, middleware => function wrapMulterMiddleware (req, res, next) {
32
+ const nextResource = new AsyncResource('bound-anonymous-fn')
33
+ arguments[2] = nextResource.bind(publishRequestBodyAndNext(req, res, next))
34
+ return middleware.apply(this, arguments)
35
+ })
36
+ })
37
+ })
@@ -3,8 +3,8 @@
3
3
  const { addHook } = require('./helpers/instrument')
4
4
  const shimmer = require('../../datadog-shimmer')
5
5
 
6
- const tracingChannel = require('dc-polyfill').tracingChannel
7
- const ch = tracingChannel('apm:openai:request')
6
+ const dc = require('dc-polyfill')
7
+ const ch = dc.tracingChannel('apm:openai:request')
8
8
 
9
9
  const V4_PACKAGE_SHIMS = [
10
10
  {
@@ -0,0 +1,23 @@
1
+ 'use strict'
2
+
3
+ const shimmer = require('../../datadog-shimmer')
4
+ const { channel, addHook } = require('./helpers/instrument')
5
+
6
+ const pugCompileCh = channel('datadog:pug:compile:start')
7
+
8
+ function wrapCompile (compile) {
9
+ return function wrappedCompile (source) {
10
+ if (pugCompileCh.hasSubscribers) {
11
+ pugCompileCh.publish({ source })
12
+ }
13
+
14
+ return compile.apply(this, arguments)
15
+ }
16
+ }
17
+
18
+ addHook({ name: 'pug', versions: ['>=2.0.4'] }, compiler => {
19
+ shimmer.wrap(compiler, 'compile', wrapCompile)
20
+ shimmer.wrap(compiler, 'compileClientWithDependenciesTracked', wrapCompile)
21
+
22
+ return compiler
23
+ })
@@ -112,7 +112,6 @@ function createWrapRouterMethod (name) {
112
112
  path: pattern instanceof RegExp ? `(${pattern})` : pattern,
113
113
  test: layer => {
114
114
  const matchers = layerMatchers.get(layer)
115
-
116
115
  return !isFastStar(layer, matchers) &&
117
116
  !isFastSlash(layer, matchers) &&
118
117
  cachedPathToRegExp(pattern).test(layer.path)
@@ -121,7 +120,7 @@ function createWrapRouterMethod (name) {
121
120
  }
122
121
 
123
122
  function isFastStar (layer, matchers) {
124
- if (layer.regexp.fast_star !== undefined) {
123
+ if (layer.regexp?.fast_star !== undefined) {
125
124
  return layer.regexp.fast_star
126
125
  }
127
126
 
@@ -129,7 +128,7 @@ function createWrapRouterMethod (name) {
129
128
  }
130
129
 
131
130
  function isFastSlash (layer, matchers) {
132
- if (layer.regexp.fast_slash !== undefined) {
131
+ if (layer.regexp?.fast_slash !== undefined) {
133
132
  return layer.regexp.fast_slash
134
133
  }
135
134
 
@@ -0,0 +1,84 @@
1
+ 'use strict'
2
+
3
+ const { addHook, channel } = require('./helpers/instrument')
4
+ const shimmer = require('../../datadog-shimmer')
5
+ const names = ['url', 'node:url']
6
+
7
+ const parseFinishedChannel = channel('datadog:url:parse:finish')
8
+ const urlGetterChannel = channel('datadog:url:getter:finish')
9
+ const instrumentedGetters = ['host', 'origin', 'hostname']
10
+
11
+ addHook({ name: names }, function (url) {
12
+ shimmer.wrap(url, 'parse', (parse) => {
13
+ return function wrappedParse (input) {
14
+ const parsedValue = parse.apply(this, arguments)
15
+ if (!parseFinishedChannel.hasSubscribers) return parsedValue
16
+
17
+ parseFinishedChannel.publish({
18
+ input,
19
+ parsed: parsedValue,
20
+ isURL: false
21
+ })
22
+
23
+ return parsedValue
24
+ }
25
+ })
26
+
27
+ const URLPrototype = url.URL.prototype.constructor.prototype
28
+ instrumentedGetters.forEach(property => {
29
+ const originalDescriptor = Object.getOwnPropertyDescriptor(URLPrototype, property)
30
+
31
+ if (originalDescriptor?.get) {
32
+ const newDescriptor = shimmer.wrap(originalDescriptor, 'get', function (originalGet) {
33
+ return function get () {
34
+ const result = originalGet.apply(this, arguments)
35
+ if (!urlGetterChannel.hasSubscribers) return result
36
+
37
+ const context = { urlObject: this, result, property }
38
+ urlGetterChannel.publish(context)
39
+
40
+ return context.result
41
+ }
42
+ })
43
+
44
+ Object.defineProperty(URLPrototype, property, newDescriptor)
45
+ }
46
+ })
47
+
48
+ shimmer.wrap(url, 'URL', (URL) => {
49
+ return class extends URL {
50
+ constructor (input, base) {
51
+ super(...arguments)
52
+
53
+ if (!parseFinishedChannel.hasSubscribers) return
54
+
55
+ parseFinishedChannel.publish({
56
+ input,
57
+ base,
58
+ parsed: this,
59
+ isURL: true
60
+ })
61
+ }
62
+ }
63
+ })
64
+
65
+ if (url.URL.parse) {
66
+ shimmer.wrap(url.URL, 'parse', (parse) => {
67
+ return function wrappedParse (input, base) {
68
+ const parsedValue = parse.apply(this, arguments)
69
+ if (!parseFinishedChannel.hasSubscribers) return parsedValue
70
+
71
+ parseFinishedChannel.publish({
72
+ input,
73
+ base,
74
+ parsed: parsedValue,
75
+ isURL: true
76
+ })
77
+
78
+ return parsedValue
79
+ }
80
+ })
81
+ }
82
+
83
+ return url
84
+ })
@@ -6,7 +6,7 @@ const NM = 'node_modules/'
6
6
  * For a given full path to a module,
7
7
  * return the package name it belongs to and the local path to the module
8
8
  * input: '/foo/node_modules/@co/stuff/foo/bar/baz.js'
9
- * output: { pkg: '@co/stuff', path: 'foo/bar/baz.js' }
9
+ * output: { pkg: '@co/stuff', path: 'foo/bar/baz.js', pkgJson: '/foo/node_modules/@co/stuff/package.json' }
10
10
  */
11
11
  module.exports = function extractPackageAndModulePath (fullPath) {
12
12
  const nm = fullPath.lastIndexOf(NM)
@@ -17,17 +17,20 @@ module.exports = function extractPackageAndModulePath (fullPath) {
17
17
  const subPath = fullPath.substring(nm + NM.length)
18
18
  const firstSlash = subPath.indexOf('/')
19
19
 
20
+ const firstPath = fullPath.substring(fullPath[0], nm + NM.length)
21
+
20
22
  if (subPath[0] === '@') {
21
23
  const secondSlash = subPath.substring(firstSlash + 1).indexOf('/')
22
-
23
24
  return {
24
25
  pkg: subPath.substring(0, firstSlash + 1 + secondSlash),
25
- path: subPath.substring(firstSlash + 1 + secondSlash + 1)
26
+ path: subPath.substring(firstSlash + 1 + secondSlash + 1),
27
+ pkgJson: firstPath + subPath.substring(0, firstSlash + 1 + secondSlash) + '/package.json'
26
28
  }
27
29
  }
28
30
 
29
31
  return {
30
32
  pkg: subPath.substring(0, firstSlash),
31
- path: subPath.substring(firstSlash + 1)
33
+ path: subPath.substring(firstSlash + 1),
34
+ pkgJson: firstPath + subPath.substring(0, firstSlash) + '/package.json'
32
35
  }
33
36
  }
@@ -9,33 +9,34 @@ class AmqplibConsumerPlugin extends ConsumerPlugin {
9
9
  static get id () { return 'amqplib' }
10
10
  static get operation () { return 'command' }
11
11
 
12
- start ({ method, fields, message }) {
12
+ start ({ method, fields, message, queue }) {
13
13
  if (method !== 'basic.deliver' && method !== 'basic.get') return
14
14
 
15
15
  const childOf = extract(this.tracer, message)
16
16
 
17
+ const queueName = queue || fields.queue || fields.routingKey
17
18
  const span = this.startSpan({
18
19
  childOf,
19
20
  resource: getResourceName(method, fields),
20
21
  type: 'worker',
21
22
  meta: {
22
- 'amqp.queue': fields.queue,
23
+ 'amqp.queue': queueName,
23
24
  'amqp.exchange': fields.exchange,
24
25
  'amqp.routingKey': fields.routingKey,
25
26
  'amqp.consumerTag': fields.consumerTag,
26
27
  'amqp.source': fields.source,
27
28
  'amqp.destination': fields.destination
28
- }
29
+ },
30
+ extractedLinks: childOf?._links
29
31
  })
30
32
 
31
33
  if (
32
34
  this.config.dsmEnabled && message?.properties?.headers
33
35
  ) {
34
36
  const payloadSize = getAmqpMessageSize({ headers: message.properties.headers, content: message.content })
35
- const queue = fields.queue ? fields.queue : fields.routingKey
36
37
  this.tracer.decodeDataStreamsContext(message.properties.headers)
37
38
  this.tracer
38
- .setCheckpoint(['direction:in', `topic:${queue}`, 'type:rabbitmq'], span, payloadSize)
39
+ .setCheckpoint(['direction:in', `topic:${queueName}`, 'type:rabbitmq'], span, payloadSize)
39
40
  }
40
41
  }
41
42
  }
@@ -93,6 +93,7 @@ class BaseAwsSdkPlugin extends ClientPlugin {
93
93
  this.responseExtractDSMContext(operation, params, response.data ?? response, span)
94
94
  }
95
95
  this.addResponseTags(span, response)
96
+ this.addSpanPointers(span, response)
96
97
  this.finish(span, response, response.error)
97
98
  })
98
99
  }
@@ -101,6 +102,10 @@ class BaseAwsSdkPlugin extends ClientPlugin {
101
102
  // implemented by subclasses, or not
102
103
  }
103
104
 
105
+ addSpanPointers (span, response) {
106
+ // Optionally implemented by subclasses, for services where we're unable to inject trace context
107
+ }
108
+
104
109
  operationFromRequest (request) {
105
110
  // can be overriden by subclasses
106
111
  return this.operationName({
@@ -4,6 +4,7 @@ const BaseAwsSdkPlugin = require('../base')
4
4
 
5
5
  class EventBridge extends BaseAwsSdkPlugin {
6
6
  static get id () { return 'eventbridge' }
7
+ static get isPayloadReporter () { return true }
7
8
 
8
9
  generateTags (params, operation, response) {
9
10
  if (!params || !params.source) return {}
@@ -10,6 +10,7 @@ const { storage } = require('../../../datadog-core')
10
10
  class Kinesis extends BaseAwsSdkPlugin {
11
11
  static get id () { return 'kinesis' }
12
12
  static get peerServicePrecursors () { return ['streamname'] }
13
+ static get isPayloadReporter () { return true }
13
14
 
14
15
  constructor (...args) {
15
16
  super(...args)
@@ -41,7 +42,8 @@ class Kinesis extends BaseAwsSdkPlugin {
41
42
  {},
42
43
  this.requestTags.get(request) || {},
43
44
  { 'span.kind': 'server' }
44
- )
45
+ ),
46
+ extractedLinks: responseExtraction.maybeChildOf._links
45
47
  }
46
48
  span = plugin.tracer.startSpan('aws.response', options)
47
49
  this.enter(span, store)
@@ -112,14 +114,15 @@ class Kinesis extends BaseAwsSdkPlugin {
112
114
  response.Records.forEach(record => {
113
115
  const parsedAttributes = JSON.parse(Buffer.from(record.Data).toString())
114
116
 
115
- if (
116
- parsedAttributes?._datadog && streamName
117
- ) {
118
- const payloadSize = getSizeOrZero(record.Data)
117
+ const payloadSize = getSizeOrZero(record.Data)
118
+ if (parsedAttributes?._datadog) {
119
119
  this.tracer.decodeDataStreamsContext(parsedAttributes._datadog)
120
- this.tracer
121
- .setCheckpoint(['direction:in', `topic:${streamName}`, 'type:kinesis'], span, payloadSize)
122
120
  }
121
+ const tags = streamName
122
+ ? ['direction:in', `topic:${streamName}`, 'type:kinesis']
123
+ : ['direction:in', 'type:kinesis']
124
+ this.tracer
125
+ .setCheckpoint(tags, span, payloadSize)
123
126
  })
124
127
  }
125
128
 
@@ -1,10 +1,14 @@
1
1
  'use strict'
2
2
 
3
3
  const BaseAwsSdkPlugin = require('../base')
4
+ const log = require('../../../dd-trace/src/log')
5
+ const { generatePointerHash } = require('../../../dd-trace/src/util')
6
+ const { S3_PTR_KIND, SPAN_POINTER_DIRECTION } = require('../../../dd-trace/src/constants')
4
7
 
5
8
  class S3 extends BaseAwsSdkPlugin {
6
9
  static get id () { return 's3' }
7
10
  static get peerServicePrecursors () { return ['bucketname'] }
11
+ static get isPayloadReporter () { return true }
8
12
 
9
13
  generateTags (params, operation, response) {
10
14
  const tags = {}
@@ -17,6 +21,37 @@ class S3 extends BaseAwsSdkPlugin {
17
21
  bucketname: params.Bucket
18
22
  })
19
23
  }
24
+
25
+ addSpanPointers (span, response) {
26
+ const request = response?.request
27
+ const operationName = request?.operation
28
+ if (!['putObject', 'copyObject', 'completeMultipartUpload'].includes(operationName)) {
29
+ // We don't create span links for other S3 operations.
30
+ return
31
+ }
32
+
33
+ // AWS v2: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html
34
+ // AWS v3: https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3/
35
+ const bucketName = request?.params?.Bucket
36
+ const objectKey = request?.params?.Key
37
+ let eTag =
38
+ response?.ETag || // v3 PutObject & CompleteMultipartUpload
39
+ response?.CopyObjectResult?.ETag || // v3 CopyObject
40
+ response?.data?.ETag || // v2 PutObject & CompleteMultipartUpload
41
+ response?.data?.CopyObjectResult?.ETag // v2 CopyObject
42
+
43
+ if (!bucketName || !objectKey || !eTag) {
44
+ log.debug('Unable to calculate span pointer hash because of missing parameters.')
45
+ return
46
+ }
47
+
48
+ // https://github.com/DataDog/dd-span-pointer-rules/blob/main/AWS/S3/Object/README.md
49
+ if (eTag.startsWith('"') && eTag.endsWith('"')) {
50
+ eTag = eTag.slice(1, -1)
51
+ }
52
+ const pointerHash = generatePointerHash([bucketName, objectKey, eTag])
53
+ span.addSpanPointer(S3_PTR_KIND, SPAN_POINTER_DIRECTION.DOWNSTREAM, pointerHash)
54
+ }
20
55
  }
21
56
 
22
57
  module.exports = S3
@@ -9,6 +9,7 @@ const { DsmPathwayCodec } = require('../../../dd-trace/src/datastreams/pathway')
9
9
  class Sqs extends BaseAwsSdkPlugin {
10
10
  static get id () { return 'sqs' }
11
11
  static get peerServicePrecursors () { return ['queuename'] }
12
+ static get isPayloadReporter () { return true }
12
13
 
13
14
  constructor (...args) {
14
15
  super(...args)
@@ -32,7 +33,8 @@ class Sqs extends BaseAwsSdkPlugin {
32
33
  {},
33
34
  this.requestTags.get(request) || {},
34
35
  { 'span.kind': 'server' }
35
- )
36
+ ),
37
+ extractedLinks: contextExtraction.datadogContext._links
36
38
  }
37
39
  parsedMessageAttributes = contextExtraction.parsedAttributes
38
40
  span = plugin.tracer.startSpan('aws.response', options)
@@ -41,7 +43,7 @@ class Sqs extends BaseAwsSdkPlugin {
41
43
  // extract DSM context after as we might not have a parent-child but may have a DSM context
42
44
 
43
45
  this.responseExtractDSMContext(
44
- request.operation, request.params, response, span || null, { parsedMessageAttributes }
46
+ request.operation, request.params, response, span || null, { parsedAttributes: parsedMessageAttributes }
45
47
  )
46
48
  })
47
49
 
@@ -194,16 +196,16 @@ class Sqs extends BaseAwsSdkPlugin {
194
196
  parsedAttributes = this.parseDatadogAttributes(message.MessageAttributes._datadog)
195
197
  }
196
198
  }
199
+ const payloadSize = getHeadersSize({
200
+ Body: message.Body,
201
+ MessageAttributes: message.MessageAttributes
202
+ })
203
+ const queue = params.QueueUrl.split('/').pop()
197
204
  if (parsedAttributes) {
198
- const payloadSize = getHeadersSize({
199
- Body: message.Body,
200
- MessageAttributes: message.MessageAttributes
201
- })
202
- const queue = params.QueueUrl.split('/').pop()
203
205
  this.tracer.decodeDataStreamsContext(parsedAttributes)
204
- this.tracer
205
- .setCheckpoint(['direction:in', `topic:${queue}`, 'type:sqs'], span, payloadSize)
206
206
  }
207
+ this.tracer
208
+ .setCheckpoint(['direction:in', `topic:${queue}`, 'type:sqs'], span, payloadSize)
207
209
  })
208
210
  }
209
211