dd-trace 5.45.0 → 5.47.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 -2
- package/ci/init.js +8 -0
- package/ext/exporters.d.ts +2 -1
- package/ext/exporters.js +2 -1
- package/package.json +8 -9
- package/packages/datadog-instrumentations/orchestrion.yml +52 -0
- package/packages/datadog-instrumentations/src/cucumber.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +41 -1
- package/packages/datadog-instrumentations/src/jest.js +11 -2
- package/packages/datadog-instrumentations/src/langchain.js +49 -53
- package/packages/datadog-instrumentations/src/mariadb.js +19 -0
- package/packages/datadog-instrumentations/src/mocha/main.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/utils.js +11 -3
- package/packages/datadog-instrumentations/src/orchestrion-config/index.js +5 -0
- package/packages/datadog-instrumentations/src/playwright.js +333 -46
- package/packages/datadog-instrumentations/src/router.js +1 -7
- package/packages/datadog-instrumentations/src/vitest.js +11 -3
- package/packages/datadog-plugin-cucumber/src/index.js +11 -4
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +17 -5
- package/packages/datadog-plugin-jest/src/index.js +11 -4
- package/packages/datadog-plugin-langchain/src/index.js +18 -12
- package/packages/datadog-plugin-langchain/src/tracing.js +66 -6
- package/packages/datadog-plugin-mocha/src/index.js +17 -5
- package/packages/datadog-plugin-mongodb-core/src/index.js +24 -0
- package/packages/datadog-plugin-playwright/src/index.js +124 -10
- package/packages/datadog-plugin-vitest/src/index.js +13 -8
- package/packages/datadog-shimmer/src/shimmer.js +3 -42
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +39 -15
- package/packages/dd-trace/src/appsec/iast/taint-tracking/filter.js +3 -3
- package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +0 -3
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +25 -12
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-telemetry.js +3 -32
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +99 -57
- package/packages/dd-trace/src/appsec/rasp/command_injection.js +1 -1
- package/packages/dd-trace/src/appsec/rasp/index.js +4 -2
- package/packages/dd-trace/src/appsec/rasp/lfi.js +1 -1
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +1 -1
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +1 -1
- package/packages/dd-trace/src/appsec/rasp/utils.js +12 -7
- package/packages/dd-trace/src/appsec/recommended.json +256 -84
- package/packages/dd-trace/src/appsec/reporter.js +6 -4
- package/packages/dd-trace/src/appsec/telemetry/index.js +27 -3
- package/packages/dd-trace/src/appsec/telemetry/rasp.js +70 -6
- package/packages/dd-trace/src/appsec/telemetry/waf.js +0 -30
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +8 -3
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +6 -4
- package/packages/dd-trace/src/config.js +9 -0
- package/packages/dd-trace/src/constants.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +102 -22
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +263 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +69 -36
- package/packages/dd-trace/src/debugger/devtools_client/lock.js +8 -0
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +1 -7
- package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +15 -10
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +3 -3
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +69 -62
- package/packages/dd-trace/src/debugger/devtools_client/state.js +3 -2
- package/packages/dd-trace/src/debugger/index.js +3 -0
- package/packages/dd-trace/src/encode/0.4.js +24 -17
- package/packages/dd-trace/src/exporter.js +1 -0
- package/packages/dd-trace/src/exporters/common/docker.js +37 -7
- package/packages/dd-trace/src/exporters/common/request.js +1 -4
- package/packages/dd-trace/src/format.js +58 -60
- package/packages/dd-trace/src/llmobs/plugins/base.js +2 -2
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +62 -3
- package/packages/dd-trace/src/llmobs/plugins/openai.js +1 -0
- package/packages/dd-trace/src/llmobs/plugins/vertexai.js +2 -1
- package/packages/dd-trace/src/llmobs/writers/spans/base.js +3 -3
- package/packages/dd-trace/src/log/index.js +2 -0
- package/packages/dd-trace/src/log/writer.js +19 -2
- package/packages/dd-trace/src/opentelemetry/span.js +4 -4
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +17 -3
- package/packages/dd-trace/src/opentracing/span.js +10 -0
- package/packages/dd-trace/src/plugin_manager.js +2 -0
- package/packages/dd-trace/src/plugins/util/test.js +11 -0
- package/packages/dd-trace/src/profiler.js +1 -1
- package/packages/dd-trace/src/profiling/config.js +6 -0
- package/packages/dd-trace/src/profiling/exporters/agent.js +1 -5
- package/packages/dd-trace/src/profiling/profiler.js +4 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +12 -8
- package/packages/dd-trace/src/proxy.js +5 -1
- package/packages/dd-trace/src/tagger.js +38 -26
- package/packages/dd-trace/src/util.js +1 -7
|
@@ -2,34 +2,64 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs')
|
|
4
4
|
|
|
5
|
+
const { DD_EXTERNAL_ENV } = process.env
|
|
6
|
+
|
|
5
7
|
// The second part is the PCF / Garden regexp. We currently assume no suffix($) to avoid matching pod UIDs
|
|
6
8
|
// See https://github.com/DataDog/datadog-agent/blob/7.40.x/pkg/util/cgroups/reader.go#L50
|
|
7
9
|
const uuidSource =
|
|
8
10
|
'[0-9a-f]{8}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{4}[-_][0-9a-f]{12}|[0-9a-f]{8}(?:-[0-9a-f]{4}){4}$'
|
|
9
11
|
const containerSource = '[0-9a-f]{64}'
|
|
10
12
|
const taskSource = '[0-9a-f]{32}-\\d+'
|
|
13
|
+
const lineReg = /^(\d+):([^:]*):(.+)$/
|
|
11
14
|
const entityReg = new RegExp(`.*(${uuidSource}|${containerSource}|${taskSource})(?:\\.scope)?$`, 'm')
|
|
12
15
|
|
|
16
|
+
const cgroup = readControlGroup()
|
|
13
17
|
const entityId = getEntityId()
|
|
18
|
+
const inode = getInode()
|
|
14
19
|
|
|
15
20
|
function getEntityId () {
|
|
16
|
-
const
|
|
17
|
-
const match = cgroup.trim().match(entityReg) || []
|
|
21
|
+
const match = cgroup.match(entityReg) || []
|
|
18
22
|
|
|
19
23
|
return match[1]
|
|
20
24
|
}
|
|
21
25
|
|
|
26
|
+
function getInode () {
|
|
27
|
+
const match = cgroup.match(lineReg) || []
|
|
28
|
+
|
|
29
|
+
return readInode(match[3])
|
|
30
|
+
}
|
|
31
|
+
|
|
22
32
|
function readControlGroup () {
|
|
23
33
|
try {
|
|
24
|
-
return fs.readFileSync('/proc/self/cgroup').toString()
|
|
34
|
+
return fs.readFileSync('/proc/self/cgroup').toString().trim()
|
|
35
|
+
} catch (err) {
|
|
36
|
+
return ''
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function readInode (path) {
|
|
41
|
+
if (!path) return 0
|
|
42
|
+
|
|
43
|
+
const strippedPath = path.replace(/^\//, '').replace(/\/$/, '')
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
return fs.statSync(`/sys/fs/cgroup/${strippedPath}`).ino
|
|
25
47
|
} catch (err) {
|
|
26
|
-
|
|
48
|
+
return 0
|
|
27
49
|
}
|
|
28
50
|
}
|
|
29
51
|
|
|
30
52
|
module.exports = {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
53
|
+
inject (carrier) {
|
|
54
|
+
if (entityId) {
|
|
55
|
+
carrier['Datadog-Container-Id'] = entityId
|
|
56
|
+
carrier['Datadog-Entity-ID'] = `ci-${entityId}`
|
|
57
|
+
} else if (inode) {
|
|
58
|
+
carrier['Datadog-Entity-ID'] = `in-${inode}`
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (DD_EXTERNAL_ENV) {
|
|
62
|
+
carrier['Datadog-External-Env'] = DD_EXTERNAL_ENV
|
|
63
|
+
}
|
|
34
64
|
}
|
|
35
65
|
}
|
|
@@ -15,7 +15,6 @@ const { storage } = require('../../../../datadog-core')
|
|
|
15
15
|
const log = require('../../log')
|
|
16
16
|
|
|
17
17
|
const maxActiveRequests = 8
|
|
18
|
-
const containerId = docker.id()
|
|
19
18
|
|
|
20
19
|
let activeRequests = 0
|
|
21
20
|
|
|
@@ -63,9 +62,7 @@ function request (data, options, callback) {
|
|
|
63
62
|
options.headers['Content-Length'] = byteLength(dataArray)
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
|
|
67
|
-
options.headers['Datadog-Container-ID'] = containerId
|
|
68
|
-
}
|
|
65
|
+
docker.inject(options.headers)
|
|
69
66
|
|
|
70
67
|
options.agent = isSecure ? httpsAgent : httpAgent
|
|
71
68
|
|
|
@@ -22,7 +22,9 @@ const PROCESS_ID = constants.PROCESS_ID
|
|
|
22
22
|
const ERROR_MESSAGE = constants.ERROR_MESSAGE
|
|
23
23
|
const ERROR_STACK = constants.ERROR_STACK
|
|
24
24
|
const ERROR_TYPE = constants.ERROR_TYPE
|
|
25
|
+
const { IGNORE_OTEL_ERROR } = constants
|
|
25
26
|
|
|
27
|
+
// TODO(BridgeAR)[31.03.2025]: Should these land in the constants file?
|
|
26
28
|
const map = {
|
|
27
29
|
'operation.name': 'name',
|
|
28
30
|
'service.name': 'service',
|
|
@@ -69,48 +71,46 @@ function setSingleSpanIngestionTags (span, options) {
|
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
function extractSpanLinks (formattedSpan, span) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (attributes && Object.keys(attributes).length > 0) {
|
|
82
|
-
formattedLink.attributes = attributes
|
|
83
|
-
}
|
|
84
|
-
if (context?._sampling?.priority >= 0) formattedLink.flags = context._sampling.priority > 0 ? 1 : 0
|
|
85
|
-
if (context?._tracestate) formattedLink.tracestate = context._tracestate.toString()
|
|
74
|
+
if (!span._links?.length) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
const links = span._links.map(link => {
|
|
78
|
+
const { context, attributes } = link
|
|
79
|
+
const formattedLink = {
|
|
80
|
+
trace_id: context.toTraceId(true),
|
|
81
|
+
span_id: context.toSpanId(true)
|
|
82
|
+
}
|
|
86
83
|
|
|
87
|
-
|
|
84
|
+
if (attributes && Object.keys(attributes).length > 0) {
|
|
85
|
+
formattedLink.attributes = attributes
|
|
88
86
|
}
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
if (context?._sampling?.priority >= 0) formattedLink.flags = context._sampling.priority > 0 ? 1 : 0
|
|
88
|
+
if (context?._tracestate) formattedLink.tracestate = context._tracestate.toString()
|
|
89
|
+
|
|
90
|
+
return formattedLink
|
|
91
|
+
})
|
|
92
|
+
formattedSpan.meta['_dd.span_links'] = JSON.stringify(links)
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
function extractSpanEvents (formattedSpan, span) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
for (const event of span._events) {
|
|
97
|
-
const formattedEvent = {
|
|
98
|
-
name: event.name,
|
|
99
|
-
time_unix_nano: Math.round(event.startTime * 1e6),
|
|
100
|
-
attributes: event.attributes && Object.keys(event.attributes).length > 0 ? event.attributes : undefined
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
events.push(formattedEvent)
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
if (events.length > 0) {
|
|
107
|
-
formattedSpan.span_events = events
|
|
96
|
+
if (!span._events?.length) {
|
|
97
|
+
return
|
|
108
98
|
}
|
|
99
|
+
const events = span._events.map(event => {
|
|
100
|
+
return {
|
|
101
|
+
name: event.name,
|
|
102
|
+
time_unix_nano: Math.round(event.startTime * 1e6),
|
|
103
|
+
attributes: event.attributes && Object.keys(event.attributes).length > 0 ? event.attributes : undefined
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
formattedSpan.span_events = events
|
|
109
107
|
}
|
|
110
108
|
|
|
111
109
|
function extractTags (formattedSpan, span) {
|
|
112
110
|
const context = span.context()
|
|
113
111
|
const origin = context._trace.origin
|
|
112
|
+
// TODO(BridgeAR)[31.03.2025]: Look into changing the way we store tags. Using
|
|
113
|
+
// a map is likely faster short term.
|
|
114
114
|
const tags = context._tags
|
|
115
115
|
const hostname = context._hostname
|
|
116
116
|
const priority = context._sampling.priority
|
|
@@ -126,43 +126,48 @@ function extractTags (formattedSpan, span) {
|
|
|
126
126
|
registerExtraService(tags['service.name'])
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
for (const tag
|
|
129
|
+
for (const [tag, value] of Object.entries(tags)) {
|
|
130
|
+
// TODO(BridgeAR)[31.03.2025]: Check how many tags are defined in average.
|
|
131
|
+
// In case there are more than 2 tags in average, check for all special
|
|
132
|
+
// cases up front and loop over the tags afterwards, skipping the already
|
|
133
|
+
// visited property names by checking a map with these keys.
|
|
130
134
|
switch (tag) {
|
|
131
135
|
case 'service.name':
|
|
132
136
|
case 'span.type':
|
|
133
137
|
case 'resource.name':
|
|
134
|
-
addTag(formattedSpan, {}, map[tag],
|
|
138
|
+
addTag(formattedSpan, {}, map[tag], value)
|
|
135
139
|
break
|
|
136
140
|
// HACK: remove when Datadog supports numeric status code
|
|
137
141
|
case 'http.status_code':
|
|
138
|
-
addTag(formattedSpan.meta, {}, tag,
|
|
142
|
+
addTag(formattedSpan.meta, {}, tag, value && String(value))
|
|
139
143
|
break
|
|
140
144
|
case 'analytics.event':
|
|
141
|
-
addTag({}, formattedSpan.metrics, ANALYTICS,
|
|
145
|
+
addTag({}, formattedSpan.metrics, ANALYTICS, value === undefined || value ? 1 : 0)
|
|
142
146
|
break
|
|
143
147
|
case HOSTNAME_KEY:
|
|
144
148
|
case MEASURED:
|
|
145
|
-
addTag({}, formattedSpan.metrics, tag,
|
|
149
|
+
addTag({}, formattedSpan.metrics, tag, value === undefined || value ? 1 : 0)
|
|
146
150
|
break
|
|
151
|
+
// TODO(BridgeAR)[31.03.2025]: How come we use two different ways to pass
|
|
152
|
+
// through errors? Can we just unify the behavior to always use one way?
|
|
147
153
|
case 'error':
|
|
148
154
|
if (context._name !== 'fs.operation') {
|
|
149
|
-
extractError(formattedSpan,
|
|
155
|
+
extractError(formattedSpan, value)
|
|
150
156
|
}
|
|
151
157
|
break
|
|
152
158
|
case ERROR_TYPE:
|
|
153
159
|
case ERROR_MESSAGE:
|
|
154
160
|
case ERROR_STACK:
|
|
155
161
|
// HACK: remove when implemented in the backend
|
|
156
|
-
if (context._name
|
|
157
|
-
// HACK: to ensure otel.recordException does not influence formattedSpan.error
|
|
158
|
-
if (tags.setTraceError) {
|
|
159
|
-
formattedSpan.error = 1
|
|
160
|
-
}
|
|
161
|
-
} else {
|
|
162
|
+
if (context._name === 'fs.operation') {
|
|
162
163
|
break
|
|
163
164
|
}
|
|
165
|
+
// otel.recordException should not influence trace.error
|
|
166
|
+
if (!tags[IGNORE_OTEL_ERROR]) {
|
|
167
|
+
formattedSpan.error = 1
|
|
168
|
+
}
|
|
164
169
|
default: // eslint-disable-line no-fallthrough
|
|
165
|
-
addTag(formattedSpan.meta, formattedSpan.metrics, tag,
|
|
170
|
+
addTag(formattedSpan.meta, formattedSpan.metrics, tag, value)
|
|
166
171
|
}
|
|
167
172
|
}
|
|
168
173
|
setSingleSpanIngestionTags(formattedSpan, context._spanSampling)
|
|
@@ -193,8 +198,8 @@ function extractChunkTags (formattedSpan, span) {
|
|
|
193
198
|
|
|
194
199
|
if (!isLocalRoot) return
|
|
195
200
|
|
|
196
|
-
for (const key
|
|
197
|
-
addTag(formattedSpan.meta, formattedSpan.metrics, key,
|
|
201
|
+
for (const [key, value] of Object.entries(context._trace.tags)) {
|
|
202
|
+
addTag(formattedSpan.meta, formattedSpan.metrics, key, value)
|
|
198
203
|
}
|
|
199
204
|
}
|
|
200
205
|
|
|
@@ -205,6 +210,8 @@ function extractError (formattedSpan, error) {
|
|
|
205
210
|
|
|
206
211
|
if (isError(error)) {
|
|
207
212
|
// AggregateError only has a code and no message.
|
|
213
|
+
// TODO(BridgeAR)[31.03.2025]: An AggregateError can have a message. Should
|
|
214
|
+
// the code just generally be added, if available?
|
|
208
215
|
addTag(formattedSpan.meta, formattedSpan.metrics, ERROR_MESSAGE, error.message || error.code)
|
|
209
216
|
addTag(formattedSpan.meta, formattedSpan.metrics, ERROR_TYPE, error.name)
|
|
210
217
|
addTag(formattedSpan.meta, formattedSpan.metrics, ERROR_STACK, error.stack)
|
|
@@ -223,30 +230,21 @@ function addTag (meta, metrics, key, value, nested) {
|
|
|
223
230
|
case 'boolean':
|
|
224
231
|
metrics[key] = value ? 1 : 0
|
|
225
232
|
break
|
|
226
|
-
|
|
227
|
-
break
|
|
228
|
-
case 'object':
|
|
229
|
-
if (value === null) break
|
|
233
|
+
default:
|
|
234
|
+
if (value == null) break
|
|
230
235
|
|
|
231
236
|
// Special case for Node.js Buffer and URL
|
|
237
|
+
// TODO(BridgeAR)[31.03.2025]: Figure out if all typed arrays should be treated as buffers.
|
|
232
238
|
if (isNodeBuffer(value) || isUrl(value)) {
|
|
233
239
|
metrics[key] = value.toString()
|
|
234
240
|
} else if (!Array.isArray(value) && !nested) {
|
|
235
|
-
for (const prop
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
addTag(meta, metrics, `${key}.${prop}`, value[prop], true)
|
|
241
|
+
for (const [prop, val] of Object.entries(value)) {
|
|
242
|
+
addTag(meta, metrics, `${key}.${prop}`, val, true)
|
|
239
243
|
}
|
|
240
244
|
}
|
|
241
|
-
|
|
242
|
-
break
|
|
243
245
|
}
|
|
244
246
|
}
|
|
245
247
|
|
|
246
|
-
function hasOwn (object, prop) {
|
|
247
|
-
return Object.prototype.hasOwnProperty.call(object, prop)
|
|
248
|
-
}
|
|
249
|
-
|
|
250
248
|
function isNodeBuffer (obj) {
|
|
251
249
|
return obj.constructor && obj.constructor.name === 'Buffer' &&
|
|
252
250
|
typeof obj.readInt8 === 'function' &&
|
|
@@ -37,13 +37,13 @@ class LLMObsPlugin extends TracingPlugin {
|
|
|
37
37
|
// register options may not be set for operations we do not trace with llmobs
|
|
38
38
|
// ie OpenAI fine tuning jobs, file jobs, etc.
|
|
39
39
|
if (registerOptions) {
|
|
40
|
-
telemetry.incrementLLMObsSpanStartCount({ autoinstrumented: true, integration: this.constructor.
|
|
40
|
+
telemetry.incrementLLMObsSpanStartCount({ autoinstrumented: true, integration: this.constructor.integration })
|
|
41
41
|
|
|
42
42
|
ctx.llmobs = {} // initialize context-based namespace
|
|
43
43
|
llmobsStorage.enterWith({ span })
|
|
44
44
|
ctx.llmobs.parent = parent
|
|
45
45
|
|
|
46
|
-
this._tagger.registerLLMObsSpan(span, { parent, integration: this.constructor.
|
|
46
|
+
this._tagger.registerLLMObsSpan(span, { parent, integration: this.constructor.integration, ...registerOptions })
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -20,7 +20,8 @@ const ChatModelHandler = require('./handlers/chat_model')
|
|
|
20
20
|
const LlmHandler = require('./handlers/llm')
|
|
21
21
|
const EmbeddingHandler = require('./handlers/embedding')
|
|
22
22
|
|
|
23
|
-
class
|
|
23
|
+
class BaseLangChainLLMObsPlugin extends LLMObsPlugin {
|
|
24
|
+
static get integration () { return 'langchain' }
|
|
24
25
|
static get id () { return 'langchain' }
|
|
25
26
|
static get prefix () {
|
|
26
27
|
return 'tracing:apm:langchain:invoke'
|
|
@@ -55,8 +56,11 @@ class LangChainLLMObsPlugin extends LLMObsPlugin {
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
setLLMObsTags (ctx) {
|
|
59
|
+
ctx.args = ctx.arguments
|
|
60
|
+
ctx.instance = ctx.self
|
|
61
|
+
|
|
58
62
|
const span = ctx.currentStore?.span
|
|
59
|
-
const type = ctx.type // langchain operation type (oneof chain,chat_model,llm,embedding)
|
|
63
|
+
const type = ctx.type = this.constructor.lcType // langchain operation type (oneof chain,chat_model,llm,embedding)
|
|
60
64
|
|
|
61
65
|
if (!Object.keys(this._handlers).includes(type)) {
|
|
62
66
|
log.warn(`Unsupported LangChain operation type: ${type}`)
|
|
@@ -129,4 +133,59 @@ class LangChainLLMObsPlugin extends LLMObsPlugin {
|
|
|
129
133
|
}
|
|
130
134
|
}
|
|
131
135
|
|
|
132
|
-
|
|
136
|
+
class RunnableSequenceInvokePlugin extends BaseLangChainLLMObsPlugin {
|
|
137
|
+
static get id () { return 'llmobs_langchain_rs_invoke' }
|
|
138
|
+
static get lcType () { return 'chain' }
|
|
139
|
+
static get prefix () {
|
|
140
|
+
return 'tracing:orchestrion:@langchain/core:RunnableSequence_invoke'
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
class RunnableSequenceBatchPlugin extends BaseLangChainLLMObsPlugin {
|
|
145
|
+
static get id () { return 'llmobs_langchain_rs_batch' }
|
|
146
|
+
static get lcType () { return 'chain' }
|
|
147
|
+
static get prefix () {
|
|
148
|
+
return 'tracing:orchestrion:@langchain/core:RunnableSequence_batch'
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
class BaseChatModelGeneratePlugin extends BaseLangChainLLMObsPlugin {
|
|
153
|
+
static get id () { return 'llmobs_langchain_chat_model_generate' }
|
|
154
|
+
static get lcType () { return 'chat_model' }
|
|
155
|
+
static get prefix () {
|
|
156
|
+
return 'tracing:orchestrion:@langchain/core:BaseChatModel_generate'
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
class BaseLLMGeneratePlugin extends BaseLangChainLLMObsPlugin {
|
|
161
|
+
static get id () { return 'llmobs_langchain_llm_generate' }
|
|
162
|
+
static get lcType () { return 'llm' }
|
|
163
|
+
static get prefix () {
|
|
164
|
+
return 'tracing:orchestrion:@langchain/core:BaseLLM_generate'
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
class EmbeddingsEmbedQueryPlugin extends BaseLangChainLLMObsPlugin {
|
|
169
|
+
static get id () { return 'llmobs_langchain_embeddings_embed_query' }
|
|
170
|
+
static get lcType () { return 'embedding' }
|
|
171
|
+
static get prefix () {
|
|
172
|
+
return 'tracing:apm:@langchain/core:Embeddings_embedQuery'
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
class EmbeddingsEmbedDocumentsPlugin extends BaseLangChainLLMObsPlugin {
|
|
177
|
+
static get id () { return 'llmobs_langchain_embeddings_embed_documents' }
|
|
178
|
+
static get lcType () { return 'embedding' }
|
|
179
|
+
static get prefix () {
|
|
180
|
+
return 'tracing:apm:@langchain/core:Embeddings_embedDocuments'
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = [
|
|
185
|
+
RunnableSequenceInvokePlugin,
|
|
186
|
+
RunnableSequenceBatchPlugin,
|
|
187
|
+
BaseChatModelGeneratePlugin,
|
|
188
|
+
BaseLLMGeneratePlugin,
|
|
189
|
+
EmbeddingsEmbedQueryPlugin,
|
|
190
|
+
EmbeddingsEmbedDocumentsPlugin
|
|
191
|
+
]
|
|
@@ -7,7 +7,8 @@ const {
|
|
|
7
7
|
} = require('../../../../datadog-plugin-google-cloud-vertexai/src/utils')
|
|
8
8
|
|
|
9
9
|
class VertexAILLMObsPlugin extends LLMObsPlugin {
|
|
10
|
-
static get
|
|
10
|
+
static get integration () { return 'vertexai' } // used for llmobs telemetry
|
|
11
|
+
static get id () { return 'vertexai' }
|
|
11
12
|
static get prefix () {
|
|
12
13
|
return 'tracing:apm:vertexai:request'
|
|
13
14
|
}
|
|
@@ -41,12 +41,12 @@ class LLMObsSpanWriter extends BaseWriter {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
makePayload (events) {
|
|
44
|
-
return {
|
|
44
|
+
return events.map(event => ({
|
|
45
45
|
'_dd.stage': 'raw',
|
|
46
46
|
'_dd.tracer_version': tracerVersion,
|
|
47
47
|
event_type: this._eventType,
|
|
48
|
-
spans:
|
|
49
|
-
}
|
|
48
|
+
spans: [event]
|
|
49
|
+
}))
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
_truncateSpanEvent (event) {
|
|
@@ -13,6 +13,7 @@ const defaultLogger = {
|
|
|
13
13
|
let enabled = false
|
|
14
14
|
let logger = defaultLogger
|
|
15
15
|
let logChannel = new LogChannel()
|
|
16
|
+
let stackTraceLimitFunction = onError
|
|
16
17
|
|
|
17
18
|
function withNoop (fn) {
|
|
18
19
|
const store = storage('legacy').getStore()
|
|
@@ -61,12 +62,28 @@ function getErrorLog (err) {
|
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
function setStackTraceLimitFunction (fn) {
|
|
66
|
+
if (typeof fn !== 'function') {
|
|
67
|
+
throw new TypeError('stackTraceLimitFunction must be a function')
|
|
68
|
+
}
|
|
69
|
+
stackTraceLimitFunction = fn
|
|
70
|
+
}
|
|
71
|
+
|
|
64
72
|
function onError (err) {
|
|
65
73
|
const { formatted, cause } = getErrorLog(err)
|
|
66
74
|
|
|
67
75
|
// calling twice logger.error() because Error cause is only available in nodejs v16.9.0
|
|
68
76
|
// TODO: replace it with Error(message, { cause }) when cause has broad support
|
|
69
|
-
if (formatted)
|
|
77
|
+
if (formatted) {
|
|
78
|
+
withNoop(() => {
|
|
79
|
+
const l = Error.stackTraceLimit
|
|
80
|
+
Error.stackTraceLimit = 0
|
|
81
|
+
const e = new Error(formatted)
|
|
82
|
+
Error.stackTraceLimit = l
|
|
83
|
+
Error.captureStackTrace(e, stackTraceLimitFunction)
|
|
84
|
+
logger.error(e)
|
|
85
|
+
})
|
|
86
|
+
}
|
|
70
87
|
if (cause) withNoop(() => logger.error(cause))
|
|
71
88
|
}
|
|
72
89
|
|
|
@@ -122,4 +139,4 @@ function trace (...args) {
|
|
|
122
139
|
onTrace(Log.parse(...args))
|
|
123
140
|
}
|
|
124
141
|
|
|
125
|
-
module.exports = { use, toggle, reset, error, warn, info, debug, trace }
|
|
142
|
+
module.exports = { use, toggle, reset, error, warn, info, debug, trace, setStackTraceLimitFunction }
|
|
@@ -9,7 +9,7 @@ const { timeInputToHrTime } = require('@opentelemetry/core')
|
|
|
9
9
|
|
|
10
10
|
const tracer = require('../../')
|
|
11
11
|
const DatadogSpan = require('../opentracing/span')
|
|
12
|
-
const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../constants')
|
|
12
|
+
const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK, IGNORE_OTEL_ERROR } = require('../constants')
|
|
13
13
|
const { SERVICE_NAME, RESOURCE_NAME } = require('../../../../ext/tags')
|
|
14
14
|
const kinds = require('../../../../ext/kinds')
|
|
15
15
|
|
|
@@ -237,7 +237,8 @@ class Span {
|
|
|
237
237
|
this._hasStatus = true
|
|
238
238
|
if (code === 2) {
|
|
239
239
|
this._ddSpan.addTags({
|
|
240
|
-
[ERROR_MESSAGE]: message
|
|
240
|
+
[ERROR_MESSAGE]: message,
|
|
241
|
+
[IGNORE_OTEL_ERROR]: false
|
|
241
242
|
})
|
|
242
243
|
}
|
|
243
244
|
}
|
|
@@ -278,12 +279,11 @@ class Span {
|
|
|
278
279
|
}
|
|
279
280
|
|
|
280
281
|
recordException (exception, timeInput) {
|
|
281
|
-
// HACK: identifier is added so that trace.error remains unchanged after a call to otel.recordException
|
|
282
282
|
this._ddSpan.addTags({
|
|
283
283
|
[ERROR_TYPE]: exception.name,
|
|
284
284
|
[ERROR_MESSAGE]: exception.message,
|
|
285
285
|
[ERROR_STACK]: exception.stack,
|
|
286
|
-
|
|
286
|
+
[IGNORE_OTEL_ERROR]: this._ddSpan.context()._tags[IGNORE_OTEL_ERROR] ?? true
|
|
287
287
|
})
|
|
288
288
|
const attributes = {}
|
|
289
289
|
if (exception.message) attributes['exception.message'] = exception.message
|
|
@@ -298,6 +298,7 @@ class TextMapPropagator {
|
|
|
298
298
|
|
|
299
299
|
_extractSpanContext (carrier) {
|
|
300
300
|
let context = null
|
|
301
|
+
let style = ''
|
|
301
302
|
for (const extractor of this._config.tracePropagationStyle.extract) {
|
|
302
303
|
let extractedContext = null
|
|
303
304
|
switch (extractor) {
|
|
@@ -331,9 +332,9 @@ class TextMapPropagator {
|
|
|
331
332
|
|
|
332
333
|
if (context === null) {
|
|
333
334
|
context = extractedContext
|
|
335
|
+
style = extractor
|
|
334
336
|
if (this._config.tracePropagationExtractFirst) {
|
|
335
|
-
|
|
336
|
-
return context
|
|
337
|
+
break
|
|
337
338
|
}
|
|
338
339
|
} else {
|
|
339
340
|
// If extractor is tracecontext, add tracecontext specific information to the context
|
|
@@ -342,7 +343,7 @@ class TextMapPropagator {
|
|
|
342
343
|
this._extractTraceparentContext(carrier), context, carrier)
|
|
343
344
|
}
|
|
344
345
|
if (extractedContext._traceId && extractedContext._spanId &&
|
|
345
|
-
|
|
346
|
+
extractedContext.toTraceId(true) !== context.toTraceId(true)) {
|
|
346
347
|
const link = {
|
|
347
348
|
context: extractedContext,
|
|
348
349
|
attributes: { reason: 'terminated_context', context_headers: extractor }
|
|
@@ -354,6 +355,19 @@ class TextMapPropagator {
|
|
|
354
355
|
|
|
355
356
|
this._extractBaggageItems(carrier, context)
|
|
356
357
|
|
|
358
|
+
if (this._config.tracePropagationBehaviorExtract === 'ignore') {
|
|
359
|
+
context._links = []
|
|
360
|
+
} else if (this._config.tracePropagationBehaviorExtract === 'restart') {
|
|
361
|
+
context._links = []
|
|
362
|
+
context._links.push({
|
|
363
|
+
context,
|
|
364
|
+
attributes:
|
|
365
|
+
{
|
|
366
|
+
reason: 'propagation_behavior_extract', context_headers: style
|
|
367
|
+
}
|
|
368
|
+
})
|
|
369
|
+
}
|
|
370
|
+
|
|
357
371
|
return context || this._extractSqsdContext(carrier)
|
|
358
372
|
}
|
|
359
373
|
|
|
@@ -319,6 +319,12 @@ class DatadogSpan {
|
|
|
319
319
|
let spanContext
|
|
320
320
|
let startTime
|
|
321
321
|
|
|
322
|
+
let baggage = {}
|
|
323
|
+
if (parent && parent._isRemote && this._parentTracer?._config?.tracePropagationBehaviorExtract !== 'continue') {
|
|
324
|
+
baggage = parent._baggageItems
|
|
325
|
+
parent = null
|
|
326
|
+
}
|
|
327
|
+
|
|
322
328
|
if (fields.context) {
|
|
323
329
|
spanContext = fields.context
|
|
324
330
|
if (!spanContext._trace.startTime) {
|
|
@@ -352,6 +358,10 @@ class DatadogSpan {
|
|
|
352
358
|
.padStart(8, '0')
|
|
353
359
|
.padEnd(16, '0')
|
|
354
360
|
}
|
|
361
|
+
|
|
362
|
+
if (this._parentTracer?._config?.tracePropagationBehaviorExtract === 'restart') {
|
|
363
|
+
spanContext._baggageItems = baggage
|
|
364
|
+
}
|
|
355
365
|
}
|
|
356
366
|
|
|
357
367
|
spanContext._trace.ticks = spanContext._trace.ticks || now()
|
|
@@ -133,6 +133,7 @@ module.exports = class PluginManager {
|
|
|
133
133
|
dbmPropagationMode,
|
|
134
134
|
dsmEnabled,
|
|
135
135
|
clientIpEnabled,
|
|
136
|
+
clientIpHeader,
|
|
136
137
|
memcachedCommandEnabled,
|
|
137
138
|
ciVisibilityTestSessionName,
|
|
138
139
|
ciVisAgentlessLogSubmissionEnabled,
|
|
@@ -148,6 +149,7 @@ module.exports = class PluginManager {
|
|
|
148
149
|
site,
|
|
149
150
|
url,
|
|
150
151
|
headers: headerTags || [],
|
|
152
|
+
clientIpHeader,
|
|
151
153
|
ciVisibilityTestSessionName,
|
|
152
154
|
ciVisAgentlessLogSubmissionEnabled,
|
|
153
155
|
isTestDynamicInstrumentationEnabled,
|
|
@@ -96,6 +96,9 @@ const CUCUMBER_WORKER_TRACE_PAYLOAD_CODE = 70
|
|
|
96
96
|
// mocha worker variables
|
|
97
97
|
const MOCHA_WORKER_TRACE_PAYLOAD_CODE = 80
|
|
98
98
|
|
|
99
|
+
// playwright worker variables
|
|
100
|
+
const PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE = 90
|
|
101
|
+
|
|
99
102
|
// Early flake detection util strings
|
|
100
103
|
const EFD_STRING = "Retried by Datadog's Early Flake Detection"
|
|
101
104
|
const EFD_TEST_NAME_REGEX = new RegExp(EFD_STRING + ' \\(#\\d+\\): ', 'g')
|
|
@@ -117,6 +120,12 @@ const TEST_LEVEL_EVENT_TYPES = [
|
|
|
117
120
|
'test_module_end',
|
|
118
121
|
'test_session_end'
|
|
119
122
|
]
|
|
123
|
+
const TEST_RETRY_REASON_TYPES = {
|
|
124
|
+
efd: 'early_flake_detection',
|
|
125
|
+
atr: 'auto_test_retry',
|
|
126
|
+
atf: 'attempt_to_fix',
|
|
127
|
+
ext: 'external'
|
|
128
|
+
}
|
|
120
129
|
|
|
121
130
|
const DD_TEST_IS_USER_PROVIDED_SERVICE = '_dd.test.is_user_provided_service'
|
|
122
131
|
|
|
@@ -162,6 +171,7 @@ module.exports = {
|
|
|
162
171
|
JEST_WORKER_LOGS_PAYLOAD_CODE,
|
|
163
172
|
CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
|
|
164
173
|
MOCHA_WORKER_TRACE_PAYLOAD_CODE,
|
|
174
|
+
PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE,
|
|
165
175
|
TEST_SOURCE_START,
|
|
166
176
|
TEST_SKIPPED_BY_ITR,
|
|
167
177
|
TEST_IS_NEW,
|
|
@@ -223,6 +233,7 @@ module.exports = {
|
|
|
223
233
|
DD_CAPABILITIES_TEST_MANAGEMENT_DISABLE,
|
|
224
234
|
DD_CAPABILITIES_TEST_MANAGEMENT_ATTEMPT_TO_FIX,
|
|
225
235
|
TEST_LEVEL_EVENT_TYPES,
|
|
236
|
+
TEST_RETRY_REASON_TYPES,
|
|
226
237
|
getNumFromKnownTests,
|
|
227
238
|
getFileAndLineNumberFromError,
|
|
228
239
|
DI_ERROR_DEBUG_INFO_CAPTURED,
|
|
@@ -14,7 +14,7 @@ module.exports = {
|
|
|
14
14
|
debug: (message) => log.debug(message),
|
|
15
15
|
info: (message) => log.info(message),
|
|
16
16
|
warn: (message) => log.warn(message),
|
|
17
|
-
error: (
|
|
17
|
+
error: (...args) => log.error(...args)
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const libraryInjected = injectionEnabled.length > 0
|