dd-trace 5.80.0 → 5.81.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 +79 -88
- package/ext/tags.d.ts +1 -0
- package/ext/tags.js +1 -0
- package/index.d.ts +35 -35
- package/loader-hook.mjs +10 -3
- package/package.json +22 -40
- package/packages/datadog-esbuild/index.js +36 -19
- package/packages/datadog-instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/anthropic.js +12 -0
- package/packages/datadog-instrumentations/src/aws-sdk.js +5 -1
- package/packages/datadog-instrumentations/src/cucumber.js +2 -2
- package/packages/datadog-instrumentations/src/find-my-way.js +6 -5
- package/packages/datadog-instrumentations/src/google-genai.js +120 -0
- package/packages/datadog-instrumentations/src/graphql.js +20 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +10 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +6 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +27 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +152 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +5 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langchain.js +237 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/loader.js +9 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +11 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +139 -0
- package/packages/datadog-instrumentations/src/langchain.js +3 -109
- package/packages/datadog-instrumentations/src/mocha/main.js +1 -1
- package/packages/datadog-instrumentations/src/mysql2.js +1 -1
- package/packages/datadog-instrumentations/src/playwright.js +45 -16
- package/packages/datadog-instrumentations/src/router.js +1 -1
- package/packages/datadog-instrumentations/src/selenium.js +3 -1
- package/packages/datadog-instrumentations/src/ws.js +35 -17
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +23 -2
- package/packages/datadog-plugin-cypress/src/plugin.js +1 -1
- package/packages/datadog-plugin-cypress/src/support.js +73 -31
- package/packages/datadog-plugin-google-genai/src/index.js +17 -0
- package/packages/datadog-plugin-google-genai/src/tracing.js +41 -0
- package/packages/datadog-plugin-graphql/src/tools/transforms.js +5 -4
- package/packages/datadog-plugin-jest/src/util.js +1 -1
- package/packages/datadog-plugin-langchain/src/tracing.js +7 -3
- package/packages/datadog-plugin-next/src/index.js +11 -3
- package/packages/dd-trace/src/aiguard/sdk.js +18 -10
- package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
- package/packages/dd-trace/src/appsec/iast/overhead-controller.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +1 -2
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -1
- package/packages/dd-trace/src/appsec/reporter.js +0 -4
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +4 -8
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +4 -2
- package/packages/dd-trace/src/config.js +81 -7
- package/packages/dd-trace/src/config_defaults.js +14 -2
- package/packages/dd-trace/src/datastreams/encoding.js +23 -6
- package/packages/dd-trace/src/datastreams/pathway.js +40 -1
- package/packages/dd-trace/src/datastreams/processor.js +1 -1
- package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +15 -5
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +30 -15
- package/packages/dd-trace/src/debugger/devtools_client/inspector_promises_polyfill.js +2 -0
- package/packages/dd-trace/src/debugger/devtools_client/json-buffer.js +24 -18
- package/packages/dd-trace/src/debugger/devtools_client/send.js +18 -8
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +103 -15
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/constants.js +25 -0
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +56 -25
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +64 -23
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/symbols.js +3 -1
- package/packages/dd-trace/src/debugger/devtools_client/snapshot-pruner.js +404 -0
- package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/state.js +7 -2
- package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
- package/packages/dd-trace/src/debugger/index.js +1 -1
- package/packages/dd-trace/src/encode/span-stats.js +7 -1
- package/packages/dd-trace/src/histogram.js +1 -1
- package/packages/dd-trace/src/id.js +60 -0
- package/packages/dd-trace/src/lambda/runtime/ritm.js +1 -1
- package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +104 -0
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +486 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +2 -2
- package/packages/dd-trace/src/llmobs/plugins/{openai.js → openai/index.js} +48 -6
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +114 -0
- package/packages/dd-trace/src/llmobs/sdk.js +5 -0
- package/packages/dd-trace/src/llmobs/span_processor.js +6 -1
- package/packages/dd-trace/src/llmobs/tagger.js +4 -0
- package/packages/dd-trace/src/opentelemetry/logs/index.js +2 -2
- package/packages/dd-trace/src/opentelemetry/logs/logger.js +3 -2
- package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +5 -3
- package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +8 -8
- package/packages/dd-trace/src/opentelemetry/metrics/constants.js +34 -0
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +81 -0
- package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +225 -0
- package/packages/dd-trace/src/opentelemetry/metrics/meter.js +171 -0
- package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +54 -0
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +62 -0
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +251 -0
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +532 -0
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +10 -18
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +36 -22
- package/packages/dd-trace/src/opentelemetry/otlp/protobuf_loader.js +1 -1
- package/packages/dd-trace/src/opentelemetry/span.js +1 -1
- package/packages/dd-trace/src/opentelemetry/tracer.js +1 -1
- package/packages/dd-trace/src/opentelemetry/tracer_provider.js +1 -1
- package/packages/dd-trace/src/payload-tagging/index.js +2 -2
- package/packages/dd-trace/src/plugin_manager.js +4 -2
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/test.js +3 -3
- package/packages/dd-trace/src/plugins/util/url.js +119 -1
- package/packages/dd-trace/src/plugins/util/web.js +10 -41
- package/packages/dd-trace/src/process-tags/index.js +81 -0
- package/packages/dd-trace/src/profiling/config.js +1 -1
- package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/events.js +10 -1
- package/packages/dd-trace/src/proxy.js +5 -0
- package/packages/dd-trace/src/rate_limiter.js +1 -1
- package/packages/dd-trace/src/remote_config/manager.js +1 -1
- package/packages/dd-trace/src/ritm.js +1 -1
- package/packages/dd-trace/src/service-naming/schemas/v0/web.js +4 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/web.js +4 -0
- package/packages/dd-trace/src/span_format.js +9 -4
- package/packages/dd-trace/src/span_processor.js +8 -3
- package/packages/dd-trace/src/span_stats.js +15 -4
- package/packages/dd-trace/src/spanleak.js +1 -1
- package/packages/dd-trace/src/supported-configurations.json +13 -0
- package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +11 -2
- package/vendor/dist/@datadog/sketches-js/LICENSE +39 -0
- package/vendor/dist/@datadog/sketches-js/index.js +1 -0
- package/vendor/dist/@datadog/source-map/LICENSE +28 -0
- package/vendor/dist/@datadog/source-map/index.js +1 -0
- package/vendor/dist/@isaacs/ttlcache/LICENSE +55 -0
- package/vendor/dist/@isaacs/ttlcache/index.js +1 -0
- package/vendor/dist/@opentelemetry/core/LICENSE +201 -0
- package/vendor/dist/@opentelemetry/core/index.js +1 -0
- package/vendor/dist/@opentelemetry/resources/LICENSE +201 -0
- package/vendor/dist/@opentelemetry/resources/index.js +1 -0
- package/vendor/dist/astring/LICENSE +19 -0
- package/vendor/dist/astring/index.js +1 -0
- package/vendor/dist/crypto-randomuuid/index.js +1 -0
- package/vendor/dist/escape-string-regexp/LICENSE +9 -0
- package/vendor/dist/escape-string-regexp/index.js +1 -0
- package/vendor/dist/esquery/LICENSE +24 -0
- package/vendor/dist/esquery/index.js +1 -0
- package/vendor/dist/ignore/LICENSE +21 -0
- package/vendor/dist/ignore/index.js +1 -0
- package/vendor/dist/istanbul-lib-coverage/LICENSE +24 -0
- package/vendor/dist/istanbul-lib-coverage/index.js +1 -0
- package/vendor/dist/jest-docblock/LICENSE +21 -0
- package/vendor/dist/jest-docblock/index.js +1 -0
- package/vendor/dist/jsonpath-plus/LICENSE +22 -0
- package/vendor/dist/jsonpath-plus/index.js +1 -0
- package/vendor/dist/limiter/LICENSE +19 -0
- package/vendor/dist/limiter/index.js +1 -0
- package/vendor/dist/lodash.sortby/LICENSE +47 -0
- package/vendor/dist/lodash.sortby/index.js +1 -0
- package/vendor/dist/lru-cache/LICENSE +15 -0
- package/vendor/dist/lru-cache/index.js +1 -0
- package/vendor/dist/meriyah/LICENSE +7 -0
- package/vendor/dist/meriyah/index.js +1 -0
- package/vendor/dist/module-details-from-path/LICENSE +21 -0
- package/vendor/dist/module-details-from-path/index.js +1 -0
- package/vendor/dist/mutexify/promise/LICENSE +21 -0
- package/vendor/dist/mutexify/promise/index.js +1 -0
- package/vendor/dist/opentracing/LICENSE +201 -0
- package/vendor/dist/opentracing/binary_carrier.d.ts +11 -0
- package/vendor/dist/opentracing/constants.d.ts +61 -0
- package/vendor/dist/opentracing/examples/demo/demo.d.ts +2 -0
- package/vendor/dist/opentracing/ext/tags.d.ts +90 -0
- package/vendor/dist/opentracing/functions.d.ts +20 -0
- package/vendor/dist/opentracing/global_tracer.d.ts +14 -0
- package/vendor/dist/opentracing/index.d.ts +12 -0
- package/vendor/dist/opentracing/index.js +1 -0
- package/vendor/dist/opentracing/mock_tracer/index.d.ts +5 -0
- package/vendor/dist/opentracing/mock_tracer/mock_context.d.ts +13 -0
- package/vendor/dist/opentracing/mock_tracer/mock_report.d.ts +16 -0
- package/vendor/dist/opentracing/mock_tracer/mock_span.d.ts +50 -0
- package/vendor/dist/opentracing/mock_tracer/mock_tracer.d.ts +26 -0
- package/vendor/dist/opentracing/noop.d.ts +8 -0
- package/vendor/dist/opentracing/reference.d.ts +33 -0
- package/vendor/dist/opentracing/span.d.ts +147 -0
- package/vendor/dist/opentracing/span_context.d.ts +26 -0
- package/vendor/dist/opentracing/test/api_compatibility.d.ts +16 -0
- package/vendor/dist/opentracing/test/mocktracer_implemenation.d.ts +3 -0
- package/vendor/dist/opentracing/test/noop_implementation.d.ts +4 -0
- package/vendor/dist/opentracing/test/opentracing_api.d.ts +3 -0
- package/vendor/dist/opentracing/test/unittest.d.ts +2 -0
- package/vendor/dist/opentracing/tracer.d.ts +127 -0
- package/vendor/dist/path-to-regexp/LICENSE +21 -0
- package/vendor/dist/path-to-regexp/index.js +1 -0
- package/vendor/dist/pprof-format/LICENSE +8 -0
- package/vendor/dist/pprof-format/index.js +1 -0
- package/vendor/dist/protobufjs/LICENSE +39 -0
- package/vendor/dist/protobufjs/index.js +1 -0
- package/vendor/dist/protobufjs/minimal/LICENSE +39 -0
- package/vendor/dist/protobufjs/minimal/index.js +1 -0
- package/vendor/dist/retry/LICENSE +21 -0
- package/vendor/dist/retry/index.js +1 -0
- package/vendor/dist/rfdc/LICENSE +15 -0
- package/vendor/dist/rfdc/index.js +1 -0
- package/vendor/dist/semifies/LICENSE +201 -0
- package/vendor/dist/semifies/index.js +1 -0
- package/vendor/dist/shell-quote/LICENSE +24 -0
- package/vendor/dist/shell-quote/index.js +1 -0
- package/vendor/dist/source-map/LICENSE +28 -0
- package/vendor/dist/source-map/index.js +1 -0
- package/vendor/dist/source-map/lib/util/LICENSE +28 -0
- package/vendor/dist/source-map/lib/util/index.js +1 -0
- package/vendor/dist/source-map/mappings.wasm +0 -0
- package/vendor/dist/tlhunter-sorted-set/LICENSE +21 -0
- package/vendor/dist/tlhunter-sorted-set/index.js +1 -0
- package/vendor/dist/ttl-set/LICENSE +21 -0
- package/vendor/dist/ttl-set/index.js +1 -0
|
@@ -1,16 +1,42 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { collectionSizeSym, fieldCountSym } = require('./symbols')
|
|
3
|
+
const { collectionSizeSym, largeCollectionSkipThresholdSym, fieldCountSym, timeBudgetSym } = require('./symbols')
|
|
4
|
+
const { LARGE_OBJECT_SKIP_THRESHOLD } = require('./constants')
|
|
4
5
|
const session = require('../session')
|
|
5
6
|
|
|
6
7
|
const LEAF_SUBTYPES = new Set(['date', 'regexp'])
|
|
7
8
|
const ITERABLE_SUBTYPES = new Set(['map', 'set', 'weakmap', 'weakset'])
|
|
9
|
+
const SIZE_IN_DESCRIPTION_SUBTYPES = new Set(['array', 'typedarray', 'arraybuffer', 'dataview', 'map', 'set'])
|
|
8
10
|
|
|
9
11
|
module.exports = {
|
|
10
|
-
|
|
12
|
+
collectObjectProperties
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Object} GetObjectOptions
|
|
17
|
+
* @property {Object} maxReferenceDepth - The maximum depth of the object to traverse
|
|
18
|
+
* @property {number} maxCollectionSize - The maximum size of a collection to include in the snapshot
|
|
19
|
+
* @property {number} maxFieldCount - The maximum number of properties on an object to include in the snapshot
|
|
20
|
+
* @property {bigint} deadlineNs - The deadline in nanoseconds compared to `process.hrtime.bigint()`
|
|
21
|
+
* @property {Object} ctx - A context object to track the state/progress of the snapshot collection.
|
|
22
|
+
* @property {boolean} ctx.deadlineReached - Will be set to `true` if the deadline has been reached.
|
|
23
|
+
* @property {Error[]} ctx.captureErrors - An array on which errors can be pushed if an issue is detected while
|
|
24
|
+
* collecting the snapshot.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Collect the properties of an object using the Chrome DevTools Protocol.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} objectId - The ID of the object to get the properties of
|
|
31
|
+
* @param {GetObjectOptions} opts - The options for the snapshot. Also used to track the deadline and communicate the
|
|
32
|
+
* deadline overrun to the caller using the `deadlineReached` flag.
|
|
33
|
+
* @param {number} [depth=0] - The depth of the object. Only used internally by this module to track the current depth
|
|
34
|
+
* and should not be set by the caller.
|
|
35
|
+
* @param {boolean} [collection=false] - Whether the object is a collection. Only used internally by this module to
|
|
36
|
+
* track the current object type and should not be set by the caller.
|
|
37
|
+
* @returns {Promise<Object[]>} The properties of the object
|
|
38
|
+
*/
|
|
39
|
+
async function collectObjectProperties (objectId, opts, depth = 0, collection = false) {
|
|
14
40
|
const { result, privateProperties } = await session.post('Runtime.getProperties', {
|
|
15
41
|
objectId,
|
|
16
42
|
ownProperties: true // exclude inherited properties
|
|
@@ -28,6 +54,13 @@ async function getObject (objectId, opts, depth = 0, collection = false) {
|
|
|
28
54
|
} else if (result.length > opts.maxFieldCount) {
|
|
29
55
|
// Trim the number of properties on the object if there's too many.
|
|
30
56
|
const size = result.length
|
|
57
|
+
if (size > LARGE_OBJECT_SKIP_THRESHOLD) {
|
|
58
|
+
opts.ctx.captureErrors.push(new Error(
|
|
59
|
+
`An object with ${size} properties was detected while collecting a snapshot. ` +
|
|
60
|
+
`This exceeds the maximum number of allowed properties of ${LARGE_OBJECT_SKIP_THRESHOLD}. ` +
|
|
61
|
+
'Future snapshots for existing probes in this location will be skipped until the Node.js process is restarted'
|
|
62
|
+
))
|
|
63
|
+
}
|
|
31
64
|
result.length = opts.maxFieldCount
|
|
32
65
|
result[fieldCountSym] = size
|
|
33
66
|
} else if (privateProperties) {
|
|
@@ -43,32 +76,61 @@ async function traverseGetPropertiesResult (props, opts, depth) {
|
|
|
43
76
|
|
|
44
77
|
if (depth >= opts.maxReferenceDepth) return props
|
|
45
78
|
|
|
46
|
-
const
|
|
79
|
+
const work = []
|
|
47
80
|
|
|
48
81
|
for (const prop of props) {
|
|
49
82
|
if (prop.value === undefined) continue
|
|
50
|
-
const { value: { type, objectId, subtype } } = prop
|
|
83
|
+
const { value: { type, objectId, subtype, description } } = prop
|
|
51
84
|
if (type === 'object') {
|
|
52
85
|
if (objectId === undefined) continue // if `subtype` is "null"
|
|
53
86
|
if (LEAF_SUBTYPES.has(subtype)) continue // don't waste time with these subtypes
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
87
|
+
const size = parseLengthFromDescription(description, subtype)
|
|
88
|
+
if (size !== null && size >= LARGE_OBJECT_SKIP_THRESHOLD) {
|
|
89
|
+
const empty = []
|
|
90
|
+
empty[largeCollectionSkipThresholdSym] = size
|
|
91
|
+
prop.value.properties = empty
|
|
92
|
+
continue
|
|
93
|
+
}
|
|
94
|
+
work.push([
|
|
95
|
+
prop.value,
|
|
96
|
+
() => collectPropertiesBySubtype(subtype, objectId, opts, depth).then((properties) => {
|
|
97
|
+
prop.value.properties = properties
|
|
98
|
+
})
|
|
99
|
+
])
|
|
57
100
|
} else if (type === 'function') {
|
|
58
|
-
|
|
59
|
-
prop.value
|
|
60
|
-
|
|
101
|
+
work.push([
|
|
102
|
+
prop.value,
|
|
103
|
+
() => getFunctionProperties(objectId, opts, depth + 1).then((properties) => {
|
|
104
|
+
prop.value.properties = properties
|
|
105
|
+
})
|
|
106
|
+
])
|
|
61
107
|
}
|
|
62
108
|
}
|
|
63
109
|
|
|
64
|
-
if (
|
|
65
|
-
|
|
110
|
+
if (work.length) {
|
|
111
|
+
// Iterate over the work in chunks of 2. The closer to 1, the less we'll overshoot the deadline, but the longer it
|
|
112
|
+
// takes to complete. `2` seems to be the best compromise.
|
|
113
|
+
// Anecdotally, on my machine, with no deadline, a concurrency of `1` takes twice as long as a concurrency of `2`.
|
|
114
|
+
// From thereon, there's no real measurable savings with a higher concurrency.
|
|
115
|
+
for (let i = 0; i < work.length; i += 2) {
|
|
116
|
+
if (overBudget(opts)) {
|
|
117
|
+
for (let j = i; j < work.length; j++) {
|
|
118
|
+
work[j][0][timeBudgetSym] = true
|
|
119
|
+
}
|
|
120
|
+
break
|
|
121
|
+
}
|
|
122
|
+
// eslint-disable-next-line no-await-in-loop
|
|
123
|
+
await Promise.all([
|
|
124
|
+
work[i][1](),
|
|
125
|
+
work[i + 1]?.[1]()
|
|
126
|
+
])
|
|
127
|
+
}
|
|
66
128
|
}
|
|
67
129
|
|
|
68
130
|
return props
|
|
69
131
|
}
|
|
70
132
|
|
|
71
|
-
function
|
|
133
|
+
function collectPropertiesBySubtype (subtype, objectId, opts, depth) {
|
|
72
134
|
if (ITERABLE_SUBTYPES.has(subtype)) {
|
|
73
135
|
return getIterable(objectId, opts, depth)
|
|
74
136
|
} else if (subtype === 'promise') {
|
|
@@ -78,7 +140,7 @@ function getObjectProperties (subtype, objectId, opts, depth) {
|
|
|
78
140
|
} else if (subtype === 'arraybuffer') {
|
|
79
141
|
return getArrayBuffer(objectId, opts, depth)
|
|
80
142
|
}
|
|
81
|
-
return
|
|
143
|
+
return collectObjectProperties(objectId, opts, depth + 1, subtype === 'array' || subtype === 'typedarray')
|
|
82
144
|
}
|
|
83
145
|
|
|
84
146
|
// TODO: The following extra information from `internalProperties` might be relevant to include for functions:
|
|
@@ -189,3 +251,29 @@ function removeNonEnumerableProperties (props) {
|
|
|
189
251
|
}
|
|
190
252
|
}
|
|
191
253
|
}
|
|
254
|
+
|
|
255
|
+
function parseLengthFromDescription (description, subtype) {
|
|
256
|
+
if (typeof description !== 'string') return null
|
|
257
|
+
if (!SIZE_IN_DESCRIPTION_SUBTYPES.has(subtype)) return null
|
|
258
|
+
|
|
259
|
+
const open = description.lastIndexOf('(')
|
|
260
|
+
if (open === -1) return null
|
|
261
|
+
|
|
262
|
+
const close = description.indexOf(')', open + 1)
|
|
263
|
+
if (close === -1) return null
|
|
264
|
+
|
|
265
|
+
const s = description.slice(open + 1, close)
|
|
266
|
+
if (s === '') return null
|
|
267
|
+
|
|
268
|
+
const n = Number(s)
|
|
269
|
+
if (!Number.isSafeInteger(n) || n < 0) return null
|
|
270
|
+
if (String(n) !== s) return null
|
|
271
|
+
|
|
272
|
+
return n
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function overBudget (opts) {
|
|
276
|
+
if (opts.ctx.deadlineReached) return true
|
|
277
|
+
opts.ctx.deadlineReached = process.hrtime.bigint() >= opts.deadlineNs
|
|
278
|
+
return opts.ctx.deadlineReached
|
|
279
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { getEnvironmentVariable } = require('../../../config-helper')
|
|
4
|
+
|
|
5
|
+
const largeObjectSkipThreshold = Number(
|
|
6
|
+
getEnvironmentVariable('_DD_DYNAMIC_INSTRUMENTATION_EXPERIMENTAL_LARGE_OBJECT_SKIP_THRESHOLD')
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
/**
|
|
11
|
+
* When collecting a snapshot, this constant controls what happens when objects with a large number of properties or
|
|
12
|
+
* collections (arrays, maps, sets, etc.) with a large number of elements are detected:
|
|
13
|
+
*
|
|
14
|
+
* - If a collection is detected with more than this number of elements, none of its elements will be included in the
|
|
15
|
+
* snapshot.
|
|
16
|
+
* - If an object is detected with more than this number of properties, it will be included in the snapshot, but
|
|
17
|
+
* snapshotting will be turned off for that probe in the future, until the probe is either updated or the Node.js
|
|
18
|
+
* process is restarted.
|
|
19
|
+
*/
|
|
20
|
+
LARGE_OBJECT_SKIP_THRESHOLD: Number.isNaN(largeObjectSkipThreshold) ? 500 : largeObjectSkipThreshold,
|
|
21
|
+
DEFAULT_MAX_COLLECTION_SIZE: 100,
|
|
22
|
+
DEFAULT_MAX_FIELD_COUNT: 20,
|
|
23
|
+
DEFAULT_MAX_LENGTH: 255,
|
|
24
|
+
DEFAULT_MAX_REFERENCE_DEPTH: 3,
|
|
25
|
+
}
|
|
@@ -1,52 +1,83 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const {
|
|
4
|
+
DEFAULT_MAX_REFERENCE_DEPTH,
|
|
5
|
+
DEFAULT_MAX_COLLECTION_SIZE,
|
|
6
|
+
DEFAULT_MAX_FIELD_COUNT,
|
|
7
|
+
DEFAULT_MAX_LENGTH
|
|
8
|
+
} = require('./constants')
|
|
9
|
+
const { collectObjectProperties } = require('./collector')
|
|
4
10
|
const { processRawState } = require('./processor')
|
|
5
|
-
const log = require('../log')
|
|
6
11
|
|
|
7
|
-
const
|
|
8
|
-
const DEFAULT_MAX_COLLECTION_SIZE = 100
|
|
9
|
-
const DEFAULT_MAX_FIELD_COUNT = 20
|
|
10
|
-
const DEFAULT_MAX_LENGTH = 255
|
|
12
|
+
const BIGINT_MAX = (1n << 256n) - 1n
|
|
11
13
|
|
|
12
14
|
module.exports = {
|
|
13
15
|
getLocalStateForCallFrame
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {Object} GetLocalStateForCallFrameOptions
|
|
20
|
+
* @property {number} [maxReferenceDepth] - The maximum depth of the object to traverse. Defaults to
|
|
21
|
+
* {@link DEFAULT_MAX_REFERENCE_DEPTH}.
|
|
22
|
+
* @property {number} [maxCollectionSize] - The maximum size of a collection to include in the snapshot. Defaults to
|
|
23
|
+
* {@link DEFAULT_MAX_COLLECTION_SIZE}.
|
|
24
|
+
* @property {number} [maxFieldCount] - The maximum number of properties on an object to include in the snapshot.
|
|
25
|
+
* Defaults to {@link DEFAULT_MAX_FIELD_COUNT}.
|
|
26
|
+
* @property {number} [maxLength] - The maximum length of a string to include in the snapshot. Defaults to
|
|
27
|
+
* {@link DEFAULT_MAX_LENGTH}.
|
|
28
|
+
* @property {bigint} [deadlineNs] - The deadline in nanoseconds compared to `process.hrtime.bigint()`. Defaults to
|
|
29
|
+
* {@link BIGINT_MAX}. If the deadline is reached, the snapshot will be truncated.
|
|
30
|
+
*/
|
|
19
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Get the local state for a call frame.
|
|
34
|
+
*
|
|
35
|
+
* @param {import('inspector').Debugger.CallFrame} callFrame - The call frame to get the local state for
|
|
36
|
+
* @param {GetLocalStateForCallFrameOptions} [opts] - The options for the snapshot
|
|
37
|
+
* @returns {Promise<Object>} The local state for the call frame
|
|
38
|
+
*/
|
|
20
39
|
async function getLocalStateForCallFrame (
|
|
21
40
|
callFrame,
|
|
22
41
|
{
|
|
23
42
|
maxReferenceDepth = DEFAULT_MAX_REFERENCE_DEPTH,
|
|
24
43
|
maxCollectionSize = DEFAULT_MAX_COLLECTION_SIZE,
|
|
25
44
|
maxFieldCount = DEFAULT_MAX_FIELD_COUNT,
|
|
26
|
-
maxLength = DEFAULT_MAX_LENGTH
|
|
45
|
+
maxLength = DEFAULT_MAX_LENGTH,
|
|
46
|
+
deadlineNs = BIGINT_MAX
|
|
27
47
|
} = {}
|
|
28
48
|
) {
|
|
49
|
+
/** @type {{ deadlineReached: boolean, captureErrors: Error[] }} */
|
|
50
|
+
const ctx = { deadlineReached: false, captureErrors: [] }
|
|
51
|
+
const opts = { maxReferenceDepth, maxCollectionSize, maxFieldCount, deadlineNs, ctx }
|
|
29
52
|
const rawState = []
|
|
30
53
|
let processedState = null
|
|
31
54
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
55
|
+
for (const scope of callFrame.scopeChain) {
|
|
56
|
+
if (scope.type === 'global') continue // The global scope is too noisy
|
|
57
|
+
const { objectId } = scope.object
|
|
58
|
+
if (objectId === undefined) continue // I haven't seen this happen, but according to the types it's possible
|
|
59
|
+
try {
|
|
60
|
+
// The objectId for a scope points to a pseudo-object whose properties are the actual variables in the scope.
|
|
61
|
+
// This is why we can just call `collectObjectProperties` directly and expect it to return the in-scope variables
|
|
62
|
+
// as an array.
|
|
63
|
+
// eslint-disable-next-line no-await-in-loop
|
|
64
|
+
rawState.push(...await collectObjectProperties(objectId, opts))
|
|
65
|
+
} catch (err) {
|
|
66
|
+
ctx.captureErrors.push(new Error(
|
|
67
|
+
`Error getting local state for closure scope (type: ${scope.type}). ` +
|
|
68
|
+
'Future snapshots for existing probes in this location will be skipped until the Node.js process is restarted',
|
|
69
|
+
{ cause: err } // TODO: The cause is not used by the backend
|
|
38
70
|
))
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// TODO: We might be able to get part of the scope chain.
|
|
42
|
-
// Consider if we could set errors just for the part of the scope chain that throws during collection.
|
|
43
|
-
log.error('[debugger:devtools_client] Error getting local state for call frame', err)
|
|
44
|
-
return returnError
|
|
71
|
+
}
|
|
72
|
+
if (ctx.deadlineReached === true) break // TODO: Bad UX; Variables in remaining scopes are silently dropped
|
|
45
73
|
}
|
|
46
74
|
|
|
47
75
|
// Delay calling `processRawState` so the caller gets a chance to resume the main thread before processing `rawState`
|
|
48
|
-
return
|
|
49
|
-
|
|
50
|
-
|
|
76
|
+
return {
|
|
77
|
+
processLocalState () {
|
|
78
|
+
processedState = processedState ?? processRawState(rawState, maxLength)
|
|
79
|
+
return processedState
|
|
80
|
+
},
|
|
81
|
+
captureErrors: ctx.captureErrors
|
|
51
82
|
}
|
|
52
83
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const { LARGE_OBJECT_SKIP_THRESHOLD } = require('./constants')
|
|
4
|
+
const { collectionSizeSym, largeCollectionSkipThresholdSym, fieldCountSym, timeBudgetSym } = require('./symbols')
|
|
4
5
|
const { normalizeName, REDACTED_IDENTIFIERS } = require('./redaction')
|
|
5
6
|
|
|
6
7
|
module.exports = {
|
|
@@ -38,7 +39,7 @@ function getPropertyValue (prop, maxLength) {
|
|
|
38
39
|
|
|
39
40
|
function getPropertyValueRaw (prop, maxLength) {
|
|
40
41
|
// Special case for getters and setters which does not have a value property
|
|
41
|
-
if (
|
|
42
|
+
if (prop.get) {
|
|
42
43
|
const hasGet = prop.get.type !== 'undefined'
|
|
43
44
|
const hasSet = prop.set.type !== 'undefined'
|
|
44
45
|
if (hasGet) {
|
|
@@ -74,11 +75,13 @@ function getPropertyValueRaw (prop, maxLength) {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
function getObjectValue (obj, maxLength) {
|
|
78
|
+
const timeBudgetReached = obj[timeBudgetSym] === true
|
|
79
|
+
|
|
77
80
|
switch (obj.subtype) {
|
|
78
81
|
case undefined:
|
|
79
|
-
return toObject(obj.className, obj.properties, maxLength)
|
|
82
|
+
return toObject(obj.className, obj.properties, maxLength, timeBudgetReached)
|
|
80
83
|
case 'array':
|
|
81
|
-
return toArray(obj.className, obj.properties, maxLength)
|
|
84
|
+
return toArray(obj.className, obj.properties, maxLength, timeBudgetReached)
|
|
82
85
|
case 'null':
|
|
83
86
|
return { type: 'null', isNull: true }
|
|
84
87
|
// case 'node': // TODO: What does this subtype represent?
|
|
@@ -89,28 +92,28 @@ function getObjectValue (obj, maxLength) {
|
|
|
89
92
|
// in the `description` field. Unfortunately that's all we get from the Chrome DevTools Protocol.
|
|
90
93
|
return { type: obj.className, value: `${new Date(obj.description).toISOString().slice(0, -5)}Z` }
|
|
91
94
|
case 'map':
|
|
92
|
-
return toMap(obj.className, obj.properties, maxLength)
|
|
95
|
+
return toMap(obj.className, obj.properties, maxLength, timeBudgetReached)
|
|
93
96
|
case 'set':
|
|
94
|
-
return toSet(obj.className, obj.properties, maxLength)
|
|
97
|
+
return toSet(obj.className, obj.properties, maxLength, timeBudgetReached)
|
|
95
98
|
case 'error':
|
|
96
99
|
// TODO: Convert stack trace to array to avoid string truncation or disable truncation in this case?
|
|
97
|
-
return toObject(obj.className, obj.properties, maxLength)
|
|
100
|
+
return toObject(obj.className, obj.properties, maxLength, timeBudgetReached)
|
|
98
101
|
case 'proxy':
|
|
99
102
|
// Use `description` instead of `className` as the `type` to get type of target object (`Proxy(Error)` vs `proxy`)
|
|
100
|
-
return toObject(obj.description, obj.properties, maxLength)
|
|
103
|
+
return toObject(obj.description, obj.properties, maxLength, timeBudgetReached)
|
|
101
104
|
case 'promise':
|
|
102
|
-
return toObject(obj.className, obj.properties, maxLength)
|
|
105
|
+
return toObject(obj.className, obj.properties, maxLength, timeBudgetReached)
|
|
103
106
|
case 'typedarray':
|
|
104
|
-
return toArray(obj.className, obj.properties, maxLength)
|
|
107
|
+
return toArray(obj.className, obj.properties, maxLength, timeBudgetReached)
|
|
105
108
|
case 'generator':
|
|
106
109
|
// Use `subtype` instead of `className` to make it obvious it's a generator
|
|
107
|
-
return toObject(obj.subtype, obj.properties, maxLength)
|
|
110
|
+
return toObject(obj.subtype, obj.properties, maxLength, timeBudgetReached)
|
|
108
111
|
case 'arraybuffer':
|
|
109
|
-
return toArrayBuffer(obj.className, obj.properties, maxLength)
|
|
112
|
+
return toArrayBuffer(obj.className, obj.properties, maxLength, timeBudgetReached)
|
|
110
113
|
case 'weakmap':
|
|
111
|
-
return toMap(obj.className, obj.properties, maxLength)
|
|
114
|
+
return toMap(obj.className, obj.properties, maxLength, timeBudgetReached)
|
|
112
115
|
case 'weakset':
|
|
113
|
-
return toSet(obj.className, obj.properties, maxLength)
|
|
116
|
+
return toSet(obj.className, obj.properties, maxLength, timeBudgetReached)
|
|
114
117
|
// case 'iterator': // TODO: I've not been able to trigger this subtype
|
|
115
118
|
// case 'dataview': // TODO: Looks like the internal ArrayBuffer is only accessible via the `buffer` getter
|
|
116
119
|
// case 'webassemblymemory': // TODO: Looks like the internal ArrayBuffer is only accessible via the `buffer` getter
|
|
@@ -127,8 +130,9 @@ function toFunctionOrClass (value, maxLength) {
|
|
|
127
130
|
|
|
128
131
|
if (classMatch === null) {
|
|
129
132
|
// This is a function
|
|
133
|
+
const timeBudgetReached = value[timeBudgetSym] === true
|
|
130
134
|
// TODO: Would it make sense to detect if it's an arrow function or not?
|
|
131
|
-
return toObject(value.className, value.properties, maxLength)
|
|
135
|
+
return toObject(value.className, value.properties, maxLength, timeBudgetReached)
|
|
132
136
|
}
|
|
133
137
|
// This is a class
|
|
134
138
|
const className = classMatch[1].trim()
|
|
@@ -150,7 +154,8 @@ function toString (str, maxLength) {
|
|
|
150
154
|
}
|
|
151
155
|
}
|
|
152
156
|
|
|
153
|
-
function toObject (type, props, maxLength) {
|
|
157
|
+
function toObject (type, props, maxLength, timeBudgetReached) {
|
|
158
|
+
if (timeBudgetReached === true) return notCapturedTimeBudget(type)
|
|
154
159
|
if (props === undefined) return notCapturedDepth(type)
|
|
155
160
|
|
|
156
161
|
const result = {
|
|
@@ -158,7 +163,7 @@ function toObject (type, props, maxLength) {
|
|
|
158
163
|
fields: processProperties(props, maxLength)
|
|
159
164
|
}
|
|
160
165
|
|
|
161
|
-
if (
|
|
166
|
+
if (props[fieldCountSym] !== undefined) {
|
|
162
167
|
result.notCapturedReason = 'fieldCount'
|
|
163
168
|
result.size = props[fieldCountSym]
|
|
164
169
|
}
|
|
@@ -166,7 +171,8 @@ function toObject (type, props, maxLength) {
|
|
|
166
171
|
return result
|
|
167
172
|
}
|
|
168
173
|
|
|
169
|
-
function toArray (type, elements, maxLength) {
|
|
174
|
+
function toArray (type, elements, maxLength, timeBudgetReached) {
|
|
175
|
+
if (timeBudgetReached === true) return notCapturedTimeBudget(type)
|
|
170
176
|
if (elements === undefined) return notCapturedDepth(type)
|
|
171
177
|
|
|
172
178
|
const result = {
|
|
@@ -177,12 +183,15 @@ function toArray (type, elements, maxLength) {
|
|
|
177
183
|
}
|
|
178
184
|
|
|
179
185
|
setNotCaptureReasonOnCollection(result, elements)
|
|
186
|
+
setNotCaptureReasonOnTooLargeCollection(result, elements)
|
|
180
187
|
|
|
181
188
|
return result
|
|
182
189
|
}
|
|
183
190
|
|
|
184
|
-
function toMap (type, pairs, maxLength) {
|
|
191
|
+
function toMap (type, pairs, maxLength, timeBudgetReached) {
|
|
192
|
+
if (timeBudgetReached === true) return notCapturedTimeBudget(type)
|
|
185
193
|
if (pairs === undefined) return notCapturedDepth(type)
|
|
194
|
+
if (pairs.length > 0 && pairs.every(({ value }) => value[timeBudgetSym] === true)) return notCapturedTimeBudget(type)
|
|
186
195
|
|
|
187
196
|
const result = {
|
|
188
197
|
type,
|
|
@@ -195,6 +204,14 @@ function toMap (type, pairs, maxLength) {
|
|
|
195
204
|
// This can be skipped and we can go directly to its children, of which
|
|
196
205
|
// there will always be exactly two, the first containing the key, and the
|
|
197
206
|
// second containing the value of this entry of the Map.
|
|
207
|
+
|
|
208
|
+
if (value[timeBudgetSym] === true) {
|
|
209
|
+
return [{ notCapturedReason: 'timeout' }, { notCapturedReason: 'timeout' }]
|
|
210
|
+
}
|
|
211
|
+
if (value.properties === undefined) {
|
|
212
|
+
return [{ notCapturedReason: 'unknown' }, { notCapturedReason: 'unknown' }]
|
|
213
|
+
}
|
|
214
|
+
|
|
198
215
|
const shouldRedact = shouldRedactMapValue(value.properties[0])
|
|
199
216
|
const key = getPropertyValue(value.properties[0], maxLength)
|
|
200
217
|
const val = shouldRedact
|
|
@@ -205,12 +222,17 @@ function toMap (type, pairs, maxLength) {
|
|
|
205
222
|
}
|
|
206
223
|
|
|
207
224
|
setNotCaptureReasonOnCollection(result, pairs)
|
|
225
|
+
setNotCaptureReasonOnTooLargeCollection(result, pairs)
|
|
208
226
|
|
|
209
227
|
return result
|
|
210
228
|
}
|
|
211
229
|
|
|
212
|
-
function toSet (type, values, maxLength) {
|
|
230
|
+
function toSet (type, values, maxLength, timeBudgetReached) {
|
|
231
|
+
if (timeBudgetReached === true) return notCapturedTimeBudget(type)
|
|
213
232
|
if (values === undefined) return notCapturedDepth(type)
|
|
233
|
+
if (values.length > 0 && values.every(({ value }) => value[timeBudgetSym] === true)) {
|
|
234
|
+
return notCapturedTimeBudget(type)
|
|
235
|
+
}
|
|
214
236
|
|
|
215
237
|
const result = {
|
|
216
238
|
type,
|
|
@@ -223,16 +245,22 @@ function toSet (type, values, maxLength) {
|
|
|
223
245
|
// `internal#entry`. This can be skipped and we can go directly to its
|
|
224
246
|
// children, of which there will always be exactly one, which contain the
|
|
225
247
|
// actual value in this entry of the Set.
|
|
248
|
+
|
|
249
|
+
if (value[timeBudgetSym] === true) return { notCapturedReason: 'timeout' }
|
|
250
|
+
if (value.properties === undefined) return { notCapturedReason: 'unknown' }
|
|
251
|
+
|
|
226
252
|
return getPropertyValue(value.properties[0], maxLength)
|
|
227
253
|
})
|
|
228
254
|
}
|
|
229
255
|
|
|
230
256
|
setNotCaptureReasonOnCollection(result, values)
|
|
257
|
+
setNotCaptureReasonOnTooLargeCollection(result, values)
|
|
231
258
|
|
|
232
259
|
return result
|
|
233
260
|
}
|
|
234
261
|
|
|
235
|
-
function toArrayBuffer (type, bytes, maxLength) {
|
|
262
|
+
function toArrayBuffer (type, bytes, maxLength, timeBudgetReached) {
|
|
263
|
+
if (timeBudgetReached === true) return notCapturedTimeBudget(type)
|
|
236
264
|
if (bytes === undefined) return notCapturedDepth(type)
|
|
237
265
|
|
|
238
266
|
const size = bytes.length
|
|
@@ -271,16 +299,25 @@ function shouldRedactMapValue (key) {
|
|
|
271
299
|
}
|
|
272
300
|
|
|
273
301
|
function getNormalizedNameFromProp (prop) {
|
|
274
|
-
return normalizeName(prop.name,
|
|
302
|
+
return normalizeName(prop.name, prop.symbol !== undefined)
|
|
275
303
|
}
|
|
276
304
|
|
|
277
305
|
function setNotCaptureReasonOnCollection (result, collection) {
|
|
278
|
-
if (
|
|
306
|
+
if (collection[collectionSizeSym] !== undefined) {
|
|
279
307
|
result.notCapturedReason = 'collectionSize'
|
|
280
308
|
result.size = collection[collectionSizeSym]
|
|
281
309
|
}
|
|
282
310
|
}
|
|
283
311
|
|
|
312
|
+
function setNotCaptureReasonOnTooLargeCollection (result, collection) {
|
|
313
|
+
if (collection[largeCollectionSkipThresholdSym] !== undefined) {
|
|
314
|
+
result.notCapturedReason = `Large collection with too many elements (skip threshold: ${
|
|
315
|
+
LARGE_OBJECT_SKIP_THRESHOLD
|
|
316
|
+
})`
|
|
317
|
+
result.size = collection[largeCollectionSkipThresholdSym]
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
284
321
|
function notCapturedDepth (type) {
|
|
285
322
|
return { type, notCapturedReason: 'depth' }
|
|
286
323
|
}
|
|
@@ -288,3 +325,7 @@ function notCapturedDepth (type) {
|
|
|
288
325
|
function notCapturedRedacted (type) {
|
|
289
326
|
return { type, notCapturedReason: 'redactedIdent' }
|
|
290
327
|
}
|
|
328
|
+
|
|
329
|
+
function notCapturedTimeBudget (type) {
|
|
330
|
+
return { type, notCapturedReason: 'timeout' }
|
|
331
|
+
}
|
|
@@ -2,5 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
4
|
collectionSizeSym: Symbol('datadog.collectionSize'),
|
|
5
|
-
|
|
5
|
+
largeCollectionSkipThresholdSym: Symbol('datadog.largeCollectionSkipThresholdSym'),
|
|
6
|
+
fieldCountSym: Symbol('datadog.fieldCount'),
|
|
7
|
+
timeBudgetSym: Symbol('datadog.timeout')
|
|
6
8
|
}
|