dd-trace 5.40.0 → 5.41.1

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.
@@ -37,6 +37,7 @@ dev,@eslint/eslintrc,MIT,Copyright OpenJS Foundation and other contributors, <ww
37
37
  dev,@eslint/js,MIT,Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
38
38
  dev,@msgpack/msgpack,ISC,Copyright 2019 The MessagePack Community
39
39
  dev,@stylistic/eslint-plugin-js,MIT,Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
40
+ dev,application-config-path,MIT,Copyright (c) 2015, 2023 Linus Unnebäck
40
41
  dev,autocannon,MIT,Copyright 2016 Matteo Collina
41
42
  dev,aws-sdk,Apache 2.0,Copyright 2012-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
42
43
  dev,axios,MIT,Copyright 2014-present Matt Zabriskie
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.40.0",
3
+ "version": "5.41.1",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -82,7 +82,7 @@
82
82
  "node": ">=18"
83
83
  },
84
84
  "dependencies": {
85
- "@datadog/libdatadog": "^0.4.0",
85
+ "@datadog/libdatadog": "^0.5.0",
86
86
  "@datadog/native-appsec": "8.4.0",
87
87
  "@datadog/native-iast-rewriter": "2.8.0",
88
88
  "@datadog/native-iast-taint-tracking": "3.3.0",
@@ -95,7 +95,7 @@
95
95
  "crypto-randomuuid": "^1.0.0",
96
96
  "dc-polyfill": "^0.1.4",
97
97
  "ignore": "^5.2.4",
98
- "import-in-the-middle": "1.11.2",
98
+ "import-in-the-middle": "1.13.1",
99
99
  "istanbul-lib-coverage": "3.2.0",
100
100
  "jest-docblock": "^29.7.0",
101
101
  "koalas": "^1.0.2",
@@ -122,6 +122,7 @@
122
122
  "@msgpack/msgpack": "^3.0.0-beta3",
123
123
  "@stylistic/eslint-plugin-js": "^3.0.1",
124
124
  "@types/node": "^16.0.0",
125
+ "application-config-path": "^1.0.0",
125
126
  "autocannon": "^4.5.2",
126
127
  "aws-sdk": "^2.1446.0",
127
128
  "axios": "^1.7.4",
@@ -17,7 +17,7 @@ if (globalThis.fetch) {
17
17
 
18
18
  const ch = tracingChannel('apm:fetch:request')
19
19
  const wrapFetch = createWrapFetch(globalThis.Request, ch, () => {
20
- channel('dd-trace:instrumentation:load').publish({ name: 'fetch' })
20
+ channel('dd-trace:instrumentation:load').publish({ name: 'global:fetch' })
21
21
  })
22
22
 
23
23
  fetch = wrapFetch(globalFetch)
@@ -10,9 +10,7 @@ class GraphQLParsePlugin extends TracingPlugin {
10
10
  this.startSpan('graphql.parse', {
11
11
  service: this.config.service,
12
12
  type: 'graphql',
13
- meta: {
14
- 'graphql.source': ''
15
- }
13
+ meta: {}
16
14
  })
17
15
  }
18
16
 
@@ -25,7 +25,10 @@ class MongodbCorePlugin extends DatabasePlugin {
25
25
  'out.port': options.port
26
26
  }
27
27
  })
28
- ops.comment = this.injectDbmComment(span, ops.comment, service)
28
+ const comment = this.injectDbmComment(span, ops.comment, service)
29
+ if (comment) {
30
+ ops.comment = comment
31
+ }
29
32
  }
30
33
 
31
34
  getPeerService (tags) {
@@ -11,7 +11,7 @@ const tagger = require('./tagger')
11
11
  const get = require('../../datadog-core/src/utils/src/get')
12
12
  const has = require('../../datadog-core/src/utils/src/has')
13
13
  const set = require('../../datadog-core/src/utils/src/set')
14
- const { isTrue, isFalse } = require('./util')
14
+ const { isTrue, isFalse, normalizeProfilingEnabledValue } = require('./util')
15
15
  const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('./plugins/util/tags')
16
16
  const { getGitMetadataFromGitProperties, removeUserSensitiveInfo } = require('./git_properties')
17
17
  const { updateConfig } = require('./telemetry')
@@ -236,6 +236,12 @@ function reformatSpanSamplingRules (rules) {
236
236
 
237
237
  class Config {
238
238
  constructor (options = {}) {
239
+ if (!isInServerlessEnvironment()) {
240
+ // Bail out early if we're in a serverless environment, stable config isn't supported
241
+ const StableConfig = require('./config_stable')
242
+ this.stableConfig = new StableConfig()
243
+ }
244
+
239
245
  options = {
240
246
  ...options,
241
247
  appsec: options.appsec != null ? options.appsec : options.experimental?.appsec,
@@ -244,13 +250,24 @@ class Config {
244
250
 
245
251
  // Configure the logger first so it can be used to warn about other configs
246
252
  const logConfig = log.getConfig()
247
- this.debug = logConfig.enabled
253
+ this.debug = log.isEnabled(
254
+ this.stableConfig?.fleetEntries?.DD_TRACE_DEBUG,
255
+ this.stableConfig?.localEntries?.DD_TRACE_DEBUG
256
+ )
248
257
  this.logger = coalesce(options.logger, logConfig.logger)
249
- this.logLevel = coalesce(options.logLevel, logConfig.logLevel)
250
-
258
+ this.logLevel = log.getLogLevel(
259
+ options.logLevel,
260
+ this.stableConfig?.fleetEntries?.DD_TRACE_LOG_LEVEL,
261
+ this.stableConfig?.localEntries?.DD_TRACE_LOG_LEVEL
262
+ )
251
263
  log.use(this.logger)
252
264
  log.toggle(this.debug, this.logLevel)
253
265
 
266
+ // Process stable config warnings, if any
267
+ for (const warning of this.stableConfig?.warnings ?? []) {
268
+ log.warn(warning)
269
+ }
270
+
254
271
  checkIfBothOtelAndDdEnvVarSet()
255
272
 
256
273
  const DD_API_KEY = coalesce(
@@ -337,7 +354,9 @@ class Config {
337
354
  }
338
355
 
339
356
  this._applyDefaults()
357
+ this._applyLocalStableConfig()
340
358
  this._applyEnvironment()
359
+ this._applyFleetStableConfig()
341
360
  this._applyOptions(options)
342
361
  this._applyCalculated()
343
362
  this._applyRemote({})
@@ -576,6 +595,45 @@ class Config {
576
595
  this._setValue(defaults, 'trace.dynamoDb.tablePrimaryKeys', undefined)
577
596
  }
578
597
 
598
+ _applyLocalStableConfig () {
599
+ const obj = setHiddenProperty(this, '_localStableConfig', {})
600
+ this._applyStableConfig(this.stableConfig?.localEntries ?? {}, obj)
601
+ }
602
+
603
+ _applyFleetStableConfig () {
604
+ const obj = setHiddenProperty(this, '_fleetStableConfig', {})
605
+ this._applyStableConfig(this.stableConfig?.fleetEntries ?? {}, obj)
606
+ }
607
+
608
+ _applyStableConfig (config, obj) {
609
+ const {
610
+ DD_APPSEC_ENABLED,
611
+ DD_APPSEC_SCA_ENABLED,
612
+ DD_DATA_STREAMS_ENABLED,
613
+ DD_DYNAMIC_INSTRUMENTATION_ENABLED,
614
+ DD_ENV,
615
+ DD_IAST_ENABLED,
616
+ DD_LOGS_INJECTION,
617
+ DD_PROFILING_ENABLED,
618
+ DD_RUNTIME_METRICS_ENABLED,
619
+ DD_SERVICE,
620
+ DD_VERSION
621
+ } = config
622
+
623
+ this._setBoolean(obj, 'appsec.enabled', DD_APPSEC_ENABLED)
624
+ this._setBoolean(obj, 'appsec.sca.enabled', DD_APPSEC_SCA_ENABLED)
625
+ this._setBoolean(obj, 'dsmEnabled', DD_DATA_STREAMS_ENABLED)
626
+ this._setBoolean(obj, 'dynamicInstrumentation.enabled', DD_DYNAMIC_INSTRUMENTATION_ENABLED)
627
+ this._setString(obj, 'env', DD_ENV)
628
+ this._setBoolean(obj, 'iast.enabled', DD_IAST_ENABLED)
629
+ this._setBoolean(obj, 'logInjection', DD_LOGS_INJECTION)
630
+ const profilingEnabled = normalizeProfilingEnabledValue(DD_PROFILING_ENABLED)
631
+ this._setString(obj, 'profiling.enabled', profilingEnabled)
632
+ this._setBoolean(obj, 'runtimeMetrics', DD_RUNTIME_METRICS_ENABLED)
633
+ this._setString(obj, 'service', DD_SERVICE)
634
+ this._setString(obj, 'version', DD_VERSION)
635
+ }
636
+
579
637
  _applyEnvironment () {
580
638
  const {
581
639
  AWS_LAMBDA_FUNCTION_NAME,
@@ -715,8 +773,8 @@ class Config {
715
773
  const env = setHiddenProperty(this, '_env', {})
716
774
  setHiddenProperty(this, '_envUnprocessed', {})
717
775
 
718
- tagger.add(tags, OTEL_RESOURCE_ATTRIBUTES, true)
719
- tagger.add(tags, DD_TAGS)
776
+ tagger.add(tags, parseSpaceSeparatedTags(handleOtel(OTEL_RESOURCE_ATTRIBUTES)))
777
+ tagger.add(tags, parseSpaceSeparatedTags(DD_TAGS))
720
778
  tagger.add(tags, DD_TRACE_TAGS)
721
779
  tagger.add(tags, DD_TRACE_GLOBAL_TAGS)
722
780
 
@@ -831,16 +889,13 @@ class Config {
831
889
  this._envUnprocessed.peerServiceMapping = DD_TRACE_PEER_SERVICE_MAPPING
832
890
  }
833
891
  this._setString(env, 'port', DD_TRACE_AGENT_PORT)
834
- const profilingEnabledEnv = coalesce(
835
- DD_EXPERIMENTAL_PROFILING_ENABLED,
836
- DD_PROFILING_ENABLED,
837
- this._isInServerlessEnvironment() ? 'false' : undefined
892
+ const profilingEnabled = normalizeProfilingEnabledValue(
893
+ coalesce(
894
+ DD_EXPERIMENTAL_PROFILING_ENABLED,
895
+ DD_PROFILING_ENABLED,
896
+ this._isInServerlessEnvironment() ? 'false' : undefined
897
+ )
838
898
  )
839
- const profilingEnabled = isTrue(profilingEnabledEnv)
840
- ? 'true'
841
- : isFalse(profilingEnabledEnv)
842
- ? 'false'
843
- : profilingEnabledEnv === 'auto' ? 'auto' : undefined
844
899
  this._setString(env, 'profiling.enabled', profilingEnabled)
845
900
  this._setString(env, 'profiling.exporters', DD_PROFILING_EXPORTERS)
846
901
  this._setBoolean(env, 'profiling.sourceMap', DD_PROFILING_SOURCE_MAP && !isFalse(DD_PROFILING_SOURCE_MAP))
@@ -1347,9 +1402,33 @@ class Config {
1347
1402
  // eslint-disable-next-line @stylistic/js/max-len
1348
1403
  // https://github.com/DataDog/dd-go/blob/prod/trace/apps/tracer-telemetry-intake/telemetry-payload/static/config_norm_rules.json
1349
1404
  _merge () {
1350
- const containers = [this._remote, this._options, this._env, this._calculated, this._defaults]
1351
- const origins = ['remote_config', 'code', 'env_var', 'calculated', 'default']
1352
- const unprocessedValues = [this._remoteUnprocessed, this._optsUnprocessed, this._envUnprocessed, {}, {}]
1405
+ const containers = [
1406
+ this._remote,
1407
+ this._options,
1408
+ this._fleetStableConfig,
1409
+ this._env,
1410
+ this._localStableConfig,
1411
+ this._calculated,
1412
+ this._defaults
1413
+ ]
1414
+ const origins = [
1415
+ 'remote_config',
1416
+ 'code',
1417
+ 'fleet_stable_config',
1418
+ 'env_var',
1419
+ 'local_stable_config',
1420
+ 'calculated',
1421
+ 'default'
1422
+ ]
1423
+ const unprocessedValues = [
1424
+ this._remoteUnprocessed,
1425
+ this._optsUnprocessed,
1426
+ {},
1427
+ this._envUnprocessed,
1428
+ {},
1429
+ {},
1430
+ {}
1431
+ ]
1353
1432
  const changes = []
1354
1433
 
1355
1434
  for (const name in this._defaults) {
@@ -1394,6 +1473,21 @@ class Config {
1394
1473
  }
1395
1474
  }
1396
1475
 
1476
+ function handleOtel (tagString) {
1477
+ return tagString
1478
+ ?.replace(/(^|,)deployment\.environment=/, '$1env:')
1479
+ .replace(/(^|,)service\.name=/, '$1service:')
1480
+ .replace(/(^|,)service\.version=/, '$1version:')
1481
+ .replace(/=/g, ':')
1482
+ }
1483
+
1484
+ function parseSpaceSeparatedTags (tagString) {
1485
+ if (tagString && !tagString.includes(',')) {
1486
+ tagString = tagString.replace(/\s+/g, ',')
1487
+ }
1488
+ return tagString
1489
+ }
1490
+
1397
1491
  function maybeInt (number) {
1398
1492
  const parsed = parseInt(number)
1399
1493
  return isNaN(parsed) ? undefined : parsed
@@ -0,0 +1,100 @@
1
+ const os = require('os')
2
+ const fs = require('fs')
3
+
4
+ class StableConfig {
5
+ constructor () {
6
+ this.warnings = [] // Logger hasn't been initialized yet, so we can't use log.warn
7
+ this.localEntries = {}
8
+ this.fleetEntries = {}
9
+ this.wasm_loaded = false
10
+
11
+ const { localConfigPath, fleetConfigPath } = this._getStableConfigPaths()
12
+ if (!fs.existsSync(localConfigPath) && !fs.existsSync(fleetConfigPath)) {
13
+ // Bail out early if files don't exist to avoid unnecessary library loading
14
+ return
15
+ }
16
+
17
+ const localConfig = this._readConfigFromPath(localConfigPath)
18
+ const fleetConfig = this._readConfigFromPath(fleetConfigPath)
19
+ if (!localConfig && !fleetConfig) {
20
+ // Bail out early if files are empty or we can't read them to avoid unnecessary library loading
21
+ return
22
+ }
23
+
24
+ // Note: we don't enforce loading because there may be cases where the library is not available and we
25
+ // want to avoid breaking the application. In those cases, we will not have the file-based configuration.
26
+ let libdatadog
27
+ try {
28
+ libdatadog = require('@datadog/libdatadog')
29
+ this.wasm_loaded = true
30
+ } catch (e) {
31
+ this.warnings.push('Can\'t load libdatadog library')
32
+ return
33
+ }
34
+
35
+ const libconfig = libdatadog.maybeLoad('library_config')
36
+ if (libconfig === undefined) {
37
+ this.warnings.push('Can\'t load library_config library')
38
+ return
39
+ }
40
+
41
+ try {
42
+ const configurator = new libconfig.JsConfigurator()
43
+ configurator.set_envp(Object.entries(process.env).map(([key, value]) => `${key}=${value}`))
44
+ configurator.set_args(process.argv)
45
+ configurator.get_configuration(localConfig.toString(), fleetConfig.toString()).forEach((entry) => {
46
+ if (entry.source === 'local_stable_config') {
47
+ this.localEntries[entry.name] = entry.value
48
+ } else if (entry.source === 'fleet_stable_config') {
49
+ this.fleetEntries[entry.name] = entry.value
50
+ }
51
+ })
52
+ } catch (e) {
53
+ this.warnings.push(`Error parsing configuration from file: ${e.message}`)
54
+ }
55
+ }
56
+
57
+ _readConfigFromPath (path) {
58
+ try {
59
+ return fs.readFileSync(path, 'utf8')
60
+ } catch (err) {
61
+ if (err.code !== 'ENOENT') {
62
+ this.warnings.push(`Error reading config file at ${path}. ${err.code}: ${err.message}`)
63
+ }
64
+ return '' // Always return a string to avoid undefined.toString() errors
65
+ }
66
+ }
67
+
68
+ _getStableConfigPaths () {
69
+ let localConfigPath = ''
70
+ let fleetConfigPath = ''
71
+ switch (os.type().toLowerCase()) {
72
+ case 'linux':
73
+ localConfigPath = '/etc/datadog-agent/application_monitoring.yaml'
74
+ fleetConfigPath = '/etc/datadog-agent/managed/datadog-agent/stable/application_monitoring.yaml'
75
+ break
76
+ case 'darwin':
77
+ localConfigPath = '/opt/datadog-agent/etc/application_monitoring.yaml'
78
+ fleetConfigPath = '/opt/datadog-agent/etc/managed/datadog-agent/stable/application_monitoring.yaml'
79
+ break
80
+ case 'win32':
81
+ localConfigPath = 'C:\\ProgramData\\Datadog\\application_monitoring.yaml'
82
+ fleetConfigPath = 'C:\\ProgramData\\Datadog\\managed\\datadog-agent\\stable\\application_monitoring.yaml'
83
+ break
84
+ default:
85
+ break
86
+ }
87
+
88
+ // Allow overriding the paths for testing
89
+ if (process.env.DD_TEST_LOCAL_CONFIG_PATH !== undefined) {
90
+ localConfigPath = process.env.DD_TEST_LOCAL_CONFIG_PATH
91
+ }
92
+ if (process.env.DD_TEST_FLEET_CONFIG_PATH !== undefined) {
93
+ fleetConfigPath = process.env.DD_TEST_FLEET_CONFIG_PATH
94
+ }
95
+
96
+ return { localConfigPath, fleetConfigPath }
97
+ }
98
+ }
99
+
100
+ module.exports = StableConfig
@@ -6,6 +6,7 @@ const dgram = require('dgram')
6
6
  const isIP = require('net').isIP
7
7
  const log = require('./log')
8
8
  const { URL, format } = require('url')
9
+ const Histogram = require('./histogram')
9
10
 
10
11
  const MAX_BUFFER_SIZE = 1024 // limit from the agent
11
12
 
@@ -193,6 +194,117 @@ class DogStatsDClient {
193
194
  }
194
195
  }
195
196
 
197
+ // TODO: Handle arrays of tags and tags translation.
198
+ class MetricsAggregationClient {
199
+ constructor (client) {
200
+ this._client = client
201
+
202
+ this.reset()
203
+ }
204
+
205
+ flush () {
206
+ this._captureCounters()
207
+ this._captureGauges()
208
+ this._captureHistograms()
209
+
210
+ this._client.flush()
211
+ }
212
+
213
+ reset () {
214
+ this._counters = {}
215
+ this._gauges = {}
216
+ this._histograms = {}
217
+ }
218
+
219
+ distribution (name, value, tag) {
220
+ this._client.distribution(name, value, tag && [tag])
221
+ }
222
+
223
+ boolean (name, value, tag) {
224
+ this.gauge(name, value ? 1 : 0, tag)
225
+ }
226
+
227
+ histogram (name, value, tag) {
228
+ this._histograms[name] = this._histograms[name] || new Map()
229
+
230
+ if (!this._histograms[name].has(tag)) {
231
+ this._histograms[name].set(tag, new Histogram())
232
+ }
233
+
234
+ this._histograms[name].get(tag).record(value)
235
+ }
236
+
237
+ count (name, count, tag, monotonic = false) {
238
+ if (typeof tag === 'boolean') {
239
+ monotonic = tag
240
+ tag = undefined
241
+ }
242
+
243
+ const map = monotonic ? this._counters : this._gauges
244
+
245
+ map[name] = map[name] || new Map()
246
+
247
+ const value = map[name].get(tag) || 0
248
+
249
+ map[name].set(tag, value + count)
250
+ }
251
+
252
+ gauge (name, value, tag) {
253
+ this._gauges[name] = this._gauges[name] || new Map()
254
+ this._gauges[name].set(tag, value)
255
+ }
256
+
257
+ increment (name, count = 1, tag, monotonic) {
258
+ this.count(name, count, tag, monotonic)
259
+ }
260
+
261
+ decrement (name, count = 1, tag) {
262
+ this.count(name, -count, tag)
263
+ }
264
+
265
+ _captureGauges () {
266
+ Object.keys(this._gauges).forEach(name => {
267
+ this._gauges[name].forEach((value, tag) => {
268
+ this._client.gauge(name, value, tag && [tag])
269
+ })
270
+ })
271
+ }
272
+
273
+ _captureCounters () {
274
+ Object.keys(this._counters).forEach(name => {
275
+ this._counters[name].forEach((value, tag) => {
276
+ this._client.increment(name, value, tag && [tag])
277
+ })
278
+ })
279
+
280
+ this._counters = {}
281
+ }
282
+
283
+ _captureHistograms () {
284
+ Object.keys(this._histograms).forEach(name => {
285
+ this._histograms[name].forEach((stats, tag) => {
286
+ const tags = tag && [tag]
287
+
288
+ // Stats can contain garbage data when a value was never recorded.
289
+ if (stats.count === 0) {
290
+ stats = { max: 0, min: 0, sum: 0, avg: 0, median: 0, p95: 0, count: 0, reset: stats.reset }
291
+ }
292
+
293
+ this._client.gauge(`${name}.min`, stats.min, tags)
294
+ this._client.gauge(`${name}.max`, stats.max, tags)
295
+ this._client.increment(`${name}.sum`, stats.sum, tags)
296
+ this._client.increment(`${name}.total`, stats.sum, tags)
297
+ this._client.gauge(`${name}.avg`, stats.avg, tags)
298
+ this._client.increment(`${name}.count`, stats.count, tags)
299
+ this._client.gauge(`${name}.median`, stats.median, tags)
300
+ this._client.gauge(`${name}.95percentile`, stats.p95, tags)
301
+
302
+ stats.reset()
303
+ })
304
+ })
305
+ }
306
+ }
307
+
196
308
  /**
197
309
  * This is a simplified user-facing proxy to the underlying DogStatsDClient instance
198
310
  *
@@ -201,7 +313,7 @@ class DogStatsDClient {
201
313
  class CustomMetrics {
202
314
  constructor (config) {
203
315
  const clientConfig = DogStatsDClient.generateClientConfig(config)
204
- this.dogstatsd = new DogStatsDClient(clientConfig)
316
+ this._client = new MetricsAggregationClient(new DogStatsDClient(clientConfig))
205
317
 
206
318
  const flush = this.flush.bind(this)
207
319
 
@@ -212,47 +324,43 @@ class CustomMetrics {
212
324
  }
213
325
 
214
326
  increment (stat, value = 1, tags) {
215
- return this.dogstatsd.increment(
216
- stat,
217
- value,
218
- CustomMetrics.tagTranslator(tags)
219
- )
327
+ for (const tag of this._normalizeTags(tags)) {
328
+ this._client.increment(stat, value, tag)
329
+ }
220
330
  }
221
331
 
222
332
  decrement (stat, value = 1, tags) {
223
- return this.dogstatsd.decrement(
224
- stat,
225
- value,
226
- CustomMetrics.tagTranslator(tags)
227
- )
333
+ for (const tag of this._normalizeTags(tags)) {
334
+ this._client.decrement(stat, value, tag)
335
+ }
228
336
  }
229
337
 
230
338
  gauge (stat, value, tags) {
231
- return this.dogstatsd.gauge(
232
- stat,
233
- value,
234
- CustomMetrics.tagTranslator(tags)
235
- )
339
+ for (const tag of this._normalizeTags(tags)) {
340
+ this._client.gauge(stat, value, tag)
341
+ }
236
342
  }
237
343
 
238
344
  distribution (stat, value, tags) {
239
- return this.dogstatsd.distribution(
240
- stat,
241
- value,
242
- CustomMetrics.tagTranslator(tags)
243
- )
345
+ for (const tag of this._normalizeTags(tags)) {
346
+ this._client.distribution(stat, value, tag)
347
+ }
244
348
  }
245
349
 
246
350
  histogram (stat, value, tags) {
247
- return this.dogstatsd.histogram(
248
- stat,
249
- value,
250
- CustomMetrics.tagTranslator(tags)
251
- )
351
+ for (const tag of this._normalizeTags(tags)) {
352
+ this._client.histogram(stat, value, tag)
353
+ }
252
354
  }
253
355
 
254
356
  flush () {
255
- return this.dogstatsd.flush()
357
+ return this._client.flush()
358
+ }
359
+
360
+ _normalizeTags (tags) {
361
+ tags = CustomMetrics.tagTranslator(tags)
362
+
363
+ return tags.length === 0 ? [undefined] : tags
256
364
  }
257
365
 
258
366
  /**
@@ -274,5 +382,6 @@ class CustomMetrics {
274
382
 
275
383
  module.exports = {
276
384
  DogStatsDClient,
277
- CustomMetrics
385
+ CustomMetrics,
386
+ MetricsAggregationClient
278
387
  }
@@ -212,7 +212,6 @@ function extractError (trace, error) {
212
212
  function addTag (meta, metrics, key, value, nested) {
213
213
  switch (typeof value) {
214
214
  case 'string':
215
- if (!value) break
216
215
  meta[key] = value
217
216
  break
218
217
  case 'number':
@@ -2,6 +2,7 @@
2
2
 
3
3
  const log = require('../../log')
4
4
  const { storage: llmobsStorage } = require('../storage')
5
+ const telemetry = require('../telemetry')
5
6
 
6
7
  const TracingPlugin = require('../../plugins/tracing')
7
8
  const LLMObsTagger = require('../tagger')
@@ -36,6 +37,8 @@ class LLMObsPlugin extends TracingPlugin {
36
37
  // register options may not be set for operations we do not trace with llmobs
37
38
  // ie OpenAI fine tuning jobs, file jobs, etc.
38
39
  if (registerOptions) {
40
+ telemetry.incrementLLMObsSpanStartCount({ autoinstrumented: true, integration: this.constructor.id })
41
+
39
42
  ctx.llmobs = {} // initialize context-based namespace
40
43
  llmobsStorage.enterWith({ span })
41
44
  ctx.llmobs.parent = parent
@@ -1,6 +1,7 @@
1
1
  const BaseLLMObsPlugin = require('./base')
2
2
  const { storage } = require('../../../../datadog-core')
3
3
  const llmobsStore = storage('llmobs')
4
+ const telemetry = require('../telemetry')
4
5
 
5
6
  const {
6
7
  extractRequestParams,
@@ -46,6 +47,8 @@ class BedrockRuntimeLLMObsPlugin extends BaseLLMObsPlugin {
46
47
  }
47
48
 
48
49
  setLLMObsTags ({ request, span, response, modelProvider, modelName }) {
50
+ telemetry.incrementLLMObsSpanStartCount({ autoinstrumented: true, integration: 'bedrock' })
51
+
49
52
  const parent = llmobsStore.getStore()?.span
50
53
  this._tagger.registerLLMObsSpan(span, {
51
54
  parent,
@@ -21,6 +21,7 @@ const LlmHandler = require('./handlers/llm')
21
21
  const EmbeddingHandler = require('./handlers/embedding')
22
22
 
23
23
  class LangChainLLMObsPlugin extends LLMObsPlugin {
24
+ static get id () { return 'langchain' }
24
25
  static get prefix () {
25
26
  return 'tracing:apm:langchain:invoke'
26
27
  }
@@ -3,6 +3,7 @@
3
3
  const LLMObsPlugin = require('./base')
4
4
 
5
5
  class OpenAiLLMObsPlugin extends LLMObsPlugin {
6
+ static get id () { return 'openai' }
6
7
  static get prefix () {
7
8
  return 'tracing:apm:openai:request'
8
9
  }
@@ -14,6 +14,7 @@ const Span = require('../opentracing/span')
14
14
 
15
15
  const tracerVersion = require('../../../../package.json').version
16
16
  const logger = require('../log')
17
+ const telemetry = require('./telemetry')
17
18
 
18
19
  const LLMObsTagger = require('./tagger')
19
20
 
@@ -88,6 +89,8 @@ class LLMObs extends NoopLLMObs {
88
89
 
89
90
  const kind = validateKind(options.kind) // will throw if kind is undefined or not an expected kind
90
91
 
92
+ telemetry.incrementLLMObsSpanStartCount({ autoinstrumented: false, kind })
93
+
91
94
  // name is required for spans generated with `trace`
92
95
  // while `kind` is required, this should never throw (as otherwise it would have thrown above)
93
96
  const name = options.name || kind
@@ -133,6 +136,8 @@ class LLMObs extends NoopLLMObs {
133
136
  const llmobs = this
134
137
 
135
138
  function wrapped () {
139
+ telemetry.incrementLLMObsSpanStartCount({ autoinstrumented: false, kind })
140
+
136
141
  const span = llmobs._tracer.scope().active()
137
142
  const fnArgs = arguments
138
143
 
@@ -0,0 +1,12 @@
1
+ 'use strict'
2
+
3
+ const telemetryMetrics = require('../telemetry/metrics')
4
+ const llmobsMetrics = telemetryMetrics.manager.namespace('mlobs')
5
+
6
+ function incrementLLMObsSpanStartCount (tags, value = 1) {
7
+ llmobsMetrics.count('span.start', tags).inc(value)
8
+ }
9
+
10
+ module.exports = {
11
+ incrementLLMObsSpanStartCount
12
+ }
@@ -105,23 +105,36 @@ const log = {
105
105
 
106
106
  deprecate (code, message) {
107
107
  return this._deprecate(code, message)
108
+ },
109
+
110
+ isEnabled (fleetStableConfigValue = undefined, localStableConfigValue = undefined) {
111
+ return isTrue(coalesce(
112
+ fleetStableConfigValue,
113
+ process.env?.DD_TRACE_DEBUG,
114
+ process.env?.OTEL_LOG_LEVEL === 'debug' || undefined,
115
+ localStableConfigValue,
116
+ config.enabled
117
+ ))
118
+ },
119
+
120
+ getLogLevel (
121
+ optionsValue = undefined,
122
+ fleetStableConfigValue = undefined,
123
+ localStableConfigValue = undefined
124
+ ) {
125
+ return coalesce(
126
+ optionsValue,
127
+ fleetStableConfigValue,
128
+ process.env?.DD_TRACE_LOG_LEVEL,
129
+ process.env?.OTEL_LOG_LEVEL,
130
+ localStableConfigValue,
131
+ config.logLevel
132
+ )
108
133
  }
109
134
  }
110
135
 
111
136
  log.reset()
112
137
 
113
- const enabled = isTrue(coalesce(
114
- process.env.DD_TRACE_DEBUG,
115
- process.env.OTEL_LOG_LEVEL === 'debug',
116
- config.enabled
117
- ))
118
-
119
- const logLevel = coalesce(
120
- process.env.DD_TRACE_LOG_LEVEL,
121
- process.env.OTEL_LOG_LEVEL,
122
- config.logLevel
123
- )
124
-
125
- log.toggle(enabled, logLevel)
138
+ log.toggle(log.isEnabled(), log.getLogLevel())
126
139
 
127
140
  module.exports = log
@@ -103,10 +103,6 @@ module.exports = class PluginManager {
103
103
  this._tracerConfig = config
104
104
  this._tracer._nomenclature.configure(config)
105
105
 
106
- if (!config._isInServerlessEnvironment?.()) {
107
- maybeEnable(require('../../datadog-plugin-fetch/src'))
108
- }
109
-
110
106
  for (const name in pluginClasses) {
111
107
  this.loadPlugin(name)
112
108
  }
@@ -39,6 +39,7 @@ module.exports = {
39
39
  get express () { return require('../../../datadog-plugin-express/src') },
40
40
  get fastify () { return require('../../../datadog-plugin-fastify/src') },
41
41
  get 'find-my-way' () { return require('../../../datadog-plugin-find-my-way/src') },
42
+ get 'global:fetch' () { return require('../../../datadog-plugin-fetch/src') },
42
43
  get graphql () { return require('../../../datadog-plugin-graphql/src') },
43
44
  get grpc () { return require('../../../datadog-plugin-grpc/src') },
44
45
  get hapi () { return require('../../../datadog-plugin-hapi/src') },
@@ -328,22 +328,34 @@ function getGitMetadata (ciMetadata) {
328
328
  committerDate
329
329
  ] = sanitizedExec('git', ['show', '-s', '--format=%an,%ae,%aI,%cn,%ce,%cI']).split(',')
330
330
 
331
- return {
332
- [GIT_REPOSITORY_URL]:
333
- filterSensitiveInfoFromRepository(repositoryUrl || sanitizedExec('git', ['ls-remote', '--get-url'])),
331
+ const tags = {
334
332
  [GIT_COMMIT_MESSAGE]:
335
333
  commitMessage || sanitizedExec('git', ['show', '-s', '--format=%s']),
336
- [GIT_COMMIT_AUTHOR_DATE]: authorDate,
337
- [GIT_COMMIT_AUTHOR_NAME]: ciAuthorName || authorName,
338
- [GIT_COMMIT_AUTHOR_EMAIL]: ciAuthorEmail || authorEmail,
339
- [GIT_COMMIT_COMMITTER_DATE]: committerDate,
340
- [GIT_COMMIT_COMMITTER_NAME]: committerName,
341
- [GIT_COMMIT_COMMITTER_EMAIL]: committerEmail,
342
334
  [GIT_BRANCH]: branch || sanitizedExec('git', ['rev-parse', '--abbrev-ref', 'HEAD']),
343
335
  [GIT_COMMIT_SHA]: commitSHA || sanitizedExec('git', ['rev-parse', 'HEAD']),
344
- [GIT_TAG]: tag,
345
336
  [CI_WORKSPACE_PATH]: ciWorkspacePath || sanitizedExec('git', ['rev-parse', '--show-toplevel'])
346
337
  }
338
+
339
+ const entries = [
340
+ GIT_REPOSITORY_URL,
341
+ filterSensitiveInfoFromRepository(repositoryUrl || sanitizedExec('git', ['ls-remote', '--get-url'])),
342
+ GIT_COMMIT_AUTHOR_DATE, authorDate,
343
+ GIT_COMMIT_AUTHOR_NAME, ciAuthorName || authorName,
344
+ GIT_COMMIT_AUTHOR_EMAIL, ciAuthorEmail || authorEmail,
345
+ GIT_COMMIT_COMMITTER_DATE, committerDate,
346
+ GIT_COMMIT_COMMITTER_NAME, committerName,
347
+ GIT_COMMIT_COMMITTER_EMAIL, committerEmail,
348
+ GIT_TAG, tag
349
+ ]
350
+
351
+ for (let i = 0; i < entries.length; i += 2) {
352
+ const value = entries[i + 1]
353
+ if (value) {
354
+ tags[entries[i]] = value
355
+ }
356
+ }
357
+
358
+ return tags
347
359
  }
348
360
 
349
361
  module.exports = {
@@ -475,8 +475,9 @@ function addRequestTags (context, spanType) {
475
475
  function addResponseTags (context) {
476
476
  const { req, res, paths, span, inferredProxySpan } = context
477
477
 
478
- if (paths.length > 0) {
479
- span.setTag(HTTP_ROUTE, paths.join(''))
478
+ const route = paths.join('')
479
+ if (route) {
480
+ span.setTag(HTTP_ROUTE, route)
480
481
  }
481
482
 
482
483
  span.addTags({
@@ -4,7 +4,7 @@
4
4
 
5
5
  const v8 = require('v8')
6
6
  const os = require('os')
7
- const { DogStatsDClient } = require('../dogstatsd')
7
+ const { DogStatsDClient, MetricsAggregationClient } = require('../dogstatsd')
8
8
  const log = require('../log')
9
9
  const Histogram = require('../histogram')
10
10
  const { performance, PerformanceObserver } = require('perf_hooks')
@@ -25,9 +25,6 @@ let interval
25
25
  let client
26
26
  let time
27
27
  let cpuUsage
28
- let gauges
29
- let counters
30
- let histograms
31
28
  let elu
32
29
 
33
30
  reset()
@@ -49,7 +46,7 @@ const runtimeMetrics = module.exports = {
49
46
  nativeMetrics = null
50
47
  }
51
48
 
52
- client = new DogStatsDClient(clientConfig)
49
+ client = new MetricsAggregationClient(new DogStatsDClient(clientConfig))
53
50
 
54
51
  time = process.hrtime()
55
52
 
@@ -98,50 +95,27 @@ const runtimeMetrics = module.exports = {
98
95
  },
99
96
 
100
97
  boolean (name, value, tag) {
101
- this.gauge(name, value ? 1 : 0, tag)
98
+ client && client.boolean(name, value, tag)
102
99
  },
103
100
 
104
101
  histogram (name, value, tag) {
105
- if (!client) return
106
-
107
- histograms[name] = histograms[name] || new Map()
108
-
109
- if (!histograms[name].has(tag)) {
110
- histograms[name].set(tag, new Histogram())
111
- }
112
-
113
- histograms[name].get(tag).record(value)
102
+ client && client.histogram(name, value, tag)
114
103
  },
115
104
 
116
105
  count (name, count, tag, monotonic = false) {
117
- if (!client) return
118
- if (typeof tag === 'boolean') {
119
- monotonic = tag
120
- tag = undefined
121
- }
122
-
123
- const map = monotonic ? counters : gauges
124
-
125
- map[name] = map[name] || new Map()
126
-
127
- const value = map[name].get(tag) || 0
128
-
129
- map[name].set(tag, value + count)
106
+ client && client.count(name, count, tag, monotonic)
130
107
  },
131
108
 
132
109
  gauge (name, value, tag) {
133
- if (!client) return
134
-
135
- gauges[name] = gauges[name] || new Map()
136
- gauges[name].set(tag, value)
110
+ client && client.gauge(name, value, tag)
137
111
  },
138
112
 
139
113
  increment (name, tag, monotonic) {
140
- this.count(name, 1, tag, monotonic)
114
+ client && client.increment(name, 1, tag, monotonic)
141
115
  },
142
116
 
143
117
  decrement (name, tag) {
144
- this.count(name, -1, tag)
118
+ client && client.decrement(name, 1, tag)
145
119
  }
146
120
  }
147
121
 
@@ -150,9 +124,6 @@ function reset () {
150
124
  client = null
151
125
  time = null
152
126
  cpuUsage = null
153
- gauges = {}
154
- counters = {}
155
- histograms = {}
156
127
  nativeMetrics = null
157
128
  gcObserver && gcObserver.disconnect()
158
129
  gcObserver = null
@@ -246,33 +217,6 @@ function captureGCMetrics () {
246
217
  gcProfiler.start()
247
218
  }
248
219
 
249
- function captureGauges () {
250
- Object.keys(gauges).forEach(name => {
251
- gauges[name].forEach((value, tag) => {
252
- client.gauge(name, value, tag && [tag])
253
- })
254
- })
255
- }
256
-
257
- function captureCounters () {
258
- Object.keys(counters).forEach(name => {
259
- counters[name].forEach((value, tag) => {
260
- client.increment(name, value, tag && [tag])
261
- })
262
- })
263
-
264
- counters = {}
265
- }
266
-
267
- function captureHistograms () {
268
- Object.keys(histograms).forEach(name => {
269
- histograms[name].forEach((stats, tag) => {
270
- histogram(name, stats, tag && [tag])
271
- stats.reset()
272
- })
273
- })
274
- }
275
-
276
220
  /**
277
221
  * Gathers and reports Event Loop Utilization (ELU) since last run
278
222
  *
@@ -295,9 +239,6 @@ function captureCommonMetrics () {
295
239
  captureMemoryUsage()
296
240
  captureProcess()
297
241
  captureHeapStats()
298
- captureGauges()
299
- captureCounters()
300
- captureHistograms()
301
242
  captureELU()
302
243
  captureGCMetrics()
303
244
  }
@@ -339,21 +280,15 @@ function captureNativeMetrics () {
339
280
  }
340
281
 
341
282
  function histogram (name, stats, tags) {
342
- tags = [].concat(tags)
283
+ tags = tags ? [].concat(tags) : []
343
284
 
344
- // Stats can contain garbage data when a value was never recorded.
345
- if (stats.count === 0) {
346
- stats = { max: 0, min: 0, sum: 0, avg: 0, median: 0, p95: 0, count: 0 }
285
+ if (tags.length > 0) {
286
+ for (const tag of tags) {
287
+ client.histogram(name, stats, tag)
288
+ }
289
+ } else {
290
+ client.histogram(name, stats)
347
291
  }
348
-
349
- client.gauge(`${name}.min`, stats.min, tags)
350
- client.gauge(`${name}.max`, stats.max, tags)
351
- client.increment(`${name}.sum`, stats.sum, tags)
352
- client.increment(`${name}.total`, stats.sum, tags)
353
- client.gauge(`${name}.avg`, stats.avg, tags)
354
- client.increment(`${name}.count`, stats.count, tags)
355
- client.gauge(`${name}.median`, stats.median, tags)
356
- client.gauge(`${name}.95percentile`, stats.p95, tags)
357
292
  }
358
293
 
359
294
  function startGCObserver () {
@@ -6,13 +6,7 @@ const ERROR_MESSAGE = constants.ERROR_MESSAGE
6
6
  const ERROR_STACK = constants.ERROR_STACK
7
7
  const ERROR_TYPE = constants.ERROR_TYPE
8
8
 
9
- const otelTagMap = {
10
- 'deployment.environment': 'env',
11
- 'service.name': 'service',
12
- 'service.version': 'version'
13
- }
14
-
15
- function add (carrier, keyValuePairs, parseOtelTags = false) {
9
+ function add (carrier, keyValuePairs) {
16
10
  if (!carrier || !keyValuePairs) return
17
11
 
18
12
  if (Array.isArray(keyValuePairs)) {
@@ -22,14 +16,13 @@ function add (carrier, keyValuePairs, parseOtelTags = false) {
22
16
  if (typeof keyValuePairs === 'string') {
23
17
  const segments = keyValuePairs.split(',')
24
18
  for (const segment of segments) {
25
- const separatorIndex = parseOtelTags ? segment.indexOf('=') : segment.indexOf(':')
26
- if (separatorIndex === -1) continue
27
-
28
- let key = segment.slice(0, separatorIndex)
29
- const value = segment.slice(separatorIndex + 1)
19
+ const separatorIndex = segment.indexOf(':')
30
20
 
31
- if (parseOtelTags && key in otelTagMap) {
32
- key = otelTagMap[key]
21
+ let value = ''
22
+ let key = segment
23
+ if (separatorIndex !== -1) {
24
+ key = segment.slice(0, separatorIndex)
25
+ value = segment.slice(separatorIndex + 1)
33
26
  }
34
27
 
35
28
  carrier[key.trim()] = value.trim()
@@ -79,11 +79,20 @@ function hasOwn (object, prop) {
79
79
  return Object.prototype.hasOwnProperty.call(object, prop)
80
80
  }
81
81
 
82
+ function normalizeProfilingEnabledValue (configValue) {
83
+ return isTrue(configValue)
84
+ ? 'true'
85
+ : isFalse(configValue)
86
+ ? 'false'
87
+ : configValue === 'auto' ? 'auto' : undefined
88
+ }
89
+
82
90
  module.exports = {
83
91
  isTrue,
84
92
  isFalse,
85
93
  isError,
86
94
  globMatch,
87
95
  calculateDDBasePath,
88
- hasOwn
96
+ hasOwn,
97
+ normalizeProfilingEnabledValue
89
98
  }