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.
- package/LICENSE-3rdparty.csv +3 -0
- package/index.d.ts +345 -8
- package/init.js +60 -47
- package/package.json +16 -7
- package/packages/datadog-code-origin/index.js +4 -4
- package/packages/datadog-core/index.js +1 -3
- package/packages/datadog-core/src/storage.js +21 -0
- package/packages/datadog-core/src/utils/src/parse-tags.js +33 -0
- package/packages/datadog-esbuild/index.js +4 -2
- package/packages/datadog-instrumentations/src/amqplib.js +65 -5
- package/packages/datadog-instrumentations/src/child_process.js +135 -27
- package/packages/datadog-instrumentations/src/express.js +1 -1
- package/packages/datadog-instrumentations/src/handlebars.js +40 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +5 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +9 -0
- package/packages/datadog-instrumentations/src/jest.js +6 -2
- package/packages/datadog-instrumentations/src/kafkajs.js +123 -63
- package/packages/datadog-instrumentations/src/mocha/utils.js +2 -2
- package/packages/datadog-instrumentations/src/multer.js +37 -0
- package/packages/datadog-instrumentations/src/openai.js +2 -2
- package/packages/datadog-instrumentations/src/pug.js +23 -0
- package/packages/datadog-instrumentations/src/router.js +2 -3
- package/packages/datadog-instrumentations/src/url.js +84 -0
- package/packages/datadog-instrumentations/src/utils/src/extract-package-and-module-path.js +7 -4
- package/packages/datadog-plugin-amqplib/src/consumer.js +6 -5
- package/packages/datadog-plugin-aws-sdk/src/base.js +5 -0
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +10 -7
- package/packages/datadog-plugin-aws-sdk/src/services/s3.js +35 -0
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +11 -9
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +59 -45
- package/packages/datadog-plugin-cypress/src/support.js +1 -0
- package/packages/datadog-plugin-fastify/src/code_origin.js +2 -2
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +10 -2
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +8 -0
- package/packages/datadog-plugin-grpc/src/client.js +3 -0
- package/packages/datadog-plugin-grpc/src/server.js +5 -1
- package/packages/datadog-plugin-http/src/client.js +42 -1
- package/packages/datadog-plugin-http2/src/client.js +26 -1
- package/packages/datadog-plugin-jest/src/index.js +2 -1
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +6 -3
- package/packages/datadog-plugin-kafkajs/src/consumer.js +10 -5
- package/packages/datadog-plugin-kafkajs/src/producer.js +10 -4
- package/packages/datadog-plugin-mocha/src/index.js +5 -2
- package/packages/datadog-plugin-moleculer/src/server.js +2 -2
- package/packages/datadog-plugin-openai/src/index.js +9 -1015
- package/packages/datadog-plugin-openai/src/tracing.js +1023 -0
- package/packages/datadog-plugin-rhea/src/consumer.js +2 -1
- package/packages/datadog-plugin-vitest/src/index.js +2 -1
- package/packages/dd-trace/src/appsec/addresses.js +2 -0
- package/packages/dd-trace/src/appsec/api_security_sampler.js +50 -27
- package/packages/dd-trace/src/appsec/channels.js +3 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +33 -16
- package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +18 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +55 -7
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -2
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -2
- package/packages/dd-trace/src/appsec/index.js +9 -6
- package/packages/dd-trace/src/appsec/rasp/command_injection.js +49 -0
- package/packages/dd-trace/src/appsec/rasp/index.js +3 -0
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +4 -3
- package/packages/dd-trace/src/appsec/rasp/utils.js +3 -2
- package/packages/dd-trace/src/appsec/recommended.json +354 -158
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +2 -7
- package/packages/dd-trace/src/appsec/reporter.js +6 -4
- package/packages/dd-trace/src/appsec/sdk/track_event.js +5 -3
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +4 -0
- package/packages/dd-trace/src/azure_metadata.js +120 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +97 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +90 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +19 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +53 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +8 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +43 -0
- package/packages/dd-trace/src/config.js +88 -10
- package/packages/dd-trace/src/constants.js +8 -1
- package/packages/dd-trace/src/crashtracking/crashtracker.js +98 -0
- package/packages/dd-trace/src/crashtracking/index.js +15 -0
- package/packages/dd-trace/src/crashtracking/noop.js +8 -0
- package/packages/dd-trace/src/datastreams/pathway.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +9 -13
- package/packages/dd-trace/src/debugger/devtools_client/send.js +15 -1
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +57 -23
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +12 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +31 -20
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/symbols.js +6 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +11 -2
- package/packages/dd-trace/src/debugger/index.js +10 -3
- package/packages/dd-trace/src/llmobs/constants/tags.js +34 -0
- package/packages/dd-trace/src/llmobs/constants/text.js +6 -0
- package/packages/dd-trace/src/llmobs/constants/writers.js +13 -0
- package/packages/dd-trace/src/llmobs/index.js +103 -0
- package/packages/dd-trace/src/llmobs/noop.js +82 -0
- package/packages/dd-trace/src/llmobs/plugins/base.js +65 -0
- package/packages/dd-trace/src/llmobs/plugins/openai.js +205 -0
- package/packages/dd-trace/src/llmobs/sdk.js +377 -0
- package/packages/dd-trace/src/llmobs/span_processor.js +195 -0
- package/packages/dd-trace/src/llmobs/storage.js +7 -0
- package/packages/dd-trace/src/llmobs/tagger.js +322 -0
- package/packages/dd-trace/src/llmobs/util.js +176 -0
- package/packages/dd-trace/src/llmobs/writers/base.js +111 -0
- package/packages/dd-trace/src/llmobs/writers/evaluations.js +29 -0
- package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +23 -0
- package/packages/dd-trace/src/llmobs/writers/spans/agentless.js +17 -0
- package/packages/dd-trace/src/llmobs/writers/spans/base.js +52 -0
- package/packages/dd-trace/src/log/index.js +10 -13
- package/packages/dd-trace/src/log/log.js +52 -0
- package/packages/dd-trace/src/log/writer.js +50 -19
- package/packages/dd-trace/src/noop/proxy.js +3 -0
- package/packages/dd-trace/src/noop/span.js +4 -0
- package/packages/dd-trace/src/opentelemetry/span.js +16 -1
- package/packages/dd-trace/src/opentelemetry/tracer.js +1 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +106 -32
- package/packages/dd-trace/src/opentracing/span.js +26 -0
- package/packages/dd-trace/src/opentracing/span_context.js +1 -0
- package/packages/dd-trace/src/opentracing/tracer.js +8 -1
- package/packages/dd-trace/src/payload-tagging/config/aws.json +71 -3
- package/packages/dd-trace/src/plugins/outbound.js +9 -0
- package/packages/dd-trace/src/plugins/tracing.js +3 -3
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +121 -0
- package/packages/dd-trace/src/plugins/util/ip_extractor.js +0 -1
- package/packages/dd-trace/src/plugins/util/web.js +39 -11
- package/packages/dd-trace/src/priority_sampler.js +16 -0
- package/packages/dd-trace/src/profiling/config.js +3 -1
- package/packages/dd-trace/src/profiling/exporters/agent.js +7 -5
- package/packages/dd-trace/src/profiling/profilers/wall.js +2 -1
- package/packages/dd-trace/src/proxy.js +13 -1
- package/packages/dd-trace/src/span_processor.js +5 -0
- package/packages/dd-trace/src/telemetry/index.js +11 -1
- package/packages/dd-trace/src/telemetry/logs/index.js +16 -11
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -8
- package/packages/dd-trace/src/telemetry/metrics.js +6 -1
- package/packages/dd-trace/src/util.js +16 -1
- package/version.js +4 -2
- /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,
|
|
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 (
|
|
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,
|
|
42
|
+
return traverseGetPropertiesResult(result, opts, depth)
|
|
26
43
|
}
|
|
27
44
|
|
|
28
|
-
async function traverseGetPropertiesResult (props,
|
|
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 >=
|
|
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,
|
|
57
|
+
prop.value.properties = await getObjectProperties(subtype, objectId, opts, depth)
|
|
41
58
|
} else if (type === 'function') {
|
|
42
|
-
prop.value.properties = await getFunctionProperties(objectId,
|
|
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,
|
|
66
|
+
async function getObjectProperties (subtype, objectId, opts, depth) {
|
|
50
67
|
if (ITERABLE_SUBTYPES.has(subtype)) {
|
|
51
|
-
return getIterable(objectId,
|
|
68
|
+
return getIterable(objectId, opts, depth)
|
|
52
69
|
} else if (subtype === 'promise') {
|
|
53
|
-
return getInternalProperties(objectId,
|
|
70
|
+
return getInternalProperties(objectId, opts, depth)
|
|
54
71
|
} else if (subtype === 'proxy') {
|
|
55
|
-
return getProxy(objectId,
|
|
72
|
+
return getProxy(objectId, opts, depth)
|
|
56
73
|
} else if (subtype === 'arraybuffer') {
|
|
57
|
-
return getArrayBuffer(objectId,
|
|
74
|
+
return getArrayBuffer(objectId, opts, depth)
|
|
58
75
|
} else {
|
|
59
|
-
return getObject(objectId,
|
|
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,
|
|
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,
|
|
92
|
+
return traverseGetPropertiesResult(result, opts, depth)
|
|
76
93
|
}
|
|
77
94
|
|
|
78
|
-
async function getIterable (objectId,
|
|
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
|
-
|
|
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,
|
|
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,
|
|
134
|
+
return traverseGetPropertiesResult(props, opts, depth)
|
|
109
135
|
}
|
|
110
136
|
|
|
111
|
-
async function getProxy (objectId,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
{
|
|
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(
|
|
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
|
-
|
|
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
|
|
148
|
-
|
|
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
|
|
166
|
-
const
|
|
167
|
-
|
|
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
|
|
194
|
-
|
|
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
|
}
|
|
@@ -32,8 +32,17 @@ module.exports = {
|
|
|
32
32
|
.sort(([a], [b]) => a.length - b.length)[0]
|
|
33
33
|
},
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
return
|
|
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
|
-
|
|
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)
|
|
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,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
|