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.
- package/LICENSE-3rdparty.csv +1 -0
- package/README.md +4 -9
- package/ext/tags.d.ts +1 -0
- package/ext/tags.js +1 -0
- package/index.d.ts +44 -0
- package/package.json +9 -6
- package/packages/datadog-esbuild/index.js +57 -32
- package/packages/datadog-instrumentations/src/body-parser.js +2 -2
- package/packages/datadog-instrumentations/src/cookie-parser.js +37 -0
- package/packages/datadog-instrumentations/src/cucumber.js +30 -11
- package/packages/datadog-instrumentations/src/express.js +1 -1
- package/packages/datadog-instrumentations/src/graphql.js +10 -4
- package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
- package/packages/datadog-instrumentations/src/http/server.js +1 -1
- package/packages/datadog-instrumentations/src/jest.js +22 -11
- package/packages/datadog-instrumentations/src/kafkajs.js +3 -4
- package/packages/datadog-instrumentations/src/mocha.js +33 -8
- package/packages/datadog-instrumentations/src/mysql.js +39 -1
- package/packages/datadog-instrumentations/src/next.js +47 -19
- package/packages/datadog-instrumentations/src/openai.js +1 -1
- package/packages/datadog-instrumentations/src/pg.js +60 -15
- package/packages/datadog-instrumentations/src/playwright.js +15 -3
- package/packages/datadog-plugin-cucumber/src/index.js +14 -2
- package/packages/datadog-plugin-cypress/src/plugin.js +49 -13
- package/packages/datadog-plugin-graphql/src/index.js +3 -3
- package/packages/datadog-plugin-graphql/src/resolve.js +27 -2
- package/packages/datadog-plugin-jest/src/index.js +10 -2
- package/packages/datadog-plugin-jest/src/util.js +10 -4
- package/packages/datadog-plugin-mocha/src/index.js +14 -2
- package/packages/datadog-plugin-mongodb-core/src/index.js +6 -2
- package/packages/datadog-plugin-mysql/src/index.js +2 -2
- package/packages/datadog-plugin-next/src/index.js +22 -5
- package/packages/datadog-plugin-pg/src/index.js +2 -2
- package/packages/dd-trace/src/appsec/addresses.js +1 -0
- package/packages/dd-trace/src/appsec/channels.js +2 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/ldap-injection-analyzer.js +7 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +29 -18
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +19 -1
- package/packages/dd-trace/src/appsec/iast/path-line.js +1 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +48 -5
- package/packages/dd-trace/src/appsec/iast/telemetry/index.js +14 -5
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +131 -10
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +0 -1
- package/packages/dd-trace/src/appsec/index.js +42 -7
- package/packages/dd-trace/src/appsec/recommended.json +655 -31
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +2 -0
- package/packages/dd-trace/src/appsec/reporter.js +26 -0
- package/packages/dd-trace/src/appsec/telemetry.js +132 -0
- package/packages/dd-trace/src/appsec/waf/index.js +1 -1
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +13 -5
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +12 -14
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +1 -14
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +1 -13
- package/packages/dd-trace/src/datastreams/processor.js +6 -2
- package/packages/dd-trace/src/dogstatsd.js +108 -8
- package/packages/dd-trace/src/exporters/agent/writer.js +9 -9
- package/packages/dd-trace/src/exporters/common/request.js +13 -4
- package/packages/dd-trace/src/format.js +6 -1
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +2 -2
- package/packages/dd-trace/src/opentracing/span.js +13 -13
- package/packages/dd-trace/src/opentracing/tracer.js +3 -5
- package/packages/dd-trace/src/plugin_manager.js +1 -2
- package/packages/dd-trace/src/plugins/ci_plugin.js +22 -1
- package/packages/dd-trace/src/plugins/database.js +14 -4
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/outbound.js +4 -3
- package/packages/dd-trace/src/plugins/tracing.js +1 -1
- package/packages/dd-trace/src/plugins/util/test.js +20 -3
- package/packages/dd-trace/src/profiling/config.js +3 -1
- package/packages/dd-trace/src/profiling/profilers/wall.js +31 -7
- package/packages/dd-trace/src/proxy.js +13 -2
- package/packages/dd-trace/src/ritm.js +10 -2
- package/packages/dd-trace/src/{metrics.js → runtime_metrics.js} +1 -32
- package/packages/dd-trace/src/telemetry/dependencies.js +15 -0
- package/packages/dd-trace/src/telemetry/index.js +21 -2
- package/packages/dd-trace/src/util.js +1 -1
|
@@ -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.
|
|
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,
|
|
7
|
+
constructor (ddwafContext, requiredAddresses, wafTimeout, wafVersion, rulesVersion) {
|
|
8
8
|
this.ddwafContext = ddwafContext
|
|
9
9
|
this.requiredAddresses = requiredAddresses
|
|
10
10
|
this.wafTimeout = wafTimeout
|
|
11
|
-
this.
|
|
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.
|
|
42
|
+
rulesVersion: this.rulesVersion,
|
|
43
|
+
ruleTriggered,
|
|
44
|
+
blockTriggered,
|
|
45
|
+
wafVersion: this.wafVersion,
|
|
46
|
+
wafTimeout: result.timeout
|
|
39
47
|
})
|
|
40
48
|
|
|
41
|
-
if (
|
|
42
|
-
Reporter.reportAttack(result.
|
|
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.
|
|
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.
|
|
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()
|
package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
30
|
+
runtimeMetrics.increment(`${METRIC_PREFIX}.responses`, true)
|
|
31
|
+
runtimeMetrics.increment(`${METRIC_PREFIX}.responses.by.status`, `status:${status}`, true)
|
|
32
32
|
} else if (err) {
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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 (
|
|
46
|
-
|
|
47
|
-
? urlToOptions(new URL(
|
|
48
|
-
: urlParse(
|
|
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
|
|
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 =
|
|
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'] =
|
|
349
|
+
spanContext._trace.tags['_dd.p.dm'] = `-${mechanism}`
|
|
350
350
|
}
|
|
351
351
|
break
|
|
352
352
|
}
|