dd-trace 3.32.0 → 3.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +43 -0
- package/package.json +3 -2
- package/packages/datadog-esbuild/index.js +10 -4
- package/packages/datadog-instrumentations/src/cucumber.js +30 -11
- package/packages/datadog-instrumentations/src/jest.js +22 -11
- package/packages/datadog-instrumentations/src/mocha.js +30 -8
- package/packages/datadog-instrumentations/src/next.js +16 -14
- package/packages/datadog-instrumentations/src/pg.js +46 -0
- package/packages/datadog-plugin-cucumber/src/index.js +14 -2
- package/packages/datadog-plugin-cypress/src/plugin.js +17 -8
- 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-next/src/index.js +11 -3
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +19 -4
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +1 -1
- 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 +120 -10
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +0 -1
- package/packages/dd-trace/src/dogstatsd.js +65 -5
- package/packages/dd-trace/src/exporters/agent/writer.js +9 -9
- package/packages/dd-trace/src/opentracing/span.js +13 -13
- package/packages/dd-trace/src/opentracing/tracer.js +3 -3
- package/packages/dd-trace/src/plugins/ci_plugin.js +22 -1
- package/packages/dd-trace/src/plugins/util/test.js +18 -1
- package/packages/dd-trace/src/profiling/profilers/wall.js +7 -5
- package/packages/dd-trace/src/proxy.js +23 -2
- package/packages/dd-trace/src/ritm.js +10 -2
- /package/packages/dd-trace/src/{metrics.js → runtime_metrics.js} +0 -0
|
@@ -135,7 +135,12 @@ class MochaPlugin extends CiPlugin {
|
|
|
135
135
|
this._testNameToParams[name] = params
|
|
136
136
|
})
|
|
137
137
|
|
|
138
|
-
this.addSub('ci:mocha:session:finish', ({
|
|
138
|
+
this.addSub('ci:mocha:session:finish', ({
|
|
139
|
+
status,
|
|
140
|
+
isSuitesSkipped,
|
|
141
|
+
testCodeCoverageLinesTotal,
|
|
142
|
+
numSkippedSuites
|
|
143
|
+
}) => {
|
|
139
144
|
if (this.testSessionSpan) {
|
|
140
145
|
const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.itrConfig || {}
|
|
141
146
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
@@ -144,7 +149,14 @@ class MochaPlugin extends CiPlugin {
|
|
|
144
149
|
addIntelligentTestRunnerSpanTags(
|
|
145
150
|
this.testSessionSpan,
|
|
146
151
|
this.testModuleSpan,
|
|
147
|
-
{
|
|
152
|
+
{
|
|
153
|
+
isSuitesSkipped,
|
|
154
|
+
isSuitesSkippingEnabled,
|
|
155
|
+
isCodeCoverageEnabled,
|
|
156
|
+
testCodeCoverageLinesTotal,
|
|
157
|
+
skippingCount: numSkippedSuites,
|
|
158
|
+
skippingType: 'suite'
|
|
159
|
+
}
|
|
148
160
|
)
|
|
149
161
|
|
|
150
162
|
this.testModuleSpan.finish()
|
|
@@ -4,6 +4,7 @@ const ServerPlugin = require('../../dd-trace/src/plugins/server')
|
|
|
4
4
|
const { storage } = require('../../datadog-core')
|
|
5
5
|
const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
|
|
6
6
|
const { COMPONENT } = require('../../dd-trace/src/constants')
|
|
7
|
+
const web = require('../../dd-trace/src/plugins/util/web')
|
|
7
8
|
|
|
8
9
|
class NextPlugin extends ServerPlugin {
|
|
9
10
|
static get id () {
|
|
@@ -16,7 +17,7 @@ class NextPlugin extends ServerPlugin {
|
|
|
16
17
|
this.addSub('apm:next:page:load', message => this.pageLoad(message))
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
bindStart ({ req, res }) {
|
|
20
21
|
const store = storage.getStore()
|
|
21
22
|
const childOf = store ? store.span : store
|
|
22
23
|
const span = this.tracer.startSpan(this.operationName(), {
|
|
@@ -33,9 +34,13 @@ class NextPlugin extends ServerPlugin {
|
|
|
33
34
|
|
|
34
35
|
analyticsSampler.sample(span, this.config.measured, true)
|
|
35
36
|
|
|
36
|
-
this.enter(span, store)
|
|
37
|
-
|
|
38
37
|
this._requests.set(span, req)
|
|
38
|
+
|
|
39
|
+
return { ...store, span }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
error ({ span, error }) {
|
|
43
|
+
this.addError(error, span)
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
finish ({ req, res }) {
|
|
@@ -45,6 +50,7 @@ class NextPlugin extends ServerPlugin {
|
|
|
45
50
|
|
|
46
51
|
const span = store.span
|
|
47
52
|
const error = span.context()._tags['error']
|
|
53
|
+
const page = span.context()._tags['next.page']
|
|
48
54
|
|
|
49
55
|
if (!this.config.validateStatus(res.statusCode) && !error) {
|
|
50
56
|
span.setTag('error', true)
|
|
@@ -54,6 +60,8 @@ class NextPlugin extends ServerPlugin {
|
|
|
54
60
|
'http.status_code': res.statusCode
|
|
55
61
|
})
|
|
56
62
|
|
|
63
|
+
if (page) web.setRoute(req, page)
|
|
64
|
+
|
|
57
65
|
this.config.hooks.request(span, req, res)
|
|
58
66
|
|
|
59
67
|
span.finish()
|
|
@@ -8,7 +8,7 @@ const { getIastContext } = require('../iast-context')
|
|
|
8
8
|
const { addVulnerability } = require('../vulnerability-reporter')
|
|
9
9
|
const { getNodeModulesPaths } = require('../path-line')
|
|
10
10
|
|
|
11
|
-
const EXCLUDED_PATHS = getNodeModulesPaths('mysql2', 'sequelize')
|
|
11
|
+
const EXCLUDED_PATHS = getNodeModulesPaths('mysql2', 'sequelize', 'pg-pool')
|
|
12
12
|
|
|
13
13
|
class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
14
14
|
constructor () {
|
|
@@ -23,7 +23,7 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
23
23
|
this.addSub('datadog:sequelize:query:start', ({ sql, dialect }) => {
|
|
24
24
|
const parentStore = storage.getStore()
|
|
25
25
|
if (parentStore) {
|
|
26
|
-
this.analyze(sql, dialect.toUpperCase())
|
|
26
|
+
this.analyze(sql, dialect.toUpperCase(), parentStore)
|
|
27
27
|
|
|
28
28
|
storage.enterWith({ ...parentStore, sqlAnalyzed: true, sequelizeParentStore: parentStore })
|
|
29
29
|
}
|
|
@@ -35,6 +35,22 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
35
35
|
storage.enterWith(store.sequelizeParentStore)
|
|
36
36
|
}
|
|
37
37
|
})
|
|
38
|
+
|
|
39
|
+
this.addSub('datadog:pg:pool:query:start', ({ query }) => {
|
|
40
|
+
const parentStore = storage.getStore()
|
|
41
|
+
if (parentStore) {
|
|
42
|
+
this.analyze(query.text, 'POSTGRES', parentStore)
|
|
43
|
+
|
|
44
|
+
storage.enterWith({ ...parentStore, sqlAnalyzed: true, pgPoolParentStore: parentStore })
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
this.addSub('datadog:pg:pool:query:finish', () => {
|
|
49
|
+
const store = storage.getStore()
|
|
50
|
+
if (store && store.pgPoolParentStore) {
|
|
51
|
+
storage.enterWith(store.pgPoolParentStore)
|
|
52
|
+
}
|
|
53
|
+
})
|
|
38
54
|
}
|
|
39
55
|
|
|
40
56
|
_getEvidence (value, iastContext, dialect) {
|
|
@@ -42,8 +58,7 @@ class SqlInjectionAnalyzer extends InjectionAnalyzer {
|
|
|
42
58
|
return { value, ranges, dialect }
|
|
43
59
|
}
|
|
44
60
|
|
|
45
|
-
analyze (value, dialect) {
|
|
46
|
-
const store = storage.getStore()
|
|
61
|
+
analyze (value, dialect, store = storage.getStore()) {
|
|
47
62
|
if (!(store && store.sqlAnalyzed)) {
|
|
48
63
|
const iastContext = getIastContext(store)
|
|
49
64
|
if (this._isInvalidContext(store, iastContext)) return
|
|
@@ -66,7 +66,7 @@ function taintObject (iastContext, object, type, keyTainting, keyType) {
|
|
|
66
66
|
const taintedProperty = TaintedUtils.newTaintedString(transactionId, key, property, keyType)
|
|
67
67
|
parent[taintedProperty] = tainted
|
|
68
68
|
} else {
|
|
69
|
-
parent[
|
|
69
|
+
parent[key] = tainted
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
} else if (typeof value === 'object' && !visited.has(value)) {
|
|
@@ -5,11 +5,20 @@ const telemetryLogs = require('./log')
|
|
|
5
5
|
const { Verbosity, getVerbosity } = require('./verbosity')
|
|
6
6
|
const { initRequestNamespace, finalizeRequestNamespace, globalNamespace } = require('./namespaces')
|
|
7
7
|
|
|
8
|
+
function isIastMetricsEnabled (metrics) {
|
|
9
|
+
// TODO: let DD_TELEMETRY_METRICS_ENABLED as undefined in config.js to avoid read here the env property
|
|
10
|
+
return process.env.DD_TELEMETRY_METRICS_ENABLED !== undefined ? metrics : true
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
class Telemetry {
|
|
9
14
|
configure (config, verbosity) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
const telemetryAndMetricsEnabled = config &&
|
|
16
|
+
config.telemetry &&
|
|
17
|
+
config.telemetry.enabled &&
|
|
18
|
+
isIastMetricsEnabled(config.telemetry.metrics)
|
|
19
|
+
|
|
20
|
+
this.verbosity = telemetryAndMetricsEnabled ? getVerbosity(verbosity) : Verbosity.OFF
|
|
21
|
+
this.enabled = this.verbosity !== Verbosity.OFF
|
|
13
22
|
|
|
14
23
|
if (this.enabled) {
|
|
15
24
|
telemetryMetrics.manager.set('iast', globalNamespace)
|
|
@@ -30,13 +39,13 @@ class Telemetry {
|
|
|
30
39
|
}
|
|
31
40
|
|
|
32
41
|
onRequestStart (context) {
|
|
33
|
-
if (this.isEnabled()
|
|
42
|
+
if (this.isEnabled()) {
|
|
34
43
|
initRequestNamespace(context)
|
|
35
44
|
}
|
|
36
45
|
}
|
|
37
46
|
|
|
38
47
|
onRequestEnd (context, rootSpan) {
|
|
39
|
-
if (this.isEnabled()
|
|
48
|
+
if (this.isEnabled()) {
|
|
40
49
|
finalizeRequestNamespace(context, rootSpan)
|
|
41
50
|
}
|
|
42
51
|
}
|
|
@@ -14,6 +14,8 @@ const DEFAULT_IAST_REDACTION_NAME_PATTERN = '(?:p(?:ass)?w(?:or)?d|pass(?:_?phra
|
|
|
14
14
|
// eslint-disable-next-line max-len
|
|
15
15
|
const DEFAULT_IAST_REDACTION_VALUE_PATTERN = '(?:bearer\\s+[a-z0-9\\._\\-]+|glpat-[\\w\\-]{20}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=\\-]+\\.ey[I-L][\\w=\\-]+(?:\\.[\\w.+/=\\-]+)?|(?:[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY[\\-]{5}|ssh-rsa\\s*[a-z0-9/\\.+]{100,}))'
|
|
16
16
|
|
|
17
|
+
const REDACTED_SOURCE_BUFFER = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
|
18
|
+
|
|
17
19
|
class SensitiveHandler {
|
|
18
20
|
constructor () {
|
|
19
21
|
this._namePattern = new RegExp(DEFAULT_IAST_REDACTION_NAME_PATTERN, 'gmi')
|
|
@@ -54,6 +56,7 @@ class SensitiveHandler {
|
|
|
54
56
|
toRedactedJson (evidence, sensitive, sourcesIndexes, sources) {
|
|
55
57
|
const valueParts = []
|
|
56
58
|
const redactedSources = []
|
|
59
|
+
const redactedSourcesContext = []
|
|
57
60
|
|
|
58
61
|
const { value, ranges } = evidence
|
|
59
62
|
|
|
@@ -71,21 +74,41 @@ class SensitiveHandler {
|
|
|
71
74
|
sourceIndex = sourcesIndexes[nextTaintedIndex]
|
|
72
75
|
|
|
73
76
|
while (nextSensitive != null && contains(nextTainted, nextSensitive)) {
|
|
74
|
-
|
|
77
|
+
const redactionStart = nextSensitive.start - nextTainted.start
|
|
78
|
+
const redactionEnd = nextSensitive.end - nextTainted.start
|
|
79
|
+
this.redactSource(sources, redactedSources, redactedSourcesContext, sourceIndex, redactionStart, redactionEnd)
|
|
75
80
|
nextSensitive = sensitive.shift()
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
if (nextSensitive != null && intersects(nextSensitive, nextTainted)) {
|
|
79
|
-
|
|
84
|
+
const redactionStart = nextSensitive.start - nextTainted.start
|
|
85
|
+
const redactionEnd = nextSensitive.end - nextTainted.start
|
|
86
|
+
this.redactSource(sources, redactedSources, redactedSourcesContext, sourceIndex, redactionStart, redactionEnd)
|
|
80
87
|
|
|
81
88
|
const entries = remove(nextSensitive, nextTainted)
|
|
82
89
|
nextSensitive = entries.length > 0 ? entries[0] : null
|
|
83
90
|
}
|
|
84
91
|
|
|
85
|
-
this.isSensibleSource(sources[sourceIndex])
|
|
92
|
+
if (this.isSensibleSource(sources[sourceIndex])) {
|
|
93
|
+
if (!sources[sourceIndex].redacted) {
|
|
94
|
+
redactedSources.push(sourceIndex)
|
|
95
|
+
sources[sourceIndex].pattern = ''.padEnd(sources[sourceIndex].value.length, REDACTED_SOURCE_BUFFER)
|
|
96
|
+
sources[sourceIndex].redacted = true
|
|
97
|
+
}
|
|
98
|
+
}
|
|
86
99
|
|
|
87
100
|
if (redactedSources.indexOf(sourceIndex) > -1) {
|
|
88
|
-
|
|
101
|
+
const partValue = value.substring(i, i + (nextTainted.end - nextTainted.start))
|
|
102
|
+
this.writeRedactedValuePart(
|
|
103
|
+
valueParts,
|
|
104
|
+
partValue.length,
|
|
105
|
+
sourceIndex,
|
|
106
|
+
partValue,
|
|
107
|
+
sources[sourceIndex],
|
|
108
|
+
redactedSourcesContext[sourceIndex],
|
|
109
|
+
this.isSensibleSource(sources[sourceIndex])
|
|
110
|
+
)
|
|
111
|
+
redactedSourcesContext[sourceIndex] = []
|
|
89
112
|
} else {
|
|
90
113
|
const substringEnd = Math.min(nextTainted.end, value.length)
|
|
91
114
|
this.writeValuePart(valueParts, value.substring(nextTainted.start, substringEnd), sourceIndex)
|
|
@@ -100,7 +123,10 @@ class SensitiveHandler {
|
|
|
100
123
|
this.writeValuePart(valueParts, value.substring(start, i), sourceIndex)
|
|
101
124
|
if (nextTainted != null && intersects(nextSensitive, nextTainted)) {
|
|
102
125
|
sourceIndex = sourcesIndexes[nextTaintedIndex]
|
|
103
|
-
|
|
126
|
+
|
|
127
|
+
const redactionStart = nextSensitive.start - nextTainted.start
|
|
128
|
+
const redactionEnd = nextSensitive.end - nextTainted.start
|
|
129
|
+
this.redactSource(sources, redactedSources, redactedSourcesContext, sourceIndex, redactionStart, redactionEnd)
|
|
104
130
|
|
|
105
131
|
for (const entry of remove(nextSensitive, nextTainted)) {
|
|
106
132
|
if (entry.start === i) {
|
|
@@ -111,9 +137,10 @@ class SensitiveHandler {
|
|
|
111
137
|
}
|
|
112
138
|
}
|
|
113
139
|
|
|
114
|
-
|
|
140
|
+
const _length = nextSensitive.end - nextSensitive.start
|
|
141
|
+
this.writeRedactedValuePart(valueParts, _length)
|
|
115
142
|
|
|
116
|
-
start = i +
|
|
143
|
+
start = i + _length
|
|
117
144
|
i = start - 1
|
|
118
145
|
nextSensitive = sensitive.shift()
|
|
119
146
|
}
|
|
@@ -126,6 +153,24 @@ class SensitiveHandler {
|
|
|
126
153
|
return { redactedValueParts: valueParts, redactedSources }
|
|
127
154
|
}
|
|
128
155
|
|
|
156
|
+
redactSource (sources, redactedSources, redactedSourcesContext, sourceIndex, start, end) {
|
|
157
|
+
if (sourceIndex != null) {
|
|
158
|
+
if (!sources[sourceIndex].redacted) {
|
|
159
|
+
redactedSources.push(sourceIndex)
|
|
160
|
+
sources[sourceIndex].pattern = ''.padEnd(sources[sourceIndex].value.length, REDACTED_SOURCE_BUFFER)
|
|
161
|
+
sources[sourceIndex].redacted = true
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (!redactedSourcesContext[sourceIndex]) {
|
|
165
|
+
redactedSourcesContext[sourceIndex] = []
|
|
166
|
+
}
|
|
167
|
+
redactedSourcesContext[sourceIndex].push({
|
|
168
|
+
start,
|
|
169
|
+
end
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
129
174
|
writeValuePart (valueParts, value, source) {
|
|
130
175
|
if (value.length > 0) {
|
|
131
176
|
if (source != null) {
|
|
@@ -136,9 +181,74 @@ class SensitiveHandler {
|
|
|
136
181
|
}
|
|
137
182
|
}
|
|
138
183
|
|
|
139
|
-
writeRedactedValuePart (
|
|
140
|
-
|
|
141
|
-
|
|
184
|
+
writeRedactedValuePart (
|
|
185
|
+
valueParts,
|
|
186
|
+
length,
|
|
187
|
+
sourceIndex,
|
|
188
|
+
partValue,
|
|
189
|
+
source,
|
|
190
|
+
sourceRedactionContext,
|
|
191
|
+
isSensibleSource
|
|
192
|
+
) {
|
|
193
|
+
if (sourceIndex != null) {
|
|
194
|
+
const placeholder = source.value.includes(partValue)
|
|
195
|
+
? source.pattern
|
|
196
|
+
: '*'.repeat(length)
|
|
197
|
+
|
|
198
|
+
if (isSensibleSource) {
|
|
199
|
+
valueParts.push({ redacted: true, source: sourceIndex, pattern: placeholder })
|
|
200
|
+
} else {
|
|
201
|
+
let _value = partValue
|
|
202
|
+
const dedupedSourceRedactionContexts = []
|
|
203
|
+
|
|
204
|
+
sourceRedactionContext.forEach(_sourceRedactionContext => {
|
|
205
|
+
const isPresentInDeduped = dedupedSourceRedactionContexts.some(_dedupedSourceRedactionContext =>
|
|
206
|
+
_dedupedSourceRedactionContext.start === _sourceRedactionContext.start &&
|
|
207
|
+
_dedupedSourceRedactionContext.end === _sourceRedactionContext.end
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
if (!isPresentInDeduped) {
|
|
211
|
+
dedupedSourceRedactionContexts.push(_sourceRedactionContext)
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
let offset = 0
|
|
216
|
+
dedupedSourceRedactionContexts.forEach((_sourceRedactionContext) => {
|
|
217
|
+
if (_sourceRedactionContext.start > 0) {
|
|
218
|
+
valueParts.push({
|
|
219
|
+
source: sourceIndex,
|
|
220
|
+
value: _value.substring(0, _sourceRedactionContext.start - offset)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
_value = _value.substring(_sourceRedactionContext.start - offset)
|
|
224
|
+
offset = _sourceRedactionContext.start
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const sensitive =
|
|
228
|
+
_value.substring(_sourceRedactionContext.start - offset, _sourceRedactionContext.end - offset)
|
|
229
|
+
const indexOfPartValueInPattern = source.value.indexOf(sensitive)
|
|
230
|
+
|
|
231
|
+
const pattern = indexOfPartValueInPattern > -1
|
|
232
|
+
? placeholder.substring(indexOfPartValueInPattern, indexOfPartValueInPattern + sensitive.length)
|
|
233
|
+
: placeholder.substring(_sourceRedactionContext.start, _sourceRedactionContext.end)
|
|
234
|
+
|
|
235
|
+
valueParts.push({
|
|
236
|
+
redacted: true,
|
|
237
|
+
source: sourceIndex,
|
|
238
|
+
pattern
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
_value = _value.substring(pattern.length)
|
|
242
|
+
offset += pattern.length
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
if (_value.length) {
|
|
246
|
+
valueParts.push({
|
|
247
|
+
source: sourceIndex,
|
|
248
|
+
value: _value
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
}
|
|
142
252
|
} else {
|
|
143
253
|
valueParts.push({ redacted: true })
|
|
144
254
|
}
|
|
@@ -35,14 +35,14 @@ class DogStatsDClient {
|
|
|
35
35
|
this._udp6 = this._socket('udp6')
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
gauge (stat, value, tags) {
|
|
39
|
-
this._add(stat, value, TYPE_GAUGE, tags)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
38
|
increment (stat, value, tags) {
|
|
43
39
|
this._add(stat, value, TYPE_COUNTER, tags)
|
|
44
40
|
}
|
|
45
41
|
|
|
42
|
+
gauge (stat, value, tags) {
|
|
43
|
+
this._add(stat, value, TYPE_GAUGE, tags)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
46
|
distribution (stat, value, tags) {
|
|
47
47
|
this._add(stat, value, TYPE_DISTRIBUTION, tags)
|
|
48
48
|
}
|
|
@@ -153,7 +153,67 @@ class NoopDogStatsDClient {
|
|
|
153
153
|
flush () { }
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
// This is a simplified user-facing proxy to the underlying DogStatsDClient instance
|
|
157
|
+
class CustomMetrics {
|
|
158
|
+
constructor (options) {
|
|
159
|
+
this.dogstatsd = new DogStatsDClient(options)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
increment (stat, value = 1, tags) {
|
|
163
|
+
return this.dogstatsd.increment(
|
|
164
|
+
stat,
|
|
165
|
+
value,
|
|
166
|
+
CustomMetrics.tagTranslator(tags)
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
decrement (stat, value = 1, tags) {
|
|
171
|
+
return this.dogstatsd.increment(
|
|
172
|
+
stat,
|
|
173
|
+
value * -1,
|
|
174
|
+
CustomMetrics.tagTranslator(tags)
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
gauge (stat, value, tags) {
|
|
179
|
+
return this.dogstatsd.gauge(
|
|
180
|
+
stat,
|
|
181
|
+
value,
|
|
182
|
+
CustomMetrics.tagTranslator(tags)
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
distribution (stat, value, tags) {
|
|
187
|
+
return this.dogstatsd.distribution(
|
|
188
|
+
stat,
|
|
189
|
+
value,
|
|
190
|
+
CustomMetrics.tagTranslator(tags)
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
flush () {
|
|
195
|
+
return this.dogstatsd.flush()
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Exposing { tagName: 'tagValue' } to the end user
|
|
200
|
+
* These are translated into [ 'tagName:tagValue' ] for internal use
|
|
201
|
+
*/
|
|
202
|
+
static tagTranslator (objTags) {
|
|
203
|
+
const arrTags = []
|
|
204
|
+
|
|
205
|
+
if (!objTags) return arrTags
|
|
206
|
+
|
|
207
|
+
for (const [key, value] of Object.entries(objTags)) {
|
|
208
|
+
arrTags.push(`${key}:${value}`)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return arrTags
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
156
215
|
module.exports = {
|
|
157
216
|
DogStatsDClient,
|
|
158
|
-
NoopDogStatsDClient
|
|
217
|
+
NoopDogStatsDClient,
|
|
218
|
+
CustomMetrics
|
|
159
219
|
}
|
|
@@ -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
|
})
|
|
@@ -8,7 +8,7 @@ const semver = require('semver')
|
|
|
8
8
|
const SpanContext = require('./span_context')
|
|
9
9
|
const id = require('../id')
|
|
10
10
|
const tagger = require('../tagger')
|
|
11
|
-
const
|
|
11
|
+
const runtimeMetrics = require('../runtime_metrics')
|
|
12
12
|
const log = require('../log')
|
|
13
13
|
const { storage } = require('../../../datadog-core')
|
|
14
14
|
const telemetryMetrics = require('../telemetry/metrics')
|
|
@@ -79,11 +79,11 @@ class DatadogSpan {
|
|
|
79
79
|
this._startTime = fields.startTime || this._getTime()
|
|
80
80
|
|
|
81
81
|
if (DD_TRACE_EXPERIMENTAL_SPAN_COUNTS && finishedRegistry) {
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
runtimeMetrics.increment('runtime.node.spans.unfinished')
|
|
83
|
+
runtimeMetrics.increment('runtime.node.spans.unfinished.by.name', `span_name:${operationName}`)
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
runtimeMetrics.increment('runtime.node.spans.open') // unfinished for real
|
|
86
|
+
runtimeMetrics.increment('runtime.node.spans.open.by.name', `span_name:${operationName}`)
|
|
87
87
|
|
|
88
88
|
unfinishedRegistry.register(this, operationName, this)
|
|
89
89
|
}
|
|
@@ -159,13 +159,13 @@ class DatadogSpan {
|
|
|
159
159
|
getIntegrationCounter('span_finished', this._integrationName).inc()
|
|
160
160
|
|
|
161
161
|
if (DD_TRACE_EXPERIMENTAL_SPAN_COUNTS && finishedRegistry) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
162
|
+
runtimeMetrics.decrement('runtime.node.spans.unfinished')
|
|
163
|
+
runtimeMetrics.decrement('runtime.node.spans.unfinished.by.name', `span_name:${this._name}`)
|
|
164
|
+
runtimeMetrics.increment('runtime.node.spans.finished')
|
|
165
|
+
runtimeMetrics.increment('runtime.node.spans.finished.by.name', `span_name:${this._name}`)
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
|
|
167
|
+
runtimeMetrics.decrement('runtime.node.spans.open') // unfinished for real
|
|
168
|
+
runtimeMetrics.decrement('runtime.node.spans.open.by.name', `span_name:${this._name}`)
|
|
169
169
|
|
|
170
170
|
unfinishedRegistry.unregister(this)
|
|
171
171
|
finishedRegistry.register(this, this._name)
|
|
@@ -243,8 +243,8 @@ function createRegistry (type) {
|
|
|
243
243
|
if (!semver.satisfies(process.version, '>=14.6')) return
|
|
244
244
|
|
|
245
245
|
return new global.FinalizationRegistry(name => {
|
|
246
|
-
|
|
247
|
-
|
|
246
|
+
runtimeMetrics.decrement(`runtime.node.spans.${type}`)
|
|
247
|
+
runtimeMetrics.decrement(`runtime.node.spans.${type}.by.name`, [`span_name:${name}`])
|
|
248
248
|
})
|
|
249
249
|
}
|
|
250
250
|
|
|
@@ -11,7 +11,7 @@ const LogPropagator = require('./propagation/log')
|
|
|
11
11
|
const formats = require('../../../../ext/formats')
|
|
12
12
|
|
|
13
13
|
const log = require('../log')
|
|
14
|
-
const
|
|
14
|
+
const runtimeMetrics = require('../runtime_metrics')
|
|
15
15
|
const getExporter = require('../exporter')
|
|
16
16
|
const SpanContext = require('./span_context')
|
|
17
17
|
|
|
@@ -82,7 +82,7 @@ class DatadogTracer {
|
|
|
82
82
|
this._propagators[format].inject(spanContext, carrier)
|
|
83
83
|
} catch (e) {
|
|
84
84
|
log.error(e)
|
|
85
|
-
|
|
85
|
+
runtimeMetrics.increment('datadog.tracer.node.inject.errors', true)
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
@@ -91,7 +91,7 @@ class DatadogTracer {
|
|
|
91
91
|
return this._propagators[format].extract(carrier)
|
|
92
92
|
} catch (e) {
|
|
93
93
|
log.error(e)
|
|
94
|
-
|
|
94
|
+
runtimeMetrics.increment('datadog.tracer.node.extract.errors', true)
|
|
95
95
|
return null
|
|
96
96
|
}
|
|
97
97
|
}
|
|
@@ -12,7 +12,10 @@ const {
|
|
|
12
12
|
TEST_MODULE_ID,
|
|
13
13
|
TEST_SESSION_ID,
|
|
14
14
|
TEST_COMMAND,
|
|
15
|
-
TEST_MODULE
|
|
15
|
+
TEST_MODULE,
|
|
16
|
+
getTestSuiteCommonTags,
|
|
17
|
+
TEST_STATUS,
|
|
18
|
+
TEST_SKIPPED_BY_ITR
|
|
16
19
|
} = require('./util/test')
|
|
17
20
|
const Plugin = require('./plugin')
|
|
18
21
|
const { COMPONENT } = require('../constants')
|
|
@@ -77,6 +80,24 @@ module.exports = class CiPlugin extends Plugin {
|
|
|
77
80
|
}
|
|
78
81
|
})
|
|
79
82
|
})
|
|
83
|
+
|
|
84
|
+
this.addSub(`ci:${this.constructor.id}:itr:skipped-suites`, ({ skippedSuites, frameworkVersion }) => {
|
|
85
|
+
const testCommand = this.testSessionSpan.context()._tags[TEST_COMMAND]
|
|
86
|
+
skippedSuites.forEach((testSuite) => {
|
|
87
|
+
const testSuiteMetadata = getTestSuiteCommonTags(testCommand, frameworkVersion, testSuite, this.constructor.id)
|
|
88
|
+
|
|
89
|
+
this.tracer.startSpan(`${this.constructor.id}.test_suite`, {
|
|
90
|
+
childOf: this.testModuleSpan,
|
|
91
|
+
tags: {
|
|
92
|
+
[COMPONENT]: this.constructor.id,
|
|
93
|
+
...this.testEnvironmentMetadata,
|
|
94
|
+
...testSuiteMetadata,
|
|
95
|
+
[TEST_STATUS]: 'skip',
|
|
96
|
+
[TEST_SKIPPED_BY_ITR]: 'true'
|
|
97
|
+
}
|
|
98
|
+
}).finish()
|
|
99
|
+
})
|
|
100
|
+
})
|
|
80
101
|
}
|
|
81
102
|
|
|
82
103
|
configure (config) {
|