dd-trace 5.30.0 → 5.32.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 +1 -0
- package/README.md +9 -7
- package/package.json +7 -6
- package/packages/datadog-core/src/storage.js +11 -2
- package/packages/datadog-instrumentations/src/aerospike.js +1 -1
- package/packages/datadog-instrumentations/src/aws-sdk.js +2 -1
- package/packages/datadog-instrumentations/src/cucumber.js +14 -5
- package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
- package/packages/datadog-instrumentations/src/jest.js +70 -36
- package/packages/datadog-instrumentations/src/mocha/utils.js +23 -7
- package/packages/datadog-instrumentations/src/node-serialize.js +22 -0
- package/packages/datadog-instrumentations/src/openai.js +2 -0
- package/packages/datadog-instrumentations/src/vitest.js +107 -59
- package/packages/datadog-instrumentations/src/vm.js +49 -0
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime.js +295 -0
- package/packages/datadog-plugin-aws-sdk/src/services/index.js +1 -0
- package/packages/datadog-plugin-cucumber/src/index.js +30 -32
- package/packages/datadog-plugin-jest/src/index.js +34 -37
- package/packages/datadog-plugin-langchain/src/index.js +12 -80
- package/packages/datadog-plugin-langchain/src/tracing.js +89 -0
- package/packages/datadog-plugin-mocha/src/index.js +18 -36
- package/packages/datadog-plugin-vitest/src/index.js +20 -34
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +2 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/untrusted-deserialization-analyzer.js +16 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +9 -8
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
- package/packages/dd-trace/src/appsec/remote_config/manager.js +11 -1
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +37 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +65 -28
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +57 -17
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +18 -3
- package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +20 -3
- package/packages/dd-trace/src/config.js +39 -3
- package/packages/dd-trace/src/crashtracking/crashtracker.js +9 -0
- package/packages/dd-trace/src/crashtracking/noop.js +3 -0
- package/packages/dd-trace/src/datastreams/fnv.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -1
- package/packages/dd-trace/src/debugger/devtools_client/defaults.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +32 -14
- package/packages/dd-trace/src/debugger/devtools_client/json-buffer.js +36 -0
- package/packages/dd-trace/src/debugger/devtools_client/send.js +29 -10
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +35 -1
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +112 -0
- package/packages/dd-trace/src/debugger/devtools_client/status.js +20 -11
- package/packages/dd-trace/src/debugger/index.js +2 -13
- package/packages/dd-trace/src/llmobs/plugins/base.js +40 -11
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chain.js +24 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +111 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/embedding.js +42 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +102 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/llm.js +32 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +131 -0
- package/packages/dd-trace/src/llmobs/plugins/openai.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +90 -26
- package/packages/dd-trace/src/llmobs/tagger.js +11 -3
- package/packages/dd-trace/src/llmobs/util.js +7 -1
- package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +3 -3
- package/packages/dd-trace/src/log/index.js +8 -9
- package/packages/dd-trace/src/noop/proxy.js +2 -2
- package/packages/dd-trace/src/noop/span.js +1 -1
- package/packages/dd-trace/src/opentelemetry/context_manager.js +43 -3
- package/packages/dd-trace/src/opentracing/span.js +11 -1
- package/packages/dd-trace/src/opentracing/span_context.js +12 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +57 -27
- package/packages/dd-trace/src/plugins/util/test.js +42 -12
- package/packages/dd-trace/src/priority_sampler.js +7 -2
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +21 -0
- package/packages/dd-trace/src/profiling/profiler.js +11 -8
- package/packages/dd-trace/src/profiling/profilers/events.js +17 -1
- package/packages/dd-trace/src/proxy.js +6 -3
- package/packages/dd-trace/src/scope.js +1 -1
- package/packages/dd-trace/src/telemetry/index.js +2 -0
|
@@ -8,6 +8,7 @@ const send = require('./send')
|
|
|
8
8
|
const { getStackFromCallFrames } = require('./state')
|
|
9
9
|
const { ackEmitting, ackError } = require('./status')
|
|
10
10
|
const { parentThreadId } = require('./config')
|
|
11
|
+
const { MAX_SNAPSHOTS_PER_SECOND_GLOBALLY } = require('./defaults')
|
|
11
12
|
const log = require('../../log')
|
|
12
13
|
const { version } = require('../../../../../package.json')
|
|
13
14
|
|
|
@@ -24,11 +25,14 @@ const expression = `
|
|
|
24
25
|
const threadId = parentThreadId === 0 ? `pid:${process.pid}` : `pid:${process.pid};tid:${parentThreadId}`
|
|
25
26
|
const threadName = parentThreadId === 0 ? 'MainThread' : `WorkerThread:${parentThreadId}`
|
|
26
27
|
|
|
28
|
+
const oneSecondNs = 1_000_000_000n
|
|
29
|
+
let globalSnapshotSamplingRateWindowStart = 0n
|
|
30
|
+
let snapshotsSampledWithinTheLastSecond = 0
|
|
31
|
+
|
|
27
32
|
// WARNING: The code above the line `await session.post('Debugger.resume')` is highly optimized. Please edit with care!
|
|
28
33
|
session.on('Debugger.paused', async ({ params }) => {
|
|
29
34
|
const start = process.hrtime.bigint()
|
|
30
35
|
|
|
31
|
-
let captureSnapshotForProbe = null
|
|
32
36
|
let maxReferenceDepth, maxCollectionSize, maxFieldCount, maxLength
|
|
33
37
|
|
|
34
38
|
// V8 doesn't allow seting more than one breakpoint at a specific location, however, it's possible to set two
|
|
@@ -38,25 +42,39 @@ session.on('Debugger.paused', async ({ params }) => {
|
|
|
38
42
|
let sampled = false
|
|
39
43
|
const length = params.hitBreakpoints.length
|
|
40
44
|
let probes = new Array(length)
|
|
45
|
+
// TODO: Consider reusing this array between pauses and only recreating it if it needs to grow
|
|
46
|
+
const snapshotProbeIndex = new Uint8Array(length) // TODO: Is a limit of 256 probes ever going to be a problem?
|
|
47
|
+
let numberOfProbesWithSnapshots = 0
|
|
41
48
|
for (let i = 0; i < length; i++) {
|
|
42
49
|
const id = params.hitBreakpoints[i]
|
|
43
50
|
const probe = breakpoints.get(id)
|
|
44
51
|
|
|
45
|
-
if (start - probe.lastCaptureNs < probe.
|
|
52
|
+
if (start - probe.lastCaptureNs < probe.nsBetweenSampling) {
|
|
46
53
|
continue
|
|
47
54
|
}
|
|
48
55
|
|
|
49
|
-
sampled = true
|
|
50
|
-
probe.lastCaptureNs = start
|
|
51
|
-
|
|
52
56
|
if (probe.captureSnapshot === true) {
|
|
53
|
-
|
|
57
|
+
// This algorithm to calculate number of sampled snapshots within the last second is not perfect, as it's not a
|
|
58
|
+
// sliding window. But it's quick and easy :)
|
|
59
|
+
if (i === 0 && start - globalSnapshotSamplingRateWindowStart > oneSecondNs) {
|
|
60
|
+
snapshotsSampledWithinTheLastSecond = 1
|
|
61
|
+
globalSnapshotSamplingRateWindowStart = start
|
|
62
|
+
} else if (snapshotsSampledWithinTheLastSecond >= MAX_SNAPSHOTS_PER_SECOND_GLOBALLY) {
|
|
63
|
+
continue
|
|
64
|
+
} else {
|
|
65
|
+
snapshotsSampledWithinTheLastSecond++
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
snapshotProbeIndex[numberOfProbesWithSnapshots++] = i
|
|
54
69
|
maxReferenceDepth = highestOrUndefined(probe.capture.maxReferenceDepth, maxReferenceDepth)
|
|
55
70
|
maxCollectionSize = highestOrUndefined(probe.capture.maxCollectionSize, maxCollectionSize)
|
|
56
71
|
maxFieldCount = highestOrUndefined(probe.capture.maxFieldCount, maxFieldCount)
|
|
57
72
|
maxLength = highestOrUndefined(probe.capture.maxLength, maxLength)
|
|
58
73
|
}
|
|
59
74
|
|
|
75
|
+
sampled = true
|
|
76
|
+
probe.lastCaptureNs = start
|
|
77
|
+
|
|
60
78
|
probes[i] = probe
|
|
61
79
|
}
|
|
62
80
|
|
|
@@ -68,7 +86,7 @@ session.on('Debugger.paused', async ({ params }) => {
|
|
|
68
86
|
const dd = await getDD(params.callFrames[0].callFrameId)
|
|
69
87
|
|
|
70
88
|
let processLocalState
|
|
71
|
-
if (
|
|
89
|
+
if (numberOfProbesWithSnapshots !== 0) {
|
|
72
90
|
try {
|
|
73
91
|
// TODO: Create unique states for each affected probe based on that probes unique `capture` settings (DEBUG-2863)
|
|
74
92
|
processLocalState = await getLocalStateForCallFrame(
|
|
@@ -76,9 +94,9 @@ session.on('Debugger.paused', async ({ params }) => {
|
|
|
76
94
|
{ maxReferenceDepth, maxCollectionSize, maxFieldCount, maxLength }
|
|
77
95
|
)
|
|
78
96
|
} catch (err) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
97
|
+
for (let i = 0; i < numberOfProbesWithSnapshots; i++) {
|
|
98
|
+
ackError(err, probes[snapshotProbeIndex[i]]) // TODO: Ok to continue after sending ackError?
|
|
99
|
+
}
|
|
82
100
|
}
|
|
83
101
|
}
|
|
84
102
|
|
|
@@ -128,11 +146,9 @@ session.on('Debugger.paused', async ({ params }) => {
|
|
|
128
146
|
}
|
|
129
147
|
}
|
|
130
148
|
|
|
149
|
+
ackEmitting(probe)
|
|
131
150
|
// TODO: Process template (DEBUG-2628)
|
|
132
|
-
send(probe.template, logger, dd, snapshot
|
|
133
|
-
if (err) log.error('Debugger error', err)
|
|
134
|
-
else ackEmitting(probe)
|
|
135
|
-
})
|
|
151
|
+
send(probe.template, logger, dd, snapshot)
|
|
136
152
|
}
|
|
137
153
|
})
|
|
138
154
|
|
|
@@ -141,6 +157,8 @@ function highestOrUndefined (num, max) {
|
|
|
141
157
|
}
|
|
142
158
|
|
|
143
159
|
async function getDD (callFrameId) {
|
|
160
|
+
// TODO: Consider if an `objectGroup` should be used, so it can be explicitly released using
|
|
161
|
+
// `Runtime.releaseObjectGroup`
|
|
144
162
|
const { result } = await session.post('Debugger.evaluateOnCallFrame', {
|
|
145
163
|
callFrameId,
|
|
146
164
|
expression,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
class JSONBuffer {
|
|
4
|
+
constructor ({ size, timeout, onFlush }) {
|
|
5
|
+
this._maxSize = size
|
|
6
|
+
this._timeout = timeout
|
|
7
|
+
this._onFlush = onFlush
|
|
8
|
+
this._reset()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
_reset () {
|
|
12
|
+
clearTimeout(this._timer)
|
|
13
|
+
this._timer = null
|
|
14
|
+
this._partialJson = null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_flush () {
|
|
18
|
+
const json = `${this._partialJson}]`
|
|
19
|
+
this._reset()
|
|
20
|
+
this._onFlush(json)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
write (str, size = Buffer.byteLength(str)) {
|
|
24
|
+
if (this._timer === null) {
|
|
25
|
+
this._partialJson = `[${str}`
|
|
26
|
+
this._timer = setTimeout(() => this._flush(), this._timeout)
|
|
27
|
+
} else if (Buffer.byteLength(this._partialJson) + size + 2 > this._maxSize) {
|
|
28
|
+
this._flush()
|
|
29
|
+
this.write(str, size)
|
|
30
|
+
} else {
|
|
31
|
+
this._partialJson += `,${str}`
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = JSONBuffer
|
|
@@ -4,32 +4,34 @@ const { hostname: getHostname } = require('os')
|
|
|
4
4
|
const { stringify } = require('querystring')
|
|
5
5
|
|
|
6
6
|
const config = require('./config')
|
|
7
|
+
const JSONBuffer = require('./json-buffer')
|
|
7
8
|
const request = require('../../exporters/common/request')
|
|
8
9
|
const { GIT_COMMIT_SHA, GIT_REPOSITORY_URL } = require('../../plugins/util/tags')
|
|
10
|
+
const log = require('../../log')
|
|
11
|
+
const { version } = require('../../../../../package.json')
|
|
9
12
|
|
|
10
13
|
module.exports = send
|
|
11
14
|
|
|
12
|
-
const
|
|
15
|
+
const MAX_LOG_PAYLOAD_SIZE = 1024 * 1024 // 1MB
|
|
13
16
|
|
|
14
17
|
const ddsource = 'dd_debugger'
|
|
15
18
|
const hostname = getHostname()
|
|
16
19
|
const service = config.service
|
|
17
20
|
|
|
18
21
|
const ddtags = [
|
|
22
|
+
['env', process.env.DD_ENV],
|
|
23
|
+
['version', process.env.DD_VERSION],
|
|
24
|
+
['debugger_version', version],
|
|
25
|
+
['host_name', hostname],
|
|
19
26
|
[GIT_COMMIT_SHA, config.commitSHA],
|
|
20
27
|
[GIT_REPOSITORY_URL, config.repositoryUrl]
|
|
21
28
|
].map((pair) => pair.join(':')).join(',')
|
|
22
29
|
|
|
23
30
|
const path = `/debugger/v1/input?${stringify({ ddtags })}`
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
const opts = {
|
|
27
|
-
method: 'POST',
|
|
28
|
-
url: config.url,
|
|
29
|
-
path,
|
|
30
|
-
headers: { 'Content-Type': 'application/json; charset=utf-8' }
|
|
31
|
-
}
|
|
32
|
+
const jsonBuffer = new JSONBuffer({ size: config.maxTotalPayloadSize, timeout: 1000, onFlush })
|
|
32
33
|
|
|
34
|
+
function send (message, logger, dd, snapshot) {
|
|
33
35
|
const payload = {
|
|
34
36
|
ddsource,
|
|
35
37
|
hostname,
|
|
@@ -41,8 +43,9 @@ function send (message, logger, dd, snapshot, cb) {
|
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
let json = JSON.stringify(payload)
|
|
46
|
+
let size = Buffer.byteLength(json)
|
|
44
47
|
|
|
45
|
-
if (
|
|
48
|
+
if (size > MAX_LOG_PAYLOAD_SIZE) {
|
|
46
49
|
// TODO: This is a very crude way to handle large payloads. Proper pruning will be implemented later (DEBUG-2624)
|
|
47
50
|
const line = Object.values(payload['debugger.snapshot'].captures.lines)[0]
|
|
48
51
|
line.locals = {
|
|
@@ -50,7 +53,23 @@ function send (message, logger, dd, snapshot, cb) {
|
|
|
50
53
|
size: Object.keys(line.locals).length
|
|
51
54
|
}
|
|
52
55
|
json = JSON.stringify(payload)
|
|
56
|
+
size = Buffer.byteLength(json)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
jsonBuffer.write(json, size)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function onFlush (payload) {
|
|
63
|
+
log.debug('[debugger:devtools_client] Flushing probe payload buffer')
|
|
64
|
+
|
|
65
|
+
const opts = {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
url: config.url,
|
|
68
|
+
path,
|
|
69
|
+
headers: { 'Content-Type': 'application/json; charset=utf-8' }
|
|
53
70
|
}
|
|
54
71
|
|
|
55
|
-
request(
|
|
72
|
+
request(payload, opts, (err) => {
|
|
73
|
+
if (err) log.error('[debugger:devtools_client] Error sending probe payload', err)
|
|
74
|
+
})
|
|
56
75
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { collectionSizeSym, fieldCountSym } = require('./symbols')
|
|
4
|
+
const { normalizeName, REDACTED_IDENTIFIERS } = require('./redaction')
|
|
4
5
|
|
|
5
6
|
module.exports = {
|
|
6
7
|
processRawState: processProperties
|
|
@@ -24,7 +25,14 @@ function processProperties (props, maxLength) {
|
|
|
24
25
|
return result
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
// TODO: Improve performance of redaction algorithm.
|
|
29
|
+
// This algorithm is probably slower than if we embedded the redaction logic inside the functions below.
|
|
30
|
+
// That way we didn't have to traverse objects that will just be redacted anyway.
|
|
27
31
|
function getPropertyValue (prop, maxLength) {
|
|
32
|
+
return redact(prop, getPropertyValueRaw(prop, maxLength))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getPropertyValueRaw (prop, maxLength) {
|
|
28
36
|
// Special case for getters and setters which does not have a value property
|
|
29
37
|
if ('get' in prop) {
|
|
30
38
|
const hasGet = prop.get.type !== 'undefined'
|
|
@@ -185,8 +193,11 @@ function toMap (type, pairs, maxLength) {
|
|
|
185
193
|
// `pair.value` is a special wrapper-object with subtype `internal#entry`. This can be skipped and we can go
|
|
186
194
|
// directly to its children, of which there will always be exactly two, the first containing the key, and the
|
|
187
195
|
// second containing the value of this entry of the Map.
|
|
196
|
+
const shouldRedact = shouldRedactMapValue(pair.value.properties[0])
|
|
188
197
|
const key = getPropertyValue(pair.value.properties[0], maxLength)
|
|
189
|
-
const val =
|
|
198
|
+
const val = shouldRedact
|
|
199
|
+
? notCapturedRedacted(pair.value.properties[1].value.type)
|
|
200
|
+
: getPropertyValue(pair.value.properties[1], maxLength)
|
|
190
201
|
result.entries[i++] = [key, val]
|
|
191
202
|
}
|
|
192
203
|
|
|
@@ -240,6 +251,25 @@ function arrayBufferToString (bytes, size) {
|
|
|
240
251
|
return buf.toString()
|
|
241
252
|
}
|
|
242
253
|
|
|
254
|
+
function redact (prop, obj) {
|
|
255
|
+
const name = getNormalizedNameFromProp(prop)
|
|
256
|
+
return REDACTED_IDENTIFIERS.has(name) ? notCapturedRedacted(obj.type) : obj
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function shouldRedactMapValue (key) {
|
|
260
|
+
const isSymbol = key.value.type === 'symbol'
|
|
261
|
+
if (!isSymbol && key.value.type !== 'string') return false // WeakMaps uses objects as keys
|
|
262
|
+
const name = normalizeName(
|
|
263
|
+
isSymbol ? key.value.description : key.value.value,
|
|
264
|
+
isSymbol
|
|
265
|
+
)
|
|
266
|
+
return REDACTED_IDENTIFIERS.has(name)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function getNormalizedNameFromProp (prop) {
|
|
270
|
+
return normalizeName(prop.name, 'symbol' in prop)
|
|
271
|
+
}
|
|
272
|
+
|
|
243
273
|
function setNotCaptureReasonOnCollection (result, collection) {
|
|
244
274
|
if (collectionSizeSym in collection) {
|
|
245
275
|
result.notCapturedReason = 'collectionSize'
|
|
@@ -250,3 +280,7 @@ function setNotCaptureReasonOnCollection (result, collection) {
|
|
|
250
280
|
function notCapturedDepth (type) {
|
|
251
281
|
return { type, notCapturedReason: 'depth' }
|
|
252
282
|
}
|
|
283
|
+
|
|
284
|
+
function notCapturedRedacted (type) {
|
|
285
|
+
return { type, notCapturedReason: 'redactedIdent' }
|
|
286
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const config = require('../config')
|
|
4
|
+
|
|
5
|
+
const excludedIdentifiers = config.dynamicInstrumentation.redactionExcludedIdentifiers
|
|
6
|
+
.map((name) => normalizeName(name))
|
|
7
|
+
|
|
8
|
+
const REDACTED_IDENTIFIERS = new Set(
|
|
9
|
+
[
|
|
10
|
+
'2fa',
|
|
11
|
+
'_csrf',
|
|
12
|
+
'_csrf_token',
|
|
13
|
+
'_session',
|
|
14
|
+
'_xsrf',
|
|
15
|
+
'access_token',
|
|
16
|
+
'aiohttp_session',
|
|
17
|
+
'api_key',
|
|
18
|
+
'apisecret',
|
|
19
|
+
'apisignature',
|
|
20
|
+
'applicationkey',
|
|
21
|
+
'appkey',
|
|
22
|
+
'auth',
|
|
23
|
+
'authtoken',
|
|
24
|
+
'authorization',
|
|
25
|
+
'cc_number',
|
|
26
|
+
'certificatepin',
|
|
27
|
+
'cipher',
|
|
28
|
+
'client_secret',
|
|
29
|
+
'clientid',
|
|
30
|
+
'connect.sid',
|
|
31
|
+
'connectionstring',
|
|
32
|
+
'cookie',
|
|
33
|
+
'credentials',
|
|
34
|
+
'creditcard',
|
|
35
|
+
'csrf',
|
|
36
|
+
'csrf_token',
|
|
37
|
+
'cvv',
|
|
38
|
+
'databaseurl',
|
|
39
|
+
'db_url',
|
|
40
|
+
'encryption_key',
|
|
41
|
+
'encryptionkeyid',
|
|
42
|
+
'geo_location',
|
|
43
|
+
'gpg_key',
|
|
44
|
+
'ip_address',
|
|
45
|
+
'jti',
|
|
46
|
+
'jwt',
|
|
47
|
+
'license_key',
|
|
48
|
+
'masterkey',
|
|
49
|
+
'mysql_pwd',
|
|
50
|
+
'nonce',
|
|
51
|
+
'oauth',
|
|
52
|
+
'oauthtoken',
|
|
53
|
+
'otp',
|
|
54
|
+
'passhash',
|
|
55
|
+
'passwd',
|
|
56
|
+
'password',
|
|
57
|
+
'passwordb',
|
|
58
|
+
'pem_file',
|
|
59
|
+
'pgp_key',
|
|
60
|
+
'PHPSESSID',
|
|
61
|
+
'pin',
|
|
62
|
+
'pincode',
|
|
63
|
+
'pkcs8',
|
|
64
|
+
'private_key',
|
|
65
|
+
'publickey',
|
|
66
|
+
'pwd',
|
|
67
|
+
'recaptcha_key',
|
|
68
|
+
'refresh_token',
|
|
69
|
+
'routingnumber',
|
|
70
|
+
'salt',
|
|
71
|
+
'secret',
|
|
72
|
+
'secretKey',
|
|
73
|
+
'secrettoken',
|
|
74
|
+
'securitycode',
|
|
75
|
+
'security_answer',
|
|
76
|
+
'security_question',
|
|
77
|
+
'serviceaccountcredentials',
|
|
78
|
+
'session',
|
|
79
|
+
'sessionid',
|
|
80
|
+
'sessionkey',
|
|
81
|
+
'set_cookie',
|
|
82
|
+
'signature',
|
|
83
|
+
'signaturekey',
|
|
84
|
+
'ssh_key',
|
|
85
|
+
'ssn',
|
|
86
|
+
'symfony',
|
|
87
|
+
'token',
|
|
88
|
+
'transactionid',
|
|
89
|
+
'twilio_token',
|
|
90
|
+
'user_session',
|
|
91
|
+
'voterid',
|
|
92
|
+
'x-auth-token',
|
|
93
|
+
'x_api_key',
|
|
94
|
+
'x_csrftoken',
|
|
95
|
+
'x_forwarded_for',
|
|
96
|
+
'x_real_ip',
|
|
97
|
+
'XSRF-TOKEN',
|
|
98
|
+
...config.dynamicInstrumentation.redactedIdentifiers
|
|
99
|
+
]
|
|
100
|
+
.map((name) => normalizeName(name))
|
|
101
|
+
.filter((name) => excludedIdentifiers.includes(name) === false)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
function normalizeName (name, isSymbol) {
|
|
105
|
+
if (isSymbol) name = name.slice(7, -1) // Remove `Symbol(` and `)`
|
|
106
|
+
return name.toLowerCase().replace(/[-_@$.]/g, '')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = {
|
|
110
|
+
REDACTED_IDENTIFIERS,
|
|
111
|
+
normalizeName
|
|
112
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const TTLSet = require('ttl-set')
|
|
4
4
|
const config = require('./config')
|
|
5
|
+
const JSONBuffer = require('./json-buffer')
|
|
5
6
|
const request = require('../../exporters/common/request')
|
|
6
7
|
const FormData = require('../../exporters/common/form-data')
|
|
7
8
|
const log = require('../../log')
|
|
@@ -17,13 +18,9 @@ const ddsource = 'dd_debugger'
|
|
|
17
18
|
const service = config.service
|
|
18
19
|
const runtimeId = config.runtimeId
|
|
19
20
|
|
|
20
|
-
const cache = new
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// It will emit a warning unless `ttlAutopurge`, `max`, or `maxSize` is set when using `ttl`.
|
|
24
|
-
// TODO: Consider alternative as this is NOT performant :(
|
|
25
|
-
ttlAutopurge: true
|
|
26
|
-
})
|
|
21
|
+
const cache = new TTLSet(60 * 60 * 1000) // 1 hour
|
|
22
|
+
|
|
23
|
+
const jsonBuffer = new JSONBuffer({ size: config.maxTotalPayloadSize, timeout: 1000, onFlush })
|
|
27
24
|
|
|
28
25
|
const STATUSES = {
|
|
29
26
|
RECEIVED: 'RECEIVED',
|
|
@@ -34,6 +31,8 @@ const STATUSES = {
|
|
|
34
31
|
}
|
|
35
32
|
|
|
36
33
|
function ackReceived ({ id: probeId, version }) {
|
|
34
|
+
log.debug('[debugger:devtools_client] Queueing RECEIVED status for probe %s (version: %d)', probeId, version)
|
|
35
|
+
|
|
37
36
|
onlyUniqueUpdates(
|
|
38
37
|
STATUSES.RECEIVED, probeId, version,
|
|
39
38
|
() => send(statusPayload(probeId, version, STATUSES.RECEIVED))
|
|
@@ -41,6 +40,8 @@ function ackReceived ({ id: probeId, version }) {
|
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
function ackInstalled ({ id: probeId, version }) {
|
|
43
|
+
log.debug('[debugger:devtools_client] Queueing INSTALLED status for probe %s (version: %d)', probeId, version)
|
|
44
|
+
|
|
44
45
|
onlyUniqueUpdates(
|
|
45
46
|
STATUSES.INSTALLED, probeId, version,
|
|
46
47
|
() => send(statusPayload(probeId, version, STATUSES.INSTALLED))
|
|
@@ -48,6 +49,8 @@ function ackInstalled ({ id: probeId, version }) {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
function ackEmitting ({ id: probeId, version }) {
|
|
52
|
+
log.debug('[debugger:devtools_client] Queueing EMITTING status for probe %s (version: %d)', probeId, version)
|
|
53
|
+
|
|
51
54
|
onlyUniqueUpdates(
|
|
52
55
|
STATUSES.EMITTING, probeId, version,
|
|
53
56
|
() => send(statusPayload(probeId, version, STATUSES.EMITTING))
|
|
@@ -71,11 +74,17 @@ function ackError (err, { id: probeId, version }) {
|
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
function send (payload) {
|
|
77
|
+
jsonBuffer.write(JSON.stringify(payload))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function onFlush (payload) {
|
|
81
|
+
log.debug('[debugger:devtools_client] Flushing diagnostics payload buffer')
|
|
82
|
+
|
|
74
83
|
const form = new FormData()
|
|
75
84
|
|
|
76
85
|
form.append(
|
|
77
86
|
'event',
|
|
78
|
-
|
|
87
|
+
payload,
|
|
79
88
|
{ filename: 'event.json', contentType: 'application/json; charset=utf-8' }
|
|
80
89
|
)
|
|
81
90
|
|
|
@@ -87,7 +96,7 @@ function send (payload) {
|
|
|
87
96
|
}
|
|
88
97
|
|
|
89
98
|
request(form, options, (err) => {
|
|
90
|
-
if (err) log.error('[debugger:devtools_client] Error sending
|
|
99
|
+
if (err) log.error('[debugger:devtools_client] Error sending diagnostics payload', err)
|
|
91
100
|
})
|
|
92
101
|
}
|
|
93
102
|
|
|
@@ -105,5 +114,5 @@ function onlyUniqueUpdates (type, id, version, fn) {
|
|
|
105
114
|
const key = `${type}-${id}-${version}`
|
|
106
115
|
if (cache.has(key)) return
|
|
107
116
|
fn()
|
|
108
|
-
cache.
|
|
117
|
+
cache.add(key)
|
|
109
118
|
}
|
|
@@ -48,7 +48,7 @@ function start (config, rc) {
|
|
|
48
48
|
execArgv: [], // Avoid worker thread inheriting the `-r` command line argument
|
|
49
49
|
env, // Avoid worker thread inheriting the `NODE_OPTIONS` environment variable (in case it contains `-r`)
|
|
50
50
|
workerData: {
|
|
51
|
-
config:
|
|
51
|
+
config: config.serialize(),
|
|
52
52
|
parentThreadId,
|
|
53
53
|
rcPort: rcChannel.port1,
|
|
54
54
|
configPort: configChannel.port1
|
|
@@ -88,16 +88,5 @@ function start (config, rc) {
|
|
|
88
88
|
|
|
89
89
|
function configure (config) {
|
|
90
90
|
if (configChannel === null) return
|
|
91
|
-
configChannel.port2.postMessage(
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// TODO: Refactor the Config class so it never produces any config objects that are incompatible with MessageChannel
|
|
95
|
-
function serializableConfig (config) {
|
|
96
|
-
// URL objects cannot be serialized over the MessageChannel, so we need to convert them to strings first
|
|
97
|
-
if (config.url instanceof URL) {
|
|
98
|
-
config = { ...config }
|
|
99
|
-
config.url = config.url.toString()
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return config
|
|
91
|
+
configChannel.port2.postMessage(config.serialize())
|
|
103
92
|
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const log = require('../../log')
|
|
4
|
-
const { storage } = require('../storage')
|
|
4
|
+
const { storage: llmobsStorage } = require('../storage')
|
|
5
5
|
|
|
6
6
|
const TracingPlugin = require('../../plugins/tracing')
|
|
7
7
|
const LLMObsTagger = require('../tagger')
|
|
8
8
|
|
|
9
|
-
// we make this a `Plugin` so we don't have to worry about `finish` being called
|
|
10
9
|
class LLMObsPlugin extends TracingPlugin {
|
|
11
10
|
constructor (...args) {
|
|
12
11
|
super(...args)
|
|
@@ -14,24 +13,48 @@ class LLMObsPlugin extends TracingPlugin {
|
|
|
14
13
|
this._tagger = new LLMObsTagger(this._tracerConfig, true)
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
getName () {}
|
|
18
|
-
|
|
19
16
|
setLLMObsTags (ctx) {
|
|
20
17
|
throw new Error('setLLMObsTags must be implemented by the subclass')
|
|
21
18
|
}
|
|
22
19
|
|
|
23
|
-
|
|
20
|
+
getLLMObsSpanRegisterOptions (ctx) {
|
|
24
21
|
throw new Error('getLLMObsSPanRegisterOptions must be implemented by the subclass')
|
|
25
22
|
}
|
|
26
23
|
|
|
27
24
|
start (ctx) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
25
|
+
// even though llmobs span events won't be enqueued if llmobs is disabled
|
|
26
|
+
// we should avoid doing any computations here (these listeners aren't disabled)
|
|
27
|
+
const enabled = this._tracerConfig.llmobs.enabled
|
|
28
|
+
if (!enabled) return
|
|
29
|
+
|
|
30
|
+
const parent = this.getLLMObsParent(ctx)
|
|
31
|
+
const apmStore = ctx.currentStore
|
|
32
|
+
const span = apmStore?.span
|
|
33
|
+
|
|
34
|
+
const registerOptions = this.getLLMObsSpanRegisterOptions(ctx)
|
|
35
|
+
|
|
36
|
+
// register options may not be set for operations we do not trace with llmobs
|
|
37
|
+
// ie OpenAI fine tuning jobs, file jobs, etc.
|
|
38
|
+
if (registerOptions) {
|
|
39
|
+
ctx.llmobs = {} // initialize context-based namespace
|
|
40
|
+
llmobsStorage.enterWith({ span })
|
|
41
|
+
ctx.llmobs.parent = parent
|
|
31
42
|
|
|
32
|
-
|
|
43
|
+
this._tagger.registerLLMObsSpan(span, { parent, ...registerOptions })
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
end (ctx) {
|
|
48
|
+
const enabled = this._tracerConfig.llmobs.enabled
|
|
49
|
+
if (!enabled) return
|
|
50
|
+
|
|
51
|
+
// only attempt to restore the context if the current span was an LLMObs span
|
|
52
|
+
const apmStore = ctx.currentStore
|
|
53
|
+
const span = apmStore?.span
|
|
54
|
+
if (!LLMObsTagger.tagMap.has(span)) return
|
|
33
55
|
|
|
34
|
-
|
|
56
|
+
const parent = ctx.llmobs.parent
|
|
57
|
+
llmobsStorage.enterWith({ span: parent })
|
|
35
58
|
}
|
|
36
59
|
|
|
37
60
|
asyncEnd (ctx) {
|
|
@@ -40,7 +63,8 @@ class LLMObsPlugin extends TracingPlugin {
|
|
|
40
63
|
const enabled = this._tracerConfig.llmobs.enabled
|
|
41
64
|
if (!enabled) return
|
|
42
65
|
|
|
43
|
-
const
|
|
66
|
+
const apmStore = ctx.currentStore
|
|
67
|
+
const span = apmStore?.span
|
|
44
68
|
if (!span) {
|
|
45
69
|
log.debug(
|
|
46
70
|
`Tried to start an LLMObs span for ${this.constructor.name} without an active APM span.
|
|
@@ -60,6 +84,11 @@ class LLMObsPlugin extends TracingPlugin {
|
|
|
60
84
|
}
|
|
61
85
|
super.configure(config)
|
|
62
86
|
}
|
|
87
|
+
|
|
88
|
+
getLLMObsParent () {
|
|
89
|
+
const store = llmobsStorage.getStore()
|
|
90
|
+
return store?.span
|
|
91
|
+
}
|
|
63
92
|
}
|
|
64
93
|
|
|
65
94
|
module.exports = LLMObsPlugin
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const LangChainLLMObsHandler = require('.')
|
|
4
|
+
const { spanHasError } = require('../../../util')
|
|
5
|
+
|
|
6
|
+
class LangChainLLMObsChainHandler extends LangChainLLMObsHandler {
|
|
7
|
+
setMetaTags ({ span, inputs, results }) {
|
|
8
|
+
let input, output
|
|
9
|
+
if (inputs) {
|
|
10
|
+
input = this.formatIO(inputs)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (!results || spanHasError(span)) {
|
|
14
|
+
output = ''
|
|
15
|
+
} else {
|
|
16
|
+
output = this.formatIO(results)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// chain spans will always be workflows
|
|
20
|
+
this._tagger.tagTextIO(span, input, output)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = LangChainLLMObsChainHandler
|