dd-trace 5.105.0 → 5.107.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 +20 -1
- package/package.json +5 -7
- package/packages/datadog-core/src/storage.js +47 -48
- package/packages/datadog-esbuild/index.js +6 -1
- package/packages/datadog-instrumentations/src/ai.js +12 -3
- package/packages/datadog-instrumentations/src/aws-sdk.js +3 -2
- package/packages/datadog-instrumentations/src/body-parser.js +5 -2
- package/packages/datadog-instrumentations/src/connect.js +3 -2
- package/packages/datadog-instrumentations/src/cookie-parser.js +3 -2
- package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +19 -0
- package/packages/datadog-instrumentations/src/cucumber.js +319 -152
- package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +7 -5
- package/packages/datadog-instrumentations/src/express-session.js +12 -11
- package/packages/datadog-instrumentations/src/express.js +24 -20
- package/packages/datadog-instrumentations/src/fastify.js +18 -6
- package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +27 -12
- package/packages/datadog-instrumentations/src/http/client.js +9 -12
- package/packages/datadog-instrumentations/src/http/server.js +30 -16
- package/packages/datadog-instrumentations/src/http2/client.js +15 -12
- package/packages/datadog-instrumentations/src/http2/server.js +15 -8
- package/packages/datadog-instrumentations/src/jest/bail-reporter.js +42 -0
- package/packages/datadog-instrumentations/src/jest.js +143 -73
- package/packages/datadog-instrumentations/src/mocha/main.js +43 -8
- package/packages/datadog-instrumentations/src/mocha/utils.js +128 -17
- package/packages/datadog-instrumentations/src/multer.js +3 -2
- package/packages/datadog-instrumentations/src/mysql2.js +34 -0
- package/packages/datadog-instrumentations/src/net.js +8 -6
- package/packages/datadog-instrumentations/src/openai.js +19 -7
- package/packages/datadog-instrumentations/src/pg.js +19 -0
- package/packages/datadog-instrumentations/src/router.js +12 -10
- package/packages/datadog-instrumentations/src/vitest.js +29 -4
- package/packages/datadog-plugin-aws-sdk/src/base.js +0 -3
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +218 -4
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +62 -11
- package/packages/datadog-plugin-cucumber/src/index.js +2 -0
- package/packages/datadog-plugin-cypress/src/support.js +31 -1
- package/packages/datadog-plugin-http/src/client.js +0 -3
- package/packages/datadog-plugin-http/src/server.js +11 -1
- package/packages/datadog-plugin-mocha/src/index.js +2 -0
- package/packages/datadog-plugin-pg/src/index.js +10 -0
- package/packages/dd-trace/src/aiguard/index.js +34 -15
- package/packages/dd-trace/src/aiguard/sdk.js +34 -3
- package/packages/dd-trace/src/aiguard/tags.js +6 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
- package/packages/dd-trace/src/config/defaults.js +14 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +1 -1
- package/packages/dd-trace/src/config/helper.js +1 -0
- package/packages/dd-trace/src/config/index.js +5 -9
- package/packages/dd-trace/src/config/parsers.js +8 -0
- package/packages/dd-trace/src/config/supported-configurations.json +13 -6
- package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
- package/packages/dd-trace/src/datastreams/writer.js +1 -2
- package/packages/dd-trace/src/debugger/config.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -2
- package/packages/dd-trace/src/debugger/index.js +1 -2
- package/packages/dd-trace/src/dogstatsd.js +2 -3
- package/packages/dd-trace/src/encode/0.4.js +49 -41
- package/packages/dd-trace/src/encode/agentless-json.js +5 -1
- package/packages/dd-trace/src/encode/tags-processors.js +14 -0
- package/packages/dd-trace/src/exporters/agent/index.js +1 -2
- package/packages/dd-trace/src/exporters/agentless/index.js +6 -10
- package/packages/dd-trace/src/exporters/common/buffering-exporter.js +1 -2
- package/packages/dd-trace/src/exporters/common/request.js +26 -0
- package/packages/dd-trace/src/exporters/span-stats/index.js +1 -2
- package/packages/dd-trace/src/id.js +15 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +91 -5
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +43 -21
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +4 -0
- package/packages/dd-trace/src/llmobs/plugins/genai/util.js +45 -0
- package/packages/dd-trace/src/llmobs/sdk.js +4 -1
- package/packages/dd-trace/src/llmobs/span_processor.js +17 -1
- package/packages/dd-trace/src/llmobs/tagger.js +5 -3
- package/packages/dd-trace/src/llmobs/util.js +54 -0
- package/packages/dd-trace/src/llmobs/writers/base.js +1 -2
- package/packages/dd-trace/src/llmobs/writers/util.js +1 -2
- package/packages/dd-trace/src/openfeature/writers/base.js +1 -10
- package/packages/dd-trace/src/openfeature/writers/util.js +1 -2
- package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +26 -13
- package/packages/dd-trace/src/opentelemetry/metrics/meter.js +7 -10
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +92 -0
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +25 -5
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +2 -10
- package/packages/dd-trace/src/opentracing/span.js +23 -18
- package/packages/dd-trace/src/opentracing/span_context.js +1 -3
- package/packages/dd-trace/src/opentracing/tracer.js +16 -12
- package/packages/dd-trace/src/plugins/ci_plugin.js +131 -46
- package/packages/dd-trace/src/priority_sampler.js +6 -5
- package/packages/dd-trace/src/profiling/config.js +11 -25
- package/packages/dd-trace/src/profiling/exporters/agent.js +11 -10
- package/packages/dd-trace/src/profiling/profiler.js +19 -9
- package/packages/dd-trace/src/profiling/profilers/wall.js +2 -3
- package/packages/dd-trace/src/proxy.js +13 -10
- package/packages/dd-trace/src/remote_config/index.js +1 -2
- package/packages/dd-trace/src/runtime_metrics/client.js +30 -0
- package/packages/dd-trace/src/runtime_metrics/index.js +12 -2
- package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +284 -0
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +2 -11
- package/packages/dd-trace/src/service-naming/source-resolver.js +5 -1
- package/packages/dd-trace/src/span_format.js +33 -25
- package/packages/dd-trace/src/span_stats.js +1 -1
- package/packages/dd-trace/src/startup-log.js +1 -2
- package/packages/dd-trace/src/telemetry/send-data.js +1 -1
- package/packages/dd-trace/src/tracer.js +1 -1
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +2 -2
- package/vendor/dist/shell-quote/index.js +1 -1
- package/packages/dd-trace/src/agent/url.js +0 -28
- package/scripts/preinstall.js +0 -34
|
@@ -5,6 +5,7 @@ const {
|
|
|
5
5
|
getOperation,
|
|
6
6
|
extractMetrics,
|
|
7
7
|
extractMetadata,
|
|
8
|
+
extractToolDefinitions,
|
|
8
9
|
aggregateStreamingChunks,
|
|
9
10
|
formatInputMessages,
|
|
10
11
|
formatEmbeddingInput,
|
|
@@ -79,6 +80,9 @@ class GenAiLLMObsPlugin extends LLMObsPlugin {
|
|
|
79
80
|
const metadata = extractMetadata(config)
|
|
80
81
|
this._tagger.tagMetadata(span, metadata)
|
|
81
82
|
|
|
83
|
+
const toolDefinitions = extractToolDefinitions(config)
|
|
84
|
+
if (toolDefinitions.length > 0) this._tagger.tagToolDefinitions(span, toolDefinitions)
|
|
85
|
+
|
|
82
86
|
if (error) {
|
|
83
87
|
this._tagger.tagLLMIO(span, inputMessages, [{ content: '' }])
|
|
84
88
|
return
|
|
@@ -155,6 +155,50 @@ function extractMetadata (config) {
|
|
|
155
155
|
return metadata
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Extract tool definitions from config
|
|
160
|
+
* @param {object} config
|
|
161
|
+
* @returns {Array}
|
|
162
|
+
*/
|
|
163
|
+
function extractToolDefinitions (config) {
|
|
164
|
+
const toolDefinitions = []
|
|
165
|
+
|
|
166
|
+
if (!Array.isArray(config?.tools)) {
|
|
167
|
+
return toolDefinitions
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
for (const tool of config.tools) {
|
|
171
|
+
// Only extract tools with valid function declarations
|
|
172
|
+
if (!Array.isArray(tool?.functionDeclarations)) {
|
|
173
|
+
continue
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
for (const currDeclaration of tool.functionDeclarations) {
|
|
177
|
+
// A valid declaration must have a name
|
|
178
|
+
if (!currDeclaration?.name) {
|
|
179
|
+
continue
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const toolDef = { name: currDeclaration.name }
|
|
183
|
+
|
|
184
|
+
if (currDeclaration.description !== undefined) {
|
|
185
|
+
toolDef.description = currDeclaration.description
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Parameters can be in two different fields depending on user input
|
|
189
|
+
if (currDeclaration.parameters !== undefined) {
|
|
190
|
+
toolDef.schema = currDeclaration.parameters
|
|
191
|
+
} else if (currDeclaration.parametersJsonSchema !== undefined) {
|
|
192
|
+
toolDef.schema = currDeclaration.parametersJsonSchema
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
toolDefinitions.push(toolDef)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return toolDefinitions
|
|
200
|
+
}
|
|
201
|
+
|
|
158
202
|
/**
|
|
159
203
|
* Format function call message
|
|
160
204
|
* @param {Array} parts
|
|
@@ -498,6 +542,7 @@ module.exports = {
|
|
|
498
542
|
getOperation,
|
|
499
543
|
extractMetrics,
|
|
500
544
|
extractMetadata,
|
|
545
|
+
extractToolDefinitions,
|
|
501
546
|
aggregateStreamingChunks,
|
|
502
547
|
formatInputMessages,
|
|
503
548
|
formatEmbeddingInput,
|
|
@@ -259,7 +259,7 @@ class LLMObs extends NoopLLMObs {
|
|
|
259
259
|
throw new Error('LLMObs span must have a span kind specified')
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
-
const { inputData, outputData, metadata, metrics, tags, prompt, costTags } = options
|
|
262
|
+
const { inputData, outputData, metadata, metrics, tags, prompt, costTags, toolDefinitions } = options
|
|
263
263
|
|
|
264
264
|
if (inputData || outputData) {
|
|
265
265
|
if (spanKind === 'llm') {
|
|
@@ -289,6 +289,9 @@ class LLMObs extends NoopLLMObs {
|
|
|
289
289
|
if (prompt) {
|
|
290
290
|
this._tagger.tagPrompt(span, prompt)
|
|
291
291
|
}
|
|
292
|
+
if (toolDefinitions != null) {
|
|
293
|
+
this._tagger.tagToolDefinitions(span, toolDefinitions)
|
|
294
|
+
}
|
|
292
295
|
} catch (e) {
|
|
293
296
|
if (e.ddErrorTag) {
|
|
294
297
|
err = e.ddErrorTag
|
|
@@ -332,8 +332,24 @@ class LLMObsSpanProcessor {
|
|
|
332
332
|
return tags
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
+
/**
|
|
336
|
+
* @param {Record<string, unknown>} tags
|
|
337
|
+
*/
|
|
335
338
|
#objectTagsToStringArrayTags (tags) {
|
|
336
|
-
|
|
339
|
+
const out = []
|
|
340
|
+
for (const [key, value] of Object.entries(tags)) {
|
|
341
|
+
// Comma is the intake-side tag delimiter, so a single `"key:v1,v2"`
|
|
342
|
+
// entry fans into two orphan tags. One-per-element keeps each value
|
|
343
|
+
// addressable; empty arrays fall through to the scalar branch and
|
|
344
|
+
// still emit `key:` so `_dd.cost_tags` references keep finding a
|
|
345
|
+
// wire entry.
|
|
346
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
347
|
+
for (const item of value) out.push(`${key}:${item ?? ''}`)
|
|
348
|
+
} else {
|
|
349
|
+
out.push(`${key}:${value ?? ''}`)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return out
|
|
337
353
|
}
|
|
338
354
|
|
|
339
355
|
/**
|
|
@@ -43,7 +43,7 @@ const {
|
|
|
43
43
|
INSTRUMENTATION_METHOD_ANNOTATED,
|
|
44
44
|
} = require('./constants/tags')
|
|
45
45
|
const { storage } = require('./storage')
|
|
46
|
-
const { findGenAIAncestorSpanId, validateCostTags, writeBridgeTags } = require('./util')
|
|
46
|
+
const { findGenAIAncestorSpanId, validateCostTags, writeBridgeTags, validateToolDefinitions } = require('./util')
|
|
47
47
|
|
|
48
48
|
// global registry of LLMObs spans
|
|
49
49
|
// maps LLMObs spans to their annotations
|
|
@@ -176,8 +176,10 @@ class LLMObsTagger {
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
tagToolDefinitions (span, toolDefinitions) {
|
|
179
|
-
|
|
180
|
-
|
|
179
|
+
const validatedToolDefinitions = validateToolDefinitions(toolDefinitions)
|
|
180
|
+
|
|
181
|
+
if (validatedToolDefinitions.length > 0) {
|
|
182
|
+
this._setTag(span, TOOL_DEFINITIONS, validatedToolDefinitions)
|
|
181
183
|
} else {
|
|
182
184
|
this.#handleFailure('Tool definitions must be a non-empty array.', 'invalid_tool_definitions')
|
|
183
185
|
}
|
|
@@ -89,6 +89,59 @@ function validateCostTags (span, costTags, source, spanTags) {
|
|
|
89
89
|
return [...validatedCostTags]
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
// Validates tool definition entires
|
|
93
|
+
function validateToolDefinitions (toolDefinitions) {
|
|
94
|
+
if (!Array.isArray(toolDefinitions)) {
|
|
95
|
+
log.warn('toolDefinitions must be an array.')
|
|
96
|
+
return []
|
|
97
|
+
}
|
|
98
|
+
const validated = []
|
|
99
|
+
|
|
100
|
+
for (let i = 0; i < toolDefinitions.length; i++) {
|
|
101
|
+
const currToolDef = toolDefinitions[i]
|
|
102
|
+
if (!currToolDef || typeof currToolDef !== 'object') {
|
|
103
|
+
log.warn('Tool definition at index %d must be an object. Skipping.', i)
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Name is not optional
|
|
108
|
+
if (!currToolDef.name || typeof currToolDef.name !== 'string' || currToolDef.name.length <= 0) {
|
|
109
|
+
log.warn('Tool definition at index %d must have a non empty string "name". Skipping.', i)
|
|
110
|
+
continue
|
|
111
|
+
}
|
|
112
|
+
const validatedToolDef = { name: currToolDef.name }
|
|
113
|
+
|
|
114
|
+
// Description, Schema, and Version are optional types
|
|
115
|
+
if (currToolDef.description !== undefined) {
|
|
116
|
+
if (typeof currToolDef.description === 'string') {
|
|
117
|
+
validatedToolDef.description = currToolDef.description
|
|
118
|
+
} else {
|
|
119
|
+
log.warn('Tool definition "description" at index %d must be a string. Skipping field.', i)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (currToolDef.schema !== undefined) {
|
|
124
|
+
if (currToolDef.schema !== null && typeof currToolDef.schema === 'object' && !Array.isArray(currToolDef.schema)) {
|
|
125
|
+
validatedToolDef.schema = currToolDef.schema
|
|
126
|
+
} else {
|
|
127
|
+
log.warn('Tool definition "schema" at index %d must be a plain object. Skipping field.', i)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (currToolDef.version !== undefined) {
|
|
132
|
+
if (typeof currToolDef.version === 'string') {
|
|
133
|
+
validatedToolDef.version = currToolDef.version
|
|
134
|
+
} else {
|
|
135
|
+
log.warn('Tool definition "version" at index %d must be a string. Skipping field.', i)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
validated.push(validatedToolDef)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return validated
|
|
143
|
+
}
|
|
144
|
+
|
|
92
145
|
// extracts the argument names from a function string
|
|
93
146
|
function parseArgumentNames (str) {
|
|
94
147
|
const result = []
|
|
@@ -318,4 +371,5 @@ module.exports = {
|
|
|
318
371
|
safeJsonParse,
|
|
319
372
|
spanHasError,
|
|
320
373
|
writeBridgeTags,
|
|
374
|
+
validateToolDefinitions,
|
|
321
375
|
}
|
|
@@ -14,7 +14,6 @@ const {
|
|
|
14
14
|
EVP_SUBDOMAIN_HEADER_NAME,
|
|
15
15
|
EVP_PROXY_AGENT_BASE_PATH,
|
|
16
16
|
} = require('../constants/writers')
|
|
17
|
-
const { getAgentUrl } = require('../../agent/url')
|
|
18
17
|
const { parseResponseAndLog } = require('./util')
|
|
19
18
|
|
|
20
19
|
class LLMObsBuffer {
|
|
@@ -210,7 +209,7 @@ class BaseLLMObsWriter {
|
|
|
210
209
|
|
|
211
210
|
const overrideOriginEnv = getEnvironmentVariable('_DD_LLMOBS_OVERRIDE_ORIGIN')
|
|
212
211
|
const overrideOriginUrl = overrideOriginEnv && new URL(overrideOriginEnv)
|
|
213
|
-
const base = overrideOriginUrl ??
|
|
212
|
+
const base = overrideOriginUrl ?? this._config.url
|
|
214
213
|
|
|
215
214
|
return {
|
|
216
215
|
url: base,
|
|
@@ -4,7 +4,6 @@ const logger = require('../../log')
|
|
|
4
4
|
const { EVP_PROXY_AGENT_BASE_PATH } = require('../constants/writers')
|
|
5
5
|
const telemetry = require('../telemetry')
|
|
6
6
|
const { fetchAgentInfo } = require('../../agent/info')
|
|
7
|
-
const { getAgentUrl } = require('../../agent/url')
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
9
|
* @param {import('../../config/config-base')} config
|
|
@@ -17,7 +16,7 @@ function setAgentStrategy (config, setWritersAgentlessValue) {
|
|
|
17
16
|
return
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
fetchAgentInfo(
|
|
19
|
+
fetchAgentInfo(config.url, (err, agentInfo) => {
|
|
21
20
|
if (err) {
|
|
22
21
|
setWritersAgentlessValue(true)
|
|
23
22
|
return
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
const request = require('../../exporters/common/request')
|
|
4
4
|
const { safeJSONStringify } = require('../../exporters/common/util')
|
|
5
|
-
const { getAgentUrl } = require('../../agent/url')
|
|
6
5
|
|
|
7
6
|
const log = require('../../log')
|
|
8
7
|
|
|
@@ -37,7 +36,7 @@ class BaseFFEWriter {
|
|
|
37
36
|
|
|
38
37
|
this._config = config
|
|
39
38
|
this._endpoint = endpoint
|
|
40
|
-
this._baseUrl = agentUrl ??
|
|
39
|
+
this._baseUrl = agentUrl ?? config.url
|
|
41
40
|
this._payloadSizeLimit = payloadSizeLimit
|
|
42
41
|
this._eventSizeLimit = eventSizeLimit
|
|
43
42
|
this._headers = headers || {}
|
|
@@ -154,14 +153,6 @@ class BaseFFEWriter {
|
|
|
154
153
|
}
|
|
155
154
|
}
|
|
156
155
|
|
|
157
|
-
/**
|
|
158
|
-
* @private
|
|
159
|
-
* @returns {URL} Constructs agent URL from config
|
|
160
|
-
*/
|
|
161
|
-
_getAgentUrl () {
|
|
162
|
-
return getAgentUrl(this._config)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
156
|
/**
|
|
166
157
|
* @private
|
|
167
158
|
* @param {Array<object>} payload - Payload to encode
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
const logger = require('../../log')
|
|
4
4
|
const { EVP_PROXY_AGENT_BASE_PATH } = require('../constants/constants')
|
|
5
5
|
const { fetchAgentInfo } = require('../../agent/info')
|
|
6
|
-
const { getAgentUrl } = require('../../agent/url')
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* Determines if the agent supports EVP proxy and sets the writer enabled state accordingly
|
|
@@ -11,7 +10,7 @@ const { getAgentUrl } = require('../../agent/url')
|
|
|
11
10
|
* @param {Function} setWriterEnabledValue - Callback to set the writer enabled state
|
|
12
11
|
*/
|
|
13
12
|
function setAgentStrategy (config, setWriterEnabledValue) {
|
|
14
|
-
fetchAgentInfo(
|
|
13
|
+
fetchAgentInfo(config.url, (err, agentInfo) => {
|
|
15
14
|
if (err) {
|
|
16
15
|
logger.debug('FFE Writer disabled - error getting agent info:', err.message)
|
|
17
16
|
setWriterEnabledValue(false)
|
|
@@ -50,7 +50,7 @@ class Instrument {
|
|
|
50
50
|
* Creates a measurement object for recording metric values.
|
|
51
51
|
* @param {string} type - Metric type from METRIC_TYPES
|
|
52
52
|
* @param {number} value - Numeric value to record
|
|
53
|
-
* @param {Attributes} attributes - Key-value pairs for metric dimensions
|
|
53
|
+
* @param {Attributes} [attributes] - Key-value pairs for metric dimensions
|
|
54
54
|
* @returns {Measurement} Measurement object with metadata and timestamp
|
|
55
55
|
*/
|
|
56
56
|
createMeasurement (type, value, attributes) {
|
|
@@ -73,9 +73,9 @@ class Instrument {
|
|
|
73
73
|
* @class Counter
|
|
74
74
|
*/
|
|
75
75
|
class Counter extends Instrument {
|
|
76
|
-
add (value, attributes
|
|
76
|
+
add (value, attributes) {
|
|
77
77
|
if (value < 0) return
|
|
78
|
-
this.reader
|
|
78
|
+
this.reader.record(this.createMeasurement(METRIC_TYPES.COUNTER, value, attributes))
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -85,8 +85,8 @@ class Counter extends Instrument {
|
|
|
85
85
|
* @class UpDownCounter
|
|
86
86
|
*/
|
|
87
87
|
class UpDownCounter extends Instrument {
|
|
88
|
-
add (value, attributes
|
|
89
|
-
this.reader
|
|
88
|
+
add (value, attributes) {
|
|
89
|
+
this.reader.record(this.createMeasurement(METRIC_TYPES.UPDOWNCOUNTER, value, attributes))
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
@@ -96,9 +96,9 @@ class UpDownCounter extends Instrument {
|
|
|
96
96
|
* @class Histogram
|
|
97
97
|
*/
|
|
98
98
|
class Histogram extends Instrument {
|
|
99
|
-
record (value, attributes
|
|
99
|
+
record (value, attributes) {
|
|
100
100
|
if (value < 0) return
|
|
101
|
-
this.reader
|
|
101
|
+
this.reader.record(this.createMeasurement(METRIC_TYPES.HISTOGRAM, value, attributes))
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
|
|
@@ -108,8 +108,8 @@ class Histogram extends Instrument {
|
|
|
108
108
|
* @class Gauge
|
|
109
109
|
*/
|
|
110
110
|
class Gauge extends Instrument {
|
|
111
|
-
record (value, attributes
|
|
112
|
-
this.reader
|
|
111
|
+
record (value, attributes) {
|
|
112
|
+
this.reader.record(this.createMeasurement(METRIC_TYPES.GAUGE, value, attributes))
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
|
|
@@ -136,7 +136,7 @@ class ObservableInstrument extends Instrument {
|
|
|
136
136
|
addCallback (callback) {
|
|
137
137
|
if (typeof callback !== 'function') return
|
|
138
138
|
this.#callbacks.push(callback)
|
|
139
|
-
this.reader
|
|
139
|
+
this.reader.observableInstruments.add(this)
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
/**
|
|
@@ -150,7 +150,7 @@ class ObservableInstrument extends Instrument {
|
|
|
150
150
|
this.#callbacks.splice(index, 1)
|
|
151
151
|
if (this.#callbacks.length === 0) {
|
|
152
152
|
// Remove instrument from collection when no callbacks remain
|
|
153
|
-
this.reader
|
|
153
|
+
this.reader.observableInstruments.delete(this)
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
}
|
|
@@ -163,8 +163,8 @@ class ObservableInstrument extends Instrument {
|
|
|
163
163
|
collect () {
|
|
164
164
|
const observations = []
|
|
165
165
|
const observableResult = {
|
|
166
|
-
observe: (value, attributes
|
|
167
|
-
observations.push(this.
|
|
166
|
+
observe: (value, attributes) => {
|
|
167
|
+
observations.push(this.createObservation(value, attributes))
|
|
168
168
|
},
|
|
169
169
|
}
|
|
170
170
|
|
|
@@ -179,6 +179,18 @@ class ObservableInstrument extends Instrument {
|
|
|
179
179
|
|
|
180
180
|
return observations
|
|
181
181
|
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Builds a measurement for this instrument's metric type. Keeps the type
|
|
185
|
+
* encapsulated so callers (e.g. batch observable callbacks) don't read it.
|
|
186
|
+
*
|
|
187
|
+
* @param {number} value
|
|
188
|
+
* @param {Attributes} attributes
|
|
189
|
+
* @returns {Measurement}
|
|
190
|
+
*/
|
|
191
|
+
createObservation (value, attributes) {
|
|
192
|
+
return this.createMeasurement(this.#type, value, attributes)
|
|
193
|
+
}
|
|
182
194
|
}
|
|
183
195
|
|
|
184
196
|
/**
|
|
@@ -219,6 +231,7 @@ module.exports = {
|
|
|
219
231
|
UpDownCounter,
|
|
220
232
|
Histogram,
|
|
221
233
|
Gauge,
|
|
234
|
+
ObservableInstrument,
|
|
222
235
|
ObservableGauge,
|
|
223
236
|
ObservableCounter,
|
|
224
237
|
ObservableUpDownCounter,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { VERSION: packageVersion } = require('../../../../../version')
|
|
4
|
-
const log = require('../../log')
|
|
5
4
|
const {
|
|
6
5
|
Counter, UpDownCounter, Histogram, Gauge, ObservableGauge, ObservableCounter, ObservableUpDownCounter,
|
|
7
6
|
} = require('./instruments')
|
|
@@ -148,23 +147,21 @@ class Meter {
|
|
|
148
147
|
}
|
|
149
148
|
|
|
150
149
|
/**
|
|
151
|
-
*
|
|
150
|
+
* Registers a batch observable callback for the given observables.
|
|
152
151
|
*
|
|
153
|
-
* @param {Function} callback
|
|
154
|
-
* @param {Array} observables
|
|
152
|
+
* @param {Function} callback
|
|
153
|
+
* @param {Array} observables
|
|
155
154
|
*/
|
|
156
155
|
addBatchObservableCallback (callback, observables) {
|
|
157
|
-
|
|
156
|
+
this.meterProvider.reader.addBatchObservableCallback(callback, observables)
|
|
158
157
|
}
|
|
159
158
|
|
|
160
159
|
/**
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* @param {Function} callback - Batch observable callback
|
|
164
|
-
* @param {Array} observables - Array of observable instruments
|
|
160
|
+
* @param {Function} callback
|
|
161
|
+
* @param {Array} observables
|
|
165
162
|
*/
|
|
166
163
|
removeBatchObservableCallback (callback, observables) {
|
|
167
|
-
|
|
164
|
+
this.meterProvider.reader.removeBatchObservableCallback(callback, observables)
|
|
168
165
|
}
|
|
169
166
|
}
|
|
170
167
|
|
|
@@ -5,6 +5,7 @@ const { stableStringify } = require('../otlp/otlp_transformer_base')
|
|
|
5
5
|
const {
|
|
6
6
|
METRIC_TYPES, TEMPORALITY, DEFAULT_HISTOGRAM_BUCKETS, DEFAULT_MAX_MEASUREMENT_QUEUE_SIZE,
|
|
7
7
|
} = require('./constants')
|
|
8
|
+
const { ObservableInstrument } = require('./instruments')
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* @typedef {import('@opentelemetry/api').Attributes} Attributes
|
|
@@ -102,6 +103,7 @@ class PeriodicMetricReader {
|
|
|
102
103
|
#isShutdown = false
|
|
103
104
|
#exportInterval
|
|
104
105
|
#aggregator
|
|
106
|
+
#batchCallbacks = []
|
|
105
107
|
|
|
106
108
|
/**
|
|
107
109
|
* Creates a new PeriodicMetricReader instance.
|
|
@@ -132,6 +134,66 @@ class PeriodicMetricReader {
|
|
|
132
134
|
this.#measurements.push(measurement)
|
|
133
135
|
}
|
|
134
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Registers a batch observable callback. Mirrors
|
|
139
|
+
* `@opentelemetry/sdk-metrics` `ObservableRegistry.addBatchCallback`.
|
|
140
|
+
*
|
|
141
|
+
* @param {Function} callback
|
|
142
|
+
* @param {Array} observables
|
|
143
|
+
*/
|
|
144
|
+
addBatchObservableCallback (callback, observables) {
|
|
145
|
+
if (typeof callback !== 'function') return
|
|
146
|
+
const instruments = new Set(observables?.filter(isObservableInstrument))
|
|
147
|
+
if (instruments.size === 0) return
|
|
148
|
+
if (this.#findBatchCallback(callback, instruments) !== -1) return
|
|
149
|
+
this.#batchCallbacks.push({ callback, instruments })
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @param {Function} callback
|
|
154
|
+
* @param {Array} observables
|
|
155
|
+
*/
|
|
156
|
+
removeBatchObservableCallback (callback, observables) {
|
|
157
|
+
const instruments = new Set(observables?.filter(isObservableInstrument))
|
|
158
|
+
const idx = this.#findBatchCallback(callback, instruments)
|
|
159
|
+
if (idx !== -1) this.#batchCallbacks.splice(idx, 1)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* @param {Function} callback
|
|
164
|
+
* @param {Set} instruments
|
|
165
|
+
* @returns {number} index in #batchCallbacks, or -1
|
|
166
|
+
*/
|
|
167
|
+
#findBatchCallback (callback, instruments) {
|
|
168
|
+
return this.#batchCallbacks.findIndex(record =>
|
|
169
|
+
record.callback === callback && setEquals(record.instruments, instruments))
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Invokes batch observable callbacks and returns the produced measurements.
|
|
174
|
+
*
|
|
175
|
+
* @returns {Measurement[]}
|
|
176
|
+
*/
|
|
177
|
+
#collectBatchObservables () {
|
|
178
|
+
if (this.#batchCallbacks.length === 0) return []
|
|
179
|
+
const out = []
|
|
180
|
+
for (const { callback, instruments } of this.#batchCallbacks) {
|
|
181
|
+
const result = {
|
|
182
|
+
observe: (instrument, value, attributes) => {
|
|
183
|
+
if (instruments.has(instrument)) {
|
|
184
|
+
out.push(instrument.createObservation(value, attributes))
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
callback(result)
|
|
190
|
+
} catch (e) {
|
|
191
|
+
log.error('Error running batch observable callback', e)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return out
|
|
195
|
+
}
|
|
196
|
+
|
|
135
197
|
/**
|
|
136
198
|
* Forces an immediate collection and export of all metrics.
|
|
137
199
|
* @returns {void}
|
|
@@ -210,6 +272,17 @@ class PeriodicMetricReader {
|
|
|
210
272
|
}
|
|
211
273
|
}
|
|
212
274
|
|
|
275
|
+
const batchMeasurements = this.#collectBatchObservables()
|
|
276
|
+
if (batchMeasurements.length > 0) {
|
|
277
|
+
const remainingCapacity = DEFAULT_MAX_MEASUREMENT_QUEUE_SIZE - allMeasurements.length
|
|
278
|
+
if (batchMeasurements.length <= remainingCapacity) {
|
|
279
|
+
allMeasurements.push(...batchMeasurements)
|
|
280
|
+
} else {
|
|
281
|
+
allMeasurements.push(...batchMeasurements.slice(0, remainingCapacity))
|
|
282
|
+
this.#droppedCount += batchMeasurements.length - remainingCapacity
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
213
286
|
if (this.#droppedCount > 0) {
|
|
214
287
|
log.warn('Metric queue exceeded limit (max: %d). Dropping %d measurements.',
|
|
215
288
|
DEFAULT_MAX_MEASUREMENT_QUEUE_SIZE, this.#droppedCount)
|
|
@@ -555,4 +628,23 @@ class MetricAggregator {
|
|
|
555
628
|
}
|
|
556
629
|
}
|
|
557
630
|
|
|
631
|
+
/**
|
|
632
|
+
* @param {object} x
|
|
633
|
+
* @returns {boolean}
|
|
634
|
+
*/
|
|
635
|
+
function isObservableInstrument (x) {
|
|
636
|
+
return x instanceof ObservableInstrument
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* @param {Set} a
|
|
641
|
+
* @param {Set} b
|
|
642
|
+
* @returns {boolean}
|
|
643
|
+
*/
|
|
644
|
+
function setEquals (a, b) {
|
|
645
|
+
if (a.size !== b.size) return false
|
|
646
|
+
for (const x of a) if (!b.has(x)) return false
|
|
647
|
+
return true
|
|
648
|
+
}
|
|
649
|
+
|
|
558
650
|
module.exports = PeriodicMetricReader
|
|
@@ -4,6 +4,7 @@ const OtlpTransformerBase = require('../otlp/otlp_transformer_base')
|
|
|
4
4
|
const { getProtobufTypes } = require('../otlp/protobuf_loader')
|
|
5
5
|
const { VERSION } = require('../../../../../version')
|
|
6
6
|
const id = require('../../id')
|
|
7
|
+
const { eventTimeNano } = require('../../encode/tags-processors')
|
|
7
8
|
|
|
8
9
|
const { protoSpanKind } = getProtobufTypes()
|
|
9
10
|
const SPAN_KIND_UNSPECIFIED = protoSpanKind.values.SPAN_KIND_UNSPECIFIED
|
|
@@ -16,6 +17,11 @@ const SPAN_KIND_CONSUMER = protoSpanKind.values.SPAN_KIND_CONSUMER
|
|
|
16
17
|
// Cached zero Identifier used to detect zero IDs without re-allocating per span.
|
|
17
18
|
const ZERO_ID = id('0')
|
|
18
19
|
|
|
20
|
+
// DD propagation tag carrying the upper 64 bits of a 128-bit trace ID as 16 hex chars.
|
|
21
|
+
// span_format.js#extractChunkTags only copies this onto the first-in-chunk span, so the
|
|
22
|
+
// transformer scans the batch to find it and applies it to every span's traceId.
|
|
23
|
+
const TRACE_ID_128 = '_dd.p.tid'
|
|
24
|
+
|
|
19
25
|
/**
|
|
20
26
|
* @typedef {import('../../id').Identifier} Identifier
|
|
21
27
|
*
|
|
@@ -28,7 +34,7 @@ const ZERO_ID = id('0')
|
|
|
28
34
|
*
|
|
29
35
|
* @typedef {object} DDSpanEvent
|
|
30
36
|
* @property {string} name - Event name
|
|
31
|
-
* @property {number}
|
|
37
|
+
* @property {number} startTime - Event start time in milliseconds (sub-ms precision)
|
|
32
38
|
* @property {Record<string, string | number | boolean>} [attributes] - Event attributes
|
|
33
39
|
*
|
|
34
40
|
* @typedef {object} DDFormattedSpan
|
|
@@ -65,6 +71,7 @@ const STATUS_CODE_ERROR = 2
|
|
|
65
71
|
const EXCLUDED_META_KEYS = new Set([
|
|
66
72
|
'_dd.span_links',
|
|
67
73
|
'span.kind',
|
|
74
|
+
TRACE_ID_128,
|
|
68
75
|
])
|
|
69
76
|
|
|
70
77
|
/**
|
|
@@ -113,6 +120,18 @@ class OtlpTraceTransformer extends OtlpTransformerBase {
|
|
|
113
120
|
* @returns {object[]} Array of scope span objects
|
|
114
121
|
*/
|
|
115
122
|
#transformScopeSpans (spans) {
|
|
123
|
+
let traceKey
|
|
124
|
+
let traceIdHigh
|
|
125
|
+
const otlpSpans = spans.map((span) => {
|
|
126
|
+
// `_dd.p.tid` lives only on the first-in-chunk span of each trace.
|
|
127
|
+
// Reset at each trace boundary for batching of multiple traces.
|
|
128
|
+
const key = span.trace_id.toString(16)
|
|
129
|
+
if (key !== traceKey) {
|
|
130
|
+
traceKey = key
|
|
131
|
+
traceIdHigh = span.meta?.[TRACE_ID_128]?.toLowerCase()
|
|
132
|
+
}
|
|
133
|
+
return this.#transformSpan(span, traceIdHigh)
|
|
134
|
+
})
|
|
116
135
|
return [{
|
|
117
136
|
scope: {
|
|
118
137
|
name: 'dd-trace-js',
|
|
@@ -121,7 +140,7 @@ class OtlpTraceTransformer extends OtlpTransformerBase {
|
|
|
121
140
|
droppedAttributesCount: 0,
|
|
122
141
|
},
|
|
123
142
|
schemaUrl: '',
|
|
124
|
-
spans:
|
|
143
|
+
spans: otlpSpans,
|
|
125
144
|
}]
|
|
126
145
|
}
|
|
127
146
|
|
|
@@ -129,14 +148,15 @@ class OtlpTraceTransformer extends OtlpTransformerBase {
|
|
|
129
148
|
* Transforms a single DD-formatted span to an OTLP Span object.
|
|
130
149
|
*
|
|
131
150
|
* @param {DDFormattedSpan} span - DD-formatted span to transform
|
|
151
|
+
* @param {string | undefined} traceIdHigh - 16-char hex of the upper 64 bits of the trace ID
|
|
132
152
|
* @returns {object} OTLP Span object
|
|
133
153
|
*/
|
|
134
|
-
#transformSpan (span) {
|
|
154
|
+
#transformSpan (span, traceIdHigh) {
|
|
135
155
|
const parentId = span.parent_id
|
|
136
156
|
const links = this.#extractLinks(span.meta?.['_dd.span_links'])
|
|
137
157
|
|
|
138
158
|
return {
|
|
139
|
-
traceId:
|
|
159
|
+
traceId: span.trace_id.toTraceIdHex(traceIdHigh).padStart(32, '0'),
|
|
140
160
|
spanId: this.#idToBytes(span.span_id, 8),
|
|
141
161
|
parentSpanId: (parentId && !parentId.equals(ZERO_ID)) ? this.#idToBytes(parentId, 8) : undefined,
|
|
142
162
|
name: span.resource,
|
|
@@ -255,7 +275,7 @@ class OtlpTraceTransformer extends OtlpTransformerBase {
|
|
|
255
275
|
*/
|
|
256
276
|
#transformEvent (event) {
|
|
257
277
|
return {
|
|
258
|
-
timeUnixNano: event
|
|
278
|
+
timeUnixNano: eventTimeNano(event),
|
|
259
279
|
name: event.name || '',
|
|
260
280
|
attributes: this.transformAttributes(event.attributes ?? {}),
|
|
261
281
|
droppedAttributesCount: 0,
|