dd-trace 5.33.0 → 5.34.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/package.json +3 -4
- package/packages/datadog-plugin-amqplib/src/producer.js +9 -1
- package/packages/datadog-plugin-graphql/src/execute.js +6 -0
- package/packages/datadog-plugin-graphql/src/utils.js +40 -0
- package/packages/datadog-plugin-graphql/src/validate.js +6 -0
- package/packages/datadog-plugin-langchain/src/handlers/default.js +6 -30
- package/packages/datadog-plugin-langchain/src/tracing.js +5 -6
- package/packages/datadog-plugin-openai/src/tracing.js +14 -33
- package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +19 -1
- package/packages/dd-trace/src/config.js +10 -2
- package/packages/dd-trace/src/debugger/devtools_client/state.js +67 -17
- package/packages/dd-trace/src/id.js +0 -2
- package/packages/dd-trace/src/plugin_manager.js +9 -1
- package/packages/dd-trace/src/plugins/util/llm.js +35 -0
package/LICENSE-3rdparty.csv
CHANGED
|
@@ -35,6 +35,7 @@ dev,@apollo/server,MIT,Copyright (c) 2016-2020 Apollo Graph, Inc. (Formerly Mete
|
|
|
35
35
|
dev,@types/node,MIT,Copyright Authors
|
|
36
36
|
dev,@eslint/eslintrc,MIT,Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
|
|
37
37
|
dev,@eslint/js,MIT,Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
|
|
38
|
+
dev,@msgpack/msgpack,ISC,Copyright 2019 The MessagePack Community
|
|
38
39
|
dev,@stylistic/eslint-plugin-js,MIT,Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
|
|
39
40
|
dev,autocannon,MIT,Copyright 2016 Matteo Collina
|
|
40
41
|
dev,aws-sdk,Apache 2.0,Copyright 2012-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
@@ -58,12 +59,10 @@ dev,get-port,MIT,Copyright Sindre Sorhus
|
|
|
58
59
|
dev,glob,ISC,Copyright Isaac Z. Schlueter and Contributors
|
|
59
60
|
dev,globals,MIT,Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
|
60
61
|
dev,graphql,MIT,Copyright 2015 Facebook Inc.
|
|
61
|
-
dev,int64-buffer,MIT,Copyright 2015-2016 Yusuke Kawasaki
|
|
62
62
|
dev,jszip,MIT,Copyright 2015-2016 Stuart Knightley and contributors
|
|
63
63
|
dev,knex,MIT,Copyright (c) 2013-present Tim Griesser
|
|
64
64
|
dev,mkdirp,MIT,Copyright 2010 James Halliday
|
|
65
65
|
dev,mocha,MIT,Copyright 2011-2018 JS Foundation and contributors https://js.foundation
|
|
66
|
-
dev,msgpack-lite,MIT,Copyright 2015 Yusuke Kawasaki
|
|
67
66
|
dev,multer,MIT,Copyright 2014 Hage Yaapa
|
|
68
67
|
dev,nock,MIT,Copyright 2017 Pedro Teixeira and other contributors
|
|
69
68
|
dev,nyc,ISC,Copyright 2015 Contributors
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.34.0",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"@datadog/native-iast-rewriter": "2.6.1",
|
|
87
87
|
"@datadog/native-iast-taint-tracking": "3.2.0",
|
|
88
88
|
"@datadog/native-metrics": "^3.1.0",
|
|
89
|
-
"@datadog/pprof": "5.5.
|
|
89
|
+
"@datadog/pprof": "5.5.1",
|
|
90
90
|
"@datadog/sketches-js": "^2.1.0",
|
|
91
91
|
"@isaacs/ttlcache": "^1.4.1",
|
|
92
92
|
"@opentelemetry/api": ">=1.0.0 <1.9.0",
|
|
@@ -118,6 +118,7 @@
|
|
|
118
118
|
"@apollo/server": "^4.11.0",
|
|
119
119
|
"@eslint/eslintrc": "^3.1.0",
|
|
120
120
|
"@eslint/js": "^9.11.1",
|
|
121
|
+
"@msgpack/msgpack": "^3.0.0-beta3",
|
|
121
122
|
"@stylistic/eslint-plugin-js": "^2.8.0",
|
|
122
123
|
"@types/node": "^16.0.0",
|
|
123
124
|
"autocannon": "^4.5.2",
|
|
@@ -142,12 +143,10 @@
|
|
|
142
143
|
"glob": "^7.1.6",
|
|
143
144
|
"globals": "^15.10.0",
|
|
144
145
|
"graphql": "0.13.2",
|
|
145
|
-
"int64-buffer": "^0.1.9",
|
|
146
146
|
"jszip": "^3.5.0",
|
|
147
147
|
"knex": "^2.4.2",
|
|
148
148
|
"mkdirp": "^3.0.1",
|
|
149
149
|
"mocha": "^10",
|
|
150
|
-
"msgpack-lite": "^0.1.26",
|
|
151
150
|
"multer": "^1.4.5-lts.1",
|
|
152
151
|
"nock": "^11.3.3",
|
|
153
152
|
"nyc": "^15.1.0",
|
|
@@ -36,9 +36,17 @@ class AmqplibProducerPlugin extends ProducerPlugin {
|
|
|
36
36
|
if (this.config.dsmEnabled) {
|
|
37
37
|
const hasRoutingKey = fields.routingKey != null
|
|
38
38
|
const payloadSize = getAmqpMessageSize({ content: message, headers: fields.headers })
|
|
39
|
+
|
|
40
|
+
// there are two ways to send messages in RabbitMQ:
|
|
41
|
+
// 1. using an exchange and a routing key in which DSM connects via the exchange
|
|
42
|
+
// 2. using an unnamed exchange and a routing key in which DSM connects via the topic
|
|
43
|
+
const exchangeOrTopicTag = hasRoutingKey && !fields.exchange
|
|
44
|
+
? `topic:${fields.routingKey}`
|
|
45
|
+
: `exchange:${fields.exchange}`
|
|
46
|
+
|
|
39
47
|
const dataStreamsContext = this.tracer
|
|
40
48
|
.setCheckpoint(
|
|
41
|
-
['direction:out',
|
|
49
|
+
['direction:out', exchangeOrTopicTag, `has_routing_key:${hasRoutingKey}`, 'type:rabbitmq']
|
|
42
50
|
, span, payloadSize)
|
|
43
51
|
DsmPathwayCodec.encode(dataStreamsContext, fields.headers)
|
|
44
52
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
4
|
+
const { extractErrorIntoSpanEvent } = require('./utils')
|
|
4
5
|
|
|
5
6
|
let tools
|
|
6
7
|
|
|
@@ -34,6 +35,11 @@ class GraphQLExecutePlugin extends TracingPlugin {
|
|
|
34
35
|
finish ({ res, args }) {
|
|
35
36
|
const span = this.activeSpan
|
|
36
37
|
this.config.hooks.execute(span, args, res)
|
|
38
|
+
if (res?.errors) {
|
|
39
|
+
for (const err of res.errors) {
|
|
40
|
+
extractErrorIntoSpanEvent(this._tracerConfig, span, err)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
37
43
|
super.finish()
|
|
38
44
|
}
|
|
39
45
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
function extractErrorIntoSpanEvent (config, span, exc) {
|
|
2
|
+
const attributes = {}
|
|
3
|
+
|
|
4
|
+
if (exc.name) {
|
|
5
|
+
attributes.type = exc.name
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (exc.stack) {
|
|
9
|
+
attributes.stacktrace = exc.stack
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (exc.locations) {
|
|
13
|
+
attributes.locations = []
|
|
14
|
+
for (const location of exc.locations) {
|
|
15
|
+
attributes.locations.push(`${location.line}:${location.column}`)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (exc.path) {
|
|
20
|
+
attributes.path = exc.path.map(String)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (exc.message) {
|
|
24
|
+
attributes.message = exc.message
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (config.graphqlErrorExtensions) {
|
|
28
|
+
for (const ext of config.graphqlErrorExtensions) {
|
|
29
|
+
if (exc.extensions?.[ext]) {
|
|
30
|
+
attributes[`extensions.${ext}`] = exc.extensions[ext].toString()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
span.addEvent('dd.graphql.query.error', attributes, Date.now())
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
extractErrorIntoSpanEvent
|
|
40
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
|
|
4
|
+
const { extractErrorIntoSpanEvent } = require('./utils')
|
|
4
5
|
|
|
5
6
|
class GraphQLValidatePlugin extends TracingPlugin {
|
|
6
7
|
static get id () { return 'graphql' }
|
|
@@ -21,6 +22,11 @@ class GraphQLValidatePlugin extends TracingPlugin {
|
|
|
21
22
|
finish ({ document, errors }) {
|
|
22
23
|
const span = this.activeSpan
|
|
23
24
|
this.config.hooks.validate(span, document, errors)
|
|
25
|
+
if (errors) {
|
|
26
|
+
for (const err of errors) {
|
|
27
|
+
extractErrorIntoSpanEvent(this._tracerConfig, span, err)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
24
30
|
super.finish()
|
|
25
31
|
}
|
|
26
32
|
}
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const makeUtilities = require('../../../dd-trace/src/plugins/util/llm')
|
|
4
4
|
|
|
5
|
-
const RE_NEWLINE = /\n/g
|
|
6
|
-
const RE_TAB = /\t/g
|
|
7
|
-
|
|
8
|
-
// TODO: should probably refactor the OpenAI integration to use a shared LLMTracingPlugin base class
|
|
9
|
-
// This logic isn't particular to LangChain
|
|
10
5
|
class LangChainHandler {
|
|
11
|
-
constructor (
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
constructor (tracerConfig) {
|
|
7
|
+
const utilities = makeUtilities('langchain', tracerConfig)
|
|
8
|
+
|
|
9
|
+
this.normalize = utilities.normalize
|
|
10
|
+
this.isPromptCompletionSampled = utilities.isPromptCompletionSampled
|
|
14
11
|
}
|
|
15
12
|
|
|
16
13
|
// no-op for default handler
|
|
@@ -27,27 +24,6 @@ class LangChainHandler {
|
|
|
27
24
|
|
|
28
25
|
// no-op for default handler
|
|
29
26
|
extractModel (instance) {}
|
|
30
|
-
|
|
31
|
-
normalize (text) {
|
|
32
|
-
if (!text) return
|
|
33
|
-
if (typeof text !== 'string' || !text || (typeof text === 'string' && text.length === 0)) return
|
|
34
|
-
|
|
35
|
-
const max = this.config.spanCharLimit
|
|
36
|
-
|
|
37
|
-
text = text
|
|
38
|
-
.replace(RE_NEWLINE, '\\n')
|
|
39
|
-
.replace(RE_TAB, '\\t')
|
|
40
|
-
|
|
41
|
-
if (text.length > max) {
|
|
42
|
-
return text.substring(0, max) + '...'
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return text
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
isPromptCompletionSampled () {
|
|
49
|
-
return this.sampler.isSampled()
|
|
50
|
-
}
|
|
51
27
|
}
|
|
52
28
|
|
|
53
29
|
module.exports = LangChainHandler
|
|
@@ -26,13 +26,12 @@ class LangChainTracingPlugin extends TracingPlugin {
|
|
|
26
26
|
constructor () {
|
|
27
27
|
super(...arguments)
|
|
28
28
|
|
|
29
|
-
const langchainConfig = this._tracerConfig.langchain || {}
|
|
30
29
|
this.handlers = {
|
|
31
|
-
chain: new LangChainChainHandler(
|
|
32
|
-
chat_model: new LangChainChatModelHandler(
|
|
33
|
-
llm: new LangChainLLMHandler(
|
|
34
|
-
embedding: new LangChainEmbeddingHandler(
|
|
35
|
-
default: new LangChainHandler(
|
|
30
|
+
chain: new LangChainChainHandler(this._tracerConfig),
|
|
31
|
+
chat_model: new LangChainChatModelHandler(this._tracerConfig),
|
|
32
|
+
llm: new LangChainLLMHandler(this._tracerConfig),
|
|
33
|
+
embedding: new LangChainEmbeddingHandler(this._tracerConfig),
|
|
34
|
+
default: new LangChainHandler(this._tracerConfig)
|
|
36
35
|
}
|
|
37
36
|
}
|
|
38
37
|
|
|
@@ -9,12 +9,9 @@ const Sampler = require('../../dd-trace/src/sampler')
|
|
|
9
9
|
const { MEASURED } = require('../../../ext/tags')
|
|
10
10
|
const { estimateTokens } = require('./token-estimator')
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
const RE_NEWLINE = /\n/g
|
|
14
|
-
const RE_TAB = /\t/g
|
|
12
|
+
const makeUtilities = require('../../dd-trace/src/plugins/util/llm')
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
let MAX_TEXT_LEN = 128
|
|
14
|
+
let normalize
|
|
18
15
|
|
|
19
16
|
function safeRequire (path) {
|
|
20
17
|
try {
|
|
@@ -44,9 +41,11 @@ class OpenAiTracingPlugin extends TracingPlugin {
|
|
|
44
41
|
|
|
45
42
|
this.sampler = new Sampler(0.1) // default 10% log sampling
|
|
46
43
|
|
|
47
|
-
// hoist the
|
|
44
|
+
// hoist the normalize function to avoid making all of these functions a class method
|
|
48
45
|
if (this._tracerConfig) {
|
|
49
|
-
|
|
46
|
+
const utilities = makeUtilities('openai', this._tracerConfig)
|
|
47
|
+
|
|
48
|
+
normalize = utilities.normalize
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
|
|
@@ -116,7 +115,7 @@ class OpenAiTracingPlugin extends TracingPlugin {
|
|
|
116
115
|
// createEdit, createEmbedding, createModeration
|
|
117
116
|
if (payload.input) {
|
|
118
117
|
const normalized = normalizeStringOrTokenArray(payload.input, false)
|
|
119
|
-
tags['openai.request.input'] =
|
|
118
|
+
tags['openai.request.input'] = normalize(normalized)
|
|
120
119
|
openaiStore.input = normalized
|
|
121
120
|
}
|
|
122
121
|
|
|
@@ -594,7 +593,7 @@ function commonImageResponseExtraction (tags, body) {
|
|
|
594
593
|
for (let i = 0; i < body.data.length; i++) {
|
|
595
594
|
const image = body.data[i]
|
|
596
595
|
// exactly one of these two options is provided
|
|
597
|
-
tags[`openai.response.images.${i}.url`] =
|
|
596
|
+
tags[`openai.response.images.${i}.url`] = normalize(image.url)
|
|
598
597
|
tags[`openai.response.images.${i}.b64_json`] = image.b64_json && 'returned'
|
|
599
598
|
}
|
|
600
599
|
}
|
|
@@ -731,14 +730,14 @@ function commonCreateResponseExtraction (tags, body, openaiStore, methodName) {
|
|
|
731
730
|
|
|
732
731
|
tags[`openai.response.choices.${choiceIdx}.finish_reason`] = choice.finish_reason
|
|
733
732
|
tags[`openai.response.choices.${choiceIdx}.logprobs`] = specifiesLogProb ? 'returned' : undefined
|
|
734
|
-
tags[`openai.response.choices.${choiceIdx}.text`] =
|
|
733
|
+
tags[`openai.response.choices.${choiceIdx}.text`] = normalize(choice.text)
|
|
735
734
|
|
|
736
735
|
// createChatCompletion only
|
|
737
736
|
const message = choice.message || choice.delta // delta for streamed responses
|
|
738
737
|
if (message) {
|
|
739
738
|
tags[`openai.response.choices.${choiceIdx}.message.role`] = message.role
|
|
740
|
-
tags[`openai.response.choices.${choiceIdx}.message.content`] =
|
|
741
|
-
tags[`openai.response.choices.${choiceIdx}.message.name`] =
|
|
739
|
+
tags[`openai.response.choices.${choiceIdx}.message.content`] = normalize(message.content)
|
|
740
|
+
tags[`openai.response.choices.${choiceIdx}.message.name`] = normalize(message.name)
|
|
742
741
|
if (message.tool_calls) {
|
|
743
742
|
const toolCalls = message.tool_calls
|
|
744
743
|
for (let toolIdx = 0; toolIdx < toolCalls.length; toolIdx++) {
|
|
@@ -795,24 +794,6 @@ function truncateApiKey (apiKey) {
|
|
|
795
794
|
return apiKey && `sk-...${apiKey.substr(apiKey.length - 4)}`
|
|
796
795
|
}
|
|
797
796
|
|
|
798
|
-
/**
|
|
799
|
-
* for cleaning up prompt and response
|
|
800
|
-
*/
|
|
801
|
-
function truncateText (text) {
|
|
802
|
-
if (!text) return
|
|
803
|
-
if (typeof text !== 'string' || !text || (typeof text === 'string' && text.length === 0)) return
|
|
804
|
-
|
|
805
|
-
text = text
|
|
806
|
-
.replace(RE_NEWLINE, '\\n')
|
|
807
|
-
.replace(RE_TAB, '\\t')
|
|
808
|
-
|
|
809
|
-
if (text.length > MAX_TEXT_LEN) {
|
|
810
|
-
return text.substring(0, MAX_TEXT_LEN) + '...'
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
return text
|
|
814
|
-
}
|
|
815
|
-
|
|
816
797
|
function tagChatCompletionRequestContent (contents, messageIdx, tags) {
|
|
817
798
|
if (typeof contents === 'string') {
|
|
818
799
|
tags[`openai.request.messages.${messageIdx}.content`] = contents
|
|
@@ -824,10 +805,10 @@ function tagChatCompletionRequestContent (contents, messageIdx, tags) {
|
|
|
824
805
|
const type = content.type
|
|
825
806
|
tags[`openai.request.messages.${messageIdx}.content.${contentIdx}.type`] = content.type
|
|
826
807
|
if (type === 'text') {
|
|
827
|
-
tags[`openai.request.messages.${messageIdx}.content.${contentIdx}.text`] =
|
|
808
|
+
tags[`openai.request.messages.${messageIdx}.content.${contentIdx}.text`] = normalize(content.text)
|
|
828
809
|
} else if (type === 'image_url') {
|
|
829
810
|
tags[`openai.request.messages.${messageIdx}.content.${contentIdx}.image_url.url`] =
|
|
830
|
-
|
|
811
|
+
normalize(content.image_url.url)
|
|
831
812
|
}
|
|
832
813
|
// unsupported type otherwise, won't be tagged
|
|
833
814
|
}
|
|
@@ -1004,7 +985,7 @@ function normalizeStringOrTokenArray (input, truncate) {
|
|
|
1004
985
|
const normalized = Array.isArray(input)
|
|
1005
986
|
? `[${input.join(', ')}]` // "[1, 2, 999]"
|
|
1006
987
|
: input // "foo"
|
|
1007
|
-
return truncate ?
|
|
988
|
+
return truncate ? normalize(normalized) : normalized
|
|
1008
989
|
}
|
|
1009
990
|
|
|
1010
991
|
function defensiveArrayLength (maybeArray) {
|
|
@@ -2,14 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
const InjectionAnalyzer = require('./injection-analyzer')
|
|
4
4
|
const { CODE_INJECTION } = require('../vulnerabilities')
|
|
5
|
+
const { INSTRUMENTED_SINK } = require('../telemetry/iast-metric')
|
|
6
|
+
const { storage } = require('../../../../../datadog-core')
|
|
7
|
+
const { getIastContext } = require('../iast-context')
|
|
5
8
|
|
|
6
9
|
class CodeInjectionAnalyzer extends InjectionAnalyzer {
|
|
7
10
|
constructor () {
|
|
8
11
|
super(CODE_INJECTION)
|
|
12
|
+
this.evalInstrumentedInc = false
|
|
9
13
|
}
|
|
10
14
|
|
|
11
15
|
onConfigure () {
|
|
12
|
-
this.addSub('datadog:eval:call', ({ script }) =>
|
|
16
|
+
this.addSub('datadog:eval:call', ({ script }) => {
|
|
17
|
+
if (!this.evalInstrumentedInc) {
|
|
18
|
+
const store = storage.getStore()
|
|
19
|
+
const iastContext = getIastContext(store)
|
|
20
|
+
const tags = INSTRUMENTED_SINK.formatTags(CODE_INJECTION)
|
|
21
|
+
|
|
22
|
+
for (const tag of tags) {
|
|
23
|
+
INSTRUMENTED_SINK.inc(iastContext, tag)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this.evalInstrumentedInc = true
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.analyze(script)
|
|
30
|
+
})
|
|
13
31
|
this.addSub('datadog:vm:run-script:start', ({ code }) => this.analyze(code))
|
|
14
32
|
this.addSub('datadog:vm:source-text-module:start', ({ code }) => this.analyze(code))
|
|
15
33
|
}
|
|
@@ -482,6 +482,7 @@ class Config {
|
|
|
482
482
|
this._setValue(defaults, 'flushInterval', 2000)
|
|
483
483
|
this._setValue(defaults, 'flushMinSpans', 1000)
|
|
484
484
|
this._setValue(defaults, 'gitMetadataEnabled', true)
|
|
485
|
+
this._setValue(defaults, 'graphqlErrorExtensions', [])
|
|
485
486
|
this._setValue(defaults, 'grpc.client.error.statuses', GRPC_CLIENT_ERROR_STATUSES)
|
|
486
487
|
this._setValue(defaults, 'grpc.server.error.statuses', GRPC_SERVER_ERROR_STATUSES)
|
|
487
488
|
this._setValue(defaults, 'headerTags', [])
|
|
@@ -521,8 +522,9 @@ class Config {
|
|
|
521
522
|
this._setValue(defaults, 'lookup', undefined)
|
|
522
523
|
this._setValue(defaults, 'inferredProxyServicesEnabled', false)
|
|
523
524
|
this._setValue(defaults, 'memcachedCommandEnabled', false)
|
|
525
|
+
this._setValue(defaults, 'middlewareTracingEnabled', true)
|
|
524
526
|
this._setValue(defaults, 'openAiLogsEnabled', false)
|
|
525
|
-
this._setValue(defaults, '
|
|
527
|
+
this._setValue(defaults, 'openai.spanCharLimit', 128)
|
|
526
528
|
this._setValue(defaults, 'peerServiceMapping', {})
|
|
527
529
|
this._setValue(defaults, 'plugins', true)
|
|
528
530
|
this._setValue(defaults, 'port', '8126')
|
|
@@ -669,9 +671,11 @@ class Config {
|
|
|
669
671
|
DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED,
|
|
670
672
|
DD_TRACE_GIT_METADATA_ENABLED,
|
|
671
673
|
DD_TRACE_GLOBAL_TAGS,
|
|
674
|
+
DD_TRACE_GRAPHQL_ERROR_EXTENSIONS,
|
|
672
675
|
DD_TRACE_HEADER_TAGS,
|
|
673
676
|
DD_TRACE_LEGACY_BAGGAGE_ENABLED,
|
|
674
677
|
DD_TRACE_MEMCACHED_COMMAND_ENABLED,
|
|
678
|
+
DD_TRACE_MIDDLEWARE_TRACING_ENABLED,
|
|
675
679
|
DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP,
|
|
676
680
|
DD_TRACE_PARTIAL_FLUSH_MIN_SPANS,
|
|
677
681
|
DD_TRACE_PEER_SERVICE_MAPPING,
|
|
@@ -804,8 +808,9 @@ class Config {
|
|
|
804
808
|
this._setBoolean(env, 'logInjection', DD_LOGS_INJECTION)
|
|
805
809
|
// Requires an accompanying DD_APM_OBFUSCATION_MEMCACHED_KEEP_COMMAND=true in the agent
|
|
806
810
|
this._setBoolean(env, 'memcachedCommandEnabled', DD_TRACE_MEMCACHED_COMMAND_ENABLED)
|
|
811
|
+
this._setBoolean(env, 'middlewareTracingEnabled', DD_TRACE_MIDDLEWARE_TRACING_ENABLED)
|
|
807
812
|
this._setBoolean(env, 'openAiLogsEnabled', DD_OPENAI_LOGS_ENABLED)
|
|
808
|
-
this._setValue(env, '
|
|
813
|
+
this._setValue(env, 'openai.spanCharLimit', maybeInt(DD_OPENAI_SPAN_CHAR_LIMIT))
|
|
809
814
|
this._envUnprocessed.openaiSpanCharLimit = DD_OPENAI_SPAN_CHAR_LIMIT
|
|
810
815
|
if (DD_TRACE_PEER_SERVICE_MAPPING) {
|
|
811
816
|
this._setValue(env, 'peerServiceMapping', fromEntries(
|
|
@@ -895,6 +900,7 @@ class Config {
|
|
|
895
900
|
this._setString(env, 'version', DD_VERSION || tags.version)
|
|
896
901
|
this._setBoolean(env, 'inferredProxyServicesEnabled', DD_TRACE_INFERRED_PROXY_SERVICES_ENABLED)
|
|
897
902
|
this._setString(env, 'aws.dynamoDb.tablePrimaryKeys', DD_AWS_SDK_DYNAMODB_TABLE_PRIMARY_KEYS)
|
|
903
|
+
this._setArray(env, 'graphqlErrorExtensions', DD_TRACE_GRAPHQL_ERROR_EXTENSIONS)
|
|
898
904
|
}
|
|
899
905
|
|
|
900
906
|
_applyOptions (options) {
|
|
@@ -986,6 +992,7 @@ class Config {
|
|
|
986
992
|
this._setString(opts, 'llmobs.mlApp', options.llmobs?.mlApp)
|
|
987
993
|
this._setBoolean(opts, 'logInjection', options.logInjection)
|
|
988
994
|
this._setString(opts, 'lookup', options.lookup)
|
|
995
|
+
this._setBoolean(opts, 'middlewareTracingEnabled', options.middlewareTracingEnabled)
|
|
989
996
|
this._setBoolean(opts, 'openAiLogsEnabled', options.openAiLogsEnabled)
|
|
990
997
|
this._setValue(opts, 'peerServiceMapping', options.peerServiceMapping)
|
|
991
998
|
this._setBoolean(opts, 'plugins', options.plugins)
|
|
@@ -1020,6 +1027,7 @@ class Config {
|
|
|
1020
1027
|
this._setBoolean(opts, 'traceId128BitLoggingEnabled', options.traceId128BitLoggingEnabled)
|
|
1021
1028
|
this._setString(opts, 'version', options.version || tags.version)
|
|
1022
1029
|
this._setBoolean(opts, 'inferredProxyServicesEnabled', options.inferredProxyServicesEnabled)
|
|
1030
|
+
this._setBoolean(opts, 'graphqlErrorExtensions', options.graphqlErrorExtensions)
|
|
1023
1031
|
|
|
1024
1032
|
// For LLMObs, we want the environment variable to take precedence over the options.
|
|
1025
1033
|
// This is reliant on environment config being set before options.
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
const session = require('./session')
|
|
4
4
|
|
|
5
|
+
const WINDOWS_DRIVE_LETTER_REGEX = /[a-zA-Z]/
|
|
6
|
+
|
|
5
7
|
const scriptIds = []
|
|
6
8
|
const scriptUrls = new Map()
|
|
7
9
|
|
|
@@ -10,26 +12,74 @@ module.exports = {
|
|
|
10
12
|
breakpoints: new Map(),
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
|
-
* Find the
|
|
14
|
-
*
|
|
15
|
-
* Algorithm: Find the sortest url that ends in the requested path.
|
|
16
|
-
*
|
|
17
|
-
* Will identify the correct script as long as Node.js doesn't load a module from a `node_modules` folder outside the
|
|
18
|
-
* project root. If so, there's a risk that this path is shorter than the expected path inside the project root.
|
|
19
|
-
* Example of mismatch where path = `index.js`:
|
|
20
|
-
*
|
|
21
|
-
* Expected match: /www/code/my-projects/demo-project1/index.js
|
|
22
|
-
* Actual shorter match: /www/node_modules/dd-trace/index.js
|
|
15
|
+
* Find the script to inspect based on a partial or absolute path. Handles both Windows and POSIX paths.
|
|
23
16
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* @returns {[string, string] | undefined}
|
|
17
|
+
* @param {string} path - Partial or absolute path to match against loaded scripts
|
|
18
|
+
* @returns {[string, string, string | undefined] | null} - Array containing [url, scriptId, sourceMapURL]
|
|
19
|
+
* or null if no match
|
|
28
20
|
*/
|
|
29
21
|
findScriptFromPartialPath (path) {
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
|
|
22
|
+
if (!path) return null // This shouldn't happen, but better safe than sorry
|
|
23
|
+
|
|
24
|
+
const bestMatch = new Array(3)
|
|
25
|
+
let maxMatchLength = -1
|
|
26
|
+
|
|
27
|
+
for (const [url, scriptId, sourceMapURL] of scriptIds) {
|
|
28
|
+
let i = url.length - 1
|
|
29
|
+
let j = path.length - 1
|
|
30
|
+
let matchLength = 0
|
|
31
|
+
let lastBoundaryPos = -1
|
|
32
|
+
let atBoundary = false
|
|
33
|
+
|
|
34
|
+
// Compare characters from the end
|
|
35
|
+
while (i >= 0 && j >= 0) {
|
|
36
|
+
const urlChar = url[i]
|
|
37
|
+
const pathChar = path[j]
|
|
38
|
+
|
|
39
|
+
// Check if both characters is a path boundary
|
|
40
|
+
const isBoundary = (urlChar === '/' || urlChar === '\\') && (pathChar === '/' || pathChar === '\\' ||
|
|
41
|
+
(j === 1 && pathChar === ':' && WINDOWS_DRIVE_LETTER_REGEX.test(path[0])))
|
|
42
|
+
|
|
43
|
+
// If both are boundaries, or if characters match exactly
|
|
44
|
+
if (isBoundary || urlChar === pathChar) {
|
|
45
|
+
if (isBoundary) {
|
|
46
|
+
lastBoundaryPos = matchLength
|
|
47
|
+
atBoundary = true
|
|
48
|
+
} else {
|
|
49
|
+
atBoundary = false
|
|
50
|
+
}
|
|
51
|
+
matchLength++
|
|
52
|
+
i--
|
|
53
|
+
j--
|
|
54
|
+
} else {
|
|
55
|
+
break
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// If we've matched the entire path pattern, ensure it starts at a path boundary
|
|
60
|
+
if (j === -1) {
|
|
61
|
+
if (i >= 0) {
|
|
62
|
+
// If there are more characters in the URL, the next one must be a slash
|
|
63
|
+
if (url[i] === '/' || url[i] === '\\') {
|
|
64
|
+
atBoundary = true
|
|
65
|
+
lastBoundaryPos = matchLength
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
atBoundary = true
|
|
69
|
+
lastBoundaryPos = matchLength
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// If we found a valid match and it's better than our previous best
|
|
74
|
+
if (atBoundary && lastBoundaryPos !== -1 && lastBoundaryPos > maxMatchLength) {
|
|
75
|
+
maxMatchLength = lastBoundaryPos
|
|
76
|
+
bestMatch[0] = url
|
|
77
|
+
bestMatch[1] = scriptId
|
|
78
|
+
bestMatch[2] = sourceMapURL
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return maxMatchLength > -1 ? bestMatch : null
|
|
33
83
|
},
|
|
34
84
|
|
|
35
85
|
getStackFromCallFrames (callFrames) {
|
|
@@ -15,7 +15,6 @@ let batch = 0
|
|
|
15
15
|
// Internal representation of a trace or span ID.
|
|
16
16
|
class Identifier {
|
|
17
17
|
constructor (value, radix = 16) {
|
|
18
|
-
this._isUint64BE = true // msgpack-lite compatibility
|
|
19
18
|
this._buffer = radix === 16
|
|
20
19
|
? createBuffer(value)
|
|
21
20
|
: fromString(value, radix)
|
|
@@ -31,7 +30,6 @@ class Identifier {
|
|
|
31
30
|
return this._buffer
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
// msgpack-lite compatibility
|
|
35
33
|
toArray () {
|
|
36
34
|
if (this._buffer.length === 8) {
|
|
37
35
|
return this._buffer
|
|
@@ -139,7 +139,8 @@ module.exports = class PluginManager {
|
|
|
139
139
|
memcachedCommandEnabled,
|
|
140
140
|
ciVisibilityTestSessionName,
|
|
141
141
|
ciVisAgentlessLogSubmissionEnabled,
|
|
142
|
-
isTestDynamicInstrumentationEnabled
|
|
142
|
+
isTestDynamicInstrumentationEnabled,
|
|
143
|
+
middlewareTracingEnabled
|
|
143
144
|
} = this._tracerConfig
|
|
144
145
|
|
|
145
146
|
const sharedConfig = {
|
|
@@ -170,6 +171,13 @@ module.exports = class PluginManager {
|
|
|
170
171
|
sharedConfig.clientIpEnabled = clientIpEnabled
|
|
171
172
|
}
|
|
172
173
|
|
|
174
|
+
// For the global setting, we use the name `middlewareTracingEnabled`, but
|
|
175
|
+
// for the plugin-specific setting, we use `middleware`. They mean the same
|
|
176
|
+
// to an individual plugin, so we normalize them here.
|
|
177
|
+
if (middlewareTracingEnabled !== undefined) {
|
|
178
|
+
sharedConfig.middleware = middlewareTracingEnabled
|
|
179
|
+
}
|
|
180
|
+
|
|
173
181
|
return sharedConfig
|
|
174
182
|
}
|
|
175
183
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const Sampler = require('../../sampler')
|
|
2
|
+
|
|
3
|
+
const RE_NEWLINE = /\n/g
|
|
4
|
+
const RE_TAB = /\t/g
|
|
5
|
+
|
|
6
|
+
function normalize (text, limit = 128) {
|
|
7
|
+
if (!text) return
|
|
8
|
+
if (typeof text !== 'string' || !text || (typeof text === 'string' && text.length === 0)) return
|
|
9
|
+
|
|
10
|
+
text = text
|
|
11
|
+
.replace(RE_NEWLINE, '\\n')
|
|
12
|
+
.replace(RE_TAB, '\\t')
|
|
13
|
+
|
|
14
|
+
if (text.length > limit) {
|
|
15
|
+
return text.substring(0, limit) + '...'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return text
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isPromptCompletionSampled (sampler) {
|
|
22
|
+
return sampler.isSampled()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = function (integrationName, tracerConfig) {
|
|
26
|
+
const integrationConfig = tracerConfig[integrationName] || {}
|
|
27
|
+
const { spanCharLimit, spanPromptCompletionSampleRate } = integrationConfig
|
|
28
|
+
|
|
29
|
+
const sampler = new Sampler(spanPromptCompletionSampleRate ?? 1.0)
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
normalize: str => normalize(str, spanCharLimit),
|
|
33
|
+
isPromptCompletionSampled: () => isPromptCompletionSampled(sampler)
|
|
34
|
+
}
|
|
35
|
+
}
|