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
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const { collectionSizeSym, fieldCountSym } = require('./symbols')
3
4
  const session = require('../session')
4
5
 
5
6
  const LEAF_SUBTYPES = new Set(['date', 'regexp'])
@@ -14,22 +15,38 @@ module.exports = {
14
15
  // each lookup will just finish in its own time and traverse the child nodes when the event loop allows it.
15
16
  // Alternatively, use `Promise.all` or something like that, but the code would probably be more complex.
16
17
 
17
- async function getObject (objectId, maxDepth, depth = 0) {
18
+ async function getObject (objectId, opts, depth = 0, collection = false) {
18
19
  const { result, privateProperties } = await session.post('Runtime.getProperties', {
19
20
  objectId,
20
21
  ownProperties: true // exclude inherited properties
21
22
  })
22
23
 
23
- if (privateProperties) result.push(...privateProperties)
24
+ if (collection) {
25
+ // Trim the collection if it's too large.
26
+ // Collections doesn't contain private properties, so the code in this block doesn't have to deal with it.
27
+ removeNonEnumerableProperties(result) // remove the `length` property
28
+ const size = result.length
29
+ if (size > opts.maxCollectionSize) {
30
+ result.splice(opts.maxCollectionSize)
31
+ result[collectionSizeSym] = size
32
+ }
33
+ } else if (result.length > opts.maxFieldCount) {
34
+ // Trim the number of properties on the object if there's too many.
35
+ const size = result.length
36
+ result.splice(opts.maxFieldCount)
37
+ result[fieldCountSym] = size
38
+ } else if (privateProperties) {
39
+ result.push(...privateProperties)
40
+ }
24
41
 
25
- return traverseGetPropertiesResult(result, maxDepth, depth)
42
+ return traverseGetPropertiesResult(result, opts, depth)
26
43
  }
27
44
 
28
- async function traverseGetPropertiesResult (props, maxDepth, depth) {
45
+ async function traverseGetPropertiesResult (props, opts, depth) {
29
46
  // TODO: Decide if we should filter out non-enumerable properties or not:
30
47
  // props = props.filter((e) => e.enumerable)
31
48
 
32
- if (depth >= maxDepth) return props
49
+ if (depth >= opts.maxReferenceDepth) return props
33
50
 
34
51
  for (const prop of props) {
35
52
  if (prop.value === undefined) continue
@@ -37,33 +54,33 @@ async function traverseGetPropertiesResult (props, maxDepth, depth) {
37
54
  if (type === 'object') {
38
55
  if (objectId === undefined) continue // if `subtype` is "null"
39
56
  if (LEAF_SUBTYPES.has(subtype)) continue // don't waste time with these subtypes
40
- prop.value.properties = await getObjectProperties(subtype, objectId, maxDepth, depth)
57
+ prop.value.properties = await getObjectProperties(subtype, objectId, opts, depth)
41
58
  } else if (type === 'function') {
42
- prop.value.properties = await getFunctionProperties(objectId, maxDepth, depth + 1)
59
+ prop.value.properties = await getFunctionProperties(objectId, opts, depth + 1)
43
60
  }
44
61
  }
45
62
 
46
63
  return props
47
64
  }
48
65
 
49
- async function getObjectProperties (subtype, objectId, maxDepth, depth) {
66
+ async function getObjectProperties (subtype, objectId, opts, depth) {
50
67
  if (ITERABLE_SUBTYPES.has(subtype)) {
51
- return getIterable(objectId, maxDepth, depth)
68
+ return getIterable(objectId, opts, depth)
52
69
  } else if (subtype === 'promise') {
53
- return getInternalProperties(objectId, maxDepth, depth)
70
+ return getInternalProperties(objectId, opts, depth)
54
71
  } else if (subtype === 'proxy') {
55
- return getProxy(objectId, maxDepth, depth)
72
+ return getProxy(objectId, opts, depth)
56
73
  } else if (subtype === 'arraybuffer') {
57
- return getArrayBuffer(objectId, maxDepth, depth)
74
+ return getArrayBuffer(objectId, opts, depth)
58
75
  } else {
59
- return getObject(objectId, maxDepth, depth + 1)
76
+ return getObject(objectId, opts, depth + 1, subtype === 'array' || subtype === 'typedarray')
60
77
  }
61
78
  }
62
79
 
63
80
  // TODO: The following extra information from `internalProperties` might be relevant to include for functions:
64
81
  // - Bound function: `[[TargetFunction]]`, `[[BoundThis]]` and `[[BoundArgs]]`
65
82
  // - Non-bound function: `[[FunctionLocation]]`, and `[[Scopes]]`
66
- async function getFunctionProperties (objectId, maxDepth, depth) {
83
+ async function getFunctionProperties (objectId, opts, depth) {
67
84
  let { result } = await session.post('Runtime.getProperties', {
68
85
  objectId,
69
86
  ownProperties: true // exclude inherited properties
@@ -72,10 +89,12 @@ async function getFunctionProperties (objectId, maxDepth, depth) {
72
89
  // For legacy reasons (I assume) functions has a `prototype` property besides the internal `[[Prototype]]`
73
90
  result = result.filter(({ name }) => name !== 'prototype')
74
91
 
75
- return traverseGetPropertiesResult(result, maxDepth, depth)
92
+ return traverseGetPropertiesResult(result, opts, depth)
76
93
  }
77
94
 
78
- async function getIterable (objectId, maxDepth, depth) {
95
+ async function getIterable (objectId, opts, depth) {
96
+ // TODO: If the iterable has any properties defined on the object directly, instead of in its collection, they will
97
+ // exist in the return value below in the `result` property. We currently do not collect these.
79
98
  const { internalProperties } = await session.post('Runtime.getProperties', {
80
99
  objectId,
81
100
  ownProperties: true // exclude inherited properties
@@ -93,10 +112,17 @@ async function getIterable (objectId, maxDepth, depth) {
93
112
  ownProperties: true // exclude inherited properties
94
113
  })
95
114
 
96
- return traverseGetPropertiesResult(result, maxDepth, depth)
115
+ removeNonEnumerableProperties(result) // remove the `length` property
116
+ const size = result.length
117
+ if (size > opts.maxCollectionSize) {
118
+ result.splice(opts.maxCollectionSize)
119
+ result[collectionSizeSym] = size
120
+ }
121
+
122
+ return traverseGetPropertiesResult(result, opts, depth)
97
123
  }
98
124
 
99
- async function getInternalProperties (objectId, maxDepth, depth) {
125
+ async function getInternalProperties (objectId, opts, depth) {
100
126
  const { internalProperties } = await session.post('Runtime.getProperties', {
101
127
  objectId,
102
128
  ownProperties: true // exclude inherited properties
@@ -105,10 +131,10 @@ async function getInternalProperties (objectId, maxDepth, depth) {
105
131
  // We want all internal properties except the prototype
106
132
  const props = internalProperties.filter(({ name }) => name !== '[[Prototype]]')
107
133
 
108
- return traverseGetPropertiesResult(props, maxDepth, depth)
134
+ return traverseGetPropertiesResult(props, opts, depth)
109
135
  }
110
136
 
111
- async function getProxy (objectId, maxDepth, depth) {
137
+ async function getProxy (objectId, opts, depth) {
112
138
  const { internalProperties } = await session.post('Runtime.getProperties', {
113
139
  objectId,
114
140
  ownProperties: true // exclude inherited properties
@@ -127,14 +153,14 @@ async function getProxy (objectId, maxDepth, depth) {
127
153
  ownProperties: true // exclude inherited properties
128
154
  })
129
155
 
130
- return traverseGetPropertiesResult(result, maxDepth, depth)
156
+ return traverseGetPropertiesResult(result, opts, depth)
131
157
  }
132
158
 
133
159
  // Support for ArrayBuffer is a bit trickly because the internal structure stored in `internalProperties` is not
134
160
  // documented and is not straight forward. E.g. ArrayBuffer(3) will internally contain both Int8Array(3) and
135
161
  // UInt8Array(3), whereas ArrayBuffer(8) internally contains both Int8Array(8), Uint8Array(8), Int16Array(4), and
136
162
  // Int32Array(2) - all representing the same data in different ways.
137
- async function getArrayBuffer (objectId, maxDepth, depth) {
163
+ async function getArrayBuffer (objectId, opts, depth) {
138
164
  const { internalProperties } = await session.post('Runtime.getProperties', {
139
165
  objectId,
140
166
  ownProperties: true // exclude inherited properties
@@ -149,5 +175,13 @@ async function getArrayBuffer (objectId, maxDepth, depth) {
149
175
  ownProperties: true // exclude inherited properties
150
176
  })
151
177
 
152
- return traverseGetPropertiesResult(result, maxDepth, depth)
178
+ return traverseGetPropertiesResult(result, opts, depth)
179
+ }
180
+
181
+ function removeNonEnumerableProperties (props) {
182
+ for (let i = 0; i < props.length; i++) {
183
+ if (props[i].enumerable === false) {
184
+ props.splice(i--, 1)
185
+ }
186
+ }
153
187
  }
@@ -4,6 +4,8 @@ const { getRuntimeObject } = require('./collector')
4
4
  const { processRawState } = require('./processor')
5
5
 
6
6
  const DEFAULT_MAX_REFERENCE_DEPTH = 3
7
+ const DEFAULT_MAX_COLLECTION_SIZE = 100
8
+ const DEFAULT_MAX_FIELD_COUNT = 20
7
9
  const DEFAULT_MAX_LENGTH = 255
8
10
 
9
11
  module.exports = {
@@ -12,14 +14,22 @@ module.exports = {
12
14
 
13
15
  async function getLocalStateForCallFrame (
14
16
  callFrame,
15
- { maxReferenceDepth = DEFAULT_MAX_REFERENCE_DEPTH, maxLength = DEFAULT_MAX_LENGTH } = {}
17
+ {
18
+ maxReferenceDepth = DEFAULT_MAX_REFERENCE_DEPTH,
19
+ maxCollectionSize = DEFAULT_MAX_COLLECTION_SIZE,
20
+ maxFieldCount = DEFAULT_MAX_FIELD_COUNT,
21
+ maxLength = DEFAULT_MAX_LENGTH
22
+ } = {}
16
23
  ) {
17
24
  const rawState = []
18
25
  let processedState = null
19
26
 
20
27
  for (const scope of callFrame.scopeChain) {
21
28
  if (scope.type === 'global') continue // The global scope is too noisy
22
- rawState.push(...await getRuntimeObject(scope.object.objectId, maxReferenceDepth))
29
+ rawState.push(...await getRuntimeObject(
30
+ scope.object.objectId,
31
+ { maxReferenceDepth, maxCollectionSize, maxFieldCount }
32
+ ))
23
33
  }
24
34
 
25
35
  // Deplay calling `processRawState` so the caller gets a chance to resume the main thread before processing `rawState`
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const { collectionSizeSym, fieldCountSym } = require('./symbols')
4
+
3
5
  module.exports = {
4
6
  processRawState: processProperties
5
7
  }
@@ -137,38 +139,46 @@ function toString (str, maxLength) {
137
139
 
138
140
  function toObject (type, props, maxLength) {
139
141
  if (props === undefined) return notCapturedDepth(type)
140
- return { type, fields: processProperties(props, maxLength) }
142
+
143
+ const result = {
144
+ type,
145
+ fields: processProperties(props, maxLength)
146
+ }
147
+
148
+ if (fieldCountSym in props) {
149
+ result.notCapturedReason = 'fieldCount'
150
+ result.size = props[fieldCountSym]
151
+ }
152
+
153
+ return result
141
154
  }
142
155
 
143
156
  function toArray (type, elements, maxLength) {
144
157
  if (elements === undefined) return notCapturedDepth(type)
145
158
 
146
159
  // Perf: Create array of expected size in advance (expect that it contains only one non-enumrable element)
147
- const expectedLength = elements.length - 1
148
- const result = { type, elements: new Array(expectedLength) }
160
+ const result = { type, elements: new Array(elements.length) }
161
+
162
+ setNotCaptureReasonOnCollection(result, elements)
149
163
 
150
164
  let i = 0
151
165
  for (const elm of elements) {
152
- if (elm.enumerable === false) continue // the value of the `length` property should not be part of the array
153
166
  result.elements[i++] = getPropertyValue(elm, maxLength)
154
167
  }
155
168
 
156
- // Safe-guard in case there were more than one non-enumerable element
157
- if (i < expectedLength) result.elements.length = i
158
-
159
169
  return result
160
170
  }
161
171
 
162
172
  function toMap (type, pairs, maxLength) {
163
173
  if (pairs === undefined) return notCapturedDepth(type)
164
174
 
165
- // Perf: Create array of expected size in advance (expect that it contains only one non-enumrable element)
166
- const expectedLength = pairs.length - 1
167
- const result = { type, entries: new Array(expectedLength) }
175
+ // Perf: Create array of expected size in advance
176
+ const result = { type, entries: new Array(pairs.length) }
177
+
178
+ setNotCaptureReasonOnCollection(result, pairs)
168
179
 
169
180
  let i = 0
170
181
  for (const pair of pairs) {
171
- if (pair.enumerable === false) continue // the value of the `length` property should not be part of the map
172
182
  // The following code is based on assumptions made when researching the output of the Chrome DevTools Protocol.
173
183
  // There doesn't seem to be any documentation to back it up:
174
184
  //
@@ -180,9 +190,6 @@ function toMap (type, pairs, maxLength) {
180
190
  result.entries[i++] = [key, val]
181
191
  }
182
192
 
183
- // Safe-guard in case there were more than one non-enumerable element
184
- if (i < expectedLength) result.entries.length = i
185
-
186
193
  return result
187
194
  }
188
195
 
@@ -190,12 +197,12 @@ function toSet (type, values, maxLength) {
190
197
  if (values === undefined) return notCapturedDepth(type)
191
198
 
192
199
  // Perf: Create array of expected size in advance (expect that it contains only one non-enumrable element)
193
- const expectedLength = values.length - 1
194
- const result = { type, elements: new Array(expectedLength) }
200
+ const result = { type, elements: new Array(values.length) }
201
+
202
+ setNotCaptureReasonOnCollection(result, values)
195
203
 
196
204
  let i = 0
197
205
  for (const value of values) {
198
- if (value.enumerable === false) continue // the value of the `length` property should not be part of the set
199
206
  // The following code is based on assumptions made when researching the output of the Chrome DevTools Protocol.
200
207
  // There doesn't seem to be any documentation to back it up:
201
208
  //
@@ -205,9 +212,6 @@ function toSet (type, values, maxLength) {
205
212
  result.elements[i++] = getPropertyValue(value.value.properties[0], maxLength)
206
213
  }
207
214
 
208
- // Safe-guard in case there were more than one non-enumerable element
209
- if (i < expectedLength) result.elements.length = i
210
-
211
215
  return result
212
216
  }
213
217
 
@@ -236,6 +240,13 @@ function arrayBufferToString (bytes, size) {
236
240
  return buf.toString()
237
241
  }
238
242
 
243
+ function setNotCaptureReasonOnCollection (result, collection) {
244
+ if (collectionSizeSym in collection) {
245
+ result.notCapturedReason = 'collectionSize'
246
+ result.size = collection[collectionSizeSym]
247
+ }
248
+ }
249
+
239
250
  function notCapturedDepth (type) {
240
251
  return { type, notCapturedReason: 'depth' }
241
252
  }
@@ -0,0 +1,6 @@
1
+ 'use stict'
2
+
3
+ module.exports = {
4
+ collectionSizeSym: Symbol('datadog.collectionSize'),
5
+ fieldCountSym: Symbol('datadog.fieldCount')
6
+ }
@@ -32,8 +32,17 @@ module.exports = {
32
32
  .sort(([a], [b]) => a.length - b.length)[0]
33
33
  },
34
34
 
35
- getScriptUrlFromId (id) {
36
- return scriptUrls.get(id)
35
+ getStackFromCallFrames (callFrames) {
36
+ return callFrames.map((frame) => {
37
+ let fileName = scriptUrls.get(frame.location.scriptId)
38
+ if (fileName.startsWith('file://')) fileName = fileName.substr(7) // TODO: This might not be required
39
+ return {
40
+ fileName,
41
+ function: frame.functionName,
42
+ lineNumber: frame.location.lineNumber + 1, // Beware! lineNumber is zero-indexed
43
+ columnNumber: frame.location.columnNumber + 1 // Beware! columnNumber is zero-indexed
44
+ }
45
+ })
37
46
  }
38
47
  }
39
48
 
@@ -6,6 +6,7 @@ const log = require('../log')
6
6
 
7
7
  let worker = null
8
8
  let configChannel = null
9
+ let ackId = 0
9
10
 
10
11
  const { NODE_OPTIONS, ...env } = process.env
11
12
 
@@ -24,13 +25,19 @@ function start (config, rc) {
24
25
  configChannel = new MessageChannel()
25
26
 
26
27
  rc.setProductHandler('LIVE_DEBUGGING', (action, conf, id, ack) => {
27
- const ackId = `${id}-${conf.version}`
28
- rcAckCallbacks.set(ackId, ack)
28
+ rcAckCallbacks.set(++ackId, ack)
29
29
  rcChannel.port2.postMessage({ action, conf, ackId })
30
30
  })
31
31
 
32
32
  rcChannel.port2.on('message', ({ ackId, error }) => {
33
- rcAckCallbacks.get(ackId)(error)
33
+ const ack = rcAckCallbacks.get(ackId)
34
+ if (ack === undefined) {
35
+ // This should never happen, but just in case something changes in the future, we should guard against it
36
+ log.error(`Received an unknown ackId: ${ackId}`)
37
+ if (error) log.error(error)
38
+ return
39
+ }
40
+ ack(error)
34
41
  rcAckCallbacks.delete(ackId)
35
42
  })
36
43
  rcChannel.port2.on('messageerror', (err) => log.error(err))
@@ -0,0 +1,34 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ SPAN_KINDS: ['llm', 'agent', 'workflow', 'task', 'tool', 'embedding', 'retrieval'],
5
+ SPAN_KIND: '_ml_obs.meta.span.kind',
6
+ SESSION_ID: '_ml_obs.session_id',
7
+ METADATA: '_ml_obs.meta.metadata',
8
+ METRICS: '_ml_obs.metrics',
9
+ ML_APP: '_ml_obs.meta.ml_app',
10
+ PROPAGATED_PARENT_ID_KEY: '_dd.p.llmobs_parent_id',
11
+ PARENT_ID_KEY: '_ml_obs.llmobs_parent_id',
12
+ TAGS: '_ml_obs.tags',
13
+ NAME: '_ml_obs.name',
14
+ TRACE_ID: '_ml_obs.trace_id',
15
+ PROPAGATED_TRACE_ID_KEY: '_dd.p.llmobs_trace_id',
16
+ ROOT_PARENT_ID: 'undefined',
17
+
18
+ MODEL_NAME: '_ml_obs.meta.model_name',
19
+ MODEL_PROVIDER: '_ml_obs.meta.model_provider',
20
+
21
+ INPUT_DOCUMENTS: '_ml_obs.meta.input.documents',
22
+ INPUT_MESSAGES: '_ml_obs.meta.input.messages',
23
+ INPUT_VALUE: '_ml_obs.meta.input.value',
24
+
25
+ OUTPUT_DOCUMENTS: '_ml_obs.meta.output.documents',
26
+ OUTPUT_MESSAGES: '_ml_obs.meta.output.messages',
27
+ OUTPUT_VALUE: '_ml_obs.meta.output.value',
28
+
29
+ INPUT_TOKENS_METRIC_KEY: 'input_tokens',
30
+ OUTPUT_TOKENS_METRIC_KEY: 'output_tokens',
31
+ TOTAL_TOKENS_METRIC_KEY: 'total_tokens',
32
+
33
+ DROPPED_IO_COLLECTION_ERROR: 'dropped_io'
34
+ }
@@ -0,0 +1,6 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ DROPPED_VALUE_TEXT: "[This value has been dropped because this span's size exceeds the 1MB size limit.]",
5
+ UNSERIALIZABLE_VALUE_TEXT: 'Unserializable value'
6
+ }
@@ -0,0 +1,13 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ EVP_PROXY_AGENT_BASE_PATH: 'evp_proxy/v2',
5
+ EVP_PROXY_AGENT_ENDPOINT: 'evp_proxy/v2/api/v2/llmobs',
6
+ EVP_SUBDOMAIN_HEADER_NAME: 'X-Datadog-EVP-Subdomain',
7
+ EVP_SUBDOMAIN_HEADER_VALUE: 'llmobs-intake',
8
+ AGENTLESS_SPANS_ENDPOINT: '/api/v2/llmobs',
9
+ AGENTLESS_EVALULATIONS_ENDPOINT: '/api/intake/llm-obs/v1/eval-metric',
10
+
11
+ EVP_PAYLOAD_SIZE_LIMIT: 5 << 20, // 5MB (actual limit is 5.1MB)
12
+ EVP_EVENT_SIZE_LIMIT: (1 << 20) - 1024 // 999KB (actual limit is 1MB)
13
+ }
@@ -0,0 +1,103 @@
1
+ 'use strict'
2
+
3
+ const log = require('../log')
4
+ const { PROPAGATED_PARENT_ID_KEY } = require('./constants/tags')
5
+ const { storage } = require('./storage')
6
+
7
+ const LLMObsSpanProcessor = require('./span_processor')
8
+
9
+ const { channel } = require('dc-polyfill')
10
+ const spanProcessCh = channel('dd-trace:span:process')
11
+ const evalMetricAppendCh = channel('llmobs:eval-metric:append')
12
+ const flushCh = channel('llmobs:writers:flush')
13
+ const injectCh = channel('dd-trace:span:inject')
14
+
15
+ const LLMObsAgentlessSpanWriter = require('./writers/spans/agentless')
16
+ const LLMObsAgentProxySpanWriter = require('./writers/spans/agentProxy')
17
+ const LLMObsEvalMetricsWriter = require('./writers/evaluations')
18
+
19
+ /**
20
+ * Setting writers and processor globally when LLMObs is enabled
21
+ * We're setting these in this module instead of on the SDK.
22
+ * This is to isolate any subscribers and periodic tasks to this module,
23
+ * and not conditionally instantiate in the SDK, since the SDK is always instantiated
24
+ * if the tracer is `init`ed. But, in those cases, we don't want to start writers or subscribe
25
+ * to channels.
26
+ */
27
+ let spanProcessor
28
+ let spanWriter
29
+ let evalWriter
30
+
31
+ function enable (config) {
32
+ // create writers and eval writer append and flush channels
33
+ // span writer append is handled by the span processor
34
+ evalWriter = new LLMObsEvalMetricsWriter(config)
35
+ spanWriter = createSpanWriter(config)
36
+
37
+ evalMetricAppendCh.subscribe(handleEvalMetricAppend)
38
+ flushCh.subscribe(handleFlush)
39
+
40
+ // span processing
41
+ spanProcessor = new LLMObsSpanProcessor(config)
42
+ spanProcessor.setWriter(spanWriter)
43
+ spanProcessCh.subscribe(handleSpanProcess)
44
+
45
+ // distributed tracing for llmobs
46
+ injectCh.subscribe(handleLLMObsParentIdInjection)
47
+ }
48
+
49
+ function disable () {
50
+ if (evalMetricAppendCh.hasSubscribers) evalMetricAppendCh.unsubscribe(handleEvalMetricAppend)
51
+ if (flushCh.hasSubscribers) flushCh.unsubscribe(handleFlush)
52
+ if (spanProcessCh.hasSubscribers) spanProcessCh.unsubscribe(handleSpanProcess)
53
+ if (injectCh.hasSubscribers) injectCh.unsubscribe(handleLLMObsParentIdInjection)
54
+
55
+ spanWriter?.destroy()
56
+ evalWriter?.destroy()
57
+ spanProcessor?.setWriter(null)
58
+
59
+ spanWriter = null
60
+ evalWriter = null
61
+ }
62
+
63
+ // since LLMObs traces can extend between services and be the same trace,
64
+ // we need to propogate the parent id.
65
+ function handleLLMObsParentIdInjection ({ carrier }) {
66
+ const parent = storage.getStore()?.span
67
+ if (!parent) return
68
+
69
+ const parentId = parent?.context().toSpanId()
70
+
71
+ carrier['x-datadog-tags'] += `,${PROPAGATED_PARENT_ID_KEY}=${parentId}`
72
+ }
73
+
74
+ function createSpanWriter (config) {
75
+ const SpanWriter = config.llmobs.agentlessEnabled ? LLMObsAgentlessSpanWriter : LLMObsAgentProxySpanWriter
76
+ return new SpanWriter(config)
77
+ }
78
+
79
+ function handleFlush () {
80
+ try {
81
+ spanWriter.flush()
82
+ evalWriter.flush()
83
+ } catch (e) {
84
+ log.warn(`Failed to flush LLMObs spans and evaluation metrics: ${e.message}`)
85
+ }
86
+ }
87
+
88
+ function handleSpanProcess (data) {
89
+ spanProcessor.process(data)
90
+ }
91
+
92
+ function handleEvalMetricAppend (payload) {
93
+ try {
94
+ evalWriter.append(payload)
95
+ } catch (e) {
96
+ log.warn(`
97
+ Failed to append evaluation metric to LLM Observability writer, likely due to an unserializable property.
98
+ Evaluation metrics won't be sent to LLM Observability: ${e.message}
99
+ `)
100
+ }
101
+ }
102
+
103
+ module.exports = { enable, disable }
@@ -0,0 +1,82 @@
1
+ 'use strict'
2
+
3
+ class NoopLLMObs {
4
+ constructor (noopTracer) {
5
+ this._tracer = noopTracer
6
+ }
7
+
8
+ get enabled () {
9
+ return false
10
+ }
11
+
12
+ enable (options) {}
13
+
14
+ disable () {}
15
+
16
+ trace (options = {}, fn) {
17
+ if (typeof options === 'function') {
18
+ fn = options
19
+ options = {}
20
+ }
21
+
22
+ const name = options.name || options.kind || fn.name
23
+
24
+ return this._tracer.trace(name, options, fn)
25
+ }
26
+
27
+ wrap (options = {}, fn) {
28
+ if (typeof options === 'function') {
29
+ fn = options
30
+ options = {}
31
+ }
32
+
33
+ const name = options.name || options.kind || fn.name
34
+
35
+ return this._tracer.wrap(name, options, fn)
36
+ }
37
+
38
+ decorate (options = {}) {
39
+ const llmobs = this
40
+ return function (target, ctxOrPropertyKey, descriptor) {
41
+ if (!ctxOrPropertyKey) return target
42
+ if (typeof ctxOrPropertyKey === 'object') {
43
+ const ctx = ctxOrPropertyKey
44
+ if (ctx.kind !== 'method') return target
45
+
46
+ return llmobs.wrap({ name: ctx.name, ...options }, target)
47
+ } else {
48
+ const propertyKey = ctxOrPropertyKey
49
+ if (descriptor) {
50
+ if (typeof descriptor.value !== 'function') return descriptor
51
+
52
+ const original = descriptor.value
53
+ descriptor.value = llmobs.wrap({ name: propertyKey, ...options }, original)
54
+
55
+ return descriptor
56
+ } else {
57
+ if (typeof target[propertyKey] !== 'function') return target[propertyKey]
58
+
59
+ const original = target[propertyKey]
60
+ Object.defineProperty(target, propertyKey, {
61
+ ...Object.getOwnPropertyDescriptor(target, propertyKey),
62
+ value: llmobs.wrap({ name: propertyKey, ...options }, original)
63
+ })
64
+
65
+ return target
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ annotate (span, options) {}
72
+
73
+ exportSpan (span) {
74
+ return {}
75
+ }
76
+
77
+ submitEvaluation (llmobsSpanContext, options) {}
78
+
79
+ flush () {}
80
+ }
81
+
82
+ module.exports = NoopLLMObs