dd-trace 5.41.1 → 5.43.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 (70) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/index.d.ts +8 -1
  3. package/package.json +7 -4
  4. package/packages/datadog-esbuild/index.js +3 -1
  5. package/packages/datadog-instrumentations/src/cucumber.js +37 -29
  6. package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +102 -0
  7. package/packages/datadog-instrumentations/src/{check_require_cache.js → helpers/check-require-cache.js} +2 -2
  8. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -2
  9. package/packages/datadog-instrumentations/src/helpers/register.js +4 -1
  10. package/packages/datadog-instrumentations/src/jest.js +72 -49
  11. package/packages/datadog-instrumentations/src/langchain.js +29 -10
  12. package/packages/datadog-instrumentations/src/mocha/main.js +53 -34
  13. package/packages/datadog-instrumentations/src/mocha/utils.js +34 -24
  14. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -8
  15. package/packages/datadog-instrumentations/src/openai.js +1 -1
  16. package/packages/datadog-instrumentations/src/playwright.js +37 -30
  17. package/packages/datadog-instrumentations/src/vitest.js +83 -33
  18. package/packages/datadog-plugin-cucumber/src/index.js +13 -4
  19. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +41 -35
  20. package/packages/datadog-plugin-cypress/src/plugin.js +10 -0
  21. package/packages/datadog-plugin-google-cloud-vertexai/src/index.js +195 -0
  22. package/packages/datadog-plugin-jest/src/index.js +18 -6
  23. package/packages/datadog-plugin-langchain/src/handlers/embedding.js +4 -1
  24. package/packages/datadog-plugin-mocha/src/index.js +13 -4
  25. package/packages/datadog-plugin-playwright/src/index.js +19 -5
  26. package/packages/datadog-plugin-vitest/src/index.js +41 -17
  27. package/packages/dd-trace/src/appsec/api_security_sampler.js +7 -3
  28. package/packages/dd-trace/src/appsec/blocking.js +23 -16
  29. package/packages/dd-trace/src/appsec/graphql.js +13 -6
  30. package/packages/dd-trace/src/appsec/rasp/utils.js +0 -1
  31. package/packages/dd-trace/src/appsec/reporter.js +35 -0
  32. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -3
  33. package/packages/dd-trace/src/appsec/telemetry/common.js +2 -1
  34. package/packages/dd-trace/src/appsec/telemetry/index.js +5 -1
  35. package/packages/dd-trace/src/appsec/telemetry/rasp.js +16 -1
  36. package/packages/dd-trace/src/appsec/telemetry/waf.js +62 -1
  37. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +43 -13
  38. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +2 -2
  39. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +15 -14
  40. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +1 -2
  41. package/packages/dd-trace/src/ci-visibility/telemetry.js +2 -1
  42. package/packages/dd-trace/src/ci-visibility/{quarantined-tests/get-quarantined-tests.js → test-management/get-test-management-tests.js} +5 -5
  43. package/packages/dd-trace/src/config.js +15 -1
  44. package/packages/dd-trace/src/dogstatsd.js +3 -3
  45. package/packages/dd-trace/src/encode/0.4.js +108 -12
  46. package/packages/dd-trace/src/encode/0.5.js +7 -1
  47. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -2
  48. package/packages/dd-trace/src/exporters/agent/index.js +2 -1
  49. package/packages/dd-trace/src/exporters/agent/writer.js +3 -2
  50. package/packages/dd-trace/src/format.js +34 -32
  51. package/packages/dd-trace/src/lambda/runtime/patch.js +5 -3
  52. package/packages/dd-trace/src/lambda/runtime/ritm.js +13 -18
  53. package/packages/dd-trace/src/llmobs/plugins/openai.js +27 -2
  54. package/packages/dd-trace/src/llmobs/sdk.js +3 -3
  55. package/packages/dd-trace/src/log/index.js +1 -13
  56. package/packages/dd-trace/src/log/utils.js +16 -0
  57. package/packages/dd-trace/src/opentracing/span.js +3 -0
  58. package/packages/dd-trace/src/plugins/ci_plugin.js +38 -10
  59. package/packages/dd-trace/src/plugins/index.js +1 -0
  60. package/packages/dd-trace/src/plugins/util/git.js +7 -3
  61. package/packages/dd-trace/src/plugins/util/test.js +10 -0
  62. package/packages/dd-trace/src/plugins/util/web.js +5 -2
  63. package/packages/dd-trace/src/priority_sampler.js +116 -15
  64. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +13 -14
  65. package/packages/dd-trace/src/sampler.js +9 -0
  66. package/packages/dd-trace/src/standalone/product.js +6 -2
  67. package/packages/dd-trace/src/startup-log.js +2 -1
  68. package/packages/dd-trace/src/telemetry/metrics.js +0 -8
  69. package/packages/dd-trace/src/tracer.js +1 -1
  70. /package/packages/datadog-instrumentations/src/{utils/src → helpers}/extract-package-and-module-path.js +0 -0
@@ -5,11 +5,22 @@ const { Chunk, MsgpackEncoder } = require('../msgpack')
5
5
  const log = require('../log')
6
6
  const { isTrue } = require('../util')
7
7
  const coalesce = require('koalas')
8
+ const { memoize } = require('../log/utils')
8
9
 
9
10
  const SOFT_LIMIT = 8 * 1024 * 1024 // 8MB
10
11
 
11
- function formatSpan (span) {
12
- return normalizeSpan(truncateSpan(span, false))
12
+ function formatSpan (span, config) {
13
+ span = normalizeSpan(truncateSpan(span, false))
14
+ if (span.span_events) {
15
+ // ensure span events are encoded as tags if agent doesn't support native top level span events
16
+ if (!config?.trace?.nativeSpanEvents) {
17
+ span.meta.events = JSON.stringify(span.span_events)
18
+ delete span.span_events
19
+ } else {
20
+ formatSpanEvents(span)
21
+ }
22
+ }
23
+ return span
13
24
  }
14
25
 
15
26
  class AgentEncoder {
@@ -24,6 +35,7 @@ class AgentEncoder {
24
35
  process.env.DD_TRACE_ENCODING_DEBUG,
25
36
  false
26
37
  ))
38
+ this._config = this._writer?._config
27
39
  }
28
40
 
29
41
  count () {
@@ -74,16 +86,18 @@ class AgentEncoder {
74
86
  this._encodeArrayPrefix(bytes, trace)
75
87
 
76
88
  for (let span of trace) {
77
- span = formatSpan(span)
89
+ span = formatSpan(span, this._config)
78
90
  bytes.reserve(1)
79
91
 
80
- if (span.type && span.meta_struct) {
81
- bytes.buffer[bytes.length - 1] = 0x8d
82
- } else if (span.type || span.meta_struct) {
83
- bytes.buffer[bytes.length - 1] = 0x8c
84
- } else {
85
- bytes.buffer[bytes.length - 1] = 0x8b
86
- }
92
+ // this is the original size of the fixed map for span attributes that always exist
93
+ let mapSize = 11
94
+
95
+ // increment the payload map size depending on if some optional fields exist
96
+ if (span.type) mapSize += 1
97
+ if (span.meta_struct) mapSize += 1
98
+ if (span.span_events) mapSize += 1
99
+
100
+ bytes.buffer[bytes.length - 1] = 0x80 + mapSize
87
101
 
88
102
  if (span.type) {
89
103
  this._encodeString(bytes, 'type')
@@ -112,6 +126,10 @@ class AgentEncoder {
112
126
  this._encodeMap(bytes, span.meta)
113
127
  this._encodeString(bytes, 'metrics')
114
128
  this._encodeMap(bytes, span.metrics)
129
+ if (span.span_events) {
130
+ this._encodeString(bytes, 'span_events')
131
+ this._encodeObjectAsArray(bytes, span.span_events, new Set())
132
+ }
115
133
  if (span.meta_struct) {
116
134
  this._encodeString(bytes, 'meta_struct')
117
135
  this._encodeMetaStruct(bytes, span.meta_struct)
@@ -200,6 +218,9 @@ class AgentEncoder {
200
218
  case 'number':
201
219
  this._encodeFloat(bytes, value)
202
220
  break
221
+ case 'boolean':
222
+ this._encodeBool(bytes, value)
223
+ break
203
224
  default:
204
225
  // should not happen
205
226
  }
@@ -258,7 +279,7 @@ class AgentEncoder {
258
279
  this._encodeObjectAsArray(bytes, value, circularReferencesDetector)
259
280
  } else if (value !== null && typeof value === 'object') {
260
281
  this._encodeObjectAsMap(bytes, value, circularReferencesDetector)
261
- } else if (typeof value === 'string' || typeof value === 'number') {
282
+ } else if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
262
283
  this._encodeValue(bytes, value)
263
284
  }
264
285
  }
@@ -268,7 +289,7 @@ class AgentEncoder {
268
289
  const validKeys = keys.filter(key => {
269
290
  const v = value[key]
270
291
  return typeof v === 'string' ||
271
- typeof v === 'number' ||
292
+ typeof v === 'number' || typeof v === 'boolean' ||
272
293
  (v !== null && typeof v === 'object' && !circularReferencesDetector.has(v))
273
294
  })
274
295
 
@@ -319,4 +340,79 @@ class AgentEncoder {
319
340
  }
320
341
  }
321
342
 
343
+ const memoizedLogDebug = memoize((key, message) => {
344
+ log.debug(message)
345
+ // return something to store in memoize cache
346
+ return true
347
+ })
348
+
349
+ function formatSpanEvents (span) {
350
+ for (const spanEvent of span.span_events) {
351
+ if (spanEvent.attributes) {
352
+ for (const [key, value] of Object.entries(spanEvent.attributes)) {
353
+ const newValue = convertSpanEventAttributeValues(key, value)
354
+ if (newValue !== undefined) {
355
+ spanEvent.attributes[key] = newValue
356
+ } else {
357
+ delete spanEvent.attributes[key] // delete from attributes if undefined
358
+ }
359
+ }
360
+ if (Object.entries(spanEvent.attributes).length === 0) {
361
+ delete spanEvent.attributes
362
+ }
363
+ }
364
+ }
365
+ }
366
+
367
+ function convertSpanEventAttributeValues (key, value, depth = 0) {
368
+ if (typeof value === 'string') {
369
+ return {
370
+ type: 0,
371
+ string_value: value
372
+ }
373
+ } else if (typeof value === 'boolean') {
374
+ return {
375
+ type: 1,
376
+ bool_value: value
377
+ }
378
+ } else if (Number.isInteger(value)) {
379
+ return {
380
+ type: 2,
381
+ int_value: value
382
+ }
383
+ } else if (typeof value === 'number') {
384
+ return {
385
+ type: 3,
386
+ double_value: value
387
+ }
388
+ } else if (Array.isArray(value)) {
389
+ if (depth === 0) {
390
+ const convertedArray = value
391
+ .map((val) => convertSpanEventAttributeValues(key, val, 1))
392
+ .filter((convertedVal) => convertedVal !== undefined)
393
+
394
+ // Only include array_value if there are valid elements
395
+ if (convertedArray.length > 0) {
396
+ return {
397
+ type: 4,
398
+ array_value: convertedArray
399
+ }
400
+ } else {
401
+ // If all elements were unsupported, return undefined
402
+ return undefined
403
+ }
404
+ } else {
405
+ memoizedLogDebug(key, 'Encountered nested array data type for span event v0.4 encoding. ' +
406
+ `Skipping encoding key: ${key}: with value: ${typeof value}.`
407
+ )
408
+ return undefined
409
+ }
410
+ } else {
411
+ memoizedLogDebug(key, 'Encountered unsupported data type for span event v0.4 encoding, key: ' +
412
+ `${key}: with value: ${typeof value}. Skipping encoding of pair.`
413
+ )
414
+ return undefined
415
+ }
416
+ }
417
+
322
418
  module.exports = { AgentEncoder }
@@ -7,7 +7,13 @@ const ARRAY_OF_TWO = 0x92
7
7
  const ARRAY_OF_TWELVE = 0x9c
8
8
 
9
9
  function formatSpan (span) {
10
- return normalizeSpan(truncateSpan(span, false))
10
+ span = normalizeSpan(truncateSpan(span, false))
11
+ // ensure span events are encoded as tags
12
+ if (span.span_events) {
13
+ span.meta.events = JSON.stringify(span.span_events)
14
+ delete span.span_events
15
+ }
16
+ return span
11
17
  }
12
18
 
13
19
  class AgentEncoder extends BaseEncoder {
@@ -48,8 +48,15 @@ class AgentlessCiVisibilityEncoder extends AgentEncoder {
48
48
  this.reset()
49
49
  }
50
50
 
51
- setMetadataTags (tags) {
52
- this.metadataTags = tags
51
+ addMetadataTags (tags) {
52
+ ALLOWED_CONTENT_TYPES.forEach(type => {
53
+ if (tags[type]) {
54
+ this.metadataTags[type] = {
55
+ ...this.metadataTags[type],
56
+ ...tags[type]
57
+ }
58
+ }
59
+ })
53
60
  }
54
61
 
55
62
  _encodeTestSuite (bytes, content) {
@@ -24,7 +24,8 @@ class AgentExporter {
24
24
  prioritySampler,
25
25
  lookup,
26
26
  protocolVersion,
27
- headers
27
+ headers,
28
+ config
28
29
  })
29
30
 
30
31
  this._timer = undefined
@@ -10,15 +10,16 @@ const BaseWriter = require('../common/writer')
10
10
  const METRIC_PREFIX = 'datadog.tracer.node.exporter.agent'
11
11
 
12
12
  class Writer extends BaseWriter {
13
- constructor ({ prioritySampler, lookup, protocolVersion, headers }) {
13
+ constructor ({ prioritySampler, lookup, protocolVersion, headers, config = {} }) {
14
14
  super(...arguments)
15
15
  const AgentEncoder = getEncoder(protocolVersion)
16
16
 
17
17
  this._prioritySampler = prioritySampler
18
18
  this._lookup = lookup
19
19
  this._protocolVersion = protocolVersion
20
- this._encoder = new AgentEncoder(this)
21
20
  this._headers = headers
21
+ this._config = config
22
+ this._encoder = new AgentEncoder(this)
22
23
  }
23
24
 
24
25
  _sendPayload (data, count, done) {
@@ -68,7 +68,7 @@ function setSingleSpanIngestionTags (span, options) {
68
68
  addTag({}, span.metrics, SPAN_SAMPLING_MAX_PER_SECOND, options.maxPerSecond)
69
69
  }
70
70
 
71
- function extractSpanLinks (trace, span) {
71
+ function extractSpanLinks (formattedSpan, span) {
72
72
  const links = []
73
73
  if (span._links) {
74
74
  for (const link of span._links) {
@@ -87,10 +87,10 @@ function extractSpanLinks (trace, span) {
87
87
  links.push(formattedLink)
88
88
  }
89
89
  }
90
- if (links.length > 0) { trace.meta['_dd.span_links'] = JSON.stringify(links) }
90
+ if (links.length > 0) { formattedSpan.meta['_dd.span_links'] = JSON.stringify(links) }
91
91
  }
92
92
 
93
- function extractSpanEvents (trace, span) {
93
+ function extractSpanEvents (formattedSpan, span) {
94
94
  const events = []
95
95
  if (span._events) {
96
96
  for (const event of span._events) {
@@ -103,10 +103,12 @@ function extractSpanEvents (trace, span) {
103
103
  events.push(formattedEvent)
104
104
  }
105
105
  }
106
- if (events.length > 0) { trace.meta.events = JSON.stringify(events) }
106
+ if (events.length > 0) {
107
+ formattedSpan.span_events = events
108
+ }
107
109
  }
108
110
 
109
- function extractTags (trace, span) {
111
+ function extractTags (formattedSpan, span) {
110
112
  const context = span.context()
111
113
  const origin = context._trace.origin
112
114
  const tags = context._tags
@@ -114,7 +116,7 @@ function extractTags (trace, span) {
114
116
  const priority = context._sampling.priority
115
117
 
116
118
  if (tags['span.kind'] && tags['span.kind'] !== 'internal') {
117
- addTag({}, trace.metrics, MEASURED, 1)
119
+ addTag({}, formattedSpan.metrics, MEASURED, 1)
118
120
  }
119
121
 
120
122
  const tracerService = span.tracer()._service.toLowerCase()
@@ -129,22 +131,22 @@ function extractTags (trace, span) {
129
131
  case 'service.name':
130
132
  case 'span.type':
131
133
  case 'resource.name':
132
- addTag(trace, {}, map[tag], tags[tag])
134
+ addTag(formattedSpan, {}, map[tag], tags[tag])
133
135
  break
134
136
  // HACK: remove when Datadog supports numeric status code
135
137
  case 'http.status_code':
136
- addTag(trace.meta, {}, tag, tags[tag] && String(tags[tag]))
138
+ addTag(formattedSpan.meta, {}, tag, tags[tag] && String(tags[tag]))
137
139
  break
138
140
  case 'analytics.event':
139
- addTag({}, trace.metrics, ANALYTICS, tags[tag] === undefined || tags[tag] ? 1 : 0)
141
+ addTag({}, formattedSpan.metrics, ANALYTICS, tags[tag] === undefined || tags[tag] ? 1 : 0)
140
142
  break
141
143
  case HOSTNAME_KEY:
142
144
  case MEASURED:
143
- addTag({}, trace.metrics, tag, tags[tag] === undefined || tags[tag] ? 1 : 0)
145
+ addTag({}, formattedSpan.metrics, tag, tags[tag] === undefined || tags[tag] ? 1 : 0)
144
146
  break
145
147
  case 'error':
146
148
  if (context._name !== 'fs.operation') {
147
- extractError(trace, tags[tag])
149
+ extractError(formattedSpan, tags[tag])
148
150
  }
149
151
  break
150
152
  case ERROR_TYPE:
@@ -152,60 +154,60 @@ function extractTags (trace, span) {
152
154
  case ERROR_STACK:
153
155
  // HACK: remove when implemented in the backend
154
156
  if (context._name !== 'fs.operation') {
155
- // HACK: to ensure otel.recordException does not influence trace.error
157
+ // HACK: to ensure otel.recordException does not influence formattedSpan.error
156
158
  if (tags.setTraceError) {
157
- trace.error = 1
159
+ formattedSpan.error = 1
158
160
  }
159
161
  } else {
160
162
  break
161
163
  }
162
164
  default: // eslint-disable-line no-fallthrough
163
- addTag(trace.meta, trace.metrics, tag, tags[tag])
165
+ addTag(formattedSpan.meta, formattedSpan.metrics, tag, tags[tag])
164
166
  }
165
167
  }
166
- setSingleSpanIngestionTags(trace, context._spanSampling)
168
+ setSingleSpanIngestionTags(formattedSpan, context._spanSampling)
167
169
 
168
- addTag(trace.meta, trace.metrics, 'language', 'javascript')
169
- addTag(trace.meta, trace.metrics, PROCESS_ID, process.pid)
170
- addTag(trace.meta, trace.metrics, SAMPLING_PRIORITY_KEY, priority)
171
- addTag(trace.meta, trace.metrics, ORIGIN_KEY, origin)
172
- addTag(trace.meta, trace.metrics, HOSTNAME_KEY, hostname)
170
+ addTag(formattedSpan.meta, formattedSpan.metrics, 'language', 'javascript')
171
+ addTag(formattedSpan.meta, formattedSpan.metrics, PROCESS_ID, process.pid)
172
+ addTag(formattedSpan.meta, formattedSpan.metrics, SAMPLING_PRIORITY_KEY, priority)
173
+ addTag(formattedSpan.meta, formattedSpan.metrics, ORIGIN_KEY, origin)
174
+ addTag(formattedSpan.meta, formattedSpan.metrics, HOSTNAME_KEY, hostname)
173
175
  }
174
176
 
175
- function extractRootTags (trace, span) {
177
+ function extractRootTags (formattedSpan, span) {
176
178
  const context = span.context()
177
179
  const isLocalRoot = span === context._trace.started[0]
178
180
  const parentId = context._parentId
179
181
 
180
182
  if (!isLocalRoot || (parentId && parentId.toString(10) !== '0')) return
181
183
 
182
- addTag({}, trace.metrics, SAMPLING_RULE_DECISION, context._trace[SAMPLING_RULE_DECISION])
183
- addTag({}, trace.metrics, SAMPLING_LIMIT_DECISION, context._trace[SAMPLING_LIMIT_DECISION])
184
- addTag({}, trace.metrics, SAMPLING_AGENT_DECISION, context._trace[SAMPLING_AGENT_DECISION])
185
- addTag({}, trace.metrics, TOP_LEVEL_KEY, 1)
184
+ addTag({}, formattedSpan.metrics, SAMPLING_RULE_DECISION, context._trace[SAMPLING_RULE_DECISION])
185
+ addTag({}, formattedSpan.metrics, SAMPLING_LIMIT_DECISION, context._trace[SAMPLING_LIMIT_DECISION])
186
+ addTag({}, formattedSpan.metrics, SAMPLING_AGENT_DECISION, context._trace[SAMPLING_AGENT_DECISION])
187
+ addTag({}, formattedSpan.metrics, TOP_LEVEL_KEY, 1)
186
188
  }
187
189
 
188
- function extractChunkTags (trace, span) {
190
+ function extractChunkTags (formattedSpan, span) {
189
191
  const context = span.context()
190
192
  const isLocalRoot = span === context._trace.started[0]
191
193
 
192
194
  if (!isLocalRoot) return
193
195
 
194
196
  for (const key in context._trace.tags) {
195
- addTag(trace.meta, trace.metrics, key, context._trace.tags[key])
197
+ addTag(formattedSpan.meta, formattedSpan.metrics, key, context._trace.tags[key])
196
198
  }
197
199
  }
198
200
 
199
- function extractError (trace, error) {
201
+ function extractError (formattedSpan, error) {
200
202
  if (!error) return
201
203
 
202
- trace.error = 1
204
+ formattedSpan.error = 1
203
205
 
204
206
  if (isError(error)) {
205
207
  // AggregateError only has a code and no message.
206
- addTag(trace.meta, trace.metrics, ERROR_MESSAGE, error.message || error.code)
207
- addTag(trace.meta, trace.metrics, ERROR_TYPE, error.name)
208
- addTag(trace.meta, trace.metrics, ERROR_STACK, error.stack)
208
+ addTag(formattedSpan.meta, formattedSpan.metrics, ERROR_MESSAGE, error.message || error.code)
209
+ addTag(formattedSpan.meta, formattedSpan.metrics, ERROR_TYPE, error.name)
210
+ addTag(formattedSpan.meta, formattedSpan.metrics, ERROR_STACK, error.stack)
209
211
  }
210
212
  }
211
213
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  const path = require('path')
4
4
 
5
- const { _extractModuleNameAndHandlerPath, _extractModuleRootAndHandler, _getLambdaFilePath } = require('./ritm')
5
+ const { _extractModuleNameAndHandlerPath, _extractModuleRootAndHandler, _getLambdaFilePaths } = require('./ritm')
6
6
  const { datadog } = require('../handler')
7
7
  const { addHook } = require('../../../../datadog-instrumentations/src/helpers/instrument')
8
8
  const shimmer = require('../../../../datadog-shimmer')
@@ -65,9 +65,11 @@ if (originalLambdaHandler !== undefined) {
65
65
  const [_module, handlerPath] = _extractModuleNameAndHandlerPath(moduleAndHandler)
66
66
 
67
67
  const lambdaStylePath = path.resolve(lambdaTaskRoot, moduleRoot, _module)
68
- const lambdaFilePath = _getLambdaFilePath(lambdaStylePath)
68
+ const lambdaFilePaths = _getLambdaFilePaths(lambdaStylePath)
69
69
 
70
- addHook({ name: lambdaFilePath }, patchLambdaModule(handlerPath))
70
+ for (const lambdaFilePath of lambdaFilePaths) {
71
+ addHook({ name: lambdaFilePath }, patchLambdaModule(handlerPath))
72
+ }
71
73
  } else {
72
74
  // Instrumentation is done manually.
73
75
  addHook({ name: 'datadog-lambda-js' }, patchDatadogLambdaModule)
@@ -7,7 +7,6 @@
7
7
  */
8
8
  'use strict'
9
9
 
10
- const fs = require('fs')
11
10
  const path = require('path')
12
11
 
13
12
  const log = require('../../log')
@@ -60,23 +59,18 @@ function _extractModuleNameAndHandlerPath (handler) {
60
59
  }
61
60
 
62
61
  /**
63
- * Returns the correct path of the file to be patched
64
- * when required.
62
+ * Returns all possible paths of the files to be patched when required.
65
63
  *
66
64
  * @param {*} lambdaStylePath the path comprised of the `LAMBDA_TASK_ROOT`,
67
65
  * the root of the module of the Lambda handler, and the module name.
68
- * @returns the lambdaStylePath with the appropiate extension for the hook.
66
+ * @returns the lambdaStylePath with appropiate extensions for the hook.
69
67
  */
70
- function _getLambdaFilePath (lambdaStylePath) {
71
- let lambdaFilePath = lambdaStylePath
72
- if (fs.existsSync(lambdaStylePath + '.js')) {
73
- lambdaFilePath += '.js'
74
- } else if (fs.existsSync(lambdaStylePath + '.mjs')) {
75
- lambdaFilePath += '.mjs'
76
- } else if (fs.existsSync(lambdaStylePath + '.cjs')) {
77
- lambdaFilePath += '.cjs'
78
- }
79
- return lambdaFilePath
68
+ function _getLambdaFilePaths (lambdaStylePath) {
69
+ return [
70
+ `${lambdaStylePath}.js`,
71
+ `${lambdaStylePath}.mjs`,
72
+ `${lambdaStylePath}.cjs`
73
+ ]
80
74
  }
81
75
 
82
76
  /**
@@ -92,12 +86,13 @@ const registerLambdaHook = () => {
92
86
  const [_module] = _extractModuleNameAndHandlerPath(moduleAndHandler)
93
87
 
94
88
  const lambdaStylePath = path.resolve(lambdaTaskRoot, moduleRoot, _module)
95
- const lambdaFilePath = _getLambdaFilePath(lambdaStylePath)
89
+ const lambdaFilePaths = _getLambdaFilePaths(lambdaStylePath)
96
90
 
97
- Hook([lambdaFilePath], (moduleExports) => {
91
+ // TODO: Redo this like any other instrumentation.
92
+ Hook(lambdaFilePaths, (moduleExports, name) => {
98
93
  require('./patch')
99
94
 
100
- for (const { hook } of instrumentations[lambdaFilePath]) {
95
+ for (const { hook } of instrumentations[name]) {
101
96
  try {
102
97
  moduleExports = hook(moduleExports)
103
98
  } catch (e) {
@@ -133,6 +128,6 @@ const registerLambdaHook = () => {
133
128
  module.exports = {
134
129
  _extractModuleRootAndHandler,
135
130
  _extractModuleNameAndHandlerPath,
136
- _getLambdaFilePath,
131
+ _getLambdaFilePaths,
137
132
  registerLambdaHook
138
133
  }
@@ -2,6 +2,13 @@
2
2
 
3
3
  const LLMObsPlugin = require('./base')
4
4
 
5
+ function isIterable (obj) {
6
+ if (obj == null) {
7
+ return false
8
+ }
9
+ return typeof obj[Symbol.iterator] === 'function'
10
+ }
11
+
5
12
  class OpenAiLLMObsPlugin extends LLMObsPlugin {
6
13
  static get id () { return 'openai' }
7
14
  static get prefix () {
@@ -16,10 +23,13 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
16
23
  const inputs = ctx.args[0] // completion, chat completion, and embeddings take one argument
17
24
  const operation = getOperation(methodName)
18
25
  const kind = operation === 'embedding' ? 'embedding' : 'llm'
19
- const name = `openai.${methodName}`
26
+
27
+ const { modelProvider, client } = this._getModelProviderAndClient(ctx.basePath)
28
+
29
+ const name = `${client}.${methodName}`
20
30
 
21
31
  return {
22
- modelProvider: 'openai',
32
+ modelProvider,
23
33
  modelName: inputs.model,
24
34
  kind,
25
35
  name
@@ -52,6 +62,16 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
52
62
  }
53
63
  }
54
64
 
65
+ _getModelProviderAndClient (baseUrl = '') {
66
+ if (baseUrl.includes('azure')) {
67
+ return { modelProvider: 'azure_openai', client: 'AzureOpenAI' }
68
+ } else if (baseUrl.includes('deepseek')) {
69
+ return { modelProvider: 'deepseek', client: 'DeepSeek' }
70
+ } else {
71
+ return { modelProvider: 'openai', client: 'OpenAI' }
72
+ }
73
+ }
74
+
55
75
  _extractMetrics (response) {
56
76
  const metrics = {}
57
77
  const tokenUsage = response.usage
@@ -122,6 +142,11 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
122
142
 
123
143
  const outputMessages = []
124
144
  const { choices } = response
145
+ if (!isIterable(choices)) {
146
+ this._tagger.tagLLMIO(span, messages, [{ content: '' }])
147
+ return
148
+ }
149
+
125
150
  for (const choice of choices) {
126
151
  const message = choice.message || choice.delta
127
152
  const content = message.content || ''
@@ -105,12 +105,12 @@ class LLMObs extends NoopLLMObs {
105
105
 
106
106
  if (fn.length > 1) {
107
107
  return this._tracer.trace(name, spanOptions, (span, cb) =>
108
- this._activate(span, { kind, options: llmobsOptions }, () => fn(span, cb))
108
+ this._activate(span, { kind, ...llmobsOptions }, () => fn(span, cb))
109
109
  )
110
110
  }
111
111
 
112
112
  return this._tracer.trace(name, spanOptions, span =>
113
- this._activate(span, { kind, options: llmobsOptions }, () => fn(span))
113
+ this._activate(span, { kind, ...llmobsOptions }, () => fn(span))
114
114
  )
115
115
  }
116
116
 
@@ -166,7 +166,7 @@ class LLMObs extends NoopLLMObs {
166
166
  }
167
167
 
168
168
  try {
169
- const result = llmobs._activate(span, { kind, options: llmobsOptions }, () => fn.apply(this, fnArgs))
169
+ const result = llmobs._activate(span, { kind, ...llmobsOptions }, () => fn.apply(this, fnArgs))
170
170
 
171
171
  if (result && typeof result.then === 'function') {
172
172
  return result.then(
@@ -6,19 +6,7 @@ const { isTrue } = require('../util')
6
6
  const { traceChannel, debugChannel, infoChannel, warnChannel, errorChannel } = require('./channels')
7
7
  const logWriter = require('./writer')
8
8
  const { Log } = require('./log')
9
-
10
- const memoize = func => {
11
- const cache = {}
12
- const memoized = function (key) {
13
- if (!cache[key]) {
14
- cache[key] = func.apply(this, arguments)
15
- }
16
-
17
- return cache[key]
18
- }
19
-
20
- return memoized
21
- }
9
+ const { memoize } = require('./utils')
22
10
 
23
11
  const config = {
24
12
  enabled: false,
@@ -0,0 +1,16 @@
1
+ 'use strict'
2
+
3
+ const memoize = func => {
4
+ const cache = {}
5
+ const memoized = function (key) {
6
+ if (!cache[key]) {
7
+ cache[key] = func.apply(this, arguments)
8
+ }
9
+
10
+ return cache[key]
11
+ }
12
+
13
+ return memoized
14
+ }
15
+
16
+ module.exports = { memoize }
@@ -143,6 +143,9 @@ class DatadogSpan {
143
143
  return `Span${json}`
144
144
  }
145
145
 
146
+ /**
147
+ * @returns {DatadogSpanContext}
148
+ */
146
149
  context () {
147
150
  return this._spanContext
148
151
  }