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.
Files changed (29) hide show
  1. package/index.d.ts +43 -0
  2. package/package.json +3 -2
  3. package/packages/datadog-esbuild/index.js +10 -4
  4. package/packages/datadog-instrumentations/src/cucumber.js +30 -11
  5. package/packages/datadog-instrumentations/src/jest.js +22 -11
  6. package/packages/datadog-instrumentations/src/mocha.js +30 -8
  7. package/packages/datadog-instrumentations/src/next.js +16 -14
  8. package/packages/datadog-instrumentations/src/pg.js +46 -0
  9. package/packages/datadog-plugin-cucumber/src/index.js +14 -2
  10. package/packages/datadog-plugin-cypress/src/plugin.js +17 -8
  11. package/packages/datadog-plugin-jest/src/index.js +10 -2
  12. package/packages/datadog-plugin-jest/src/util.js +10 -4
  13. package/packages/datadog-plugin-mocha/src/index.js +14 -2
  14. package/packages/datadog-plugin-next/src/index.js +11 -3
  15. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +19 -4
  16. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +1 -1
  17. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +14 -5
  18. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +120 -10
  19. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +0 -1
  20. package/packages/dd-trace/src/dogstatsd.js +65 -5
  21. package/packages/dd-trace/src/exporters/agent/writer.js +9 -9
  22. package/packages/dd-trace/src/opentracing/span.js +13 -13
  23. package/packages/dd-trace/src/opentracing/tracer.js +3 -3
  24. package/packages/dd-trace/src/plugins/ci_plugin.js +22 -1
  25. package/packages/dd-trace/src/plugins/util/test.js +18 -1
  26. package/packages/dd-trace/src/profiling/profilers/wall.js +7 -5
  27. package/packages/dd-trace/src/proxy.js +23 -2
  28. package/packages/dd-trace/src/ritm.js +10 -2
  29. /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', ({ status, isSuitesSkipped, testCodeCoverageLinesTotal }) => {
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
- { isSuitesSkipped, isSuitesSkippingEnabled, isCodeCoverageEnabled, testCodeCoverageLinesTotal }
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
- start ({ req, res }) {
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[property] = tainted
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
- // in order to telemetry be enabled, tracer telemetry and metrics collection have to be enabled
11
- this.enabled = config && config.telemetry && config.telemetry.enabled && config.telemetry.metrics
12
- this.verbosity = this.enabled ? getVerbosity(verbosity) : Verbosity.OFF
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() && this.verbosity !== Verbosity.OFF) {
42
+ if (this.isEnabled()) {
34
43
  initRequestNamespace(context)
35
44
  }
36
45
  }
37
46
 
38
47
  onRequestEnd (context, rootSpan) {
39
- if (this.isEnabled() && this.verbosity !== Verbosity.OFF) {
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
- sourceIndex != null && redactedSources.push(sourceIndex)
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
- sourceIndex != null && redactedSources.push(sourceIndex)
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]) && redactedSources.push(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
- this.writeRedactedValuePart(valueParts, sourceIndex)
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
- sourceIndex != null && redactedSources.push(sourceIndex)
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
- this.writeRedactedValuePart(valueParts)
140
+ const _length = nextSensitive.end - nextSensitive.start
141
+ this.writeRedactedValuePart(valueParts, _length)
115
142
 
116
- start = i + (nextSensitive.end - nextSensitive.start)
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 (valueParts, source) {
140
- if (source != null) {
141
- valueParts.push({ redacted: true, source })
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
  }
@@ -28,7 +28,6 @@ class VulnerabilityFormatter {
28
28
  const { redactedValueParts, redactedSources } = scrubbingResult
29
29
  redactedSources.forEach(i => {
30
30
  delete sources[i].value
31
- sources[i].redacted = true
32
31
  })
33
32
  return { valueParts: redactedValueParts }
34
33
  }
@@ -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 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
  })
@@ -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 metrics = require('../metrics')
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
- metrics.increment('runtime.node.spans.unfinished')
83
- metrics.increment('runtime.node.spans.unfinished.by.name', `span_name:${operationName}`)
82
+ runtimeMetrics.increment('runtime.node.spans.unfinished')
83
+ runtimeMetrics.increment('runtime.node.spans.unfinished.by.name', `span_name:${operationName}`)
84
84
 
85
- metrics.increment('runtime.node.spans.open') // unfinished for real
86
- metrics.increment('runtime.node.spans.open.by.name', `span_name:${operationName}`)
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
- metrics.decrement('runtime.node.spans.unfinished')
163
- metrics.decrement('runtime.node.spans.unfinished.by.name', `span_name:${this._name}`)
164
- metrics.increment('runtime.node.spans.finished')
165
- metrics.increment('runtime.node.spans.finished.by.name', `span_name:${this._name}`)
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
- metrics.decrement('runtime.node.spans.open') // unfinished for real
168
- metrics.decrement('runtime.node.spans.open.by.name', `span_name:${this._name}`)
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
- metrics.decrement(`runtime.node.spans.${type}`)
247
- metrics.decrement(`runtime.node.spans.${type}.by.name`, [`span_name:${name}`])
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 metrics = require('../metrics')
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
- metrics.increment('datadog.tracer.node.inject.errors', true)
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
- metrics.increment('datadog.tracer.node.extract.errors', true)
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) {