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.
Files changed (156) hide show
  1. package/LICENSE-3rdparty.csv +1 -1
  2. package/ext/types.d.ts +1 -0
  3. package/ext/types.js +1 -0
  4. package/index.d.ts +361 -0
  5. package/package.json +18 -13
  6. package/packages/datadog-code-origin/index.js +38 -0
  7. package/packages/datadog-core/index.js +2 -2
  8. package/packages/datadog-core/src/utils/src/parse-tags.js +33 -0
  9. package/packages/datadog-esbuild/index.js +4 -2
  10. package/packages/datadog-instrumentations/src/amqplib.js +65 -5
  11. package/packages/datadog-instrumentations/src/avsc.js +37 -0
  12. package/packages/datadog-instrumentations/src/azure-functions.js +48 -0
  13. package/packages/datadog-instrumentations/src/child_process.js +144 -27
  14. package/packages/datadog-instrumentations/src/express.js +37 -4
  15. package/packages/datadog-instrumentations/src/fastify.js +12 -1
  16. package/packages/datadog-instrumentations/src/fs.js +27 -7
  17. package/packages/datadog-instrumentations/src/helpers/hooks.js +6 -0
  18. package/packages/datadog-instrumentations/src/helpers/register.js +9 -0
  19. package/packages/datadog-instrumentations/src/jest.js +2 -1
  20. package/packages/datadog-instrumentations/src/kafkajs.js +123 -63
  21. package/packages/datadog-instrumentations/src/mocha/common.js +1 -1
  22. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -2
  23. package/packages/datadog-instrumentations/src/multer.js +37 -0
  24. package/packages/datadog-instrumentations/src/mysql2.js +220 -1
  25. package/packages/datadog-instrumentations/src/openai.js +2 -2
  26. package/packages/datadog-instrumentations/src/protobufjs.js +127 -0
  27. package/packages/datadog-instrumentations/src/url.js +84 -0
  28. package/packages/datadog-instrumentations/src/utils/src/extract-package-and-module-path.js +7 -4
  29. package/packages/datadog-instrumentations/src/winston.js +22 -0
  30. package/packages/datadog-plugin-amqplib/src/consumer.js +4 -4
  31. package/packages/datadog-plugin-avsc/src/index.js +9 -0
  32. package/packages/datadog-plugin-avsc/src/schema_iterator.js +169 -0
  33. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -0
  34. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -0
  35. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +1 -0
  36. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -0
  37. package/packages/datadog-plugin-azure-functions/src/index.js +77 -0
  38. package/packages/datadog-plugin-fastify/src/code_origin.js +31 -0
  39. package/packages/datadog-plugin-fastify/src/index.js +10 -12
  40. package/packages/datadog-plugin-fastify/src/tracing.js +19 -0
  41. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +8 -1
  42. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +8 -0
  43. package/packages/datadog-plugin-grpc/src/client.js +3 -0
  44. package/packages/datadog-plugin-grpc/src/server.js +3 -0
  45. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +6 -3
  46. package/packages/datadog-plugin-kafkajs/src/consumer.js +8 -4
  47. package/packages/datadog-plugin-kafkajs/src/producer.js +10 -4
  48. package/packages/datadog-plugin-mocha/src/index.js +4 -1
  49. package/packages/datadog-plugin-openai/src/index.js +9 -1015
  50. package/packages/datadog-plugin-openai/src/tracing.js +1023 -0
  51. package/packages/datadog-plugin-protobufjs/src/index.js +14 -0
  52. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +180 -0
  53. package/packages/dd-trace/src/appsec/addresses.js +8 -1
  54. package/packages/dd-trace/src/appsec/channels.js +7 -1
  55. package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +13 -1
  56. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +8 -1
  57. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
  58. package/packages/dd-trace/src/appsec/iast/index.js +3 -0
  59. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +1 -0
  60. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +55 -7
  61. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +15 -0
  62. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -2
  63. package/packages/dd-trace/src/appsec/index.js +61 -43
  64. package/packages/dd-trace/src/appsec/rasp/command_injection.js +49 -0
  65. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +99 -0
  66. package/packages/dd-trace/src/appsec/rasp/index.js +27 -10
  67. package/packages/dd-trace/src/appsec/rasp/lfi.js +112 -0
  68. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +24 -4
  69. package/packages/dd-trace/src/appsec/rasp/ssrf.js +4 -3
  70. package/packages/dd-trace/src/appsec/rasp/utils.js +4 -2
  71. package/packages/dd-trace/src/appsec/recommended.json +3 -7
  72. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +6 -1
  73. package/packages/dd-trace/src/appsec/remote_config/index.js +10 -0
  74. package/packages/dd-trace/src/appsec/reporter.js +17 -9
  75. package/packages/dd-trace/src/appsec/sdk/track_event.js +10 -3
  76. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +1 -1
  77. package/packages/dd-trace/src/appsec/waf/waf_manager.js +4 -0
  78. package/packages/dd-trace/src/azure_metadata.js +120 -0
  79. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +97 -0
  80. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +90 -0
  81. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +2 -14
  82. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +19 -1
  83. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +53 -0
  84. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +8 -1
  85. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +43 -0
  86. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +53 -0
  87. package/packages/dd-trace/src/config.js +86 -6
  88. package/packages/dd-trace/src/constants.js +3 -1
  89. package/packages/dd-trace/src/datastreams/pathway.js +1 -0
  90. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +25 -17
  91. package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -0
  92. package/packages/dd-trace/src/debugger/devtools_client/index.js +52 -5
  93. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +4 -4
  94. package/packages/dd-trace/src/debugger/devtools_client/send.js +29 -2
  95. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +187 -0
  96. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +40 -0
  97. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +252 -0
  98. package/packages/dd-trace/src/debugger/devtools_client/snapshot/symbols.js +6 -0
  99. package/packages/dd-trace/src/debugger/devtools_client/state.js +19 -4
  100. package/packages/dd-trace/src/debugger/index.js +10 -3
  101. package/packages/dd-trace/src/exporters/common/request.js +8 -34
  102. package/packages/dd-trace/src/exporters/common/url-to-http-options-polyfill.js +31 -0
  103. package/packages/dd-trace/src/llmobs/constants/tags.js +34 -0
  104. package/packages/dd-trace/src/llmobs/constants/text.js +6 -0
  105. package/packages/dd-trace/src/llmobs/constants/writers.js +13 -0
  106. package/packages/dd-trace/src/llmobs/index.js +103 -0
  107. package/packages/dd-trace/src/llmobs/noop.js +82 -0
  108. package/packages/dd-trace/src/llmobs/plugins/base.js +65 -0
  109. package/packages/dd-trace/src/llmobs/plugins/openai.js +205 -0
  110. package/packages/dd-trace/src/llmobs/sdk.js +377 -0
  111. package/packages/dd-trace/src/llmobs/span_processor.js +195 -0
  112. package/packages/dd-trace/src/llmobs/storage.js +7 -0
  113. package/packages/dd-trace/src/llmobs/tagger.js +322 -0
  114. package/packages/dd-trace/src/llmobs/util.js +176 -0
  115. package/packages/dd-trace/src/llmobs/writers/base.js +111 -0
  116. package/packages/dd-trace/src/llmobs/writers/evaluations.js +29 -0
  117. package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +23 -0
  118. package/packages/dd-trace/src/llmobs/writers/spans/agentless.js +17 -0
  119. package/packages/dd-trace/src/llmobs/writers/spans/base.js +49 -0
  120. package/packages/dd-trace/src/noop/proxy.js +3 -0
  121. package/packages/dd-trace/src/noop/span.js +3 -0
  122. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  123. package/packages/dd-trace/src/opentelemetry/tracer.js +1 -0
  124. package/packages/dd-trace/src/opentracing/propagation/text_map.js +73 -12
  125. package/packages/dd-trace/src/opentracing/span.js +12 -0
  126. package/packages/dd-trace/src/opentracing/tracer.js +8 -1
  127. package/packages/dd-trace/src/payload-tagging/config/aws.json +71 -3
  128. package/packages/dd-trace/src/payload-tagging/index.js +1 -1
  129. package/packages/dd-trace/src/payload-tagging/jsonpath-plus.js +2094 -0
  130. package/packages/dd-trace/src/plugin_manager.js +4 -2
  131. package/packages/dd-trace/src/plugins/ci_plugin.js +2 -0
  132. package/packages/dd-trace/src/plugins/index.js +3 -0
  133. package/packages/dd-trace/src/plugins/log_plugin.js +1 -1
  134. package/packages/dd-trace/src/plugins/outbound.js +9 -0
  135. package/packages/dd-trace/src/plugins/schema.js +35 -0
  136. package/packages/dd-trace/src/plugins/util/ci.js +23 -1
  137. package/packages/dd-trace/src/plugins/util/serverless.js +7 -0
  138. package/packages/dd-trace/src/plugins/util/stacktrace.js +94 -0
  139. package/packages/dd-trace/src/plugins/util/tags.js +7 -0
  140. package/packages/dd-trace/src/plugins/util/test.js +20 -22
  141. package/packages/dd-trace/src/plugins/util/web.js +6 -4
  142. package/packages/dd-trace/src/priority_sampler.js +16 -0
  143. package/packages/dd-trace/src/profiling/config.js +3 -1
  144. package/packages/dd-trace/src/profiling/exporters/agent.js +7 -5
  145. package/packages/dd-trace/src/profiling/profiler.js +24 -14
  146. package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
  147. package/packages/dd-trace/src/profiling/profilers/wall.js +95 -66
  148. package/packages/dd-trace/src/proxy.js +20 -1
  149. package/packages/dd-trace/src/service-naming/schemas/v0/index.js +2 -1
  150. package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +12 -0
  151. package/packages/dd-trace/src/service-naming/schemas/v1/index.js +2 -1
  152. package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +12 -0
  153. package/packages/dd-trace/src/span_processor.js +5 -0
  154. package/packages/dd-trace/src/telemetry/index.js +11 -1
  155. package/packages/datadog-core/src/storage/async_resource.js +0 -108
  156. 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
+ }
@@ -0,0 +1,6 @@
1
+ 'use stict'
2
+
3
+ module.exports = {
4
+ collectionSizeSym: Symbol('datadog.collectionSize'),
5
+ fieldCountSym: Symbol('datadog.fieldCount')
6
+ }
@@ -2,7 +2,8 @@
2
2
 
3
3
  const session = require('./session')
4
4
 
5
- const scripts = []
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
- getScript (path) {
29
- return scripts
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
- scripts.push([params.url, params.scriptId])
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
- const ackId = `${id}-${conf.version}`
28
- rcAckCallbacks.set(ackId, ack)
28
+ rcAckCallbacks.set(++ackId, ack)
29
29
  rcChannel.port2.postMessage({ action, conf, ackId })
30
30
  })
31
31
 
32
32
  rcChannel.port2.on('message', ({ ackId, error }) => {
33
- rcAckCallbacks.get(ackId)(error)
33
+ const ack = rcAckCallbacks.get(ackId)
34
+ if (ack === undefined) {
35
+ // This should never happen, but just in case something changes in the future, we should guard against it
36
+ log.error(`Received an unknown ackId: ${ackId}`)
37
+ if (error) log.error(error)
38
+ return
39
+ }
40
+ ack(error)
34
41
  rcAckCallbacks.delete(ackId)
35
42
  })
36
43
  rcChannel.port2.on('messageerror', (err) => log.error(err))
@@ -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
- // TODO: Replace with `url.urlToHttpOptions` when supported by all versions
24
- function urlToOptions (url) {
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
- function fromUrlString (urlString) {
49
- const url = typeof urlToHttpOptions === 'function'
50
- ? urlToOptions(new URL(urlString))
51
- : urlParse(urlString)
25
+ const url = urlToHttpOptions(new URL(urlObjOrString))
52
26
 
53
- // Add the 'hostname' back if we're using named pipes
54
- if (url.protocol === 'unix:' && url.host === '.') {
55
- const udsPath = urlString.replace(/^unix:/, '')
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 = typeof options.url === 'object' ? urlToOptions(options.url) : fromUrlString(options.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
+ }