dd-trace 4.47.1 → 4.49.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 -1
- package/ext/types.d.ts +1 -0
- package/ext/types.js +1 -0
- package/index.d.ts +361 -0
- package/package.json +18 -13
- package/packages/datadog-code-origin/index.js +38 -0
- package/packages/datadog-core/index.js +2 -2
- 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/avsc.js +37 -0
- package/packages/datadog-instrumentations/src/azure-functions.js +48 -0
- package/packages/datadog-instrumentations/src/child_process.js +144 -27
- package/packages/datadog-instrumentations/src/express.js +37 -4
- package/packages/datadog-instrumentations/src/fastify.js +12 -1
- package/packages/datadog-instrumentations/src/fs.js +27 -7
- package/packages/datadog-instrumentations/src/helpers/hooks.js +6 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +9 -0
- package/packages/datadog-instrumentations/src/jest.js +2 -1
- package/packages/datadog-instrumentations/src/kafkajs.js +123 -63
- package/packages/datadog-instrumentations/src/mocha/common.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/utils.js +2 -2
- package/packages/datadog-instrumentations/src/multer.js +37 -0
- package/packages/datadog-instrumentations/src/mysql2.js +220 -1
- package/packages/datadog-instrumentations/src/openai.js +2 -2
- package/packages/datadog-instrumentations/src/protobufjs.js +127 -0
- 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-instrumentations/src/winston.js +22 -0
- package/packages/datadog-plugin-amqplib/src/consumer.js +4 -4
- package/packages/datadog-plugin-avsc/src/index.js +9 -0
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +169 -0
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/s3.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -0
- package/packages/datadog-plugin-azure-functions/src/index.js +77 -0
- package/packages/datadog-plugin-fastify/src/code_origin.js +31 -0
- package/packages/datadog-plugin-fastify/src/index.js +10 -12
- package/packages/datadog-plugin-fastify/src/tracing.js +19 -0
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +8 -1
- 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 +3 -0
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +6 -3
- package/packages/datadog-plugin-kafkajs/src/consumer.js +8 -4
- package/packages/datadog-plugin-kafkajs/src/producer.js +10 -4
- package/packages/datadog-plugin-mocha/src/index.js +4 -1
- package/packages/datadog-plugin-openai/src/index.js +9 -1015
- package/packages/datadog-plugin-openai/src/tracing.js +1023 -0
- package/packages/datadog-plugin-protobufjs/src/index.js +14 -0
- package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +180 -0
- package/packages/dd-trace/src/appsec/addresses.js +8 -1
- package/packages/dd-trace/src/appsec/channels.js +7 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +13 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +8 -1
- package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
- package/packages/dd-trace/src/appsec/iast/index.js +3 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +1 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +55 -7
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +15 -0
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -2
- package/packages/dd-trace/src/appsec/index.js +61 -43
- package/packages/dd-trace/src/appsec/rasp/command_injection.js +49 -0
- package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +99 -0
- package/packages/dd-trace/src/appsec/rasp/index.js +27 -10
- package/packages/dd-trace/src/appsec/rasp/lfi.js +112 -0
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +24 -4
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +4 -3
- package/packages/dd-trace/src/appsec/rasp/utils.js +4 -2
- package/packages/dd-trace/src/appsec/recommended.json +3 -7
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +6 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +10 -0
- package/packages/dd-trace/src/appsec/reporter.js +17 -9
- package/packages/dd-trace/src/appsec/sdk/track_event.js +10 -3
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +1 -1
- 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/early-flake-detection/get-known-tests.js +2 -14
- 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/ci-visibility/log-submission/log-submission-plugin.js +53 -0
- package/packages/dd-trace/src/config.js +86 -6
- package/packages/dd-trace/src/constants.js +3 -1
- package/packages/dd-trace/src/datastreams/pathway.js +1 -0
- package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +25 -17
- package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +52 -5
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +4 -4
- package/packages/dd-trace/src/debugger/devtools_client/send.js +29 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +187 -0
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +40 -0
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +252 -0
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/symbols.js +6 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +19 -4
- package/packages/dd-trace/src/debugger/index.js +10 -3
- package/packages/dd-trace/src/exporters/common/request.js +8 -34
- package/packages/dd-trace/src/exporters/common/url-to-http-options-polyfill.js +31 -0
- 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 +49 -0
- package/packages/dd-trace/src/noop/proxy.js +3 -0
- package/packages/dd-trace/src/noop/span.js +3 -0
- package/packages/dd-trace/src/opentelemetry/span.js +1 -1
- package/packages/dd-trace/src/opentelemetry/tracer.js +1 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +73 -12
- package/packages/dd-trace/src/opentracing/span.js +12 -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/payload-tagging/index.js +1 -1
- package/packages/dd-trace/src/payload-tagging/jsonpath-plus.js +2094 -0
- package/packages/dd-trace/src/plugin_manager.js +4 -2
- package/packages/dd-trace/src/plugins/ci_plugin.js +2 -0
- package/packages/dd-trace/src/plugins/index.js +3 -0
- package/packages/dd-trace/src/plugins/log_plugin.js +1 -1
- package/packages/dd-trace/src/plugins/outbound.js +9 -0
- package/packages/dd-trace/src/plugins/schema.js +35 -0
- package/packages/dd-trace/src/plugins/util/ci.js +23 -1
- package/packages/dd-trace/src/plugins/util/serverless.js +7 -0
- package/packages/dd-trace/src/plugins/util/stacktrace.js +94 -0
- package/packages/dd-trace/src/plugins/util/tags.js +7 -0
- package/packages/dd-trace/src/plugins/util/test.js +20 -22
- package/packages/dd-trace/src/plugins/util/web.js +6 -4
- 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/profiler.js +24 -14
- package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +95 -66
- package/packages/dd-trace/src/proxy.js +20 -1
- package/packages/dd-trace/src/service-naming/schemas/v0/index.js +2 -1
- package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +12 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/index.js +2 -1
- package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +12 -0
- package/packages/dd-trace/src/span_processor.js +5 -0
- package/packages/dd-trace/src/telemetry/index.js +11 -1
- package/packages/datadog-core/src/storage/async_resource.js +0 -108
- package/packages/datadog-core/src/storage/index.js +0 -5
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { collectionSizeSym, fieldCountSym } = require('./symbols')
|
|
4
|
+
const session = require('../session')
|
|
5
|
+
|
|
6
|
+
const LEAF_SUBTYPES = new Set(['date', 'regexp'])
|
|
7
|
+
const ITERABLE_SUBTYPES = new Set(['map', 'set', 'weakmap', 'weakset'])
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
getRuntimeObject: getObject
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// TODO: Can we speed up thread pause time by calling mutiple Runtime.getProperties in parallel when possible?
|
|
14
|
+
// The most simple solution would be to swich from an async/await approach to a callback based approach, in which case
|
|
15
|
+
// each lookup will just finish in its own time and traverse the child nodes when the event loop allows it.
|
|
16
|
+
// Alternatively, use `Promise.all` or something like that, but the code would probably be more complex.
|
|
17
|
+
|
|
18
|
+
async function getObject (objectId, opts, depth = 0, collection = false) {
|
|
19
|
+
const { result, privateProperties } = await session.post('Runtime.getProperties', {
|
|
20
|
+
objectId,
|
|
21
|
+
ownProperties: true // exclude inherited properties
|
|
22
|
+
})
|
|
23
|
+
|
|
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
|
+
}
|
|
41
|
+
|
|
42
|
+
return traverseGetPropertiesResult(result, opts, depth)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function traverseGetPropertiesResult (props, opts, depth) {
|
|
46
|
+
// TODO: Decide if we should filter out non-enumerable properties or not:
|
|
47
|
+
// props = props.filter((e) => e.enumerable)
|
|
48
|
+
|
|
49
|
+
if (depth >= opts.maxReferenceDepth) return props
|
|
50
|
+
|
|
51
|
+
for (const prop of props) {
|
|
52
|
+
if (prop.value === undefined) continue
|
|
53
|
+
const { value: { type, objectId, subtype } } = prop
|
|
54
|
+
if (type === 'object') {
|
|
55
|
+
if (objectId === undefined) continue // if `subtype` is "null"
|
|
56
|
+
if (LEAF_SUBTYPES.has(subtype)) continue // don't waste time with these subtypes
|
|
57
|
+
prop.value.properties = await getObjectProperties(subtype, objectId, opts, depth)
|
|
58
|
+
} else if (type === 'function') {
|
|
59
|
+
prop.value.properties = await getFunctionProperties(objectId, opts, depth + 1)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return props
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function getObjectProperties (subtype, objectId, opts, depth) {
|
|
67
|
+
if (ITERABLE_SUBTYPES.has(subtype)) {
|
|
68
|
+
return getIterable(objectId, opts, depth)
|
|
69
|
+
} else if (subtype === 'promise') {
|
|
70
|
+
return getInternalProperties(objectId, opts, depth)
|
|
71
|
+
} else if (subtype === 'proxy') {
|
|
72
|
+
return getProxy(objectId, opts, depth)
|
|
73
|
+
} else if (subtype === 'arraybuffer') {
|
|
74
|
+
return getArrayBuffer(objectId, opts, depth)
|
|
75
|
+
} else {
|
|
76
|
+
return getObject(objectId, opts, depth + 1, subtype === 'array' || subtype === 'typedarray')
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// TODO: The following extra information from `internalProperties` might be relevant to include for functions:
|
|
81
|
+
// - Bound function: `[[TargetFunction]]`, `[[BoundThis]]` and `[[BoundArgs]]`
|
|
82
|
+
// - Non-bound function: `[[FunctionLocation]]`, and `[[Scopes]]`
|
|
83
|
+
async function getFunctionProperties (objectId, opts, depth) {
|
|
84
|
+
let { result } = await session.post('Runtime.getProperties', {
|
|
85
|
+
objectId,
|
|
86
|
+
ownProperties: true // exclude inherited properties
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
// For legacy reasons (I assume) functions has a `prototype` property besides the internal `[[Prototype]]`
|
|
90
|
+
result = result.filter(({ name }) => name !== 'prototype')
|
|
91
|
+
|
|
92
|
+
return traverseGetPropertiesResult(result, opts, depth)
|
|
93
|
+
}
|
|
94
|
+
|
|
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.
|
|
98
|
+
const { internalProperties } = await session.post('Runtime.getProperties', {
|
|
99
|
+
objectId,
|
|
100
|
+
ownProperties: true // exclude inherited properties
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
let entry = internalProperties[1]
|
|
104
|
+
if (entry.name !== '[[Entries]]') {
|
|
105
|
+
// Currently `[[Entries]]` is the last of 2 elements, but in case this ever changes, fall back to searching
|
|
106
|
+
entry = internalProperties.findLast(({ name }) => name === '[[Entries]]')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Skip the `[[Entries]]` level and go directly to the content of the iterable
|
|
110
|
+
const { result } = await session.post('Runtime.getProperties', {
|
|
111
|
+
objectId: entry.value.objectId,
|
|
112
|
+
ownProperties: true // exclude inherited properties
|
|
113
|
+
})
|
|
114
|
+
|
|
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)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function getInternalProperties (objectId, opts, depth) {
|
|
126
|
+
const { internalProperties } = await session.post('Runtime.getProperties', {
|
|
127
|
+
objectId,
|
|
128
|
+
ownProperties: true // exclude inherited properties
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
// We want all internal properties except the prototype
|
|
132
|
+
const props = internalProperties.filter(({ name }) => name !== '[[Prototype]]')
|
|
133
|
+
|
|
134
|
+
return traverseGetPropertiesResult(props, opts, depth)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function getProxy (objectId, opts, depth) {
|
|
138
|
+
const { internalProperties } = await session.post('Runtime.getProperties', {
|
|
139
|
+
objectId,
|
|
140
|
+
ownProperties: true // exclude inherited properties
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// TODO: If we do not skip the proxy wrapper, we can add a `revoked` boolean
|
|
144
|
+
let entry = internalProperties[1]
|
|
145
|
+
if (entry.name !== '[[Target]]') {
|
|
146
|
+
// Currently `[[Target]]` is the last of 2 elements, but in case this ever changes, fall back to searching
|
|
147
|
+
entry = internalProperties.findLast(({ name }) => name === '[[Target]]')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Skip the `[[Target]]` level and go directly to the target of the Proxy
|
|
151
|
+
const { result } = await session.post('Runtime.getProperties', {
|
|
152
|
+
objectId: entry.value.objectId,
|
|
153
|
+
ownProperties: true // exclude inherited properties
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
return traverseGetPropertiesResult(result, opts, depth)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Support for ArrayBuffer is a bit trickly because the internal structure stored in `internalProperties` is not
|
|
160
|
+
// documented and is not straight forward. E.g. ArrayBuffer(3) will internally contain both Int8Array(3) and
|
|
161
|
+
// UInt8Array(3), whereas ArrayBuffer(8) internally contains both Int8Array(8), Uint8Array(8), Int16Array(4), and
|
|
162
|
+
// Int32Array(2) - all representing the same data in different ways.
|
|
163
|
+
async function getArrayBuffer (objectId, opts, depth) {
|
|
164
|
+
const { internalProperties } = await session.post('Runtime.getProperties', {
|
|
165
|
+
objectId,
|
|
166
|
+
ownProperties: true // exclude inherited properties
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
// Use Uint8 to make it easy to convert to a string later.
|
|
170
|
+
const entry = internalProperties.find(({ name }) => name === '[[Uint8Array]]')
|
|
171
|
+
|
|
172
|
+
// Skip the `[[Uint8Array]]` level and go directly to the content of the ArrayBuffer
|
|
173
|
+
const { result } = await session.post('Runtime.getProperties', {
|
|
174
|
+
objectId: entry.value.objectId,
|
|
175
|
+
ownProperties: true // exclude inherited properties
|
|
176
|
+
})
|
|
177
|
+
|
|
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
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { getRuntimeObject } = require('./collector')
|
|
4
|
+
const { processRawState } = require('./processor')
|
|
5
|
+
|
|
6
|
+
const DEFAULT_MAX_REFERENCE_DEPTH = 3
|
|
7
|
+
const DEFAULT_MAX_COLLECTION_SIZE = 100
|
|
8
|
+
const DEFAULT_MAX_FIELD_COUNT = 20
|
|
9
|
+
const DEFAULT_MAX_LENGTH = 255
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
getLocalStateForCallFrame
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function getLocalStateForCallFrame (
|
|
16
|
+
callFrame,
|
|
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
|
+
} = {}
|
|
23
|
+
) {
|
|
24
|
+
const rawState = []
|
|
25
|
+
let processedState = null
|
|
26
|
+
|
|
27
|
+
for (const scope of callFrame.scopeChain) {
|
|
28
|
+
if (scope.type === 'global') continue // The global scope is too noisy
|
|
29
|
+
rawState.push(...await getRuntimeObject(
|
|
30
|
+
scope.object.objectId,
|
|
31
|
+
{ maxReferenceDepth, maxCollectionSize, maxFieldCount }
|
|
32
|
+
))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Deplay calling `processRawState` so the caller gets a chance to resume the main thread before processing `rawState`
|
|
36
|
+
return () => {
|
|
37
|
+
processedState = processedState ?? processRawState(rawState, maxLength)
|
|
38
|
+
return processedState
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { collectionSizeSym, fieldCountSym } = require('./symbols')
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
processRawState: processProperties
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Matches classes in source code, no matter how it's written:
|
|
10
|
+
// - Named: class MyClass {}
|
|
11
|
+
// - Anonymous: class {}
|
|
12
|
+
// - Named, with odd whitespace: class\n\t MyClass\n{}
|
|
13
|
+
// - Anonymous, with odd whitespace: class\n{}
|
|
14
|
+
const CLASS_REGEX = /^class\s([^{]*)/
|
|
15
|
+
|
|
16
|
+
function processProperties (props, maxLength) {
|
|
17
|
+
const result = {}
|
|
18
|
+
|
|
19
|
+
for (const prop of props) {
|
|
20
|
+
// TODO: Hack to avoid periods in keys, as EVP doesn't support that. A better solution can be implemented later
|
|
21
|
+
result[prop.name.replaceAll('.', '_')] = getPropertyValue(prop, maxLength)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return result
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getPropertyValue (prop, maxLength) {
|
|
28
|
+
// Special case for getters and setters which does not have a value property
|
|
29
|
+
if ('get' in prop) {
|
|
30
|
+
const hasGet = prop.get.type !== 'undefined'
|
|
31
|
+
const hasSet = prop.set.type !== 'undefined'
|
|
32
|
+
if (hasGet && hasSet) return { type: 'getter/setter' }
|
|
33
|
+
if (hasGet) return { type: 'getter' }
|
|
34
|
+
if (hasSet) return { type: 'setter' }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
switch (prop.value?.type) {
|
|
38
|
+
case 'object':
|
|
39
|
+
return getObjectValue(prop.value, maxLength)
|
|
40
|
+
case 'function':
|
|
41
|
+
return toFunctionOrClass(prop.value, maxLength)
|
|
42
|
+
case undefined: // TODO: Add test for when a prop has no value. I think it's if it's defined after the breakpoint?
|
|
43
|
+
case 'undefined':
|
|
44
|
+
return { type: 'undefined' }
|
|
45
|
+
case 'string':
|
|
46
|
+
return toString(prop.value.value, maxLength)
|
|
47
|
+
case 'number':
|
|
48
|
+
return { type: 'number', value: prop.value.description } // use `descripton` to get it as string
|
|
49
|
+
case 'boolean':
|
|
50
|
+
return { type: 'boolean', value: prop.value.value === true ? 'true' : 'false' }
|
|
51
|
+
case 'symbol':
|
|
52
|
+
return { type: 'symbol', value: prop.value.description }
|
|
53
|
+
case 'bigint':
|
|
54
|
+
return { type: 'bigint', value: prop.value.description.slice(0, -1) } // remove trailing `n`
|
|
55
|
+
default:
|
|
56
|
+
// As of this writing, the Chrome DevTools Protocol doesn't allow any other types than the ones listed above, but
|
|
57
|
+
// in the future new ones might be added.
|
|
58
|
+
return { type: prop.value.type, notCapturedReason: 'Unsupported property type' }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getObjectValue (obj, maxLength) {
|
|
63
|
+
switch (obj.subtype) {
|
|
64
|
+
case undefined:
|
|
65
|
+
return toObject(obj.className, obj.properties, maxLength)
|
|
66
|
+
case 'array':
|
|
67
|
+
return toArray(obj.className, obj.properties, maxLength)
|
|
68
|
+
case 'null':
|
|
69
|
+
return { type: 'null', isNull: true }
|
|
70
|
+
// case 'node': // TODO: What does this subtype represent?
|
|
71
|
+
case 'regexp':
|
|
72
|
+
return { type: obj.className, value: obj.description }
|
|
73
|
+
case 'date':
|
|
74
|
+
// TODO: This looses millisecond resolution, as that's not retained in the `.toString()` representation contained
|
|
75
|
+
// in the `description` field. Unfortunately that's all we get from the Chrome DevTools Protocol.
|
|
76
|
+
return { type: obj.className, value: `${new Date(obj.description).toISOString().slice(0, -5)}Z` }
|
|
77
|
+
case 'map':
|
|
78
|
+
return toMap(obj.className, obj.properties, maxLength)
|
|
79
|
+
case 'set':
|
|
80
|
+
return toSet(obj.className, obj.properties, maxLength)
|
|
81
|
+
case 'weakmap':
|
|
82
|
+
return toMap(obj.className, obj.properties, maxLength)
|
|
83
|
+
case 'weakset':
|
|
84
|
+
return toSet(obj.className, obj.properties, maxLength)
|
|
85
|
+
// case 'iterator': // TODO: I've not been able to trigger this subtype
|
|
86
|
+
case 'generator':
|
|
87
|
+
// Use `subtype` instead of `className` to make it obvious it's a generator
|
|
88
|
+
return toObject(obj.subtype, obj.properties, maxLength)
|
|
89
|
+
case 'error':
|
|
90
|
+
// TODO: Convert stack trace to array to avoid string trunctation or disable truncation in this case?
|
|
91
|
+
return toObject(obj.className, obj.properties, maxLength)
|
|
92
|
+
case 'proxy':
|
|
93
|
+
// Use `desciption` instead of `className` as the `type` to get type of target object (`Proxy(Error)` vs `proxy`)
|
|
94
|
+
return toObject(obj.description, obj.properties, maxLength)
|
|
95
|
+
case 'promise':
|
|
96
|
+
return toObject(obj.className, obj.properties, maxLength)
|
|
97
|
+
case 'typedarray':
|
|
98
|
+
return toArray(obj.className, obj.properties, maxLength)
|
|
99
|
+
case 'arraybuffer':
|
|
100
|
+
return toArrayBuffer(obj.className, obj.properties, maxLength)
|
|
101
|
+
// case 'dataview': // TODO: Looks like the internal ArrayBuffer is only accessible via the `buffer` getter
|
|
102
|
+
// case 'webassemblymemory': // TODO: Looks like the internal ArrayBuffer is only accessible via the `buffer` getter
|
|
103
|
+
// case 'wasmvalue': // TODO: I've not been able to trigger this subtype
|
|
104
|
+
default:
|
|
105
|
+
// As of this writing, the Chrome DevTools Protocol doesn't allow any other subtypes than the ones listed above,
|
|
106
|
+
// but in the future new ones might be added.
|
|
107
|
+
return { type: obj.subtype, notCapturedReason: 'Unsupported object type' }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function toFunctionOrClass (value, maxLength) {
|
|
112
|
+
const classMatch = value.description.match(CLASS_REGEX)
|
|
113
|
+
|
|
114
|
+
if (classMatch === null) {
|
|
115
|
+
// This is a function
|
|
116
|
+
// TODO: Would it make sense to detect if it's an arrow function or not?
|
|
117
|
+
return toObject(value.className, value.properties, maxLength)
|
|
118
|
+
} else {
|
|
119
|
+
// This is a class
|
|
120
|
+
const className = classMatch[1].trim()
|
|
121
|
+
return { type: className ? `class ${className}` : 'class' }
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function toString (str, maxLength) {
|
|
126
|
+
const size = str.length
|
|
127
|
+
|
|
128
|
+
if (size <= maxLength) {
|
|
129
|
+
return { type: 'string', value: str }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
type: 'string',
|
|
134
|
+
value: str.substr(0, maxLength),
|
|
135
|
+
truncated: true,
|
|
136
|
+
size
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function toObject (type, props, maxLength) {
|
|
141
|
+
if (props === undefined) return notCapturedDepth(type)
|
|
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
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function toArray (type, elements, maxLength) {
|
|
157
|
+
if (elements === undefined) return notCapturedDepth(type)
|
|
158
|
+
|
|
159
|
+
// Perf: Create array of expected size in advance (expect that it contains only one non-enumrable element)
|
|
160
|
+
const result = { type, elements: new Array(elements.length) }
|
|
161
|
+
|
|
162
|
+
setNotCaptureReasonOnCollection(result, elements)
|
|
163
|
+
|
|
164
|
+
let i = 0
|
|
165
|
+
for (const elm of elements) {
|
|
166
|
+
result.elements[i++] = getPropertyValue(elm, maxLength)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return result
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function toMap (type, pairs, maxLength) {
|
|
173
|
+
if (pairs === undefined) return notCapturedDepth(type)
|
|
174
|
+
|
|
175
|
+
// Perf: Create array of expected size in advance
|
|
176
|
+
const result = { type, entries: new Array(pairs.length) }
|
|
177
|
+
|
|
178
|
+
setNotCaptureReasonOnCollection(result, pairs)
|
|
179
|
+
|
|
180
|
+
let i = 0
|
|
181
|
+
for (const pair of pairs) {
|
|
182
|
+
// The following code is based on assumptions made when researching the output of the Chrome DevTools Protocol.
|
|
183
|
+
// There doesn't seem to be any documentation to back it up:
|
|
184
|
+
//
|
|
185
|
+
// `pair.value` is a special wrapper-object with subtype `internal#entry`. This can be skipped and we can go
|
|
186
|
+
// directly to its children, of which there will always be exactly two, the first containing the key, and the
|
|
187
|
+
// second containing the value of this entry of the Map.
|
|
188
|
+
const key = getPropertyValue(pair.value.properties[0], maxLength)
|
|
189
|
+
const val = getPropertyValue(pair.value.properties[1], maxLength)
|
|
190
|
+
result.entries[i++] = [key, val]
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return result
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function toSet (type, values, maxLength) {
|
|
197
|
+
if (values === undefined) return notCapturedDepth(type)
|
|
198
|
+
|
|
199
|
+
// Perf: Create array of expected size in advance (expect that it contains only one non-enumrable element)
|
|
200
|
+
const result = { type, elements: new Array(values.length) }
|
|
201
|
+
|
|
202
|
+
setNotCaptureReasonOnCollection(result, values)
|
|
203
|
+
|
|
204
|
+
let i = 0
|
|
205
|
+
for (const value of values) {
|
|
206
|
+
// The following code is based on assumptions made when researching the output of the Chrome DevTools Protocol.
|
|
207
|
+
// There doesn't seem to be any documentation to back it up:
|
|
208
|
+
//
|
|
209
|
+
// `value.value` is a special wrapper-object with subtype `internal#entry`. This can be skipped and we can go
|
|
210
|
+
// directly to its children, of which there will always be exactly one, which contain the actual value in this entry
|
|
211
|
+
// of the Set.
|
|
212
|
+
result.elements[i++] = getPropertyValue(value.value.properties[0], maxLength)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return result
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function toArrayBuffer (type, bytes, maxLength) {
|
|
219
|
+
if (bytes === undefined) return notCapturedDepth(type)
|
|
220
|
+
|
|
221
|
+
const size = bytes.length
|
|
222
|
+
|
|
223
|
+
if (size > maxLength) {
|
|
224
|
+
return {
|
|
225
|
+
type,
|
|
226
|
+
value: arrayBufferToString(bytes, maxLength),
|
|
227
|
+
truncated: true,
|
|
228
|
+
size: bytes.length
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
return { type, value: arrayBufferToString(bytes, size) }
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function arrayBufferToString (bytes, size) {
|
|
236
|
+
const buf = Buffer.allocUnsafe(size)
|
|
237
|
+
for (let i = 0; i < size; i++) {
|
|
238
|
+
buf[i] = bytes[i].value.value
|
|
239
|
+
}
|
|
240
|
+
return buf.toString()
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function setNotCaptureReasonOnCollection (result, collection) {
|
|
244
|
+
if (collectionSizeSym in collection) {
|
|
245
|
+
result.notCapturedReason = 'collectionSize'
|
|
246
|
+
result.size = collection[collectionSizeSym]
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function notCapturedDepth (type) {
|
|
251
|
+
return { type, notCapturedReason: 'depth' }
|
|
252
|
+
}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
const session = require('./session')
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const scriptIds = []
|
|
6
|
+
const scriptUrls = new Map()
|
|
6
7
|
|
|
7
8
|
module.exports = {
|
|
8
9
|
probes: new Map(),
|
|
@@ -25,10 +26,23 @@ module.exports = {
|
|
|
25
26
|
* @param {string} path
|
|
26
27
|
* @returns {[string, string] | undefined}
|
|
27
28
|
*/
|
|
28
|
-
|
|
29
|
-
return
|
|
29
|
+
findScriptFromPartialPath (path) {
|
|
30
|
+
return scriptIds
|
|
30
31
|
.filter(([url]) => url.endsWith(path))
|
|
31
32
|
.sort(([a], [b]) => a.length - b.length)[0]
|
|
33
|
+
},
|
|
34
|
+
|
|
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
|
+
})
|
|
32
46
|
}
|
|
33
47
|
}
|
|
34
48
|
|
|
@@ -41,7 +55,8 @@ module.exports = {
|
|
|
41
55
|
// - `` - Not sure what this is, but should just be ignored
|
|
42
56
|
// TODO: Event fired for all files, every time debugger is enabled. So when we disable it, we need to reset the state
|
|
43
57
|
session.on('Debugger.scriptParsed', ({ params }) => {
|
|
58
|
+
scriptUrls.set(params.scriptId, params.url)
|
|
44
59
|
if (params.url.startsWith('file:')) {
|
|
45
|
-
|
|
60
|
+
scriptIds.push([params.url, params.scriptId])
|
|
46
61
|
}
|
|
47
62
|
})
|
|
@@ -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))
|
|
@@ -6,10 +6,9 @@
|
|
|
6
6
|
const { Readable } = require('stream')
|
|
7
7
|
const http = require('http')
|
|
8
8
|
const https = require('https')
|
|
9
|
-
// eslint-disable-next-line n/no-deprecated-api
|
|
10
|
-
const { parse: urlParse } = require('url')
|
|
11
9
|
const zlib = require('zlib')
|
|
12
10
|
|
|
11
|
+
const { urlToHttpOptions } = require('./url-to-http-options-polyfill')
|
|
13
12
|
const docker = require('./docker')
|
|
14
13
|
const { httpAgent, httpsAgent } = require('./agents')
|
|
15
14
|
const { storage } = require('../../../../datadog-core')
|
|
@@ -20,39 +19,14 @@ const containerId = docker.id()
|
|
|
20
19
|
|
|
21
20
|
let activeRequests = 0
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const agent = url.agent || http.globalAgent
|
|
26
|
-
const options = {
|
|
27
|
-
protocol: url.protocol || agent.protocol,
|
|
28
|
-
hostname: typeof url.hostname === 'string' && url.hostname.startsWith('[')
|
|
29
|
-
? url.hostname.slice(1, -1)
|
|
30
|
-
: url.hostname ||
|
|
31
|
-
url.host ||
|
|
32
|
-
'localhost',
|
|
33
|
-
hash: url.hash,
|
|
34
|
-
search: url.search,
|
|
35
|
-
pathname: url.pathname,
|
|
36
|
-
path: `${url.pathname || ''}${url.search || ''}`,
|
|
37
|
-
href: url.href
|
|
38
|
-
}
|
|
39
|
-
if (url.port !== '') {
|
|
40
|
-
options.port = Number(url.port)
|
|
41
|
-
}
|
|
42
|
-
if (url.username || url.password) {
|
|
43
|
-
options.auth = `${url.username}:${url.password}`
|
|
44
|
-
}
|
|
45
|
-
return options
|
|
46
|
-
}
|
|
22
|
+
function parseUrl (urlObjOrString) {
|
|
23
|
+
if (typeof urlObjOrString === 'object') return urlToHttpOptions(urlObjOrString)
|
|
47
24
|
|
|
48
|
-
|
|
49
|
-
const url = typeof urlToHttpOptions === 'function'
|
|
50
|
-
? urlToOptions(new URL(urlString))
|
|
51
|
-
: urlParse(urlString)
|
|
25
|
+
const url = urlToHttpOptions(new URL(urlObjOrString))
|
|
52
26
|
|
|
53
|
-
//
|
|
54
|
-
if (url.protocol === 'unix:' && url.
|
|
55
|
-
const udsPath =
|
|
27
|
+
// Special handling if we're using named pipes on Windows
|
|
28
|
+
if (url.protocol === 'unix:' && url.hostname === '.') {
|
|
29
|
+
const udsPath = urlObjOrString.slice(5)
|
|
56
30
|
url.path = udsPath
|
|
57
31
|
url.pathname = udsPath
|
|
58
32
|
}
|
|
@@ -66,7 +40,7 @@ function request (data, options, callback) {
|
|
|
66
40
|
}
|
|
67
41
|
|
|
68
42
|
if (options.url) {
|
|
69
|
-
const url =
|
|
43
|
+
const url = parseUrl(options.url)
|
|
70
44
|
if (url.protocol === 'unix:') {
|
|
71
45
|
options.socketPath = url.pathname
|
|
72
46
|
} else {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { urlToHttpOptions } = require('url')
|
|
4
|
+
|
|
5
|
+
// TODO: Remove `urlToHttpOptions` polyfill once we drop support for the older Cypress versions that uses a built-in
|
|
6
|
+
// version of Node.js doesn't include that function.
|
|
7
|
+
module.exports = {
|
|
8
|
+
urlToHttpOptions: urlToHttpOptions ?? function (url) {
|
|
9
|
+
const { hostname, pathname, port, username, password, search } = url
|
|
10
|
+
const options = {
|
|
11
|
+
__proto__: null,
|
|
12
|
+
...url, // In case the url object was extended by the user.
|
|
13
|
+
protocol: url.protocol,
|
|
14
|
+
hostname: typeof hostname === 'string' && hostname.startsWith('[')
|
|
15
|
+
? hostname.slice(1, -1)
|
|
16
|
+
: hostname,
|
|
17
|
+
hash: url.hash,
|
|
18
|
+
search,
|
|
19
|
+
pathname,
|
|
20
|
+
path: `${pathname || ''}${search || ''}`,
|
|
21
|
+
href: url.href
|
|
22
|
+
}
|
|
23
|
+
if (port !== '') {
|
|
24
|
+
options.port = Number(port)
|
|
25
|
+
}
|
|
26
|
+
if (username || password) {
|
|
27
|
+
options.auth = `${decodeURIComponent(username)}:${decodeURIComponent(password)}`
|
|
28
|
+
}
|
|
29
|
+
return options
|
|
30
|
+
}
|
|
31
|
+
}
|