dd-trace 5.31.0 → 5.32.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 -0
- package/README.md +9 -7
- package/package.json +5 -4
- package/packages/datadog-instrumentations/src/aws-sdk.js +2 -1
- package/packages/datadog-instrumentations/src/cucumber.js +14 -5
- package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
- package/packages/datadog-instrumentations/src/jest.js +70 -36
- package/packages/datadog-instrumentations/src/mocha/utils.js +23 -7
- package/packages/datadog-instrumentations/src/node-serialize.js +22 -0
- package/packages/datadog-instrumentations/src/openai.js +2 -0
- package/packages/datadog-instrumentations/src/vitest.js +107 -59
- package/packages/datadog-instrumentations/src/vm.js +49 -0
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime.js +295 -0
- package/packages/datadog-plugin-aws-sdk/src/services/index.js +1 -0
- package/packages/datadog-plugin-cucumber/src/index.js +30 -32
- package/packages/datadog-plugin-jest/src/index.js +34 -37
- package/packages/datadog-plugin-langchain/src/index.js +12 -80
- package/packages/datadog-plugin-langchain/src/tracing.js +89 -0
- package/packages/datadog-plugin-mocha/src/index.js +18 -36
- package/packages/datadog-plugin-vitest/src/index.js +20 -34
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +2 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/untrusted-deserialization-analyzer.js +16 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +9 -8
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +37 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +65 -28
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +57 -17
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +18 -3
- package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +20 -3
- package/packages/dd-trace/src/config.js +39 -3
- package/packages/dd-trace/src/crashtracking/crashtracker.js +9 -0
- package/packages/dd-trace/src/crashtracking/noop.js +3 -0
- package/packages/dd-trace/src/datastreams/fnv.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/config.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/defaults.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +30 -13
- package/packages/dd-trace/src/debugger/devtools_client/send.js +4 -8
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +35 -1
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +112 -0
- package/packages/dd-trace/src/debugger/devtools_client/status.js +12 -10
- package/packages/dd-trace/src/debugger/index.js +2 -13
- package/packages/dd-trace/src/llmobs/plugins/base.js +40 -11
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chain.js +24 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +111 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/embedding.js +42 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +102 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/llm.js +32 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +131 -0
- package/packages/dd-trace/src/llmobs/plugins/openai.js +1 -1
- package/packages/dd-trace/src/llmobs/tagger.js +11 -3
- package/packages/dd-trace/src/llmobs/util.js +7 -1
- package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +3 -3
- package/packages/dd-trace/src/opentelemetry/context_manager.js +43 -3
- package/packages/dd-trace/src/plugins/ci_plugin.js +57 -27
- package/packages/dd-trace/src/plugins/util/test.js +42 -12
- package/packages/dd-trace/src/priority_sampler.js +4 -1
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +21 -0
- package/packages/dd-trace/src/profiling/profiler.js +11 -8
- package/packages/dd-trace/src/profiling/profilers/events.js +17 -1
- package/packages/dd-trace/src/proxy.js +6 -3
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const LangChainLLMObsHandler = require('.')
|
|
4
|
+
const LLMObsTagger = require('../../../tagger')
|
|
5
|
+
const { spanHasError } = require('../../../util')
|
|
6
|
+
|
|
7
|
+
class LangChainLLMObsEmbeddingHandler extends LangChainLLMObsHandler {
|
|
8
|
+
setMetaTags ({ span, inputs, results }) {
|
|
9
|
+
const isWorkflow = LLMObsTagger.getSpanKind(span) === 'workflow'
|
|
10
|
+
let embeddingInput, embeddingOutput
|
|
11
|
+
|
|
12
|
+
if (isWorkflow) {
|
|
13
|
+
embeddingInput = this.formatIO(inputs)
|
|
14
|
+
} else {
|
|
15
|
+
const input = Array.isArray(inputs) ? inputs : [inputs]
|
|
16
|
+
embeddingInput = input.map(doc => ({ text: doc }))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (spanHasError(span) || !results) {
|
|
20
|
+
embeddingOutput = ''
|
|
21
|
+
} else {
|
|
22
|
+
let embeddingDimensions, embeddingsCount
|
|
23
|
+
if (typeof results[0] === 'number') {
|
|
24
|
+
embeddingsCount = 1
|
|
25
|
+
embeddingDimensions = results.length
|
|
26
|
+
} else {
|
|
27
|
+
embeddingsCount = results.length
|
|
28
|
+
embeddingDimensions = results[0].length
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
embeddingOutput = `[${embeddingsCount} embedding(s) returned with size ${embeddingDimensions}]`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (isWorkflow) {
|
|
35
|
+
this._tagger.tagTextIO(span, embeddingInput, embeddingOutput)
|
|
36
|
+
} else {
|
|
37
|
+
this._tagger.tagEmbeddingIO(span, embeddingInput, embeddingOutput)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = LangChainLLMObsEmbeddingHandler
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const ROLE_MAPPINGS = {
|
|
4
|
+
human: 'user',
|
|
5
|
+
ai: 'assistant',
|
|
6
|
+
system: 'system'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class LangChainLLMObsHandler {
|
|
10
|
+
constructor (tagger) {
|
|
11
|
+
this._tagger = tagger
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
setMetaTags () {}
|
|
15
|
+
|
|
16
|
+
formatIO (messages) {
|
|
17
|
+
if (messages.constructor.name === 'Object') { // plain JSON
|
|
18
|
+
const formatted = {}
|
|
19
|
+
for (const [key, value] of Object.entries(messages)) {
|
|
20
|
+
formatted[key] = this.formatIO(value)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return formatted
|
|
24
|
+
} else if (Array.isArray(messages)) {
|
|
25
|
+
return messages.map(message => this.formatIO(message))
|
|
26
|
+
} else { // either a BaseMesage type or a string
|
|
27
|
+
return this.getContentFromMessage(messages)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
getContentFromMessage (message) {
|
|
32
|
+
if (typeof message === 'string') {
|
|
33
|
+
return message
|
|
34
|
+
} else {
|
|
35
|
+
try {
|
|
36
|
+
const messageContent = {}
|
|
37
|
+
messageContent.content = message.content || ''
|
|
38
|
+
|
|
39
|
+
const role = this.getRole(message)
|
|
40
|
+
if (role) messageContent.role = role
|
|
41
|
+
|
|
42
|
+
return messageContent
|
|
43
|
+
} catch {
|
|
44
|
+
return JSON.stringify(message)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
checkTokenUsageChatOrLLMResult (results) {
|
|
50
|
+
const llmOutput = results.llmOutput
|
|
51
|
+
const tokens = {
|
|
52
|
+
inputTokens: 0,
|
|
53
|
+
outputTokens: 0,
|
|
54
|
+
totalTokens: 0
|
|
55
|
+
}
|
|
56
|
+
if (!llmOutput) return tokens
|
|
57
|
+
const tokenUsage = llmOutput.tokenUsage || llmOutput.usageMetadata || llmOutput.usage || {}
|
|
58
|
+
if (!tokenUsage) return tokens
|
|
59
|
+
|
|
60
|
+
tokens.inputTokens = tokenUsage.promptTokens || tokenUsage.inputTokens || 0
|
|
61
|
+
tokens.outputTokens = tokenUsage.completionTokens || tokenUsage.outputTokens || 0
|
|
62
|
+
tokens.totalTokens = tokenUsage.totalTokens || tokens.inputTokens + tokens.outputTokens
|
|
63
|
+
|
|
64
|
+
return tokens
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
checkTokenUsageFromAIMessage (message) {
|
|
68
|
+
let usage = message.usage_metadata || message.additional_kwargs?.usage
|
|
69
|
+
const runId = message.run_id || message.id || ''
|
|
70
|
+
const runIdBase = runId ? runId.split('-').slice(0, -1).join('-') : ''
|
|
71
|
+
|
|
72
|
+
const responseMetadata = message.response_metadata || {}
|
|
73
|
+
usage = usage || responseMetadata.usage || responseMetadata.tokenUsage || {}
|
|
74
|
+
|
|
75
|
+
const inputTokens = usage.promptTokens || usage.inputTokens || usage.prompt_tokens || usage.input_tokens || 0
|
|
76
|
+
const outputTokens =
|
|
77
|
+
usage.completionTokens || usage.outputTokens || usage.completion_tokens || usage.output_tokens || 0
|
|
78
|
+
const totalTokens = usage.totalTokens || inputTokens + outputTokens
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
tokens: {
|
|
82
|
+
inputTokens,
|
|
83
|
+
outputTokens,
|
|
84
|
+
totalTokens
|
|
85
|
+
},
|
|
86
|
+
runId: runIdBase
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getRole (message) {
|
|
91
|
+
if (message.role) return ROLE_MAPPINGS[message.role] || message.role
|
|
92
|
+
|
|
93
|
+
const type = (
|
|
94
|
+
(typeof message.getType === 'function' && message.getType()) ||
|
|
95
|
+
(typeof message._getType === 'function' && message._getType())
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return ROLE_MAPPINGS[type] || type
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = LangChainLLMObsHandler
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const LangChainLLMObsHandler = require('.')
|
|
4
|
+
const LLMObsTagger = require('../../../tagger')
|
|
5
|
+
const { spanHasError } = require('../../../util')
|
|
6
|
+
|
|
7
|
+
class LangChainLLMObsLlmHandler extends LangChainLLMObsHandler {
|
|
8
|
+
setMetaTags ({ span, inputs, results }) {
|
|
9
|
+
const isWorkflow = LLMObsTagger.getSpanKind(span) === 'workflow'
|
|
10
|
+
const prompts = Array.isArray(inputs) ? inputs : [inputs]
|
|
11
|
+
|
|
12
|
+
let outputs
|
|
13
|
+
if (spanHasError(span)) {
|
|
14
|
+
outputs = [{ content: '' }]
|
|
15
|
+
} else {
|
|
16
|
+
outputs = results.generations.map(completion => ({ content: completion[0].text }))
|
|
17
|
+
|
|
18
|
+
if (!isWorkflow) {
|
|
19
|
+
const tokens = this.checkTokenUsageChatOrLLMResult(results)
|
|
20
|
+
this._tagger.tagMetrics(span, tokens)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (isWorkflow) {
|
|
25
|
+
this._tagger.tagTextIO(span, prompts, outputs)
|
|
26
|
+
} else {
|
|
27
|
+
this._tagger.tagLLMIO(span, prompts, outputs)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = LangChainLLMObsLlmHandler
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const log = require('../../../log')
|
|
4
|
+
const LLMObsPlugin = require('../base')
|
|
5
|
+
|
|
6
|
+
const pluginManager = require('../../../../../..')._pluginManager
|
|
7
|
+
|
|
8
|
+
const ANTHROPIC_PROVIDER_NAME = 'anthropic'
|
|
9
|
+
const BEDROCK_PROVIDER_NAME = 'amazon_bedrock'
|
|
10
|
+
const OPENAI_PROVIDER_NAME = 'openai'
|
|
11
|
+
|
|
12
|
+
const SUPPORTED_INTEGRATIONS = ['openai']
|
|
13
|
+
const LLM_SPAN_TYPES = ['llm', 'chat_model', 'embedding']
|
|
14
|
+
const LLM = 'llm'
|
|
15
|
+
const WORKFLOW = 'workflow'
|
|
16
|
+
const EMBEDDING = 'embedding'
|
|
17
|
+
|
|
18
|
+
const ChainHandler = require('./handlers/chain')
|
|
19
|
+
const ChatModelHandler = require('./handlers/chat_model')
|
|
20
|
+
const LlmHandler = require('./handlers/llm')
|
|
21
|
+
const EmbeddingHandler = require('./handlers/embedding')
|
|
22
|
+
|
|
23
|
+
class LangChainLLMObsPlugin extends LLMObsPlugin {
|
|
24
|
+
static get prefix () {
|
|
25
|
+
return 'tracing:apm:langchain:invoke'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
constructor () {
|
|
29
|
+
super(...arguments)
|
|
30
|
+
|
|
31
|
+
this._handlers = {
|
|
32
|
+
chain: new ChainHandler(this._tagger),
|
|
33
|
+
chat_model: new ChatModelHandler(this._tagger),
|
|
34
|
+
llm: new LlmHandler(this._tagger),
|
|
35
|
+
embedding: new EmbeddingHandler(this._tagger)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getLLMObsSpanRegisterOptions (ctx) {
|
|
40
|
+
const span = ctx.currentStore?.span
|
|
41
|
+
const tags = span?.context()._tags || {}
|
|
42
|
+
|
|
43
|
+
const modelProvider = tags['langchain.request.provider'] // could be undefined
|
|
44
|
+
const modelName = tags['langchain.request.model'] // could be undefined
|
|
45
|
+
const kind = this.getKind(ctx.type, modelProvider)
|
|
46
|
+
const name = tags['resource.name']
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
modelProvider,
|
|
50
|
+
modelName,
|
|
51
|
+
kind,
|
|
52
|
+
name
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
setLLMObsTags (ctx) {
|
|
57
|
+
const span = ctx.currentStore?.span
|
|
58
|
+
const type = ctx.type // langchain operation type (oneof chain,chat_model,llm,embedding)
|
|
59
|
+
|
|
60
|
+
if (!Object.keys(this._handlers).includes(type)) {
|
|
61
|
+
log.warn(`Unsupported LangChain operation type: ${type}`)
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const provider = span?.context()._tags['langchain.request.provider']
|
|
66
|
+
const integrationName = this.getIntegrationName(type, provider)
|
|
67
|
+
this.setMetadata(span, provider)
|
|
68
|
+
|
|
69
|
+
const inputs = ctx.args?.[0]
|
|
70
|
+
const options = ctx.args?.[1]
|
|
71
|
+
const results = ctx.result
|
|
72
|
+
|
|
73
|
+
this._handlers[type].setMetaTags({ span, inputs, results, options, integrationName })
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
setMetadata (span, provider) {
|
|
77
|
+
if (!provider) return
|
|
78
|
+
|
|
79
|
+
const metadata = {}
|
|
80
|
+
|
|
81
|
+
// these fields won't be set for non model-based operations
|
|
82
|
+
const temperature =
|
|
83
|
+
span?.context()._tags[`langchain.request.${provider}.parameters.temperature`] ||
|
|
84
|
+
span?.context()._tags[`langchain.request.${provider}.parameters.model_kwargs.temperature`]
|
|
85
|
+
|
|
86
|
+
const maxTokens =
|
|
87
|
+
span?.context()._tags[`langchain.request.${provider}.parameters.max_tokens`] ||
|
|
88
|
+
span?.context()._tags[`langchain.request.${provider}.parameters.maxTokens`] ||
|
|
89
|
+
span?.context()._tags[`langchain.request.${provider}.parameters.model_kwargs.max_tokens`]
|
|
90
|
+
|
|
91
|
+
if (temperature) {
|
|
92
|
+
metadata.temperature = parseFloat(temperature)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (maxTokens) {
|
|
96
|
+
metadata.maxTokens = parseInt(maxTokens)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this._tagger.tagMetadata(span, metadata)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getKind (type, provider) {
|
|
103
|
+
if (LLM_SPAN_TYPES.includes(type)) {
|
|
104
|
+
const llmobsIntegration = this.getIntegrationName(type, provider)
|
|
105
|
+
|
|
106
|
+
if (!this.isLLMIntegrationEnabled(llmobsIntegration)) {
|
|
107
|
+
return type === 'embedding' ? EMBEDDING : LLM
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return WORKFLOW
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
getIntegrationName (type, provider = 'custom') {
|
|
115
|
+
if (provider.startsWith(BEDROCK_PROVIDER_NAME)) {
|
|
116
|
+
return 'bedrock'
|
|
117
|
+
} else if (provider.startsWith(OPENAI_PROVIDER_NAME)) {
|
|
118
|
+
return 'openai'
|
|
119
|
+
} else if (type === 'chat_model' && provider.startsWith(ANTHROPIC_PROVIDER_NAME)) {
|
|
120
|
+
return 'anthropic'
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return provider
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
isLLMIntegrationEnabled (integration) {
|
|
127
|
+
return SUPPORTED_INTEGRATIONS.includes(integration) && pluginManager?._pluginsByName[integration]?.llmobs?._enabled
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = LangChainLLMObsPlugin
|
|
@@ -7,7 +7,7 @@ class OpenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
7
7
|
return 'tracing:apm:openai:request'
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
getLLMObsSpanRegisterOptions (ctx) {
|
|
11
11
|
const resource = ctx.methodName
|
|
12
12
|
const methodName = gateResource(normalizeOpenAIResourceName(resource))
|
|
13
13
|
if (!methodName) return // we will not trace all openai methods for llmobs
|
|
@@ -40,6 +40,10 @@ class LLMObsTagger {
|
|
|
40
40
|
return registry
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
static getSpanKind (span) {
|
|
44
|
+
return registry.get(span)?.[SPAN_KIND]
|
|
45
|
+
}
|
|
46
|
+
|
|
43
47
|
registerLLMObsSpan (span, {
|
|
44
48
|
modelName,
|
|
45
49
|
modelProvider,
|
|
@@ -60,10 +64,10 @@ class LLMObsTagger {
|
|
|
60
64
|
if (modelName) this._setTag(span, MODEL_NAME, modelName)
|
|
61
65
|
if (modelProvider) this._setTag(span, MODEL_PROVIDER, modelProvider)
|
|
62
66
|
|
|
63
|
-
sessionId = sessionId || parent?.
|
|
67
|
+
sessionId = sessionId || registry.get(parent)?.[SESSION_ID]
|
|
64
68
|
if (sessionId) this._setTag(span, SESSION_ID, sessionId)
|
|
65
69
|
|
|
66
|
-
if (!mlApp) mlApp = parent?.
|
|
70
|
+
if (!mlApp) mlApp = registry.get(parent)?.[ML_APP] || this._config.llmobs.mlApp
|
|
67
71
|
this._setTag(span, ML_APP, mlApp)
|
|
68
72
|
|
|
69
73
|
const parentId =
|
|
@@ -136,6 +140,10 @@ class LLMObsTagger {
|
|
|
136
140
|
this._setTag(span, TAGS, tags)
|
|
137
141
|
}
|
|
138
142
|
|
|
143
|
+
changeKind (span, newKind) {
|
|
144
|
+
this._setTag(span, SPAN_KIND, newKind)
|
|
145
|
+
}
|
|
146
|
+
|
|
139
147
|
_tagText (span, data, key) {
|
|
140
148
|
if (data) {
|
|
141
149
|
if (typeof data === 'string') {
|
|
@@ -310,7 +318,7 @@ class LLMObsTagger {
|
|
|
310
318
|
_setTag (span, key, value) {
|
|
311
319
|
if (!this._config.llmobs.enabled) return
|
|
312
320
|
if (!registry.has(span)) {
|
|
313
|
-
this._handleFailure(
|
|
321
|
+
this._handleFailure(`Span "${span._name}" must be an LLMObs generated span.`)
|
|
314
322
|
return
|
|
315
323
|
}
|
|
316
324
|
|
|
@@ -169,8 +169,14 @@ function getFunctionArguments (fn, args = []) {
|
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
function spanHasError (span) {
|
|
173
|
+
const tags = span.context()._tags
|
|
174
|
+
return !!(tags.error || tags['error.type'])
|
|
175
|
+
}
|
|
176
|
+
|
|
172
177
|
module.exports = {
|
|
173
178
|
encodeUnicode,
|
|
174
179
|
validateKind,
|
|
175
|
-
getFunctionArguments
|
|
180
|
+
getFunctionArguments,
|
|
181
|
+
spanHasError
|
|
176
182
|
}
|
|
@@ -10,10 +10,10 @@ const LLMObsBaseSpanWriter = require('./base')
|
|
|
10
10
|
class LLMObsAgentProxySpanWriter extends LLMObsBaseSpanWriter {
|
|
11
11
|
constructor (config) {
|
|
12
12
|
super({
|
|
13
|
-
intake: config.hostname || 'localhost',
|
|
14
|
-
protocol: 'http:',
|
|
13
|
+
intake: config.url?.hostname || config.hostname || 'localhost',
|
|
14
|
+
protocol: config.url?.protocol || 'http:',
|
|
15
15
|
endpoint: EVP_PROXY_AGENT_ENDPOINT,
|
|
16
|
-
port: config.port
|
|
16
|
+
port: config.url?.port || config.port
|
|
17
17
|
})
|
|
18
18
|
|
|
19
19
|
this._headers[EVP_SUBDOMAIN_HEADER_NAME] = EVP_SUBDOMAIN_HEADER_VALUE
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { storage } = require('../../../datadog-core')
|
|
4
|
-
const { trace, ROOT_CONTEXT } = require('@opentelemetry/api')
|
|
4
|
+
const { trace, ROOT_CONTEXT, propagation } = require('@opentelemetry/api')
|
|
5
5
|
const DataDogSpanContext = require('../opentracing/span_context')
|
|
6
6
|
|
|
7
7
|
const SpanContext = require('./span_context')
|
|
@@ -18,17 +18,40 @@ class ContextManager {
|
|
|
18
18
|
const context = (activeSpan && activeSpan.context()) || store || ROOT_CONTEXT
|
|
19
19
|
|
|
20
20
|
if (!(context instanceof DataDogSpanContext)) {
|
|
21
|
+
const span = trace.getSpan(context)
|
|
22
|
+
// span instanceof NonRecordingSpan
|
|
23
|
+
if (span && span._spanContext && span._spanContext._ddContext && span._spanContext._ddContext._baggageItems) {
|
|
24
|
+
const baggages = span._spanContext._ddContext._baggageItems
|
|
25
|
+
const entries = {}
|
|
26
|
+
for (const [key, value] of Object.entries(baggages)) {
|
|
27
|
+
entries[key] = { value }
|
|
28
|
+
}
|
|
29
|
+
const otelBaggages = propagation.createBaggage(entries)
|
|
30
|
+
return propagation.setBaggage(context, otelBaggages)
|
|
31
|
+
}
|
|
21
32
|
return context
|
|
22
33
|
}
|
|
23
34
|
|
|
35
|
+
const baggages = JSON.parse(activeSpan.getAllBaggageItems())
|
|
36
|
+
const entries = {}
|
|
37
|
+
for (const [key, value] of Object.entries(baggages)) {
|
|
38
|
+
entries[key] = { value }
|
|
39
|
+
}
|
|
40
|
+
const otelBaggages = propagation.createBaggage(entries)
|
|
41
|
+
|
|
24
42
|
if (!context._otelSpanContext) {
|
|
25
43
|
const newSpanContext = new SpanContext(context)
|
|
26
44
|
context._otelSpanContext = newSpanContext
|
|
27
45
|
}
|
|
28
46
|
if (store && trace.getSpanContext(store) === context._otelSpanContext) {
|
|
29
|
-
return
|
|
47
|
+
return otelBaggages
|
|
48
|
+
? propagation.setBaggage(store, otelBaggages)
|
|
49
|
+
: store
|
|
30
50
|
}
|
|
31
|
-
|
|
51
|
+
const wrappedContext = trace.setSpanContext(store || ROOT_CONTEXT, context._otelSpanContext)
|
|
52
|
+
return otelBaggages
|
|
53
|
+
? propagation.setBaggage(wrappedContext, otelBaggages)
|
|
54
|
+
: wrappedContext
|
|
32
55
|
}
|
|
33
56
|
|
|
34
57
|
with (context, fn, thisArg, ...args) {
|
|
@@ -38,9 +61,26 @@ class ContextManager {
|
|
|
38
61
|
const cb = thisArg == null ? fn : fn.bind(thisArg)
|
|
39
62
|
return this._store.run(context, cb, ...args)
|
|
40
63
|
}
|
|
64
|
+
const baggages = propagation.getBaggage(context)
|
|
65
|
+
let baggageItems = []
|
|
66
|
+
if (baggages) {
|
|
67
|
+
baggageItems = baggages.getAllEntries()
|
|
68
|
+
}
|
|
41
69
|
if (span && span._ddSpan) {
|
|
70
|
+
// does otel always override datadog?
|
|
71
|
+
span._ddSpan.removeAllBaggageItems()
|
|
72
|
+
for (const baggage of baggageItems) {
|
|
73
|
+
span._ddSpan.setBaggageItem(baggage[0], baggage[1].value)
|
|
74
|
+
}
|
|
42
75
|
return ddScope.activate(span._ddSpan, run)
|
|
43
76
|
}
|
|
77
|
+
// span instanceof NonRecordingSpan
|
|
78
|
+
if (span && span._spanContext && span._spanContext._ddContext && span._spanContext._ddContext._baggageItems) {
|
|
79
|
+
span._spanContext._ddContext._baggageItems = {}
|
|
80
|
+
for (const baggage of baggageItems) {
|
|
81
|
+
span._spanContext._ddContext._baggageItems[baggage[0]] = baggage[1].value
|
|
82
|
+
}
|
|
83
|
+
}
|
|
44
84
|
return run()
|
|
45
85
|
}
|
|
46
86
|
|
|
@@ -23,7 +23,11 @@ const {
|
|
|
23
23
|
TEST_LEVEL_EVENT_TYPES,
|
|
24
24
|
TEST_SUITE,
|
|
25
25
|
getFileAndLineNumberFromError,
|
|
26
|
-
|
|
26
|
+
DI_ERROR_DEBUG_INFO_CAPTURED,
|
|
27
|
+
DI_DEBUG_ERROR_PREFIX,
|
|
28
|
+
DI_DEBUG_ERROR_SNAPSHOT_ID_SUFFIX,
|
|
29
|
+
DI_DEBUG_ERROR_FILE_SUFFIX,
|
|
30
|
+
DI_DEBUG_ERROR_LINE_SUFFIX
|
|
27
31
|
} = require('./util/test')
|
|
28
32
|
const Plugin = require('./plugin')
|
|
29
33
|
const { COMPONENT } = require('../constants')
|
|
@@ -180,14 +184,18 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
180
184
|
}
|
|
181
185
|
}
|
|
182
186
|
|
|
183
|
-
configure (config) {
|
|
187
|
+
configure (config, shouldGetEnvironmentData = true) {
|
|
184
188
|
super.configure(config)
|
|
185
189
|
|
|
186
|
-
if (config.isTestDynamicInstrumentationEnabled) {
|
|
190
|
+
if (config.isTestDynamicInstrumentationEnabled && !this.di) {
|
|
187
191
|
const testVisibilityDynamicInstrumentation = require('../ci-visibility/dynamic-instrumentation')
|
|
188
192
|
this.di = testVisibilityDynamicInstrumentation
|
|
189
193
|
}
|
|
190
194
|
|
|
195
|
+
if (!shouldGetEnvironmentData) {
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
|
|
191
199
|
this.testEnvironmentMetadata = getTestEnvironmentMetadata(this.constructor.id, this.config)
|
|
192
200
|
|
|
193
201
|
const {
|
|
@@ -292,37 +300,59 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
292
300
|
return testSpan
|
|
293
301
|
}
|
|
294
302
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
303
|
+
onDiBreakpointHit ({ snapshot }) {
|
|
304
|
+
if (!this.activeTestSpan || this.activeTestSpan.context()._isFinished) {
|
|
305
|
+
// This is unexpected and is caused by a race condition.
|
|
306
|
+
log.warn('Breakpoint snapshot could not be attached to the active test span')
|
|
307
|
+
return
|
|
308
|
+
}
|
|
298
309
|
|
|
299
|
-
const
|
|
310
|
+
const stackIndex = this.testErrorStackIndex
|
|
311
|
+
|
|
312
|
+
this.activeTestSpan.setTag(DI_ERROR_DEBUG_INFO_CAPTURED, 'true')
|
|
313
|
+
this.activeTestSpan.setTag(
|
|
314
|
+
`${DI_DEBUG_ERROR_PREFIX}.${stackIndex}.${DI_DEBUG_ERROR_SNAPSHOT_ID_SUFFIX}`,
|
|
315
|
+
snapshot.id
|
|
316
|
+
)
|
|
317
|
+
this.activeTestSpan.setTag(
|
|
318
|
+
`${DI_DEBUG_ERROR_PREFIX}.${stackIndex}.${DI_DEBUG_ERROR_FILE_SUFFIX}`,
|
|
319
|
+
snapshot.probe.location.file
|
|
320
|
+
)
|
|
321
|
+
this.activeTestSpan.setTag(
|
|
322
|
+
`${DI_DEBUG_ERROR_PREFIX}.${stackIndex}.${DI_DEBUG_ERROR_LINE_SUFFIX}`,
|
|
323
|
+
Number(snapshot.probe.location.lines[0])
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
const activeTestSpanContext = this.activeTestSpan.context()
|
|
327
|
+
|
|
328
|
+
this.tracer._exporter.exportDiLogs(this.testEnvironmentMetadata, {
|
|
329
|
+
debugger: { snapshot },
|
|
330
|
+
dd: {
|
|
331
|
+
trace_id: activeTestSpanContext.toTraceId(),
|
|
332
|
+
span_id: activeTestSpanContext.toSpanId()
|
|
333
|
+
}
|
|
334
|
+
})
|
|
335
|
+
}
|
|
300
336
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
337
|
+
removeDiProbe (probeId) {
|
|
338
|
+
return this.di.removeProbe(probeId)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
addDiProbe (err) {
|
|
342
|
+
const [file, line, stackIndex] = getFileAndLineNumberFromError(err, this.repositoryRoot)
|
|
306
343
|
|
|
307
|
-
if (
|
|
308
|
-
|
|
344
|
+
if (!file || !Number.isInteger(line)) {
|
|
345
|
+
log.warn('Could not add breakpoint for dynamic instrumentation')
|
|
346
|
+
return
|
|
309
347
|
}
|
|
310
348
|
|
|
311
|
-
|
|
312
|
-
// TODO: handle race conditions for this.retriedTestIds
|
|
313
|
-
const { traceId, spanId } = this.retriedTestIds
|
|
314
|
-
this.tracer._exporter.exportDiLogs(this.testEnvironmentMetadata, {
|
|
315
|
-
debugger: { snapshot },
|
|
316
|
-
dd: {
|
|
317
|
-
trace_id: traceId,
|
|
318
|
-
span_id: spanId
|
|
319
|
-
}
|
|
320
|
-
})
|
|
321
|
-
})
|
|
349
|
+
const [probeId, setProbePromise] = this.di.addLineProbe({ file, line }, this.onDiBreakpointHit.bind(this))
|
|
322
350
|
|
|
323
351
|
return {
|
|
324
|
-
|
|
325
|
-
|
|
352
|
+
probeId,
|
|
353
|
+
setProbePromise,
|
|
354
|
+
stackIndex,
|
|
355
|
+
file,
|
|
326
356
|
line
|
|
327
357
|
}
|
|
328
358
|
}
|
|
@@ -88,6 +88,7 @@ const TEST_BROWSER_VERSION = 'test.browser.version'
|
|
|
88
88
|
// jest worker variables
|
|
89
89
|
const JEST_WORKER_TRACE_PAYLOAD_CODE = 60
|
|
90
90
|
const JEST_WORKER_COVERAGE_PAYLOAD_CODE = 61
|
|
91
|
+
const JEST_WORKER_LOGS_PAYLOAD_CODE = 62
|
|
91
92
|
|
|
92
93
|
// cucumber worker variables
|
|
93
94
|
const CUCUMBER_WORKER_TRACE_PAYLOAD_CODE = 70
|
|
@@ -108,10 +109,10 @@ const TEST_LEVEL_EVENT_TYPES = [
|
|
|
108
109
|
|
|
109
110
|
// Dynamic instrumentation - Test optimization integration tags
|
|
110
111
|
const DI_ERROR_DEBUG_INFO_CAPTURED = 'error.debug_info_captured'
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
const
|
|
112
|
+
const DI_DEBUG_ERROR_PREFIX = '_dd.debug.error'
|
|
113
|
+
const DI_DEBUG_ERROR_SNAPSHOT_ID_SUFFIX = 'snapshot_id'
|
|
114
|
+
const DI_DEBUG_ERROR_FILE_SUFFIX = 'file'
|
|
115
|
+
const DI_DEBUG_ERROR_LINE_SUFFIX = 'line'
|
|
115
116
|
|
|
116
117
|
module.exports = {
|
|
117
118
|
TEST_CODE_OWNERS,
|
|
@@ -134,6 +135,7 @@ module.exports = {
|
|
|
134
135
|
LIBRARY_VERSION,
|
|
135
136
|
JEST_WORKER_TRACE_PAYLOAD_CODE,
|
|
136
137
|
JEST_WORKER_COVERAGE_PAYLOAD_CODE,
|
|
138
|
+
JEST_WORKER_LOGS_PAYLOAD_CODE,
|
|
137
139
|
CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
|
|
138
140
|
MOCHA_WORKER_TRACE_PAYLOAD_CODE,
|
|
139
141
|
TEST_SOURCE_START,
|
|
@@ -191,9 +193,11 @@ module.exports = {
|
|
|
191
193
|
getNumFromKnownTests,
|
|
192
194
|
getFileAndLineNumberFromError,
|
|
193
195
|
DI_ERROR_DEBUG_INFO_CAPTURED,
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
196
|
+
DI_DEBUG_ERROR_PREFIX,
|
|
197
|
+
DI_DEBUG_ERROR_SNAPSHOT_ID_SUFFIX,
|
|
198
|
+
DI_DEBUG_ERROR_FILE_SUFFIX,
|
|
199
|
+
DI_DEBUG_ERROR_LINE_SUFFIX,
|
|
200
|
+
getFormattedError
|
|
197
201
|
}
|
|
198
202
|
|
|
199
203
|
// Returns pkg manager and its version, separated by '-', e.g. npm-8.15.0 or yarn-1.22.19
|
|
@@ -650,13 +654,30 @@ function getNumFromKnownTests (knownTests) {
|
|
|
650
654
|
return totalNumTests
|
|
651
655
|
}
|
|
652
656
|
|
|
653
|
-
|
|
657
|
+
const DEPENDENCY_FOLDERS = [
|
|
658
|
+
'node_modules',
|
|
659
|
+
'node:',
|
|
660
|
+
'.pnpm',
|
|
661
|
+
'.yarn',
|
|
662
|
+
'.pnp'
|
|
663
|
+
]
|
|
664
|
+
|
|
665
|
+
function getFileAndLineNumberFromError (error, repositoryRoot) {
|
|
654
666
|
// Split the stack trace into individual lines
|
|
655
667
|
const stackLines = error.stack.split('\n')
|
|
656
668
|
|
|
657
|
-
//
|
|
658
|
-
const
|
|
669
|
+
// Remove potential messages on top of the stack that are not frames
|
|
670
|
+
const frames = stackLines.filter(line => line.includes('at ') && line.includes(repositoryRoot))
|
|
671
|
+
|
|
672
|
+
const topRelevantFrameIndex = frames.findIndex(line =>
|
|
673
|
+
line.includes(repositoryRoot) && !DEPENDENCY_FOLDERS.some(pattern => line.includes(pattern))
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
if (topRelevantFrameIndex === -1) {
|
|
677
|
+
return []
|
|
678
|
+
}
|
|
659
679
|
|
|
680
|
+
const topFrame = frames[topRelevantFrameIndex]
|
|
660
681
|
// Regular expression to match the file path, line number, and column number
|
|
661
682
|
const regex = /\s*at\s+(?:.*\()?(.+):(\d+):(\d+)\)?/
|
|
662
683
|
const match = topFrame.match(regex)
|
|
@@ -664,9 +685,18 @@ function getFileAndLineNumberFromError (error) {
|
|
|
664
685
|
if (match) {
|
|
665
686
|
const filePath = match[1]
|
|
666
687
|
const lineNumber = Number(match[2])
|
|
667
|
-
const columnNumber = Number(match[3])
|
|
668
688
|
|
|
669
|
-
return [filePath, lineNumber,
|
|
689
|
+
return [filePath, lineNumber, topRelevantFrameIndex]
|
|
670
690
|
}
|
|
671
691
|
return []
|
|
672
692
|
}
|
|
693
|
+
|
|
694
|
+
function getFormattedError (error, repositoryRoot) {
|
|
695
|
+
const newError = new Error(error.message)
|
|
696
|
+
if (error.stack) {
|
|
697
|
+
newError.stack = error.stack.split('\n').filter(line => line.includes(repositoryRoot)).join('\n')
|
|
698
|
+
}
|
|
699
|
+
newError.name = error.name
|
|
700
|
+
|
|
701
|
+
return newError
|
|
702
|
+
}
|