dd-trace 5.100.0 → 5.101.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/index.d.ts +14 -0
- package/package.json +5 -5
- package/packages/datadog-instrumentations/src/cypress.js +5 -3
- package/packages/datadog-instrumentations/src/http/client.js +20 -3
- package/packages/datadog-instrumentations/src/jest.js +62 -32
- package/packages/datadog-instrumentations/src/mocha/common.js +4 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +25 -4
- package/packages/datadog-instrumentations/src/mocha/worker.js +5 -2
- package/packages/datadog-instrumentations/src/otel-sdk-trace.js +11 -6
- package/packages/datadog-plugin-bullmq/src/consumer.js +2 -2
- package/packages/datadog-plugin-bullmq/src/producer.js +14 -20
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +17 -0
- package/packages/datadog-plugin-cypress/src/plugin.js +5 -14
- package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -9
- package/packages/datadog-plugin-kafkajs/src/producer.js +2 -8
- package/packages/dd-trace/src/appsec/reporter.js +4 -1
- package/packages/dd-trace/src/ci-visibility/lage.js +2 -1
- package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -33
- package/packages/dd-trace/src/config/config-types.d.ts +0 -2
- package/packages/dd-trace/src/config/index.js +1 -55
- package/packages/dd-trace/src/datastreams/checkpointer.js +4 -10
- package/packages/dd-trace/src/datastreams/encoding.js +39 -28
- package/packages/dd-trace/src/datastreams/pathway.js +29 -26
- package/packages/dd-trace/src/datastreams/processor.js +17 -15
- package/packages/dd-trace/src/datastreams/size.js +6 -2
- package/packages/dd-trace/src/debugger/config.js +5 -2
- package/packages/dd-trace/src/debugger/devtools_client/index.js +2 -5
- package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -1
- package/packages/dd-trace/src/dogstatsd.js +10 -7
- package/packages/dd-trace/src/encode/0.4.js +2 -2
- package/packages/dd-trace/src/encode/0.5.js +2 -2
- package/packages/dd-trace/src/encode/agentless-json.js +2 -2
- package/packages/dd-trace/src/encode/tags-processors.js +2 -27
- package/packages/dd-trace/src/exporters/common/request.js +22 -11
- package/packages/dd-trace/src/exporters/common/retry.js +104 -0
- package/packages/dd-trace/src/git_metadata.js +66 -0
- package/packages/dd-trace/src/git_metadata_tagger.js +13 -5
- package/packages/dd-trace/src/id.js +15 -26
- package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +27 -16
- package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +3 -0
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +30 -13
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +20 -50
- package/packages/dd-trace/src/llmobs/sdk.js +5 -1
- package/packages/dd-trace/src/llmobs/span_processor.js +28 -2
- package/packages/dd-trace/src/llmobs/tagger.js +42 -0
- package/packages/dd-trace/src/llmobs/telemetry.js +29 -0
- package/packages/dd-trace/src/llmobs/util.js +80 -5
- package/packages/dd-trace/src/opentelemetry/active-span-proxy.js +42 -0
- package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +106 -0
- package/packages/dd-trace/src/opentelemetry/context_manager.js +11 -2
- package/packages/dd-trace/src/opentelemetry/span-helpers.js +188 -50
- package/packages/dd-trace/src/opentelemetry/span.js +42 -80
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +65 -27
- package/packages/dd-trace/src/opentracing/propagation/tracestate.js +58 -22
- package/packages/dd-trace/src/opentracing/span.js +56 -48
- package/packages/dd-trace/src/opentracing/span_context.js +1 -0
- package/packages/dd-trace/src/priority_sampler.js +6 -4
- package/packages/dd-trace/src/profiling/config.js +5 -4
- package/packages/dd-trace/src/remote_config/index.js +5 -3
- package/packages/dd-trace/src/span_format.js +52 -5
- package/packages/dd-trace/src/span_processor.js +0 -4
- package/packages/dd-trace/src/spanleak.js +0 -1
- package/packages/dd-trace/src/util.js +17 -0
|
@@ -22,6 +22,7 @@ const TYPE_HISTOGRAM = 'h'
|
|
|
22
22
|
*/
|
|
23
23
|
class DogStatsDClient {
|
|
24
24
|
#lookup
|
|
25
|
+
#tagsPrefix
|
|
25
26
|
constructor (options) {
|
|
26
27
|
this.#lookup = options.lookup
|
|
27
28
|
if (options.metricsProxyUrl) {
|
|
@@ -36,6 +37,7 @@ class DogStatsDClient {
|
|
|
36
37
|
this._family = isIP(this._host)
|
|
37
38
|
this._port = options.port
|
|
38
39
|
this._tags = options.tags
|
|
40
|
+
this.#tagsPrefix = this._tags?.length ? `|#${this._tags.join(',')}` : ''
|
|
39
41
|
this._queue = []
|
|
40
42
|
this._buffer = ''
|
|
41
43
|
this._offset = 0
|
|
@@ -66,9 +68,9 @@ class DogStatsDClient {
|
|
|
66
68
|
flush () {
|
|
67
69
|
const queue = this._enqueue()
|
|
68
70
|
|
|
69
|
-
|
|
71
|
+
if (queue.length === 0) return
|
|
70
72
|
|
|
71
|
-
|
|
73
|
+
log.debug('Flushing %s metrics via %s', queue.length, this._httpOptions ? 'HTTP' : 'UDP')
|
|
72
74
|
|
|
73
75
|
this._queue = []
|
|
74
76
|
|
|
@@ -119,11 +121,12 @@ class DogStatsDClient {
|
|
|
119
121
|
_add (stat, value, type, tags) {
|
|
120
122
|
let message = `${stat}:${value}|${type}`
|
|
121
123
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
if (tags?.length) {
|
|
125
|
+
message += this.#tagsPrefix
|
|
126
|
+
? `${this.#tagsPrefix},${tags.join(',')}`
|
|
127
|
+
: `|#${tags.join(',')}`
|
|
128
|
+
} else {
|
|
129
|
+
message += this.#tagsPrefix
|
|
127
130
|
}
|
|
128
131
|
|
|
129
132
|
if (entityId) {
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
const getConfig = require('../config')
|
|
4
4
|
const { MsgpackChunk, MsgpackEncoder } = require('../msgpack')
|
|
5
5
|
const log = require('../log')
|
|
6
|
-
const {
|
|
6
|
+
const { normalizeSpan } = require('./tags-processors')
|
|
7
7
|
|
|
8
8
|
const SOFT_LIMIT = 8 * 1024 * 1024 // 8MB
|
|
9
9
|
|
|
10
10
|
function formatSpan (span, config) {
|
|
11
|
-
span = normalizeSpan(
|
|
11
|
+
span = normalizeSpan(span)
|
|
12
12
|
if (span.span_events) {
|
|
13
13
|
// ensure span events are encoded as tags if agent doesn't support native top level span events
|
|
14
14
|
if (config.DD_TRACE_NATIVE_SPAN_EVENTS) {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const { normalizeSpan } = require('./tags-processors')
|
|
4
4
|
const { AgentEncoder: BaseEncoder } = require('./0.4')
|
|
5
5
|
|
|
6
6
|
const ARRAY_OF_TWO = 0x92
|
|
7
7
|
const ARRAY_OF_TWELVE = 0x9C
|
|
8
8
|
|
|
9
9
|
function formatSpan (span) {
|
|
10
|
-
span = normalizeSpan(
|
|
10
|
+
span = normalizeSpan(span)
|
|
11
11
|
// ensure span events are encoded as tags
|
|
12
12
|
if (span.span_events) {
|
|
13
13
|
span.meta.events = JSON.stringify(span.span_events)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const log = require('../log')
|
|
4
4
|
const { TOP_LEVEL_KEY } = require('../constants')
|
|
5
|
-
const {
|
|
5
|
+
const { normalizeSpan } = require('./tags-processors')
|
|
6
6
|
|
|
7
7
|
// Soft limit for estimated payload size. Triggers an early flush to stay under intake request size limits.
|
|
8
8
|
const SOFT_LIMIT = 8 * 1024 * 1024 // 8MB
|
|
@@ -14,7 +14,7 @@ const SOFT_LIMIT = 8 * 1024 * 1024 // 8MB
|
|
|
14
14
|
* @returns {object} The formatted span
|
|
15
15
|
*/
|
|
16
16
|
function formatSpan (span, isFirstSpan) {
|
|
17
|
-
span = normalizeSpan(
|
|
17
|
+
span = normalizeSpan(span)
|
|
18
18
|
|
|
19
19
|
// Remove _dd.p.tid (the upper 64 bits of a 128-bit trace ID) since trace_id is truncated to lower 64 bits
|
|
20
20
|
delete span.meta['_dd.p.tid']
|
|
@@ -25,35 +25,10 @@ const MAX_SERVICE_LENGTH = 100
|
|
|
25
25
|
// MAX_TYPE_LENGTH the maximum length a span type can have
|
|
26
26
|
const MAX_TYPE_LENGTH = 100
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// them yet again.
|
|
31
|
-
|
|
32
|
-
// normally the agent truncates the resource and parses it in certain scenarios (e.g. SQL Queries)
|
|
33
|
-
function truncateSpan (span, shouldTruncateResourceName = true) {
|
|
34
|
-
if (shouldTruncateResourceName && span.resource && span.resource.length > MAX_RESOURCE_NAME_LENGTH) {
|
|
28
|
+
function truncateSpan (span) {
|
|
29
|
+
if (span.resource && span.resource.length > MAX_RESOURCE_NAME_LENGTH) {
|
|
35
30
|
span.resource = `${span.resource.slice(0, MAX_RESOURCE_NAME_LENGTH)}...`
|
|
36
31
|
}
|
|
37
|
-
for (let metaKey of Object.keys(span.meta)) {
|
|
38
|
-
const val = span.meta[metaKey]
|
|
39
|
-
if (metaKey.length > MAX_META_KEY_LENGTH) {
|
|
40
|
-
delete span.meta[metaKey]
|
|
41
|
-
metaKey = `${metaKey.slice(0, MAX_META_KEY_LENGTH)}...`
|
|
42
|
-
span.meta[metaKey] = val
|
|
43
|
-
}
|
|
44
|
-
if (val && val.length > MAX_META_VALUE_LENGTH) {
|
|
45
|
-
span.meta[metaKey] = `${val.slice(0, MAX_META_VALUE_LENGTH)}...`
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
for (let metricsKey of Object.keys(span.metrics)) {
|
|
49
|
-
const val = span.metrics[metricsKey]
|
|
50
|
-
if (metricsKey.length > MAX_METRIC_KEY_LENGTH) {
|
|
51
|
-
delete span.metrics[metricsKey]
|
|
52
|
-
metricsKey = `${metricsKey.slice(0, MAX_METRIC_KEY_LENGTH)}...`
|
|
53
|
-
span.metrics[metricsKey] = val
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
32
|
return span
|
|
58
33
|
}
|
|
59
34
|
|
|
@@ -13,6 +13,12 @@ const log = require('../../log')
|
|
|
13
13
|
const { urlToHttpOptions } = require('./url-to-http-options-polyfill')
|
|
14
14
|
const docker = require('./docker')
|
|
15
15
|
const { httpAgent, httpsAgent } = require('./agents')
|
|
16
|
+
const {
|
|
17
|
+
getMaxAttempts,
|
|
18
|
+
getRetryDelay,
|
|
19
|
+
isRetriableNetworkError,
|
|
20
|
+
markEndpointReached,
|
|
21
|
+
} = require('./retry')
|
|
16
22
|
|
|
17
23
|
const maxActiveBufferSize = 1024 * 1024 * 64
|
|
18
24
|
|
|
@@ -92,6 +98,8 @@ function request (data, options, callback) {
|
|
|
92
98
|
options.agent = isSecure ? httpsAgent : httpAgent
|
|
93
99
|
|
|
94
100
|
const onResponse = (res, finalize) => {
|
|
101
|
+
markEndpointReached(options)
|
|
102
|
+
|
|
95
103
|
const chunks = []
|
|
96
104
|
|
|
97
105
|
res.setTimeout(timeout)
|
|
@@ -142,7 +150,10 @@ function request (data, options, callback) {
|
|
|
142
150
|
})
|
|
143
151
|
}
|
|
144
152
|
|
|
145
|
-
|
|
153
|
+
// Retries always run via setTimeout so the AsyncLocalStorage store survives
|
|
154
|
+
// the gap before socket.connect(); ALS.run() does not call ALS.enterWith()
|
|
155
|
+
// outside AsyncContextFrame, so a synchronous re-entry would lose the store.
|
|
156
|
+
const attempt = attemptIndex => {
|
|
146
157
|
if (!request.writable) {
|
|
147
158
|
log.debug('Maximum number of active requests reached: payload is discarded.')
|
|
148
159
|
return callback(null)
|
|
@@ -163,9 +174,16 @@ function request (data, options, callback) {
|
|
|
163
174
|
req.once('close', finalize)
|
|
164
175
|
req.once('timeout', finalize)
|
|
165
176
|
|
|
166
|
-
req.once('error',
|
|
177
|
+
req.once('error', error => {
|
|
167
178
|
finalize()
|
|
168
|
-
|
|
179
|
+
if (attemptIndex < getMaxAttempts(options) && isRetriableNetworkError(error)) {
|
|
180
|
+
// Unref so a pending retry never keeps the host process alive past
|
|
181
|
+
// its natural exit point; long-running apps still retry because the
|
|
182
|
+
// event loop is held open by their own work.
|
|
183
|
+
setTimeout(attempt, getRetryDelay(options, attemptIndex), attemptIndex + 1).unref()
|
|
184
|
+
} else {
|
|
185
|
+
callback(error)
|
|
186
|
+
}
|
|
169
187
|
})
|
|
170
188
|
|
|
171
189
|
req.setTimeout(timeout, () => {
|
|
@@ -185,14 +203,7 @@ function request (data, options, callback) {
|
|
|
185
203
|
})
|
|
186
204
|
}
|
|
187
205
|
|
|
188
|
-
|
|
189
|
-
// request before socket.connect() is called. This is a workaround for the
|
|
190
|
-
// issue that the AsyncLocalStorage.run() method does not call the
|
|
191
|
-
// AsyncLocalStorage.enterWith() method when not using AsyncContextFrame.
|
|
192
|
-
//
|
|
193
|
-
// TODO: Test that this doesn't trace itself on retry when the diagnostics
|
|
194
|
-
// channel events are available in the agent exporter.
|
|
195
|
-
makeRequest(() => setTimeout(() => makeRequest(callback)))
|
|
206
|
+
attempt(1)
|
|
196
207
|
}
|
|
197
208
|
|
|
198
209
|
function byteLength (data) {
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const RATE_LIMIT_MAX_WAIT_MS = 30_000
|
|
4
|
+
|
|
5
|
+
const SINGLE_RETRY_BASE_MS = 5000
|
|
6
|
+
const SINGLE_RETRY_JITTER_MS = 2500
|
|
7
|
+
|
|
8
|
+
const STARTUP_GRACE_MS = 30_000
|
|
9
|
+
const STARTUP_BACKOFF_BASE_MS = 1000
|
|
10
|
+
const STARTUP_BACKOFF_MAX_MS = 8000
|
|
11
|
+
const STARTUP_BACKOFF_JITTER_MS = 500
|
|
12
|
+
const STARTUP_MAX_ATTEMPTS = 5
|
|
13
|
+
const POST_STARTUP_MAX_ATTEMPTS = 2
|
|
14
|
+
|
|
15
|
+
// `ECONNREFUSED` and `ENOENT` cover the agent-not-yet-listening cases (TCP and
|
|
16
|
+
// UDS). `EAI_AGAIN` covers transient DNS in agentless intake. `ENOTFOUND` is
|
|
17
|
+
// excluded because it usually means a misconfigured host, not a transient state.
|
|
18
|
+
const RETRIABLE_NETWORK_CODES = new Set([
|
|
19
|
+
'EAI_AGAIN',
|
|
20
|
+
'ECONNREFUSED',
|
|
21
|
+
'ECONNRESET',
|
|
22
|
+
'ENOENT',
|
|
23
|
+
'EPIPE',
|
|
24
|
+
'ETIMEDOUT',
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
const startedAtMs = Date.now()
|
|
28
|
+
const reachedEndpoints = new Set()
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {object} EndpointOptions
|
|
32
|
+
* @property {string} [socketPath]
|
|
33
|
+
* @property {string} [hostname]
|
|
34
|
+
* @property {string} [host]
|
|
35
|
+
* @property {string|number} [port]
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {Error & { code?: string }} error
|
|
40
|
+
*/
|
|
41
|
+
function isRetriableNetworkError (error) {
|
|
42
|
+
return error?.code !== undefined && RETRIABLE_NETWORK_CODES.has(error.code)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function singleJitteredDelay () {
|
|
46
|
+
return SINGLE_RETRY_BASE_MS + Math.random() * SINGLE_RETRY_JITTER_MS
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Stable key identifying the destination so the startup-phase gate is scoped
|
|
51
|
+
* per endpoint. UDS path beats host:port because both can coexist on the same
|
|
52
|
+
* options object after `parseUrl` runs.
|
|
53
|
+
*
|
|
54
|
+
* @param {EndpointOptions} options
|
|
55
|
+
*/
|
|
56
|
+
function getEndpointKey (options) {
|
|
57
|
+
if (options.socketPath) return options.socketPath
|
|
58
|
+
return `${options.hostname || options.host || ''}:${options.port || ''}`
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {EndpointOptions} options
|
|
63
|
+
*/
|
|
64
|
+
function inStartupPhase (options) {
|
|
65
|
+
if ((Date.now() - startedAtMs) >= STARTUP_GRACE_MS) return false
|
|
66
|
+
return !reachedEndpoints.has(getEndpointKey(options))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Wait time before the next attempt when the previous one just failed. Bounded
|
|
71
|
+
* exponential backoff with small jitter inside the startup grace window;
|
|
72
|
+
* single 5–7.5 s jittered retry afterwards.
|
|
73
|
+
*
|
|
74
|
+
* @param {EndpointOptions} options
|
|
75
|
+
* @param {number} previousAttempt 1-based index of the attempt that just failed.
|
|
76
|
+
*/
|
|
77
|
+
function getRetryDelay (options, previousAttempt) {
|
|
78
|
+
if (!inStartupPhase(options)) return singleJitteredDelay()
|
|
79
|
+
const exp = Math.min(STARTUP_BACKOFF_MAX_MS, STARTUP_BACKOFF_BASE_MS << (previousAttempt - 1))
|
|
80
|
+
return exp + Math.random() * STARTUP_BACKOFF_JITTER_MS
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {EndpointOptions} options
|
|
85
|
+
*/
|
|
86
|
+
function getMaxAttempts (options) {
|
|
87
|
+
return inStartupPhase(options) ? STARTUP_MAX_ATTEMPTS : POST_STARTUP_MAX_ATTEMPTS
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {EndpointOptions} options
|
|
92
|
+
*/
|
|
93
|
+
function markEndpointReached (options) {
|
|
94
|
+
reachedEndpoints.add(getEndpointKey(options))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
RATE_LIMIT_MAX_WAIT_MS,
|
|
99
|
+
getMaxAttempts,
|
|
100
|
+
getRetryDelay,
|
|
101
|
+
isRetriableNetworkError,
|
|
102
|
+
markEndpointReached,
|
|
103
|
+
singleJitteredDelay,
|
|
104
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs')
|
|
4
|
+
const path = require('node:path')
|
|
5
|
+
|
|
6
|
+
const log = require('./log')
|
|
7
|
+
const { GIT_COMMIT_SHA, GIT_REPOSITORY_URL } = require('./plugins/util/tags')
|
|
8
|
+
const {
|
|
9
|
+
getGitMetadataFromGitProperties,
|
|
10
|
+
getRemoteOriginURL,
|
|
11
|
+
removeUserSensitiveInfo,
|
|
12
|
+
resolveGitHeadSHA,
|
|
13
|
+
} = require('./config/git_properties')
|
|
14
|
+
|
|
15
|
+
/** @type {{ commitSHA: string | undefined, repositoryUrl: string | undefined } | undefined} */
|
|
16
|
+
let cached
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {import('./config/config-types').ConfigProperties} config
|
|
20
|
+
*/
|
|
21
|
+
function getGitMetadata (config) {
|
|
22
|
+
if (cached) return cached
|
|
23
|
+
|
|
24
|
+
if (!config.DD_TRACE_GIT_METADATA_ENABLED) {
|
|
25
|
+
cached = { commitSHA: undefined, repositoryUrl: undefined }
|
|
26
|
+
return cached
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let repositoryUrl = removeUserSensitiveInfo(config.DD_GIT_REPOSITORY_URL ?? config.tags[GIT_REPOSITORY_URL])
|
|
30
|
+
let commitSHA = config.DD_GIT_COMMIT_SHA ?? config.tags[GIT_COMMIT_SHA]
|
|
31
|
+
|
|
32
|
+
if (!repositoryUrl || !commitSHA) {
|
|
33
|
+
const propertiesFile = config.DD_GIT_PROPERTIES_FILE
|
|
34
|
+
const gitPropertiesFile = propertiesFile ?? `${process.cwd()}/git.properties`
|
|
35
|
+
try {
|
|
36
|
+
const fromProperties = getGitMetadataFromGitProperties(fs.readFileSync(gitPropertiesFile, 'utf8'))
|
|
37
|
+
commitSHA ??= fromProperties.commitSHA
|
|
38
|
+
repositoryUrl ??= fromProperties.repositoryUrl
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (propertiesFile) {
|
|
41
|
+
log.error('Error reading DD_GIT_PROPERTIES_FILE: %s', gitPropertiesFile, error)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const folderPath = config.DD_GIT_FOLDER_PATH
|
|
47
|
+
const gitFolderPath = folderPath ?? path.join(process.cwd(), '.git')
|
|
48
|
+
|
|
49
|
+
if (!repositoryUrl) {
|
|
50
|
+
const gitConfigPath = path.join(gitFolderPath, 'config')
|
|
51
|
+
try {
|
|
52
|
+
repositoryUrl = getRemoteOriginURL(fs.readFileSync(gitConfigPath, 'utf8'))
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (folderPath) {
|
|
55
|
+
log.error('Error reading git config: %s', gitConfigPath, error)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
commitSHA ??= resolveGitHeadSHA(gitFolderPath)
|
|
61
|
+
|
|
62
|
+
cached = { commitSHA, repositoryUrl }
|
|
63
|
+
return cached
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = getGitMetadata
|
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { SCI_COMMIT_SHA, SCI_REPOSITORY_URL } = require('./constants')
|
|
4
|
+
const getGitMetadata = require('./git_metadata')
|
|
4
5
|
|
|
5
6
|
class GitMetadataTagger {
|
|
7
|
+
#commitSHA
|
|
8
|
+
#repositoryUrl
|
|
9
|
+
#enabled
|
|
10
|
+
|
|
6
11
|
constructor (config) {
|
|
7
|
-
this
|
|
12
|
+
this.#enabled = config.DD_TRACE_GIT_METADATA_ENABLED
|
|
13
|
+
const { commitSHA, repositoryUrl } = getGitMetadata(config)
|
|
14
|
+
this.#commitSHA = commitSHA
|
|
15
|
+
this.#repositoryUrl = repositoryUrl
|
|
8
16
|
}
|
|
9
17
|
|
|
10
18
|
tagGitMetadata (spanContext) {
|
|
11
|
-
if (this
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
if (this.#enabled) {
|
|
20
|
+
const tags = spanContext._trace.tags
|
|
21
|
+
tags[SCI_COMMIT_SHA] = this.#commitSHA
|
|
22
|
+
tags[SCI_REPOSITORY_URL] = this.#repositoryUrl
|
|
15
23
|
}
|
|
16
24
|
}
|
|
17
25
|
}
|
|
@@ -7,19 +7,19 @@ const UINT_MAX = 4_294_967_296
|
|
|
7
7
|
const data = new Uint8Array(8 * 8192)
|
|
8
8
|
const zeroId = new Uint8Array(8)
|
|
9
9
|
|
|
10
|
-
const map = Array.prototype.map
|
|
11
|
-
const pad = byte => `${byte < 16 ? '0' : ''}${byte.toString(16)}`
|
|
12
|
-
|
|
13
10
|
let batch = 0
|
|
14
11
|
|
|
15
12
|
// Internal representation of a trace or span ID.
|
|
16
13
|
class Identifier {
|
|
14
|
+
/** @type {number[] | Uint8Array} */
|
|
15
|
+
#buffer
|
|
16
|
+
|
|
17
17
|
/**
|
|
18
18
|
* @param {string} value
|
|
19
19
|
* @param {number} [radix]
|
|
20
20
|
*/
|
|
21
21
|
constructor (value, radix = 16) {
|
|
22
|
-
this
|
|
22
|
+
this.#buffer = radix === 16
|
|
23
23
|
? createBuffer(value)
|
|
24
24
|
: fromString(value, radix)
|
|
25
25
|
}
|
|
@@ -30,32 +30,32 @@ class Identifier {
|
|
|
30
30
|
*/
|
|
31
31
|
toString (radix = 16) {
|
|
32
32
|
return radix === 16
|
|
33
|
-
?
|
|
34
|
-
: toNumberString(this
|
|
33
|
+
? Buffer.from(this.#buffer).toString('hex')
|
|
34
|
+
: toNumberString(this.#buffer, radix)
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* @returns {bigint}
|
|
39
39
|
*/
|
|
40
40
|
toBigInt () {
|
|
41
|
-
return Buffer.from(this
|
|
41
|
+
return Buffer.from(this.#buffer).readBigUInt64BE(0)
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* @returns {number[] | Uint8Array}
|
|
46
46
|
*/
|
|
47
47
|
toBuffer () {
|
|
48
|
-
return this
|
|
48
|
+
return this.#buffer
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* @returns {number[] | Uint8Array}
|
|
53
53
|
*/
|
|
54
54
|
toArray () {
|
|
55
|
-
if (this.
|
|
56
|
-
return this
|
|
55
|
+
if (this.#buffer.length === 8) {
|
|
56
|
+
return this.#buffer
|
|
57
57
|
}
|
|
58
|
-
return this.
|
|
58
|
+
return this.#buffer.slice(-8)
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
@@ -70,12 +70,10 @@ class Identifier {
|
|
|
70
70
|
* @returns {boolean}
|
|
71
71
|
*/
|
|
72
72
|
equals (other) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
for (let i = length, j = otherLength; i >= 0 && j >= 0; i--, j--) {
|
|
78
|
-
if (this._buffer[i] !== other._buffer[j]) return false
|
|
73
|
+
// Big-endian suffix compare: when buffers differ in length, only the
|
|
74
|
+
// rightmost `min(this.length, other.length)` bytes are checked.
|
|
75
|
+
for (let i = this.#buffer.length - 1, j = other.#buffer.length - 1; i >= 0 && j >= 0; i--, j--) {
|
|
76
|
+
if (this.#buffer[i] !== other.#buffer[j]) return false
|
|
79
77
|
}
|
|
80
78
|
|
|
81
79
|
return true
|
|
@@ -174,15 +172,6 @@ function toNumberString (buffer, radix) {
|
|
|
174
172
|
return str
|
|
175
173
|
}
|
|
176
174
|
|
|
177
|
-
// Convert a buffer to a hexadecimal string.
|
|
178
|
-
/**
|
|
179
|
-
* @param {number[] | Uint8Array} buffer
|
|
180
|
-
* @returns {string}
|
|
181
|
-
*/
|
|
182
|
-
function toHexString (buffer) {
|
|
183
|
-
return map.call(buffer, pad).join('')
|
|
184
|
-
}
|
|
185
|
-
|
|
186
175
|
// Simple pseudo-random 64-bit ID generator.
|
|
187
176
|
/**
|
|
188
177
|
* @returns {number[] | Uint8Array}
|
|
@@ -7,7 +7,9 @@ module.exports = {
|
|
|
7
7
|
DECORATOR: '_ml_obs.decorator',
|
|
8
8
|
INTEGRATION: '_ml_obs.integration',
|
|
9
9
|
METADATA: '_ml_obs.meta.metadata',
|
|
10
|
+
COST_TAGS: '_ml_obs.meta.metadata._dd.cost_tags',
|
|
10
11
|
METRICS: '_ml_obs.metrics',
|
|
12
|
+
TOOL_DEFINITIONS: '_ml_obs.meta.tool_definitions',
|
|
11
13
|
ML_APP: '_ml_obs.meta.ml_app',
|
|
12
14
|
PROPAGATED_PARENT_ID_KEY: '_dd.p.llmobs_parent_id',
|
|
13
15
|
PROPAGATED_ML_APP_KEY: '_dd.p.llmobs_ml_app',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { UNKNOWN_MODEL_PROVIDER } = require('../../constants/tags')
|
|
4
|
+
const { safeJsonParse } = require('../../util')
|
|
4
5
|
const LLMObsPlugin = require('../base')
|
|
5
6
|
const { appendMessage } = require('./util')
|
|
6
7
|
|
|
@@ -47,6 +48,8 @@ class AnthropicLLMObsPlugin extends LLMObsPlugin {
|
|
|
47
48
|
const { type } = contentBlock
|
|
48
49
|
if (type === 'text') {
|
|
49
50
|
response.content.push({ type, text: contentBlock.text })
|
|
51
|
+
} else if (type === 'thinking') {
|
|
52
|
+
response.content.push({ type, thinking: contentBlock.thinking ?? '' })
|
|
50
53
|
} else if (type === 'tool_use') {
|
|
51
54
|
response.content.push({ type, name: contentBlock.name, input: '', id: contentBlock.id })
|
|
52
55
|
}
|
|
@@ -56,20 +59,29 @@ class AnthropicLLMObsPlugin extends LLMObsPlugin {
|
|
|
56
59
|
const { delta } = chunk
|
|
57
60
|
if (!delta) continue
|
|
58
61
|
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
const lastBlock = response.content[response.content.length - 1]
|
|
63
|
+
if (!lastBlock) continue
|
|
64
|
+
|
|
65
|
+
if (delta.type === 'thinking_delta') {
|
|
66
|
+
const { thinking } = delta
|
|
67
|
+
if (thinking) lastBlock.thinking += thinking
|
|
68
|
+
} else if (delta.type === 'signature_delta') {
|
|
69
|
+
// Signature is for internal verification only; skip it.
|
|
70
|
+
} else if (delta.type === 'input_json_delta') {
|
|
71
|
+
const partialJson = delta.partial_json
|
|
72
|
+
if (partialJson) lastBlock.input += partialJson
|
|
73
|
+
} else {
|
|
74
|
+
const { text } = delta
|
|
75
|
+
if (text) lastBlock.text += text
|
|
65
76
|
}
|
|
66
77
|
break
|
|
67
78
|
}
|
|
68
79
|
case 'content_block_stop': {
|
|
69
|
-
const
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
80
|
+
const lastBlock = response.content[response.content.length - 1]
|
|
81
|
+
if (!lastBlock) break
|
|
82
|
+
if (lastBlock.type === 'tool_use') {
|
|
83
|
+
const input = lastBlock.input ?? '{}'
|
|
84
|
+
lastBlock.input = safeJsonParse(input, {})
|
|
73
85
|
}
|
|
74
86
|
break
|
|
75
87
|
}
|
|
@@ -167,18 +179,17 @@ class AnthropicLLMObsPlugin extends LLMObsPlugin {
|
|
|
167
179
|
|
|
168
180
|
const outputMessages = []
|
|
169
181
|
for (const block of content) {
|
|
182
|
+
if (block.type === 'thinking') {
|
|
183
|
+
outputMessages.push({ content: block.thinking ?? '', role: 'reasoning' })
|
|
184
|
+
continue
|
|
185
|
+
}
|
|
170
186
|
const { text } = block
|
|
171
187
|
if (typeof text === 'string') {
|
|
172
188
|
outputMessages.push({ content: text, role })
|
|
173
189
|
} else if (block.type === 'tool_use') {
|
|
174
|
-
let input = block.input
|
|
175
|
-
if (typeof input === 'string') {
|
|
176
|
-
input = JSON.parse(input)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
190
|
const toolCall = {
|
|
180
191
|
name: block.name,
|
|
181
|
-
arguments: input,
|
|
192
|
+
arguments: safeJsonParse(block.input, {}),
|
|
182
193
|
toolId: block.id,
|
|
183
194
|
type: block.type,
|
|
184
195
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @typedef {{type: 'text', text: string}} TextBlock
|
|
5
5
|
* @typedef {{type: 'image'}} ImageBlock
|
|
6
|
+
* @typedef {{type: 'thinking', thinking: string, signature?: string}} ThinkingBlock
|
|
6
7
|
* @typedef {{
|
|
7
8
|
* type: 'tool_use', text: string, name: string, id: string, input: string | Record<string, unknown>
|
|
8
9
|
* }} ToolUseBlock
|
|
@@ -70,6 +71,8 @@ function appendMessage (messages, { role, content }) {
|
|
|
70
71
|
messages.push({ content: block.text, role })
|
|
71
72
|
} else if (block.type === 'image') {
|
|
72
73
|
messages.push({ content: '([IMAGE DETECTED])', role })
|
|
74
|
+
} else if (block.type === 'thinking') {
|
|
75
|
+
messages.push({ content: block.thinking ?? '', role: 'reasoning' })
|
|
73
76
|
} else if (block.type === 'tool_use') {
|
|
74
77
|
const { text, name, id, type } = block
|
|
75
78
|
let input = block.input
|