dd-trace 4.11.1 → 4.16.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 (78) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/README.md +4 -9
  3. package/ext/tags.d.ts +1 -0
  4. package/ext/tags.js +1 -0
  5. package/index.d.ts +44 -0
  6. package/package.json +9 -6
  7. package/packages/datadog-esbuild/index.js +57 -32
  8. package/packages/datadog-instrumentations/src/body-parser.js +2 -2
  9. package/packages/datadog-instrumentations/src/cookie-parser.js +37 -0
  10. package/packages/datadog-instrumentations/src/cucumber.js +30 -11
  11. package/packages/datadog-instrumentations/src/express.js +1 -1
  12. package/packages/datadog-instrumentations/src/graphql.js +10 -4
  13. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  14. package/packages/datadog-instrumentations/src/http/server.js +1 -1
  15. package/packages/datadog-instrumentations/src/jest.js +22 -11
  16. package/packages/datadog-instrumentations/src/kafkajs.js +3 -4
  17. package/packages/datadog-instrumentations/src/mocha.js +33 -8
  18. package/packages/datadog-instrumentations/src/mysql.js +39 -1
  19. package/packages/datadog-instrumentations/src/next.js +47 -19
  20. package/packages/datadog-instrumentations/src/openai.js +1 -1
  21. package/packages/datadog-instrumentations/src/pg.js +60 -15
  22. package/packages/datadog-instrumentations/src/playwright.js +15 -3
  23. package/packages/datadog-plugin-cucumber/src/index.js +14 -2
  24. package/packages/datadog-plugin-cypress/src/plugin.js +49 -13
  25. package/packages/datadog-plugin-graphql/src/index.js +3 -3
  26. package/packages/datadog-plugin-graphql/src/resolve.js +27 -2
  27. package/packages/datadog-plugin-jest/src/index.js +10 -2
  28. package/packages/datadog-plugin-jest/src/util.js +10 -4
  29. package/packages/datadog-plugin-mocha/src/index.js +14 -2
  30. package/packages/datadog-plugin-mongodb-core/src/index.js +6 -2
  31. package/packages/datadog-plugin-mysql/src/index.js +2 -2
  32. package/packages/datadog-plugin-next/src/index.js +22 -5
  33. package/packages/datadog-plugin-pg/src/index.js +2 -2
  34. package/packages/dd-trace/src/appsec/addresses.js +1 -0
  35. package/packages/dd-trace/src/appsec/channels.js +2 -0
  36. package/packages/dd-trace/src/appsec/iast/analyzers/ldap-injection-analyzer.js +7 -0
  37. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +29 -18
  38. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +19 -1
  39. package/packages/dd-trace/src/appsec/iast/path-line.js +1 -0
  40. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +1 -1
  41. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +48 -5
  42. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +14 -5
  43. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +131 -10
  44. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +0 -1
  45. package/packages/dd-trace/src/appsec/index.js +42 -7
  46. package/packages/dd-trace/src/appsec/recommended.json +655 -31
  47. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  48. package/packages/dd-trace/src/appsec/remote_config/index.js +2 -0
  49. package/packages/dd-trace/src/appsec/reporter.js +26 -0
  50. package/packages/dd-trace/src/appsec/telemetry.js +132 -0
  51. package/packages/dd-trace/src/appsec/waf/index.js +1 -1
  52. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +13 -5
  53. package/packages/dd-trace/src/appsec/waf/waf_manager.js +12 -14
  54. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +1 -14
  55. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +1 -13
  56. package/packages/dd-trace/src/datastreams/processor.js +6 -2
  57. package/packages/dd-trace/src/dogstatsd.js +108 -8
  58. package/packages/dd-trace/src/exporters/agent/writer.js +9 -9
  59. package/packages/dd-trace/src/exporters/common/request.js +13 -4
  60. package/packages/dd-trace/src/format.js +6 -1
  61. package/packages/dd-trace/src/opentracing/propagation/text_map.js +2 -2
  62. package/packages/dd-trace/src/opentracing/span.js +13 -13
  63. package/packages/dd-trace/src/opentracing/tracer.js +3 -5
  64. package/packages/dd-trace/src/plugin_manager.js +1 -2
  65. package/packages/dd-trace/src/plugins/ci_plugin.js +22 -1
  66. package/packages/dd-trace/src/plugins/database.js +14 -4
  67. package/packages/dd-trace/src/plugins/index.js +1 -0
  68. package/packages/dd-trace/src/plugins/outbound.js +4 -3
  69. package/packages/dd-trace/src/plugins/tracing.js +1 -1
  70. package/packages/dd-trace/src/plugins/util/test.js +20 -3
  71. package/packages/dd-trace/src/profiling/config.js +3 -1
  72. package/packages/dd-trace/src/profiling/profilers/wall.js +31 -7
  73. package/packages/dd-trace/src/proxy.js +13 -2
  74. package/packages/dd-trace/src/ritm.js +10 -2
  75. package/packages/dd-trace/src/{metrics.js → runtime_metrics.js} +1 -32
  76. package/packages/dd-trace/src/telemetry/dependencies.js +15 -0
  77. package/packages/dd-trace/src/telemetry/index.js +21 -2
  78. package/packages/dd-trace/src/util.js +1 -1
@@ -8,5 +8,6 @@ module.exports = {
8
8
  ASM_REQUEST_BLOCKING: 1n << 5n,
9
9
  ASM_USER_BLOCKING: 1n << 7n,
10
10
  ASM_CUSTOM_RULES: 1n << 8n,
11
- ASM_CUSTOM_BLOCKING_RESPONSE: 1n << 9n
11
+ ASM_CUSTOM_BLOCKING_RESPONSE: 1n << 9n,
12
+ ASM_TRUSTED_IPS: 1n << 10n
12
13
  }
@@ -46,6 +46,7 @@ function enableWafUpdate (appsecConfig) {
46
46
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_REQUEST_BLOCKING, true)
47
47
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_CUSTOM_RULES, true)
48
48
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_CUSTOM_BLOCKING_RESPONSE, true)
49
+ rc.updateCapabilities(RemoteConfigCapabilities.ASM_TRUSTED_IPS, true)
49
50
 
50
51
  rc.on('ASM_DATA', noop)
51
52
  rc.on('ASM_DD', noop)
@@ -66,6 +67,7 @@ function disableWafUpdate () {
66
67
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_REQUEST_BLOCKING, false)
67
68
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_CUSTOM_RULES, false)
68
69
  rc.updateCapabilities(RemoteConfigCapabilities.ASM_CUSTOM_BLOCKING_RESPONSE, false)
70
+ rc.updateCapabilities(RemoteConfigCapabilities.ASM_TRUSTED_IPS, false)
69
71
 
70
72
  rc.off('ASM_DATA', noop)
71
73
  rc.off('ASM_DD', noop)
@@ -3,6 +3,12 @@
3
3
  const Limiter = require('../rate_limiter')
4
4
  const { storage } = require('../../../datadog-core')
5
5
  const web = require('../plugins/util/web')
6
+ const {
7
+ incrementWafInitMetric,
8
+ updateWafRequestsMetricTags,
9
+ incrementWafUpdatesMetric,
10
+ incrementWafRequestsMetric
11
+ } = require('./telemetry')
6
12
 
7
13
  // default limiter, configurable with setRateLimit()
8
14
  let limiter = new Limiter(100)
@@ -63,6 +69,20 @@ function formatHeaderName (name) {
63
69
  .toLowerCase()
64
70
  }
65
71
 
72
+ function reportWafInit (wafVersion, rulesVersion, diagnosticsRules = {}) {
73
+ metricsQueue.set('_dd.appsec.waf.version', wafVersion)
74
+
75
+ metricsQueue.set('_dd.appsec.event_rules.loaded', diagnosticsRules.loaded?.length || 0)
76
+ metricsQueue.set('_dd.appsec.event_rules.error_count', diagnosticsRules.failed?.length || 0)
77
+ if (diagnosticsRules.failed?.length) {
78
+ metricsQueue.set('_dd.appsec.event_rules.errors', JSON.stringify(diagnosticsRules.errors))
79
+ }
80
+
81
+ metricsQueue.set('manual.keep', 'true')
82
+
83
+ incrementWafInitMetric(wafVersion, rulesVersion)
84
+ }
85
+
66
86
  function reportMetrics (metrics) {
67
87
  // TODO: metrics should be incremental, there already is an RFC to report metrics
68
88
  const store = storage.getStore()
@@ -80,6 +100,8 @@ function reportMetrics (metrics) {
80
100
  if (metrics.rulesVersion) {
81
101
  rootSpan.setTag('_dd.appsec.event_rules.version', metrics.rulesVersion)
82
102
  }
103
+
104
+ updateWafRequestsMetricTags(metrics, store.req)
83
105
  }
84
106
 
85
107
  function reportAttack (attackData) {
@@ -132,6 +154,8 @@ function finishRequest (req, res) {
132
154
  metricsQueue.clear()
133
155
  }
134
156
 
157
+ incrementWafRequestsMetric(req)
158
+
135
159
  if (!rootSpan.context()._tags['appsec.event']) return
136
160
 
137
161
  const newTags = filterHeaders(res.getHeaders(), RESPONSE_HEADERS_PASSLIST, 'http.response.headers.')
@@ -151,8 +175,10 @@ module.exports = {
151
175
  metricsQueue,
152
176
  filterHeaders,
153
177
  formatHeaderName,
178
+ reportWafInit,
154
179
  reportMetrics,
155
180
  reportAttack,
181
+ reportWafUpdate: incrementWafUpdatesMetric,
156
182
  finishRequest,
157
183
  setRateLimit
158
184
  }
@@ -0,0 +1,132 @@
1
+ 'use strict'
2
+
3
+ const telemetryMetrics = require('../telemetry/metrics')
4
+
5
+ const appsecMetrics = telemetryMetrics.manager.namespace('appsec')
6
+
7
+ const DD_TELEMETRY_WAF_RESULT_TAGS = Symbol('_dd.appsec.telemetry.waf.result.tags')
8
+
9
+ const tags = {
10
+ REQUEST_BLOCKED: 'request_blocked',
11
+ RULE_TRIGGERED: 'rule_triggered',
12
+ WAF_TIMEOUT: 'waf_timeout',
13
+ WAF_VERSION: 'waf_version',
14
+ EVENT_RULES_VERSION: 'event_rules_version'
15
+ }
16
+
17
+ const metricsStoreMap = new WeakMap()
18
+
19
+ let enabled = false
20
+
21
+ function enable (telemetryConfig) {
22
+ enabled = telemetryConfig?.enabled && telemetryConfig.metrics
23
+ }
24
+
25
+ function disable () {
26
+ enabled = false
27
+ }
28
+
29
+ function getStore (req) {
30
+ let store = metricsStoreMap.get(req)
31
+ if (!store) {
32
+ store = {}
33
+ metricsStoreMap.set(req, store)
34
+ }
35
+ return store
36
+ }
37
+
38
+ function getVersionsTags (wafVersion, rulesVersion) {
39
+ return {
40
+ [tags.WAF_VERSION]: wafVersion,
41
+ [tags.EVENT_RULES_VERSION]: rulesVersion
42
+ }
43
+ }
44
+
45
+ function trackWafDurations (metrics, versionsTags) {
46
+ if (metrics.duration) {
47
+ appsecMetrics.distribution('waf.duration', versionsTags).track(metrics.duration)
48
+ }
49
+ if (metrics.durationExt) {
50
+ appsecMetrics.distribution('waf.duration_ext', versionsTags).track(metrics.durationExt)
51
+ }
52
+ }
53
+
54
+ function getOrCreateMetricTags (req, versionsTags) {
55
+ const store = getStore(req)
56
+
57
+ let metricTags = store[DD_TELEMETRY_WAF_RESULT_TAGS]
58
+ if (!metricTags) {
59
+ metricTags = {
60
+ [tags.REQUEST_BLOCKED]: false,
61
+ [tags.RULE_TRIGGERED]: false,
62
+ [tags.WAF_TIMEOUT]: false,
63
+
64
+ ...versionsTags
65
+ }
66
+ store[DD_TELEMETRY_WAF_RESULT_TAGS] = metricTags
67
+ }
68
+ return metricTags
69
+ }
70
+
71
+ function updateWafRequestsMetricTags (metrics, req) {
72
+ if (!req || !enabled) return
73
+
74
+ const versionsTags = getVersionsTags(metrics.wafVersion, metrics.rulesVersion)
75
+
76
+ trackWafDurations(metrics, versionsTags)
77
+
78
+ const metricTags = getOrCreateMetricTags(req, versionsTags)
79
+
80
+ const { blockTriggered, ruleTriggered, wafTimeout } = metrics
81
+
82
+ if (blockTriggered) {
83
+ metricTags[tags.REQUEST_BLOCKED] = blockTriggered
84
+ }
85
+ if (ruleTriggered) {
86
+ metricTags[tags.RULE_TRIGGERED] = ruleTriggered
87
+ }
88
+ if (wafTimeout) {
89
+ metricTags[tags.WAF_TIMEOUT] = wafTimeout
90
+ }
91
+
92
+ return metricTags
93
+ }
94
+
95
+ function incrementWafInitMetric (wafVersion, rulesVersion) {
96
+ if (!enabled) return
97
+
98
+ const versionsTags = getVersionsTags(wafVersion, rulesVersion)
99
+
100
+ appsecMetrics.count('waf.init', versionsTags).inc()
101
+ }
102
+
103
+ function incrementWafUpdatesMetric (wafVersion, rulesVersion) {
104
+ if (!enabled) return
105
+
106
+ const versionsTags = getVersionsTags(wafVersion, rulesVersion)
107
+
108
+ appsecMetrics.count('waf.updates', versionsTags).inc()
109
+ }
110
+
111
+ function incrementWafRequestsMetric (req) {
112
+ if (!req || !enabled) return
113
+
114
+ const store = getStore(req)
115
+
116
+ const metricTags = store[DD_TELEMETRY_WAF_RESULT_TAGS]
117
+ if (metricTags) {
118
+ appsecMetrics.count('waf.requests', metricTags).inc()
119
+ }
120
+
121
+ metricsStoreMap.delete(req)
122
+ }
123
+
124
+ module.exports = {
125
+ enable,
126
+ disable,
127
+
128
+ updateWafRequestsMetricTags,
129
+ incrementWafInitMetric,
130
+ incrementWafUpdatesMetric,
131
+ incrementWafRequestsMetric
132
+ }
@@ -39,7 +39,7 @@ function update (newRules) {
39
39
  if (!waf.wafManager) throw new Error('Cannot update disabled WAF')
40
40
 
41
41
  try {
42
- waf.wafManager.ddwaf.update(newRules)
42
+ waf.wafManager.update(newRules)
43
43
  } catch (err) {
44
44
  log.error('Could not apply rules from remote config')
45
45
  throw err
@@ -4,11 +4,12 @@ const log = require('../../log')
4
4
  const Reporter = require('../reporter')
5
5
 
6
6
  class WAFContextWrapper {
7
- constructor (ddwafContext, requiredAddresses, wafTimeout, rulesInfo) {
7
+ constructor (ddwafContext, requiredAddresses, wafTimeout, wafVersion, rulesVersion) {
8
8
  this.ddwafContext = ddwafContext
9
9
  this.requiredAddresses = requiredAddresses
10
10
  this.wafTimeout = wafTimeout
11
- this.rulesInfo = rulesInfo
11
+ this.wafVersion = wafVersion
12
+ this.rulesVersion = rulesVersion
12
13
  }
13
14
 
14
15
  run (params) {
@@ -32,14 +33,21 @@ class WAFContextWrapper {
32
33
 
33
34
  const end = process.hrtime.bigint()
34
35
 
36
+ const ruleTriggered = !!result.events?.length
37
+ const blockTriggered = result.actions?.includes('block')
38
+
35
39
  Reporter.reportMetrics({
36
40
  duration: result.totalRuntime / 1e3,
37
41
  durationExt: parseInt(end - start) / 1e3,
38
- rulesVersion: this.rulesInfo.version
42
+ rulesVersion: this.rulesVersion,
43
+ ruleTriggered,
44
+ blockTriggered,
45
+ wafVersion: this.wafVersion,
46
+ wafTimeout: result.timeout
39
47
  })
40
48
 
41
- if (result.data && result.data !== '[]') {
42
- Reporter.reportAttack(result.data)
49
+ if (ruleTriggered) {
50
+ Reporter.reportAttack(JSON.stringify(result.events))
43
51
  }
44
52
 
45
53
  return result.actions
@@ -11,7 +11,10 @@ class WAFManager {
11
11
  this.config = config
12
12
  this.wafTimeout = config.wafTimeout
13
13
  this.ddwaf = this._loadDDWAF(rules)
14
- this._reportMetrics()
14
+ this.ddwafVersion = this.ddwaf.constructor.version()
15
+ this.rulesVersion = this.ddwaf.diagnostics.ruleset_version
16
+
17
+ Reporter.reportWafInit(this.ddwafVersion, this.rulesVersion, this.ddwaf.diagnostics.rules)
15
18
  }
16
19
 
17
20
  _loadDDWAF (rules) {
@@ -28,18 +31,6 @@ class WAFManager {
28
31
  }
29
32
  }
30
33
 
31
- _reportMetrics () {
32
- Reporter.metricsQueue.set('_dd.appsec.waf.version', this.ddwaf.constructor.version())
33
-
34
- const { loaded, failed, errors } = this.ddwaf.rulesInfo
35
-
36
- Reporter.metricsQueue.set('_dd.appsec.event_rules.loaded', loaded)
37
- Reporter.metricsQueue.set('_dd.appsec.event_rules.error_count', failed)
38
- if (failed) Reporter.metricsQueue.set('_dd.appsec.event_rules.errors', JSON.stringify(errors))
39
-
40
- Reporter.metricsQueue.set('manual.keep', 'true')
41
- }
42
-
43
34
  getWAFContext (req) {
44
35
  let wafContext = contexts.get(req)
45
36
 
@@ -48,7 +39,8 @@ class WAFManager {
48
39
  this.ddwaf.createContext(),
49
40
  this.ddwaf.requiredAddresses,
50
41
  this.wafTimeout,
51
- this.ddwaf.rulesInfo
42
+ this.ddwafVersion,
43
+ this.rulesVersion
52
44
  )
53
45
  contexts.set(req, wafContext)
54
46
  }
@@ -56,6 +48,12 @@ class WAFManager {
56
48
  return wafContext
57
49
  }
58
50
 
51
+ update (newRules) {
52
+ this.ddwaf.update(newRules)
53
+
54
+ Reporter.reportWafUpdate(this.ddwafVersion, this.rulesVersion)
55
+ }
56
+
59
57
  destroy () {
60
58
  if (this.ddwaf) {
61
59
  this.ddwaf.dispose()
@@ -28,25 +28,12 @@ function getItrConfiguration ({
28
28
  if (isEvpProxy) {
29
29
  options.path = '/evp_proxy/v2/api/v2/libraries/tests/services/setting'
30
30
  options.headers['X-Datadog-EVP-Subdomain'] = 'api'
31
- options.headers['X-Datadog-NeedsAppKey'] = 'true'
32
31
  } else {
33
32
  const apiKey = process.env.DATADOG_API_KEY || process.env.DD_API_KEY
34
- const appKey = process.env.DATADOG_APP_KEY ||
35
- process.env.DD_APP_KEY ||
36
- process.env.DATADOG_APPLICATION_KEY ||
37
- process.env.DD_APPLICATION_KEY
38
-
39
- const messagePrefix = 'Request to settings endpoint was not done because Datadog'
40
-
41
- if (!appKey) {
42
- return done(new Error(`${messagePrefix} application key is not defined.`))
43
- }
44
33
  if (!apiKey) {
45
- return done(new Error(`${messagePrefix} API key is not defined.`))
34
+ return done(new Error('Request to settings endpoint was not done because Datadog API key is not defined.'))
46
35
  }
47
-
48
36
  options.headers['dd-api-key'] = apiKey
49
- options.headers['dd-application-key'] = appKey
50
37
  }
51
38
 
52
39
  const data = JSON.stringify({
@@ -28,25 +28,13 @@ function getSkippableSuites ({
28
28
  if (isEvpProxy) {
29
29
  options.path = '/evp_proxy/v2/api/v2/ci/tests/skippable'
30
30
  options.headers['X-Datadog-EVP-Subdomain'] = 'api'
31
- options.headers['X-Datadog-NeedsAppKey'] = 'true'
32
31
  } else {
33
32
  const apiKey = process.env.DATADOG_API_KEY || process.env.DD_API_KEY
34
- const appKey = process.env.DATADOG_APP_KEY ||
35
- process.env.DD_APP_KEY ||
36
- process.env.DATADOG_APPLICATION_KEY ||
37
- process.env.DD_APPLICATION_KEY
38
-
39
- const messagePrefix = 'Skippable suites were not fetched because Datadog'
40
-
41
- if (!appKey) {
42
- return done(new Error(`${messagePrefix} application key is not defined.`))
43
- }
44
33
  if (!apiKey) {
45
- return done(new Error(`${messagePrefix} API key is not defined.`))
34
+ return done(new Error('Skippable suites were not fetched because Datadog API key is not defined.'))
46
35
  }
47
36
 
48
37
  options.headers['dd-api-key'] = apiKey
49
- options.headers['dd-application-key'] = appKey
50
38
  }
51
39
 
52
40
  const data = JSON.stringify({
@@ -66,7 +66,9 @@ class DataStreamsProcessor {
66
66
  port,
67
67
  url,
68
68
  env,
69
- tags
69
+ tags,
70
+ version,
71
+ service
70
72
  } = {}) {
71
73
  this.writer = new DataStreamsWriter({
72
74
  hostname,
@@ -79,7 +81,8 @@ class DataStreamsProcessor {
79
81
  this.enabled = dsmEnabled
80
82
  this.env = env
81
83
  this.tags = tags || {}
82
- this.service = this.tags.service || 'unnamed-nodejs-service'
84
+ this.service = service || 'unnamed-nodejs-service'
85
+ this.version = version || ''
83
86
  this.sequence = 0
84
87
 
85
88
  if (this.enabled) {
@@ -96,6 +99,7 @@ class DataStreamsProcessor {
96
99
  Service: this.service,
97
100
  Stats: serialized,
98
101
  TracerVersion: pkg.version,
102
+ Version: this.version,
99
103
  Lang: 'javascript'
100
104
  }
101
105
  this.writer.flush(payload)
@@ -5,6 +5,7 @@ const request = require('./exporters/common/request')
5
5
  const dgram = require('dgram')
6
6
  const isIP = require('net').isIP
7
7
  const log = require('./log')
8
+ const { URL, format } = require('url')
8
9
 
9
10
  const MAX_BUFFER_SIZE = 1024 // limit from the agent
10
11
 
@@ -13,9 +14,7 @@ const TYPE_GAUGE = 'g'
13
14
  const TYPE_DISTRIBUTION = 'd'
14
15
 
15
16
  class DogStatsDClient {
16
- constructor (options) {
17
- options = options || {}
18
-
17
+ constructor (options = {}) {
19
18
  if (options.metricsProxyUrl) {
20
19
  this._httpOptions = {
21
20
  url: options.metricsProxyUrl.toString(),
@@ -35,14 +34,14 @@ class DogStatsDClient {
35
34
  this._udp6 = this._socket('udp6')
36
35
  }
37
36
 
38
- gauge (stat, value, tags) {
39
- this._add(stat, value, TYPE_GAUGE, tags)
40
- }
41
-
42
37
  increment (stat, value, tags) {
43
38
  this._add(stat, value, TYPE_COUNTER, tags)
44
39
  }
45
40
 
41
+ gauge (stat, value, tags) {
42
+ this._add(stat, value, TYPE_GAUGE, tags)
43
+ }
44
+
46
45
  distribution (stat, value, tags) {
47
46
  this._add(stat, value, TYPE_DISTRIBUTION, tags)
48
47
  }
@@ -50,6 +49,8 @@ class DogStatsDClient {
50
49
  flush () {
51
50
  const queue = this._enqueue()
52
51
 
52
+ log.debug(`Flushing ${queue.length} metrics via ${this._httpOptions ? 'HTTP' : 'UDP'}`)
53
+
53
54
  if (this._queue.length === 0) return
54
55
 
55
56
  this._queue = []
@@ -141,6 +142,44 @@ class DogStatsDClient {
141
142
 
142
143
  return socket
143
144
  }
145
+
146
+ static generateClientConfig (config = {}) {
147
+ const tags = []
148
+
149
+ if (config.tags) {
150
+ Object.keys(config.tags)
151
+ .filter(key => typeof config.tags[key] === 'string')
152
+ .filter(key => {
153
+ // Skip runtime-id unless enabled as cardinality may be too high
154
+ if (key !== 'runtime-id') return true
155
+ return (config.experimental && config.experimental.runtimeId)
156
+ })
157
+ .forEach(key => {
158
+ // https://docs.datadoghq.com/tagging/#defining-tags
159
+ const value = config.tags[key].replace(/[^a-z0-9_:./-]/ig, '_')
160
+
161
+ tags.push(`${key}:${value}`)
162
+ })
163
+ }
164
+
165
+ const clientConfig = {
166
+ host: config.dogstatsd.hostname,
167
+ port: config.dogstatsd.port,
168
+ tags
169
+ }
170
+
171
+ if (config.url) {
172
+ clientConfig.metricsProxyUrl = config.url
173
+ } else if (config.port) {
174
+ clientConfig.metricsProxyUrl = new URL(format({
175
+ protocol: 'http:',
176
+ hostname: config.hostname || 'localhost',
177
+ port: config.port
178
+ }))
179
+ }
180
+
181
+ return clientConfig
182
+ }
144
183
  }
145
184
 
146
185
  class NoopDogStatsDClient {
@@ -153,7 +192,68 @@ class NoopDogStatsDClient {
153
192
  flush () { }
154
193
  }
155
194
 
195
+ // This is a simplified user-facing proxy to the underlying DogStatsDClient instance
196
+ class CustomMetrics {
197
+ constructor (config) {
198
+ const clientConfig = DogStatsDClient.generateClientConfig(config)
199
+ this.dogstatsd = new DogStatsDClient(clientConfig)
200
+ }
201
+
202
+ increment (stat, value = 1, tags) {
203
+ return this.dogstatsd.increment(
204
+ stat,
205
+ value,
206
+ CustomMetrics.tagTranslator(tags)
207
+ )
208
+ }
209
+
210
+ decrement (stat, value = 1, tags) {
211
+ return this.dogstatsd.increment(
212
+ stat,
213
+ value * -1,
214
+ CustomMetrics.tagTranslator(tags)
215
+ )
216
+ }
217
+
218
+ gauge (stat, value, tags) {
219
+ return this.dogstatsd.gauge(
220
+ stat,
221
+ value,
222
+ CustomMetrics.tagTranslator(tags)
223
+ )
224
+ }
225
+
226
+ distribution (stat, value, tags) {
227
+ return this.dogstatsd.distribution(
228
+ stat,
229
+ value,
230
+ CustomMetrics.tagTranslator(tags)
231
+ )
232
+ }
233
+
234
+ flush () {
235
+ return this.dogstatsd.flush()
236
+ }
237
+
238
+ /**
239
+ * Exposing { tagName: 'tagValue' } to the end user
240
+ * These are translated into [ 'tagName:tagValue' ] for internal use
241
+ */
242
+ static tagTranslator (objTags) {
243
+ const arrTags = []
244
+
245
+ if (!objTags) return arrTags
246
+
247
+ for (const [key, value] of Object.entries(objTags)) {
248
+ arrTags.push(`${key}:${value}`)
249
+ }
250
+
251
+ return arrTags
252
+ }
253
+ }
254
+
156
255
  module.exports = {
157
256
  DogStatsDClient,
158
- NoopDogStatsDClient
257
+ NoopDogStatsDClient,
258
+ CustomMetrics
159
259
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  const request = require('../common/request')
4
4
  const { startupLog } = require('../../startup-log')
5
- const metrics = require('../../metrics')
5
+ const runtimeMetrics = require('../../runtime_metrics')
6
6
  const log = require('../../log')
7
7
  const tracerVersion = require('../../../../../package.json').version
8
8
  const BaseWriter = require('../common/writer')
@@ -22,19 +22,19 @@ class Writer extends BaseWriter {
22
22
  }
23
23
 
24
24
  _sendPayload (data, count, done) {
25
- metrics.increment(`${METRIC_PREFIX}.requests`, true)
25
+ runtimeMetrics.increment(`${METRIC_PREFIX}.requests`, true)
26
26
 
27
27
  const { _headers, _lookup, _protocolVersion, _url } = this
28
28
  makeRequest(_protocolVersion, data, count, _url, _headers, _lookup, true, (err, res, status) => {
29
29
  if (status) {
30
- metrics.increment(`${METRIC_PREFIX}.responses`, true)
31
- metrics.increment(`${METRIC_PREFIX}.responses.by.status`, `status:${status}`, true)
30
+ runtimeMetrics.increment(`${METRIC_PREFIX}.responses`, true)
31
+ runtimeMetrics.increment(`${METRIC_PREFIX}.responses.by.status`, `status:${status}`, true)
32
32
  } else if (err) {
33
- metrics.increment(`${METRIC_PREFIX}.errors`, true)
34
- metrics.increment(`${METRIC_PREFIX}.errors.by.name`, `name:${err.name}`, true)
33
+ runtimeMetrics.increment(`${METRIC_PREFIX}.errors`, true)
34
+ runtimeMetrics.increment(`${METRIC_PREFIX}.errors.by.name`, `name:${err.name}`, true)
35
35
 
36
36
  if (err.code) {
37
- metrics.increment(`${METRIC_PREFIX}.errors.by.code`, `code:${err.code}`, true)
37
+ runtimeMetrics.increment(`${METRIC_PREFIX}.errors.by.code`, `code:${err.code}`, true)
38
38
  }
39
39
  }
40
40
 
@@ -53,8 +53,8 @@ class Writer extends BaseWriter {
53
53
  } catch (e) {
54
54
  log.error(e)
55
55
 
56
- metrics.increment(`${METRIC_PREFIX}.errors`, true)
57
- metrics.increment(`${METRIC_PREFIX}.errors.by.name`, `name:${e.name}`, true)
56
+ runtimeMetrics.increment(`${METRIC_PREFIX}.errors`, true)
57
+ runtimeMetrics.increment(`${METRIC_PREFIX}.errors.by.name`, `name:${e.name}`, true)
58
58
  }
59
59
  done()
60
60
  })
@@ -42,10 +42,19 @@ function urlToOptions (url) {
42
42
  return options
43
43
  }
44
44
 
45
- function fromUrlString (url) {
46
- return typeof urlToHttpOptions === 'function'
47
- ? urlToOptions(new URL(url))
48
- : urlParse(url)
45
+ function fromUrlString (urlString) {
46
+ const url = typeof urlToHttpOptions === 'function'
47
+ ? urlToOptions(new URL(urlString))
48
+ : urlParse(urlString)
49
+
50
+ // Add the 'hostname' back if we're using named pipes
51
+ if (url.protocol === 'unix:' && url.host === '.') {
52
+ const udsPath = urlString.replace(/^unix:/, '')
53
+ url.path = udsPath
54
+ url.pathname = udsPath
55
+ }
56
+
57
+ return url
49
58
  }
50
59
 
51
60
  function request (data, options, callback) {
@@ -13,7 +13,7 @@ const SPAN_SAMPLING_MECHANISM = constants.SPAN_SAMPLING_MECHANISM
13
13
  const SPAN_SAMPLING_RULE_RATE = constants.SPAN_SAMPLING_RULE_RATE
14
14
  const SPAN_SAMPLING_MAX_PER_SECOND = constants.SPAN_SAMPLING_MAX_PER_SECOND
15
15
  const SAMPLING_MECHANISM_SPAN = constants.SAMPLING_MECHANISM_SPAN
16
- const MEASURED = tags.MEASURED
16
+ const { MEASURED, BASE_SERVICE } = tags
17
17
  const ORIGIN_KEY = constants.ORIGIN_KEY
18
18
  const HOSTNAME_KEY = constants.HOSTNAME_KEY
19
19
  const TOP_LEVEL_KEY = constants.TOP_LEVEL_KEY
@@ -73,6 +73,11 @@ function extractTags (trace, span) {
73
73
  addTag({}, trace.metrics, MEASURED, 1)
74
74
  }
75
75
 
76
+ const tracerService = span.tracer()._service.toLowerCase()
77
+ if (tags['service.name']?.toLowerCase() !== tracerService) {
78
+ span.setTag(BASE_SERVICE, tracerService)
79
+ }
80
+
76
81
  for (const tag in tags) {
77
82
  switch (tag) {
78
83
  case 'service.name':
@@ -343,10 +343,10 @@ class TextMapPropagator {
343
343
  spanContext._trace.origin = value
344
344
  break
345
345
  case 't.dm': {
346
- const mechanism = -Math.abs(parseInt(value, 10))
346
+ const mechanism = Math.abs(parseInt(value, 10))
347
347
  if (Number.isInteger(mechanism)) {
348
348
  spanContext._sampling.mechanism = mechanism
349
- spanContext._trace.tags['_dd.p.dm'] = String(mechanism)
349
+ spanContext._trace.tags['_dd.p.dm'] = `-${mechanism}`
350
350
  }
351
351
  break
352
352
  }