dd-trace 6.1.0 → 6.3.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/README.md +1 -1
- package/index.d.ts +7 -0
- package/initialize.mjs +4 -2
- package/package.json +5 -5
- package/packages/datadog-instrumentations/src/cookie.js +7 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +5 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +31 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/modelcontextprotocol-sdk.js +30 -54
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +19 -5
- package/packages/datadog-instrumentations/src/http/server.js +27 -0
- package/packages/datadog-instrumentations/src/mercurius.js +11 -0
- package/packages/datadog-instrumentations/src/modelcontextprotocol-sdk.js +248 -1
- package/packages/datadog-instrumentations/src/otel-sdk-trace.js +15 -0
- package/packages/datadog-instrumentations/src/playwright.js +40 -3
- package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
- package/packages/datadog-instrumentations/src/vitest-util.js +0 -3
- package/packages/datadog-instrumentations/src/vitest-worker.js +0 -1
- package/packages/datadog-plugin-azure-functions/src/index.js +2 -1
- package/packages/datadog-plugin-graphql/src/execute.js +4 -15
- package/packages/datadog-plugin-graphql/src/index.js +6 -0
- package/packages/datadog-plugin-graphql/src/request.js +104 -0
- package/packages/datadog-plugin-graphql/src/utils.js +177 -0
- package/packages/datadog-plugin-graphql/src/validate.js +24 -1
- package/packages/datadog-plugin-modelcontextprotocol-sdk/src/tracing.js +144 -26
- package/packages/datadog-plugin-modelcontextprotocol-sdk/src/utils.js +30 -0
- package/packages/datadog-plugin-playwright/src/index.js +1 -1
- package/packages/datadog-plugin-vitest/src/index.js +0 -8
- package/packages/datadog-shimmer/src/shimmer.js +9 -2
- package/packages/dd-trace/src/appsec/channels.js +1 -0
- package/packages/dd-trace/src/appsec/index.js +6 -3
- package/packages/dd-trace/src/appsec/lambda.js +8 -8
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +23 -16
- package/packages/dd-trace/src/config/config-types.d.ts +8 -0
- package/packages/dd-trace/src/config/defaults.js +20 -15
- package/packages/dd-trace/src/config/index.js +13 -3
- package/packages/dd-trace/src/config/parsers.js +79 -0
- package/packages/dd-trace/src/datastreams/writer.js +14 -2
- package/packages/dd-trace/src/encode/0.4.js +23 -2
- package/packages/dd-trace/src/encode/0.5.js +12 -1
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +11 -1
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +20 -2
- package/packages/dd-trace/src/exporters/common/writer.js +17 -1
- package/packages/dd-trace/src/guardrails/index.js +3 -1
- package/packages/dd-trace/src/guardrails/telemetry.js +40 -3
- package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
- package/packages/dd-trace/src/llmobs/tagger.js +15 -1
- package/packages/dd-trace/src/msgpack/chunk.js +33 -1
- package/packages/dd-trace/src/msgpack/index.js +6 -1
- package/packages/dd-trace/src/opentelemetry/span_processor.js +7 -5
- package/packages/dd-trace/src/opentelemetry/tracer_provider.js +32 -16
- package/packages/dd-trace/src/plugins/index.js +3 -0
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +20 -11
- package/packages/dd-trace/src/service-naming/schemas/v0/graphql.js +6 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/graphql.js +8 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +5 -6
|
@@ -1,55 +1,173 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const Plugin = require('../../dd-trace/src/plugins/plugin')
|
|
3
4
|
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const {
|
|
7
|
+
DISTRIBUTED_TRACE_META_KEY,
|
|
8
|
+
tagErrorResult,
|
|
9
|
+
} = require('./utils')
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
const MCP_REQUEST_SPAN_NAME = 'mcp.request'
|
|
12
|
+
const CLIENT_TOOL_CALL_RESOURCE = 'client_tool_call'
|
|
13
|
+
const CLIENT_LIST_TOOLS_RESOURCE = 'ClientSession.list_tools'
|
|
14
|
+
const SERVER_TOOL_CALL_RESOURCE = 'server_tool_call'
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
function getServerRequestResource (request) {
|
|
17
|
+
if (request?.method === 'tools/call') return SERVER_TOOL_CALL_RESOURCE
|
|
18
|
+
return request?.method
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class McpPlugin extends TracingPlugin {
|
|
22
|
+
bindStart (ctx) {
|
|
23
|
+
const spanOptions = {
|
|
24
|
+
resource: this.getResource(ctx),
|
|
15
25
|
type: 'mcp',
|
|
16
|
-
kind:
|
|
17
|
-
}
|
|
26
|
+
kind: this.constructor.kind,
|
|
27
|
+
}
|
|
28
|
+
const childOf = this.getChildOf(ctx)
|
|
29
|
+
if (childOf) spanOptions.childOf = childOf
|
|
18
30
|
|
|
31
|
+
this.startSpan(this.constructor.spanName, spanOptions, ctx)
|
|
32
|
+
const span = ctx.currentStore?.span
|
|
33
|
+
if (span) this.onStart(span, ctx)
|
|
19
34
|
return ctx.currentStore
|
|
20
35
|
}
|
|
21
36
|
|
|
22
37
|
asyncEnd (ctx) {
|
|
23
|
-
const
|
|
24
|
-
if (
|
|
25
|
-
const span = ctx.currentStore?.span
|
|
26
|
-
const errorText = result.content?.find?.(c => c.type === 'text')?.text || 'Tool call returned isError: true'
|
|
27
|
-
span?.setTag('error', new Error(errorText))
|
|
28
|
-
}
|
|
38
|
+
const span = ctx.currentStore?.span
|
|
39
|
+
if (span) this.onEnd(span, ctx)
|
|
29
40
|
super.finish(ctx)
|
|
30
41
|
}
|
|
42
|
+
|
|
43
|
+
getResource () {}
|
|
44
|
+
getChildOf () {}
|
|
45
|
+
onStart () {}
|
|
46
|
+
onEnd () {}
|
|
31
47
|
}
|
|
32
48
|
|
|
33
|
-
class
|
|
49
|
+
class McpPropagationPlugin extends Plugin {
|
|
50
|
+
static id = 'modelcontextprotocol_propagation'
|
|
51
|
+
|
|
52
|
+
constructor (...args) {
|
|
53
|
+
super(...args)
|
|
54
|
+
this.addSub('apm:mcp:client:request:inject', this.injectTraceContext.bind(this))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
injectTraceContext (ctx) {
|
|
58
|
+
const span = this.tracer.scope().active()
|
|
59
|
+
if (!span) return
|
|
60
|
+
|
|
61
|
+
const traceContext = {}
|
|
62
|
+
this.tracer.inject(span, 'text_map', traceContext)
|
|
63
|
+
ctx.traceContext = traceContext
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
class McpToolCallPlugin extends McpPlugin {
|
|
68
|
+
static id = 'modelcontextprotocol_client'
|
|
69
|
+
static prefix = 'tracing:orchestrion:@modelcontextprotocol/sdk:Client_callTool'
|
|
70
|
+
static spanName = MCP_REQUEST_SPAN_NAME
|
|
71
|
+
static kind = 'client'
|
|
72
|
+
getResource () { return CLIENT_TOOL_CALL_RESOURCE }
|
|
73
|
+
onEnd (span, ctx) { tagErrorResult(span, ctx.result) }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
class McpListToolsPlugin extends McpPlugin {
|
|
34
77
|
static id = 'modelcontextprotocol_list_tools'
|
|
35
78
|
static prefix = 'tracing:orchestrion:@modelcontextprotocol/sdk:Client_listTools'
|
|
79
|
+
static spanName = MCP_REQUEST_SPAN_NAME
|
|
80
|
+
static kind = 'client'
|
|
81
|
+
getResource () { return CLIENT_LIST_TOOLS_RESOURCE }
|
|
82
|
+
}
|
|
36
83
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
84
|
+
class McpListResourcesPlugin extends McpPlugin {
|
|
85
|
+
static id = 'modelcontextprotocol_list_resources'
|
|
86
|
+
static prefix = 'tracing:orchestrion:@modelcontextprotocol/sdk:Client_listResources'
|
|
87
|
+
static spanName = 'mcp.resources.list'
|
|
88
|
+
static kind = 'client'
|
|
89
|
+
getResource () { return 'resources/list' }
|
|
90
|
+
}
|
|
43
91
|
|
|
44
|
-
|
|
92
|
+
class McpReadResourcePlugin extends McpPlugin {
|
|
93
|
+
static id = 'modelcontextprotocol_read_resource'
|
|
94
|
+
static prefix = 'tracing:orchestrion:@modelcontextprotocol/sdk:Client_readResource'
|
|
95
|
+
static spanName = 'mcp.resource.read'
|
|
96
|
+
static kind = 'client'
|
|
97
|
+
getResource () { return 'resources/read' }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
class McpListPromptsPlugin extends McpPlugin {
|
|
101
|
+
static id = 'modelcontextprotocol_list_prompts'
|
|
102
|
+
static prefix = 'tracing:orchestrion:@modelcontextprotocol/sdk:Client_listPrompts'
|
|
103
|
+
static spanName = 'mcp.prompts.list'
|
|
104
|
+
static kind = 'client'
|
|
105
|
+
getResource () { return 'prompts/list' }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
class McpGetPromptPlugin extends McpPlugin {
|
|
109
|
+
static id = 'modelcontextprotocol_get_prompt'
|
|
110
|
+
static prefix = 'tracing:orchestrion:@modelcontextprotocol/sdk:Client_getPrompt'
|
|
111
|
+
static spanName = 'mcp.prompt.get'
|
|
112
|
+
static kind = 'client'
|
|
113
|
+
getResource () { return 'prompts/get' }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
class McpServerRequestPlugin extends McpPlugin {
|
|
117
|
+
static id = 'modelcontextprotocol_server'
|
|
118
|
+
static prefix = 'tracing:apm:mcp:server:request'
|
|
119
|
+
static spanName = MCP_REQUEST_SPAN_NAME
|
|
120
|
+
static kind = 'server'
|
|
121
|
+
getResource (ctx) { return getServerRequestResource(ctx.request) }
|
|
122
|
+
getChildOf (ctx) {
|
|
123
|
+
const traceContext = ctx.request?.params?._meta?.[DISTRIBUTED_TRACE_META_KEY]
|
|
124
|
+
if (!traceContext || typeof traceContext !== 'object') return
|
|
125
|
+
|
|
126
|
+
const childOf = this.tracer.extract('text_map', traceContext)
|
|
127
|
+
const activeSpan = this.activeSpan
|
|
128
|
+
if (!childOf || activeSpan?.context().toTraceId() === childOf.toTraceId()) return
|
|
129
|
+
|
|
130
|
+
return childOf
|
|
45
131
|
}
|
|
46
132
|
|
|
47
|
-
|
|
48
|
-
|
|
133
|
+
onEnd (span, ctx) {
|
|
134
|
+
if (ctx.error) {
|
|
135
|
+
span.setTag('error', ctx.error)
|
|
136
|
+
} else {
|
|
137
|
+
tagErrorResult(span, ctx.result)
|
|
138
|
+
}
|
|
49
139
|
}
|
|
50
140
|
}
|
|
51
141
|
|
|
142
|
+
class McpServerToolCallPlugin extends McpPlugin {
|
|
143
|
+
static id = 'modelcontextprotocol_server_tool'
|
|
144
|
+
static prefix = 'tracing:orchestrion:@modelcontextprotocol/sdk:McpServer_executeToolHandler'
|
|
145
|
+
static spanName = 'mcp.server.tool.call'
|
|
146
|
+
static kind = 'internal'
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @param {...unknown} args Plugin constructor arguments.
|
|
150
|
+
*/
|
|
151
|
+
constructor (...args) {
|
|
152
|
+
super(...args)
|
|
153
|
+
this.toolNames = new WeakMap()
|
|
154
|
+
this.addSub('apm:mcp:server:tool:registered', ({ tool, name }) => {
|
|
155
|
+
this.toolNames.set(tool, name)
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
getResource (ctx) { return this.toolNames.get(ctx.arguments?.[0]) }
|
|
160
|
+
onEnd (span, ctx) { tagErrorResult(span, ctx.result) }
|
|
161
|
+
}
|
|
162
|
+
|
|
52
163
|
module.exports = [
|
|
164
|
+
McpPropagationPlugin,
|
|
53
165
|
McpToolCallPlugin,
|
|
54
166
|
McpListToolsPlugin,
|
|
167
|
+
McpListResourcesPlugin,
|
|
168
|
+
McpReadResourcePlugin,
|
|
169
|
+
McpListPromptsPlugin,
|
|
170
|
+
McpGetPromptPlugin,
|
|
171
|
+
McpServerRequestPlugin,
|
|
172
|
+
McpServerToolCallPlugin,
|
|
55
173
|
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { ERROR_MESSAGE, ERROR_TYPE } = require('../../dd-trace/src/constants')
|
|
4
|
+
|
|
5
|
+
const DISTRIBUTED_TRACE_META_KEY = '_dd_trace_context'
|
|
6
|
+
|
|
7
|
+
function getFirstTextContent (content) {
|
|
8
|
+
if (!Array.isArray(content)) return
|
|
9
|
+
|
|
10
|
+
for (const item of content) {
|
|
11
|
+
if (item.type === 'text' && item.text) return item.text
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function setErrorTags (span, message) {
|
|
16
|
+
span.setTag('error', 1)
|
|
17
|
+
span.setTag(ERROR_TYPE, 'Error')
|
|
18
|
+
span.setTag(ERROR_MESSAGE, message)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function tagErrorResult (span, result) {
|
|
22
|
+
if (result?.isError) {
|
|
23
|
+
setErrorTags(span, getFirstTextContent(result.content) || 'Tool call returned isError: true')
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = {
|
|
28
|
+
DISTRIBUTED_TRACE_META_KEY,
|
|
29
|
+
tagErrorResult,
|
|
30
|
+
}
|
|
@@ -68,7 +68,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
68
68
|
}) => {
|
|
69
69
|
const testSuite = getTestSuitePath(filePath, this.repositoryRoot)
|
|
70
70
|
const isModified = isModifiedTest(testSuite, 0, 0, modifiedFiles, this.constructor.id)
|
|
71
|
-
onDone(
|
|
71
|
+
onDone(isModified)
|
|
72
72
|
})
|
|
73
73
|
|
|
74
74
|
this.addSub('ci:playwright:session:finish', ({
|
|
@@ -67,7 +67,6 @@ class VitestPlugin extends CiPlugin {
|
|
|
67
67
|
testCommand: this.command,
|
|
68
68
|
repositoryRoot: this.repositoryRoot,
|
|
69
69
|
codeOwnersEntries: this.codeOwnersEntries,
|
|
70
|
-
testEnvironmentMetadata: this.testEnvironmentMetadata,
|
|
71
70
|
})
|
|
72
71
|
})
|
|
73
72
|
|
|
@@ -324,7 +323,6 @@ class VitestPlugin extends CiPlugin {
|
|
|
324
323
|
const {
|
|
325
324
|
codeOwnersEntries,
|
|
326
325
|
repositoryRoot,
|
|
327
|
-
testEnvironmentMetadata,
|
|
328
326
|
requestErrorTags,
|
|
329
327
|
testSuiteAbsolutePath,
|
|
330
328
|
frameworkVersion,
|
|
@@ -334,12 +332,6 @@ class VitestPlugin extends CiPlugin {
|
|
|
334
332
|
|
|
335
333
|
const testCommand = ctx.testCommand || 'vitest run'
|
|
336
334
|
const { testSessionId, testModuleId } = ctx
|
|
337
|
-
if (testEnvironmentMetadata) {
|
|
338
|
-
this.testEnvironmentMetadata = {
|
|
339
|
-
...this.testEnvironmentMetadata,
|
|
340
|
-
...testEnvironmentMetadata,
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
335
|
this._setRepositoryRoot(repositoryRoot, codeOwnersEntries)
|
|
344
336
|
this.command = testCommand
|
|
345
337
|
this.frameworkVersion = frameworkVersion
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { isModuleNamespaceObject } = require('node:util').types
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* @type {Set<string | symbol>}
|
|
5
7
|
*/
|
|
@@ -168,14 +170,19 @@ function wrap (target, name, wrapper, options) {
|
|
|
168
170
|
|
|
169
171
|
copyProperties(original, wrapped)
|
|
170
172
|
|
|
171
|
-
|
|
173
|
+
const immutableModuleNamespace = descriptor.configurable === false &&
|
|
174
|
+
descriptor.writable && isModuleNamespaceObject(target)
|
|
175
|
+
|
|
176
|
+
if (descriptor.writable && !immutableModuleNamespace) {
|
|
172
177
|
if (descriptor.configurable && descriptor.enumerable) {
|
|
173
178
|
target[name] = wrapped
|
|
174
179
|
return target
|
|
175
180
|
}
|
|
176
181
|
descriptor.value = wrapped
|
|
177
182
|
} else {
|
|
178
|
-
if (
|
|
183
|
+
if (immutableModuleNamespace) {
|
|
184
|
+
descriptor.value = wrapped
|
|
185
|
+
} else if (descriptor.set && options?.replaceGetter) {
|
|
179
186
|
// A lazy accessor pair (e.g. Node 20's `fs.opendir`). `original` already
|
|
180
187
|
// resolved the value through the getter. Keep the property an accessor pair
|
|
181
188
|
// so the shape stays observationally identical — a downstream consumer may
|
|
@@ -25,6 +25,7 @@ module.exports = {
|
|
|
25
25
|
httpClientRequestStart: dc.channel('apm:http:client:request:start'),
|
|
26
26
|
httpClientResponseStart: dc.channel('apm:http:client:response:start'),
|
|
27
27
|
httpClientResponseFinish: dc.channel('apm:http:client:response:finish'),
|
|
28
|
+
informationalResponse: dc.channel('datadog:http:server:informational-response:start'),
|
|
28
29
|
incomingHttpRequestEnd: dc.channel('dd-trace:incomingHttpRequestEnd'),
|
|
29
30
|
incomingHttpRequestStart: dc.channel('dd-trace:incomingHttpRequestStart'),
|
|
30
31
|
lambdaStartInvocation: dc.channel('datadog:lambda:start-invocation'),
|
|
@@ -29,6 +29,7 @@ const {
|
|
|
29
29
|
responseBody,
|
|
30
30
|
responseWriteHead,
|
|
31
31
|
responseSetHeader,
|
|
32
|
+
informationalResponse,
|
|
32
33
|
routerParam,
|
|
33
34
|
fastifyResponseChannel,
|
|
34
35
|
fastifyPathParams,
|
|
@@ -98,7 +99,8 @@ function enable (_config) {
|
|
|
98
99
|
responseBody.subscribe(onResponseBody)
|
|
99
100
|
fastifyResponseChannel.subscribe(onResponseBody)
|
|
100
101
|
responseWriteHead.subscribe(onResponseWriteHead)
|
|
101
|
-
responseSetHeader.subscribe(
|
|
102
|
+
responseSetHeader.subscribe(onResponseOperation)
|
|
103
|
+
informationalResponse.subscribe(onResponseOperation)
|
|
102
104
|
stripeCheckoutSessionCreate.subscribe(onStripeCheckoutSessionCreate)
|
|
103
105
|
stripePaymentIntentCreate.subscribe(onStripePaymentIntentCreate)
|
|
104
106
|
stripeConstructEvent.subscribe(onStripeConstructEvent)
|
|
@@ -403,7 +405,7 @@ function onResponseWriteHead ({ req, res, abortController, statusCode, responseH
|
|
|
403
405
|
handleResults(results?.actions, req, res, rootSpan, abortController)
|
|
404
406
|
}
|
|
405
407
|
|
|
406
|
-
function
|
|
408
|
+
function onResponseOperation ({ res, abortController }) {
|
|
407
409
|
if (isBlocked(res)) {
|
|
408
410
|
abortController?.abort()
|
|
409
411
|
}
|
|
@@ -547,7 +549,8 @@ function disable () {
|
|
|
547
549
|
if (responseBody.hasSubscribers) responseBody.unsubscribe(onResponseBody)
|
|
548
550
|
if (fastifyResponseChannel.hasSubscribers) fastifyResponseChannel.unsubscribe(onResponseBody)
|
|
549
551
|
if (responseWriteHead.hasSubscribers) responseWriteHead.unsubscribe(onResponseWriteHead)
|
|
550
|
-
if (responseSetHeader.hasSubscribers) responseSetHeader.unsubscribe(
|
|
552
|
+
if (responseSetHeader.hasSubscribers) responseSetHeader.unsubscribe(onResponseOperation)
|
|
553
|
+
if (informationalResponse.hasSubscribers) informationalResponse.unsubscribe(onResponseOperation)
|
|
551
554
|
if (stripeCheckoutSessionCreate.hasSubscribers) stripeCheckoutSessionCreate.unsubscribe(onStripeCheckoutSessionCreate)
|
|
552
555
|
if (stripePaymentIntentCreate.hasSubscribers) stripePaymentIntentCreate.unsubscribe(onStripePaymentIntentCreate)
|
|
553
556
|
if (stripeConstructEvent.hasSubscribers) stripeConstructEvent.unsubscribe(onStripeConstructEvent)
|
|
@@ -7,9 +7,7 @@ const addresses = require('./addresses')
|
|
|
7
7
|
const Reporter = require('./reporter')
|
|
8
8
|
const waf = require('./waf')
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
// end-invocation can gate correctly
|
|
12
|
-
const activeInvocations = new WeakSet()
|
|
10
|
+
const activeInvocations = new WeakMap()
|
|
13
11
|
|
|
14
12
|
/**
|
|
15
13
|
* Maps pre-extracted HTTP data from the Lambda event to WAF addresses,
|
|
@@ -31,7 +29,8 @@ function onLambdaStartInvocation (data) {
|
|
|
31
29
|
return
|
|
32
30
|
}
|
|
33
31
|
|
|
34
|
-
|
|
32
|
+
const req = { headers: headers ?? {} }
|
|
33
|
+
activeInvocations.set(span, req)
|
|
35
34
|
|
|
36
35
|
span.addTags({
|
|
37
36
|
'_dd.appsec.enabled': 1,
|
|
@@ -74,7 +73,7 @@ function onLambdaStartInvocation (data) {
|
|
|
74
73
|
persistent[addresses.HTTP_INCOMING_COOKIES] = cookies
|
|
75
74
|
}
|
|
76
75
|
|
|
77
|
-
waf.run({ persistent },
|
|
76
|
+
waf.run({ persistent }, req, undefined, span)
|
|
78
77
|
} catch (err) {
|
|
79
78
|
log.error('[ASM] Error in Lambda start-invocation handler', err)
|
|
80
79
|
}
|
|
@@ -100,6 +99,7 @@ function onLambdaEndInvocation (data) {
|
|
|
100
99
|
return
|
|
101
100
|
}
|
|
102
101
|
|
|
102
|
+
const req = activeInvocations.get(span)
|
|
103
103
|
activeInvocations.delete(span)
|
|
104
104
|
|
|
105
105
|
let hasPersistentData = false
|
|
@@ -118,12 +118,12 @@ function onLambdaEndInvocation (data) {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
if (hasPersistentData) {
|
|
121
|
-
waf.run({ persistent },
|
|
121
|
+
waf.run({ persistent }, req, undefined, span)
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
waf.disposeContext(
|
|
124
|
+
waf.disposeContext(req)
|
|
125
125
|
|
|
126
|
-
Reporter.finishRequest(
|
|
126
|
+
Reporter.finishRequest(req, null, {}, undefined, span)
|
|
127
127
|
} catch (err) {
|
|
128
128
|
log.error('[ASM] Error in Lambda end-invocation handler', err)
|
|
129
129
|
}
|
|
@@ -42,7 +42,7 @@ class WAFManager {
|
|
|
42
42
|
this.ddwafVersion = this.ddwafVersion || 'unknown'
|
|
43
43
|
Reporter.reportWafInit(this.ddwafVersion, 'unknown')
|
|
44
44
|
|
|
45
|
-
log.error('[ASM] AppSec could not load native package. In-app WAF features will not be available.')
|
|
45
|
+
log.error('[ASM] AppSec could not load native package. In-app WAF features will not be available.', err)
|
|
46
46
|
|
|
47
47
|
throw err
|
|
48
48
|
}
|
|
@@ -51,12 +51,20 @@ const limits = {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
|
-
*
|
|
54
|
+
* @param {object} value - Snapshot object or sub-object.
|
|
55
|
+
* @returns {boolean}
|
|
56
|
+
*/
|
|
57
|
+
function isCapturedValue (value) {
|
|
58
|
+
return typeof value.type === 'string'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Remove empty capture containers before sending snapshots through the Test Optimization logs path.
|
|
55
63
|
*
|
|
56
64
|
* @param {object} value - Snapshot object or sub-object.
|
|
57
65
|
* @returns {void}
|
|
58
66
|
*/
|
|
59
|
-
function
|
|
67
|
+
function removeEmptyCaptureProperties (value) {
|
|
60
68
|
const stack = [value]
|
|
61
69
|
|
|
62
70
|
while (stack.length > 0) {
|
|
@@ -72,26 +80,25 @@ function removeEmptyCollectionProperties (value) {
|
|
|
72
80
|
continue
|
|
73
81
|
}
|
|
74
82
|
|
|
75
|
-
if (
|
|
76
|
-
if (current.elements.length === 0) {
|
|
83
|
+
if (isCapturedValue(current)) {
|
|
84
|
+
if (current.elements === null || (Array.isArray(current.elements) && current.elements.length === 0)) {
|
|
77
85
|
delete current.elements
|
|
78
|
-
} else {
|
|
79
|
-
stack.push(current.elements)
|
|
80
86
|
}
|
|
81
|
-
}
|
|
82
87
|
|
|
83
|
-
|
|
84
|
-
if (current.entries.length === 0) {
|
|
88
|
+
if (current.entries === null || (Array.isArray(current.entries) && current.entries.length === 0)) {
|
|
85
89
|
delete current.entries
|
|
86
|
-
} else {
|
|
87
|
-
stack.push(current.entries)
|
|
88
90
|
}
|
|
89
|
-
}
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
if (current.fields === null || (
|
|
93
|
+
current.fields &&
|
|
94
|
+
typeof current.fields === 'object' &&
|
|
95
|
+
Object.keys(current.fields).length === 0
|
|
96
|
+
)) {
|
|
97
|
+
delete current.fields
|
|
98
|
+
}
|
|
99
|
+
}
|
|
93
100
|
|
|
94
|
-
|
|
101
|
+
for (const child of Object.values(current)) {
|
|
95
102
|
if (child && typeof child === 'object') {
|
|
96
103
|
stack.push(child)
|
|
97
104
|
}
|
|
@@ -129,7 +136,7 @@ session.on('Debugger.paused', async ({ params: { hitBreakpoints: [hitBreakpoint]
|
|
|
129
136
|
language: 'javascript',
|
|
130
137
|
}
|
|
131
138
|
|
|
132
|
-
|
|
139
|
+
removeEmptyCaptureProperties(snapshot.captures)
|
|
133
140
|
|
|
134
141
|
if (processTags) {
|
|
135
142
|
snapshot[processTags.DYNAMIC_INSTRUMENTATION_FIELD_NAME] = processTags.tagsObject
|
|
@@ -70,3 +70,11 @@ export type ConfigKey = KnownStringKeys<ConfigProperties>
|
|
|
70
70
|
export type ConfigPath = ConfigPathFor<ConfigProperties>
|
|
71
71
|
export type ConfigPathValue<TPath extends ConfigPath> = ConfigPathValueFor<ConfigProperties, TPath>
|
|
72
72
|
export type ConfigDefaults = Partial<{ [TPath in ConfigPath]: ConfigPathValue<TPath> }>
|
|
73
|
+
export type ConfigurationOption = {
|
|
74
|
+
property?: string
|
|
75
|
+
parser?: (value: unknown, optionName: string, source: string) => unknown
|
|
76
|
+
type: string
|
|
77
|
+
canonicalName?: string
|
|
78
|
+
transformer?: (value: unknown, optionName: string, source: string) => unknown
|
|
79
|
+
telemetryTransformer?: (value: unknown) => unknown
|
|
80
|
+
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const assert = require('node:assert')
|
|
3
4
|
const util = require('util')
|
|
4
5
|
|
|
5
6
|
const { DD_MAJOR } = require('../../../../version')
|
|
6
|
-
const {
|
|
7
|
+
const {
|
|
8
|
+
parsers,
|
|
9
|
+
programmaticTypeCoercions,
|
|
10
|
+
transformers,
|
|
11
|
+
telemetryTransformers,
|
|
12
|
+
setWarnInvalidValue,
|
|
13
|
+
} = require('./parsers')
|
|
7
14
|
const applyMajorOverrides = require('./major-overrides')
|
|
8
15
|
const {
|
|
9
16
|
supportedConfigurations,
|
|
@@ -70,6 +77,7 @@ for (const [name, value] of Object.entries(defaults)) {
|
|
|
70
77
|
function generateTelemetry (value = null, origin, optionName) {
|
|
71
78
|
const tableEntry = configurationsTable[optionName]
|
|
72
79
|
const { type, canonicalName = optionName } = tableEntry ?? { type: typeof value }
|
|
80
|
+
const error = parseErrors.get(`${canonicalName}${origin}`)
|
|
73
81
|
// Sensitive configurations are excluded from configuration telemetry: their
|
|
74
82
|
// entry is never added to `configWithOrigin`, the single source for every
|
|
75
83
|
// telemetry path (app-started and app-client-configuration-change).
|
|
@@ -81,7 +89,9 @@ function generateTelemetry (value = null, origin, optionName) {
|
|
|
81
89
|
// TODO: Validate that space separated tags are parsed by the backend. Optimizations would be possible with that.
|
|
82
90
|
// TODO: How to handle telemetry reporting for aliases?
|
|
83
91
|
if (value !== null) {
|
|
84
|
-
if (
|
|
92
|
+
if (error && typeof value === 'object') {
|
|
93
|
+
value = util.inspect(value, { customInspect: false, getters: false })
|
|
94
|
+
} else if (telemetryTransformers[type]) {
|
|
85
95
|
value = telemetryTransformers[type](value)
|
|
86
96
|
} else if (typeof value === 'object' && value !== null) {
|
|
87
97
|
// Custom optionsTable entries (no `configurationsTable` row, e.g. `logger`)
|
|
@@ -100,7 +110,6 @@ function generateTelemetry (value = null, origin, optionName) {
|
|
|
100
110
|
origin,
|
|
101
111
|
seq_id: seqId++,
|
|
102
112
|
}
|
|
103
|
-
const error = parseErrors.get(`${canonicalName}${origin}`)
|
|
104
113
|
if (error) {
|
|
105
114
|
parseErrors.delete(`${canonicalName}${origin}`)
|
|
106
115
|
telemetryEntry.error = error
|
|
@@ -135,12 +144,8 @@ function generateTelemetry (value = null, origin, optionName) {
|
|
|
135
144
|
const optionsTable = {
|
|
136
145
|
// Additional properties that are not supported by the supported-configurations.json file.
|
|
137
146
|
lookup: {
|
|
138
|
-
transformer (value) {
|
|
139
|
-
if (typeof value === 'function') {
|
|
140
|
-
return value
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
147
|
property: 'lookup',
|
|
148
|
+
type: 'FUNCTION',
|
|
144
149
|
},
|
|
145
150
|
logger: {
|
|
146
151
|
transformer (object) {
|
|
@@ -165,12 +170,15 @@ const optionsTable = {
|
|
|
165
170
|
}
|
|
166
171
|
},
|
|
167
172
|
property: 'logger',
|
|
173
|
+
type: 'MAP',
|
|
168
174
|
},
|
|
169
175
|
isCiVisibility: {
|
|
170
176
|
property: 'isCiVisibility',
|
|
177
|
+
type: 'BOOLEAN',
|
|
171
178
|
},
|
|
172
179
|
plugins: {
|
|
173
180
|
property: 'plugins',
|
|
181
|
+
type: 'BOOLEAN',
|
|
174
182
|
},
|
|
175
183
|
}
|
|
176
184
|
|
|
@@ -185,13 +193,7 @@ const parser = (value, optionName, source) => {
|
|
|
185
193
|
|
|
186
194
|
/**
|
|
187
195
|
* @template {import('./config-types').ConfigPath} TPath
|
|
188
|
-
* @type {Partial<Record<TPath,
|
|
189
|
-
* property?: string,
|
|
190
|
-
* parser: (value: unknown, optionName: string, source: string) => unknown,
|
|
191
|
-
* canonicalName?: string,
|
|
192
|
-
* transformer?: (value: unknown, optionName: string, source: string) => unknown,
|
|
193
|
-
* telemetryTransformer?: (value: unknown) => unknown
|
|
194
|
-
* }>>} ConfigurationsTable
|
|
196
|
+
* @type {Partial<Record<TPath, import('./config-types').ConfigurationOption>>} ConfigurationsTable
|
|
195
197
|
*/
|
|
196
198
|
const configurationsTable = {}
|
|
197
199
|
|
|
@@ -231,6 +233,7 @@ for (const [canonicalName, entries] of Object.entries(supportedConfigurations))
|
|
|
231
233
|
? `${entry.namespace}.${canonicalName}`
|
|
232
234
|
: (entry.internalPropertyName ?? entry.configurationNames?.[0] ?? canonicalName)
|
|
233
235
|
const type = entry.type.toUpperCase()
|
|
236
|
+
assert.ok(programmaticTypeCoercions[type])
|
|
234
237
|
|
|
235
238
|
let transformer = transformers[entry.transform]
|
|
236
239
|
if (entry.allowed) {
|
|
@@ -353,6 +356,8 @@ module.exports = {
|
|
|
353
356
|
parseErrors,
|
|
354
357
|
|
|
355
358
|
generateTelemetry,
|
|
359
|
+
|
|
360
|
+
warnInvalidValue,
|
|
356
361
|
}
|
|
357
362
|
|
|
358
363
|
// `dns` is instrumented, so requiring it pulls in the dns plugin, which loads
|
|
@@ -35,9 +35,10 @@ const {
|
|
|
35
35
|
configWithOrigin,
|
|
36
36
|
parseErrors,
|
|
37
37
|
generateTelemetry,
|
|
38
|
+
warnInvalidValue,
|
|
38
39
|
} = require('./defaults')
|
|
39
40
|
const { normalizeService } = require('./normalize-service')
|
|
40
|
-
const { transformers } = require('./parsers')
|
|
41
|
+
const { programmaticTypeCoercions, transformers } = require('./parsers')
|
|
41
42
|
|
|
42
43
|
const RUNTIME_ID = uuid()
|
|
43
44
|
|
|
@@ -55,6 +56,7 @@ const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
|
|
|
55
56
|
* @typedef {import('../../../../index').TracerOptions} TracerOptions
|
|
56
57
|
* @typedef {import('./config-types').ConfigKey} ConfigKey
|
|
57
58
|
* @typedef {import('./config-types').ConfigPath} ConfigPath
|
|
59
|
+
* @typedef {import('./config-types').ConfigurationOption} ConfigurationOption
|
|
58
60
|
* @typedef {{
|
|
59
61
|
* value: import('./config-types').ConfigPathValue<ConfigPath>,
|
|
60
62
|
* source: TelemetrySource
|
|
@@ -121,6 +123,9 @@ function setAndTrack (config, name, value, rawValue = value, source = 'calculate
|
|
|
121
123
|
if (value == null) {
|
|
122
124
|
// TODO: This works as before while ignoring undefined programmatic options is not ideal.
|
|
123
125
|
if (source !== 'default') {
|
|
126
|
+
if (rawValue !== value) {
|
|
127
|
+
generateTelemetry(rawValue, source, name)
|
|
128
|
+
}
|
|
124
129
|
return
|
|
125
130
|
}
|
|
126
131
|
} else if (source === 'calculated' || source === 'remote_config') {
|
|
@@ -285,8 +290,13 @@ class Config extends ConfigBase {
|
|
|
285
290
|
continue
|
|
286
291
|
}
|
|
287
292
|
}
|
|
288
|
-
|
|
289
|
-
|
|
293
|
+
const coerced = programmaticTypeCoercions[entry.type](value)
|
|
294
|
+
if (coerced === undefined && value !== undefined) {
|
|
295
|
+
warnInvalidValue(value, fullName, source, `Invalid ${entry.type} input`)
|
|
296
|
+
}
|
|
297
|
+
const transformed = coerced !== undefined && entry.transformer
|
|
298
|
+
? entry.transformer(coerced, fullName, source)
|
|
299
|
+
: coerced
|
|
290
300
|
setAndTrack(this, entry.property ?? name, transformed, value, source)
|
|
291
301
|
}
|
|
292
302
|
}
|