dd-trace 5.106.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.
Files changed (96) hide show
  1. package/index.d.ts +20 -1
  2. package/package.json +5 -7
  3. package/packages/datadog-core/src/storage.js +47 -48
  4. package/packages/datadog-esbuild/index.js +6 -1
  5. package/packages/datadog-instrumentations/src/ai.js +12 -3
  6. package/packages/datadog-instrumentations/src/body-parser.js +5 -2
  7. package/packages/datadog-instrumentations/src/connect.js +3 -2
  8. package/packages/datadog-instrumentations/src/cookie-parser.js +3 -2
  9. package/packages/datadog-instrumentations/src/cucumber.js +7 -0
  10. package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +7 -5
  11. package/packages/datadog-instrumentations/src/express-session.js +12 -11
  12. package/packages/datadog-instrumentations/src/express.js +24 -20
  13. package/packages/datadog-instrumentations/src/fastify.js +18 -6
  14. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +27 -12
  15. package/packages/datadog-instrumentations/src/http/client.js +9 -12
  16. package/packages/datadog-instrumentations/src/http/server.js +30 -16
  17. package/packages/datadog-instrumentations/src/http2/client.js +15 -12
  18. package/packages/datadog-instrumentations/src/http2/server.js +15 -8
  19. package/packages/datadog-instrumentations/src/jest/bail-reporter.js +42 -0
  20. package/packages/datadog-instrumentations/src/jest.js +143 -73
  21. package/packages/datadog-instrumentations/src/mocha/main.js +43 -8
  22. package/packages/datadog-instrumentations/src/mocha/utils.js +128 -17
  23. package/packages/datadog-instrumentations/src/multer.js +3 -2
  24. package/packages/datadog-instrumentations/src/mysql2.js +34 -0
  25. package/packages/datadog-instrumentations/src/net.js +8 -6
  26. package/packages/datadog-instrumentations/src/openai.js +19 -7
  27. package/packages/datadog-instrumentations/src/pg.js +19 -0
  28. package/packages/datadog-instrumentations/src/router.js +12 -10
  29. package/packages/datadog-instrumentations/src/vitest.js +29 -4
  30. package/packages/datadog-plugin-aws-sdk/src/base.js +0 -3
  31. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +62 -11
  32. package/packages/datadog-plugin-cucumber/src/index.js +2 -0
  33. package/packages/datadog-plugin-cypress/src/support.js +31 -1
  34. package/packages/datadog-plugin-http/src/client.js +0 -3
  35. package/packages/datadog-plugin-http/src/server.js +11 -1
  36. package/packages/datadog-plugin-mocha/src/index.js +2 -0
  37. package/packages/datadog-plugin-pg/src/index.js +10 -0
  38. package/packages/dd-trace/src/aiguard/index.js +34 -15
  39. package/packages/dd-trace/src/aiguard/sdk.js +34 -3
  40. package/packages/dd-trace/src/aiguard/tags.js +6 -0
  41. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +1 -1
  42. package/packages/dd-trace/src/config/defaults.js +14 -0
  43. package/packages/dd-trace/src/config/generated-config-types.d.ts +1 -1
  44. package/packages/dd-trace/src/config/helper.js +1 -0
  45. package/packages/dd-trace/src/config/index.js +5 -9
  46. package/packages/dd-trace/src/config/parsers.js +8 -0
  47. package/packages/dd-trace/src/config/supported-configurations.json +13 -6
  48. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
  49. package/packages/dd-trace/src/datastreams/writer.js +1 -2
  50. package/packages/dd-trace/src/debugger/config.js +1 -1
  51. package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -2
  52. package/packages/dd-trace/src/debugger/index.js +1 -2
  53. package/packages/dd-trace/src/dogstatsd.js +2 -3
  54. package/packages/dd-trace/src/encode/0.4.js +49 -41
  55. package/packages/dd-trace/src/encode/agentless-json.js +5 -1
  56. package/packages/dd-trace/src/encode/tags-processors.js +14 -0
  57. package/packages/dd-trace/src/exporters/agent/index.js +1 -2
  58. package/packages/dd-trace/src/exporters/agentless/index.js +6 -10
  59. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +1 -2
  60. package/packages/dd-trace/src/exporters/common/request.js +26 -0
  61. package/packages/dd-trace/src/exporters/span-stats/index.js +1 -2
  62. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +4 -0
  63. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +45 -0
  64. package/packages/dd-trace/src/llmobs/sdk.js +4 -1
  65. package/packages/dd-trace/src/llmobs/span_processor.js +17 -1
  66. package/packages/dd-trace/src/llmobs/tagger.js +5 -3
  67. package/packages/dd-trace/src/llmobs/util.js +54 -0
  68. package/packages/dd-trace/src/llmobs/writers/base.js +1 -2
  69. package/packages/dd-trace/src/llmobs/writers/util.js +1 -2
  70. package/packages/dd-trace/src/openfeature/writers/base.js +1 -10
  71. package/packages/dd-trace/src/openfeature/writers/util.js +1 -2
  72. package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +26 -13
  73. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +7 -10
  74. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +92 -0
  75. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +3 -2
  76. package/packages/dd-trace/src/opentracing/span.js +23 -18
  77. package/packages/dd-trace/src/opentracing/tracer.js +16 -12
  78. package/packages/dd-trace/src/plugins/ci_plugin.js +131 -46
  79. package/packages/dd-trace/src/priority_sampler.js +6 -5
  80. package/packages/dd-trace/src/profiling/config.js +1 -2
  81. package/packages/dd-trace/src/proxy.js +13 -10
  82. package/packages/dd-trace/src/remote_config/index.js +1 -2
  83. package/packages/dd-trace/src/runtime_metrics/client.js +30 -0
  84. package/packages/dd-trace/src/runtime_metrics/index.js +12 -2
  85. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +284 -0
  86. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +2 -11
  87. package/packages/dd-trace/src/service-naming/source-resolver.js +5 -1
  88. package/packages/dd-trace/src/span_format.js +33 -25
  89. package/packages/dd-trace/src/span_stats.js +1 -1
  90. package/packages/dd-trace/src/startup-log.js +1 -2
  91. package/packages/dd-trace/src/telemetry/send-data.js +1 -1
  92. package/packages/dd-trace/src/tracer.js +1 -1
  93. package/vendor/dist/@apm-js-collab/code-transformer/index.js +2 -2
  94. package/vendor/dist/shell-quote/index.js +1 -1
  95. package/packages/dd-trace/src/agent/url.js +0 -28
  96. package/scripts/preinstall.js +0 -34
@@ -2,7 +2,6 @@
2
2
 
3
3
  const { URL } = require('url')
4
4
  const log = require('../../log')
5
- const { getAgentUrl } = require('../../agent/url')
6
5
  const Writer = require('./writer')
7
6
 
8
7
  class AgentExporter {
@@ -11,7 +10,7 @@ class AgentExporter {
11
10
  constructor (config, prioritySampler) {
12
11
  this._config = config
13
12
  const { lookup, protocolVersion, stats = {}, apmTracingEnabled } = config
14
- this._url = getAgentUrl(config)
13
+ this._url = config.url
15
14
 
16
15
  const headers = {}
17
16
  if (stats.enabled || apmTracingEnabled === false) {
@@ -18,25 +18,21 @@ class AgentlessExporter {
18
18
 
19
19
  /**
20
20
  * @param {object} config - Configuration object
21
- * @param {string} [config.site] - The Datadog site. Defaults to 'datadoghq.com'.
22
- * @param {string} [config.url] - Override intake URL
21
+ * @param {string} [config.site] - The Datadog site. Defaults to 'datadoghq.com'.
23
22
  * @param {number} [config.flushInterval] - Batch flush interval in ms
24
23
  * @param {string} [config.env] - Environment name
25
24
  * @param {object} [config.tags] - Tags including runtime-id
26
25
  */
27
26
  constructor (config) {
28
27
  this._config = config
29
- const { site = 'datadoghq.com', url } = config
28
+ const site = config.site ?? 'datadoghq.com'
30
29
 
31
30
  try {
32
- this._url = url ? new URL(url) : new URL(`https://public-trace-http-intake.logs.${site}`)
31
+ // Agentless traffic carries the Datadog API key, so the intake is always the public https
32
+ // endpoint; never derive it from config.url (the agent's cleartext http) or the key leaks.
33
+ this._url = new URL(`https://public-trace-http-intake.logs.${site}`)
33
34
  } catch (err) {
34
- log.error(
35
- 'Invalid URL configuration for agentless exporter. url=%s, site=%s. Error: %s',
36
- url || 'not set',
37
- site,
38
- err.message
39
- )
35
+ log.error('Invalid site for agentless exporter. site=%s. Error: %s', site, err.message)
40
36
  this._url = null
41
37
  }
42
38
 
@@ -1,7 +1,6 @@
1
1
  'use strict'
2
2
 
3
3
  const { incrementCountMetric, TELEMETRY_EVENTS_ENQUEUED_FOR_SERIALIZATION } = require('../../ci-visibility/telemetry')
4
- const { getAgentUrl } = require('../../agent/url')
5
4
 
6
5
  /**
7
6
  * Base exporter that buffers traces until a writer is initialized.
@@ -14,7 +13,7 @@ class BufferingExporter {
14
13
 
15
14
  constructor (tracerConfig) {
16
15
  this._config = tracerConfig
17
- this._url = getAgentUrl(tracerConfig)
16
+ this._url = tracerConfig.url
18
17
  }
19
18
 
20
19
  export (trace) {
@@ -6,6 +6,7 @@
6
6
  const { Readable } = require('stream')
7
7
  const http = require('http')
8
8
  const https = require('https')
9
+ const net = require('net')
9
10
  const zlib = require('zlib')
10
11
 
11
12
  const { storage } = require('../../../../datadog-core')
@@ -45,6 +46,17 @@ function parseUrl (urlObjOrString) {
45
46
  return url
46
47
  }
47
48
 
49
+ /**
50
+ * @param {string} hostname Host as resolved by {@link parseUrl}; IPv6 is unbracketed (`::1`).
51
+ */
52
+ function isLoopbackHost (hostname) {
53
+ // The 127.0.0.0/8 block is loopback, but only when the host is an actual IPv4 literal: a
54
+ // hostname like `127.evil.com` shares the prefix yet resolves anywhere, so net.isIPv4 gates it.
55
+ return hostname === 'localhost' ||
56
+ hostname === '::1' ||
57
+ (hostname.startsWith('127.') && net.isIPv4(hostname))
58
+ }
59
+
48
60
  /**
49
61
  * @param {Buffer|string|Readable|Array<Buffer|string>} data
50
62
  * @param {object} options
@@ -67,6 +79,20 @@ function request (data, options, callback) {
67
79
  }
68
80
  }
69
81
 
82
+ // Never put the Datadog API key on a cleartext connection to a non-loopback host; that would
83
+ // expose it on the wire. Loopback (local agent, dev proxy, tests) is exempt. Strip the key
84
+ // rather than drop the request: the agent proxies telemetry with its own key, while an https
85
+ // intake URL is required to authenticate agentless traffic.
86
+ const hasApiKey = options.headers['dd-api-key'] !== undefined || options.headers['DD-API-KEY'] !== undefined
87
+ if (hasApiKey && options.protocol === 'http:' && !isLoopbackHost(options.hostname)) {
88
+ log.error(
89
+ 'Not sending the Datadog API key over a non-TLS connection to %s. Configure an https intake URL.',
90
+ options.hostname
91
+ )
92
+ delete options.headers['dd-api-key']
93
+ delete options.headers['DD-API-KEY']
94
+ }
95
+
70
96
  if (data instanceof Readable) {
71
97
  const chunks = []
72
98
 
@@ -1,11 +1,10 @@
1
1
  'use strict'
2
2
 
3
- const { getAgentUrl } = require('../../agent/url')
4
3
  const { Writer } = require('./writer')
5
4
 
6
5
  class SpanStatsExporter {
7
6
  constructor (config) {
8
- this._url = getAgentUrl(config)
7
+ this._url = config.url
9
8
  this._writer = new Writer({ url: this._url })
10
9
  }
11
10
 
@@ -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
- return Object.entries(tags).map(([key, value]) => `${key}:${value ?? ''}`)
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
- if (Array.isArray(toolDefinitions) && toolDefinitions.length > 0) {
180
- this._setTag(span, TOOL_DEFINITIONS, toolDefinitions)
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 ?? getAgentUrl(this._config)
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(getAgentUrl(config), (err, agentInfo) => {
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 ?? this._getAgentUrl()
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(getAgentUrl(config), (err, agentInfo) => {
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?.record(this.createMeasurement(METRIC_TYPES.COUNTER, value, attributes))
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?.record(this.createMeasurement(METRIC_TYPES.UPDOWNCOUNTER, value, attributes))
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?.record(this.createMeasurement(METRIC_TYPES.HISTOGRAM, value, attributes))
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?.record(this.createMeasurement(METRIC_TYPES.GAUGE, value, attributes))
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?.observableInstruments.add(this)
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?.observableInstruments.delete(this)
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.createMeasurement(this.#type, value, attributes))
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
- * Adds a batch observable callback (not implemented).
150
+ * Registers a batch observable callback for the given observables.
152
151
  *
153
- * @param {Function} callback - Batch observable callback
154
- * @param {Array} observables - Array of observable instruments
152
+ * @param {Function} callback
153
+ * @param {Array} observables
155
154
  */
156
155
  addBatchObservableCallback (callback, observables) {
157
- log.warn('addBatchObservableCallback is not implemented')
156
+ this.meterProvider.reader.addBatchObservableCallback(callback, observables)
158
157
  }
159
158
 
160
159
  /**
161
- * Removes a batch observable callback (not implemented).
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
- log.warn('removeBatchObservableCallback is not implemented')
164
+ this.meterProvider.reader.removeBatchObservableCallback(callback, observables)
168
165
  }
169
166
  }
170
167