dd-trace 5.62.0 → 5.63.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 (33) hide show
  1. package/README.md +0 -5
  2. package/package.json +2 -2
  3. package/packages/datadog-instrumentations/src/ai.js +140 -0
  4. package/packages/datadog-instrumentations/src/couchbase.js +102 -65
  5. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  6. package/packages/datadog-instrumentations/src/helpers/register.js +2 -22
  7. package/packages/datadog-instrumentations/src/hono.js +11 -8
  8. package/packages/datadog-instrumentations/src/knex.js +15 -17
  9. package/packages/datadog-instrumentations/src/mongodb-core.js +4 -6
  10. package/packages/datadog-instrumentations/src/next.js +4 -8
  11. package/packages/datadog-instrumentations/src/pg.js +38 -48
  12. package/packages/datadog-plugin-aerospike/src/index.js +6 -2
  13. package/packages/datadog-plugin-ai/src/index.js +17 -0
  14. package/packages/datadog-plugin-ai/src/tracing.js +33 -0
  15. package/packages/datadog-plugin-ai/src/utils.js +28 -0
  16. package/packages/datadog-plugin-couchbase/src/index.js +37 -17
  17. package/packages/datadog-plugin-pg/src/index.js +5 -2
  18. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +14 -7
  19. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +3 -3
  20. package/packages/dd-trace/src/appsec/recommended.json +271 -2
  21. package/packages/dd-trace/src/guardrails/telemetry.js +18 -2
  22. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +351 -0
  23. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +179 -0
  24. package/packages/dd-trace/src/llmobs/writers/base.js +3 -2
  25. package/packages/dd-trace/src/opentracing/span_context.js +4 -0
  26. package/packages/dd-trace/src/plugin_manager.js +8 -4
  27. package/packages/dd-trace/src/plugins/index.js +1 -0
  28. package/packages/dd-trace/src/plugins/util/ip_extractor.js +44 -3
  29. package/packages/dd-trace/src/profiling/profilers/event_plugins/event.js +24 -23
  30. package/packages/dd-trace/src/profiling/profilers/events.js +3 -2
  31. package/packages/dd-trace/src/profiling/profilers/wall.js +2 -2
  32. package/packages/dd-trace/src/supported-configurations.json +2 -0
  33. package/packages/dd-trace/src/tracer_metadata.js +1 -1
@@ -2,8 +2,7 @@
2
2
 
3
3
  const {
4
4
  channel,
5
- addHook,
6
- AsyncResource
5
+ addHook
7
6
  } = require('./helpers/instrument')
8
7
  const shimmer = require('../../datadog-shimmer')
9
8
 
@@ -33,8 +32,6 @@ function wrapQuery (query) {
33
32
  return query.apply(this, arguments)
34
33
  }
35
34
 
36
- const callbackResource = new AsyncResource('bound-anonymous-fn')
37
- const asyncResource = new AsyncResource('bound-anonymous-fn')
38
35
  const processId = this.processID
39
36
 
40
37
  const pgQuery = arguments[0] !== null && typeof arguments[0] === 'object'
@@ -46,8 +43,9 @@ function wrapQuery (query) {
46
43
  const stream = typeof textPropObj.read === 'function'
47
44
 
48
45
  // Only alter `text` property if safe to do so. Initially, it's a property, not a getter.
46
+ let originalText
49
47
  if (!textProp || textProp.configurable) {
50
- const originalText = textPropObj.text
48
+ originalText = textPropObj.text
51
49
 
52
50
  Object.defineProperty(textPropObj, 'text', {
53
51
  get () {
@@ -55,25 +53,24 @@ function wrapQuery (query) {
55
53
  }
56
54
  })
57
55
  }
58
-
59
- return asyncResource.runInAsyncScope(() => {
60
- const abortController = new AbortController()
61
-
62
- startCh.publish({
63
- params: this.connectionParameters,
64
- query: textPropObj,
65
- processId,
66
- abortController,
67
- stream
68
- })
69
-
70
- const finish = asyncResource.bind(function (error, res) {
71
- if (error) {
72
- errorCh.publish(error)
73
- }
74
- finishCh.publish({ result: res?.rows })
75
- })
76
-
56
+ const abortController = new AbortController()
57
+ const ctx = {
58
+ params: this.connectionParameters,
59
+ query: textPropObj,
60
+ originalText,
61
+ processId,
62
+ abortController,
63
+ stream
64
+ }
65
+ const finish = (error, res) => {
66
+ if (error) {
67
+ ctx.error = error
68
+ errorCh.publish(ctx)
69
+ }
70
+ ctx.result = res?.rows
71
+ return finishCh.publish(ctx)
72
+ }
73
+ return startCh.runStores(ctx, () => {
77
74
  if (abortController.signal.aborted) {
78
75
  const error = abortController.signal.reason || new Error('Aborted')
79
76
 
@@ -121,10 +118,10 @@ function wrapQuery (query) {
121
118
  }
122
119
 
123
120
  if (newQuery.callback) {
124
- const originalCallback = callbackResource.bind(newQuery.callback)
125
- newQuery.callback = function (err, res) {
126
- finish(err, res)
127
- return originalCallback.apply(this, arguments)
121
+ const originalCallback = newQuery.callback
122
+ newQuery.callback = function (err, ...args) {
123
+ finish(err, ...args)
124
+ return finishCh.runStores(ctx, originalCallback, this, err, ...args)
128
125
  }
129
126
  } else if (newQuery.once) {
130
127
  newQuery
@@ -139,40 +136,33 @@ function wrapQuery (query) {
139
136
 
140
137
  try {
141
138
  return retval
142
- } catch (err) {
143
- errorCh.publish(err)
139
+ } catch (error) {
140
+ ctx.error = error
141
+ errorCh.publish(ctx)
144
142
  }
145
143
  })
146
144
  }
147
145
  }
148
-
146
+ const finish = (ctx) => {
147
+ finishPoolQueryCh.publish(ctx)
148
+ }
149
149
  function wrapPoolQuery (query) {
150
150
  return function () {
151
151
  if (!startPoolQueryCh.hasSubscribers) {
152
152
  return query.apply(this, arguments)
153
153
  }
154
154
 
155
- const asyncResource = new AsyncResource('bound-anonymous-fn')
156
-
157
155
  const pgQuery = arguments[0] !== null && typeof arguments[0] === 'object' ? arguments[0] : { text: arguments[0] }
156
+ const abortController = new AbortController()
158
157
 
159
- return asyncResource.runInAsyncScope(() => {
160
- const abortController = new AbortController()
161
-
162
- startPoolQueryCh.publish({
163
- query: pgQuery,
164
- abortController
165
- })
166
-
167
- const finish = asyncResource.bind(function () {
168
- finishPoolQueryCh.publish()
169
- })
158
+ const ctx = { query: pgQuery, abortController }
170
159
 
160
+ return startPoolQueryCh.runStores(ctx, () => {
171
161
  const cb = arguments[arguments.length - 1]
172
162
 
173
163
  if (abortController.signal.aborted) {
174
164
  const error = abortController.signal.reason || new Error('Aborted')
175
- finish()
165
+ finish(ctx)
176
166
 
177
167
  if (typeof cb === 'function') {
178
168
  cb(error)
@@ -184,7 +174,7 @@ function wrapPoolQuery (query) {
184
174
 
185
175
  if (typeof cb === 'function') {
186
176
  arguments[arguments.length - 1] = shimmer.wrapFunction(cb, cb => function () {
187
- finish()
177
+ finish(ctx)
188
178
  return cb.apply(this, arguments)
189
179
  })
190
180
  }
@@ -193,9 +183,9 @@ function wrapPoolQuery (query) {
193
183
 
194
184
  if (retval?.then) {
195
185
  retval.then(() => {
196
- finish()
186
+ finish(ctx)
197
187
  }).catch(() => {
198
- finish()
188
+ finish(ctx)
199
189
  })
200
190
  }
201
191
 
@@ -64,8 +64,12 @@ class AerospikePlugin extends DatabasePlugin {
64
64
  function getMeta (resourceName, commandArgs) {
65
65
  let meta = {}
66
66
  if (resourceName.includes('Index')) {
67
- const [ns, set, bin, index] = commandArgs
68
- meta = getMetaForIndex(ns, set, bin, index)
67
+ const [ns, set, bin, exp, index] = commandArgs
68
+
69
+ // The `ext` argument was added to IndexCreate in 6.3.0
70
+ meta = commandArgs.length > 8
71
+ ? getMetaForIndex(ns, set, bin, index)
72
+ : getMetaForIndex(ns, set, bin, exp)
69
73
  } else if (resourceName === 'Query') {
70
74
  const { ns, set } = commandArgs[2]
71
75
  meta = getMetaForQuery({ ns, set })
@@ -0,0 +1,17 @@
1
+ 'use strict'
2
+
3
+ const CompositePlugin = require('../../dd-trace/src/plugins/composite')
4
+ const VercelAILLMObsPlugin = require('../../dd-trace/src/llmobs/plugins/ai')
5
+ const VercelAITracingPlugin = require('./tracing')
6
+
7
+ class VercelAIPlugin extends CompositePlugin {
8
+ static get id () { return 'ai' }
9
+ static get plugins () {
10
+ return {
11
+ llmobs: VercelAILLMObsPlugin,
12
+ tracing: VercelAITracingPlugin
13
+ }
14
+ }
15
+ }
16
+
17
+ module.exports = VercelAIPlugin
@@ -0,0 +1,33 @@
1
+ 'use strict'
2
+
3
+ const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
4
+ const { getModelProvider } = require('./utils')
5
+
6
+ class VercelAITracingPlugin extends TracingPlugin {
7
+ static id = 'ai'
8
+ static prefix = 'tracing:dd-trace:vercel-ai'
9
+
10
+ bindStart (ctx) {
11
+ const attributes = ctx.attributes
12
+
13
+ const model = attributes['ai.model.id']
14
+ const modelProvider = getModelProvider(attributes)
15
+
16
+ this.startSpan(ctx.name, {
17
+ meta: {
18
+ 'resource.name': ctx.name,
19
+ 'ai.request.model': model,
20
+ 'ai.request.model_provider': modelProvider
21
+ }
22
+ }, ctx)
23
+
24
+ return ctx.currentStore
25
+ }
26
+
27
+ asyncEnd (ctx) {
28
+ const span = ctx.currentStore?.span
29
+ span?.finish()
30
+ }
31
+ }
32
+
33
+ module.exports = VercelAITracingPlugin
@@ -0,0 +1,28 @@
1
+ 'use strict'
2
+
3
+ const { parseModelId } = require('../../datadog-plugin-aws-sdk/src/services/bedrockruntime/utils')
4
+
5
+ /**
6
+ * Get the model provider from the span tags or attributes.
7
+ * This is normalized to LLM Observability model provider standards.
8
+ *
9
+ * @param {Record<string, string>} tags
10
+ * @returns {string}
11
+ */
12
+ function getModelProvider (tags) {
13
+ const modelProviderTag = tags['ai.model.provider']
14
+ const providerParts = modelProviderTag?.split('.')
15
+ const provider = providerParts?.[0]
16
+
17
+ if (provider === 'amazon-bedrock') {
18
+ const modelId = tags['ai.model.id']
19
+ const model = modelId && parseModelId(modelId)
20
+ return model?.modelProvider ?? provider
21
+ }
22
+
23
+ return provider
24
+ }
25
+
26
+ module.exports = {
27
+ getModelProvider
28
+ }
@@ -7,13 +7,15 @@ class CouchBasePlugin extends StoragePlugin {
7
7
  static id = 'couchbase'
8
8
  static peerServicePrecursors = ['db.couchbase.seed.nodes']
9
9
 
10
- addSubs (func, start) {
11
- this.addSub(`apm:couchbase:${func}:start`, start)
12
- this.addSub(`apm:couchbase:${func}:error`, error => this.addError(error))
13
- this.addSub(`apm:couchbase:${func}:finish`, message => this.finish(message))
10
+ addBinds (func, start) {
11
+ this.addBind(`apm:couchbase:${func}:start`, start)
12
+ this.addSub(`apm:couchbase:${func}:error`, ({ error }) => this.addError(error))
13
+ this.addSub(`apm:couchbase:${func}:finish`, ctx => this.finish(ctx))
14
+ this.addBind(`apm:couchbase:${func}:callback:start`, callbackStart)
15
+ this.addBind(`apm:couchbase:${func}:callback:finish`, callbackFinish)
14
16
  }
15
17
 
16
- startSpan (operation, customTags, store, { bucket, collection, seedNodes }) {
18
+ startSpan (operation, customTags, { bucket, collection, seedNodes }, ctx) {
17
19
  const tags = {
18
20
  'db.type': 'couchbase',
19
21
  component: 'couchbase',
@@ -34,26 +36,34 @@ class CouchBasePlugin extends StoragePlugin {
34
36
  {
35
37
  service: this.serviceName({ pluginConfig: this.config }),
36
38
  meta: tags
37
- }
39
+ },
40
+ ctx
38
41
  )
39
42
  }
40
43
 
41
44
  constructor (...args) {
42
45
  super(...args)
43
46
 
44
- this.addSubs('query', ({ resource, bucket, seedNodes }) => {
45
- const store = storage('legacy').getStore()
46
- const span = this.startSpan(
47
- 'query', {
47
+ this.addBinds('query', (ctx) => {
48
+ const { resource, bucket, seedNodes } = ctx
49
+
50
+ this.startSpan(
51
+ 'query',
52
+ {
48
53
  'span.type': 'sql',
49
54
  'resource.name': resource,
50
55
  'span.kind': this.constructor.kind
51
56
  },
52
- store,
53
- { bucket, seedNodes }
57
+ { bucket, seedNodes },
58
+ ctx
54
59
  )
55
- this.enter(span, store)
60
+
61
+ return ctx.currentStore
56
62
  })
63
+ this.addBind('apm:couchbase:bucket:maybeInvoke:callback:start', callbackStart)
64
+ this.addBind('apm:couchbase:bucket:maybeInvoke:callback:finish', callbackFinish)
65
+ this.addBind('apm:couchbase:cluster:maybeInvoke:callback:start', callbackStart)
66
+ this.addBind('apm:couchbase:cluster:maybeInvoke:callback:finish', callbackFinish)
57
67
 
58
68
  this._addCommandSubs('upsert')
59
69
  this._addCommandSubs('insert')
@@ -63,12 +73,22 @@ class CouchBasePlugin extends StoragePlugin {
63
73
  }
64
74
 
65
75
  _addCommandSubs (name) {
66
- this.addSubs(name, ({ bucket, collection, seedNodes }) => {
67
- const store = storage('legacy').getStore()
68
- const span = this.startSpan(name, {}, store, { bucket, collection, seedNodes })
69
- this.enter(span, store)
76
+ this.addBinds(name, (ctx) => {
77
+ const { bucket, collection, seedNodes } = ctx
78
+
79
+ this.startSpan(name, {}, { bucket, collection, seedNodes }, ctx)
80
+ return ctx.currentStore
70
81
  })
71
82
  }
72
83
  }
73
84
 
85
+ function callbackStart (ctx) {
86
+ ctx.parentStore = storage('legacy').getStore()
87
+ return ctx.parentStore
88
+ }
89
+
90
+ function callbackFinish (ctx) {
91
+ return ctx.parentStore
92
+ }
93
+
74
94
  module.exports = CouchBasePlugin
@@ -8,7 +8,8 @@ class PGPlugin extends DatabasePlugin {
8
8
  static operation = 'query'
9
9
  static system = 'postgres'
10
10
 
11
- start ({ params = {}, query, processId, stream }) {
11
+ bindStart (ctx) {
12
+ const { params = {}, query, processId, stream } = ctx
12
13
  const service = this.serviceName({ pluginConfig: this.config, params })
13
14
  const originalStatement = this.maybeTruncate(query.text)
14
15
 
@@ -25,13 +26,15 @@ class PGPlugin extends DatabasePlugin {
25
26
  'out.host': params.host,
26
27
  [CLIENT_PORT_KEY]: params.port
27
28
  }
28
- })
29
+ }, ctx)
29
30
 
30
31
  if (stream) {
31
32
  span.setTag('db.stream', 1)
32
33
  }
33
34
 
34
35
  query.__ddInjectableQuery = this.injectDbmQuery(span, query.text, service, !!query.name)
36
+
37
+ return ctx.currentStore
35
38
  }
36
39
  }
37
40
 
@@ -16,7 +16,10 @@ class SqlInjectionAnalyzer extends StoredInjectionAnalyzer {
16
16
  onConfigure () {
17
17
  this.addSub('apm:mysql:query:start', ({ sql }) => this.analyze(sql, undefined, 'MYSQL'))
18
18
  this.addSub('datadog:mysql2:outerquery:start', ({ sql }) => this.analyze(sql, undefined, 'MYSQL'))
19
- this.addSub('apm:pg:query:start', ({ query }) => this.analyze(query.text, undefined, 'POSTGRES'))
19
+ this.addSub(
20
+ 'apm:pg:query:start',
21
+ ({ originalText, query }) => this.analyze(originalText || query.text, undefined, 'POSTGRES')
22
+ )
20
23
 
21
24
  this.addBind(
22
25
  'datadog:sequelize:query:start',
@@ -24,17 +27,22 @@ class SqlInjectionAnalyzer extends StoredInjectionAnalyzer {
24
27
  )
25
28
  this.addSub('datadog:sequelize:query:finish', () => this.returnToParentStore())
26
29
 
27
- this.addSub('datadog:pg:pool:query:start', ({ query }) => this.setStoreAndAnalyze(query.text, 'POSTGRES'))
30
+ this.addBind('datadog:pg:pool:query:start', ({ query }) => this.getStoreAndAnalyze(query.text, 'POSTGRES'))
28
31
  this.addSub('datadog:pg:pool:query:finish', () => this.returnToParentStore())
29
32
 
30
33
  this.addSub('datadog:mysql:pool:query:start', ({ sql }) => this.setStoreAndAnalyze(sql, 'MYSQL'))
31
34
  this.addSub('datadog:mysql:pool:query:finish', () => this.returnToParentStore())
32
35
 
33
- this.addSub('datadog:knex:raw:start', ({ sql, dialect: knexDialect }) => {
36
+ this.addBind('datadog:knex:raw:start', (context) => {
37
+ const { sql, dialect: knexDialect } = context
34
38
  const dialect = this.normalizeKnexDialect(knexDialect)
35
- this.setStoreAndAnalyze(sql, dialect)
39
+ const currentStore = this.getStoreAndAnalyze(sql, dialect)
40
+ context.currentStore = currentStore
41
+ return currentStore
36
42
  })
37
- this.addSub('datadog:knex:raw:finish', () => this.returnToParentStore())
43
+
44
+ this.addBind('datadog:knex:raw:subscribes', ({ currentStore }) => currentStore)
45
+ this.addBind('datadog:knex:raw:finish', ({ currentStore }) => currentStore?.sqlParentStore)
38
46
  }
39
47
 
40
48
  setStoreAndAnalyze (query, dialect) {
@@ -54,8 +62,7 @@ class SqlInjectionAnalyzer extends StoredInjectionAnalyzer {
54
62
  }
55
63
  }
56
64
 
57
- returnToParentStore () {
58
- const store = storage('legacy').getStore()
65
+ returnToParentStore (store = storage('legacy').getStore()) {
59
66
  if (store && store.sqlParentStore) {
60
67
  storage('legacy').enterWith(store.sqlParentStore)
61
68
  }
@@ -139,12 +139,12 @@ class TaintTrackingPlugin extends SourceIastPlugin {
139
139
  addDatabaseSubscriptions () {
140
140
  this.addSub(
141
141
  { channelName: 'datadog:sequelize:query:finish', tag: SQL_ROW_VALUE },
142
- ({ result }) => this._taintDatabaseResult(result, 'sequelize')
142
+ ({ result }) => this._taintDatabaseResult(result, 'sequelize', getIastContext(storage('legacy').getStore()))
143
143
  )
144
144
 
145
145
  this.addSub(
146
146
  { channelName: 'apm:pg:query:finish', tag: SQL_ROW_VALUE },
147
- ({ result }) => this._taintDatabaseResult(result, 'pg')
147
+ ({ result, currentStore }) => this._taintDatabaseResult(result, 'pg', getIastContext(currentStore))
148
148
  )
149
149
  }
150
150
 
@@ -263,7 +263,7 @@ class TaintTrackingPlugin extends SourceIastPlugin {
263
263
  this.taintUrl(req, iastContext)
264
264
  }
265
265
 
266
- _taintDatabaseResult (result, dbOrigin, iastContext = getIastContext(storage('legacy').getStore()), name) {
266
+ _taintDatabaseResult (result, dbOrigin, iastContext, name) {
267
267
  if (!iastContext) return result
268
268
 
269
269
  if (this._rowsToTaint === 0) return result