dd-trace 3.0.0 → 3.1.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 (28) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/MIGRATING.md +6 -6
  3. package/package.json +2 -1
  4. package/packages/datadog-core/src/storage/async_resource.js +19 -1
  5. package/packages/datadog-instrumentations/src/cucumber.js +15 -0
  6. package/packages/datadog-instrumentations/src/helpers/instrumentations.js +5 -1
  7. package/packages/datadog-instrumentations/src/jest.js +33 -11
  8. package/packages/datadog-plugin-cucumber/src/index.js +4 -0
  9. package/packages/datadog-plugin-jest/src/index.js +25 -4
  10. package/packages/datadog-plugin-mongodb-core/src/index.js +21 -6
  11. package/packages/datadog-plugin-oracledb/src/index.js +12 -4
  12. package/packages/dd-trace/index.js +1 -1
  13. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +50 -0
  14. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +53 -8
  15. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +23 -24
  16. package/packages/dd-trace/src/config.js +7 -0
  17. package/packages/dd-trace/src/encode/0.4.js +51 -58
  18. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +13 -34
  19. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +84 -0
  20. package/packages/dd-trace/src/exporters/agent/index.js +13 -7
  21. package/packages/dd-trace/src/exporters/agent/writer.js +1 -1
  22. package/packages/dd-trace/src/exporters/common/request.js +20 -9
  23. package/packages/dd-trace/src/exporters/common/writer.js +9 -6
  24. package/packages/dd-trace/src/index.js +10 -0
  25. package/packages/dd-trace/src/noop/proxy.js +77 -0
  26. package/packages/dd-trace/src/plugin_manager.js +6 -4
  27. package/packages/dd-trace/src/proxy.js +5 -62
  28. package/packages/dd-trace/src/telemetry.js +1 -1
@@ -7,6 +7,7 @@ require,crypto-randomuuid,MIT,Copyright 2021 Node.js Foundation and contributors
7
7
  require,diagnostics_channel,MIT,Copyright 2021 Simon D.
8
8
  require,ignore,MIT,Copyright 2013 Kael Zhang and contributors
9
9
  require,import-in-the-middle,Apache license 2.0,Copyright 2021 Datadog Inc.
10
+ require,istanbul-lib-coverage,BSD-3-Clause,Copyright 2012-2015 Yahoo! Inc.
10
11
  require,koalas,MIT,Copyright 2013-2017 Brian Woodward
11
12
  require,limiter,MIT,Copyright 2011 John Hurliman
12
13
  require,lodash.kebabcase,MIT,Copyright JS Foundation and other contributors
package/MIGRATING.md CHANGED
@@ -108,20 +108,20 @@ respectively.
108
108
 
109
109
  The use of `'dd-trace/ci/jest/env'` in [`testEnvironment`](https://jestjs.io/docs/configuration#testenvironment-string)
110
110
  is no longer supported.
111
- The way to instrument your `jest` tests now is by passing the `NODE_OPTIONS='-r dd-trace/ci/init'`
112
- environment variable to the process running the tests.
111
+ To instrument `jest` tests now, add `'-r dd-trace/ci/init'` to the `NODE_OPTIONS` environment
112
+ variable passed to the process running the tests, for example, `NODE_OPTIONS='-r dd-trace/ci/init' yarn test`.
113
113
 
114
114
  #### Mocha
115
115
 
116
116
  The use of `--require dd-trace/ci/init` as a `mocha` flag is no longer supported.
117
- The way to instrument your `mocha` tests now is by passing the `NODE_OPTIONS='-r dd-trace/ci/init'`
118
- environment variable to the process running the tests.
117
+ To instrument `mocha` tests now, add `'-r dd-trace/ci/init'` to the `NODE_OPTIONS` environment
118
+ variable passed to the process running the tests, for example, `NODE_OPTIONS='-r dd-trace/ci/init' yarn test`.
119
119
 
120
120
  #### Cucumber
121
121
 
122
122
  The use of `--require-module dd-trace/ci/init` as a `cucumber-js` flag is no longer supported.
123
- The way to instrument your `cucumber-js` tests now is by passing the `NODE_OPTIONS='-r dd-trace/ci/init'`
124
- environment variable to the process running the tests.
123
+ To instrument `cucumber-js` tests now, add `'-r dd-trace/ci/init'` to the `NODE_OPTIONS` environment
124
+ variable passed to the process running the tests, for example, `NODE_OPTIONS='-r dd-trace/ci/init' yarn test`.
125
125
 
126
126
  ## 1.0 to 2.0
127
127
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -66,6 +66,7 @@
66
66
  "diagnostics_channel": "^1.1.0",
67
67
  "ignore": "^5.2.0",
68
68
  "import-in-the-middle": "^1.3.0",
69
+ "istanbul-lib-coverage": "3.2.0",
69
70
  "koalas": "^1.0.2",
70
71
  "limiter": "^1.1.4",
71
72
  "lodash.kebabcase": "^4.1.1",
@@ -6,9 +6,27 @@ const { channel } = require('diagnostics_channel')
6
6
  const beforeCh = channel('dd-trace:storage:before')
7
7
  const afterCh = channel('dd-trace:storage:after')
8
8
 
9
+ let PrivateSymbol = Symbol
10
+ function makePrivateSymbol () {
11
+ // eslint-disable-next-line no-new-func
12
+ PrivateSymbol = new Function('name', 'return %CreatePrivateSymbol(name)')
13
+ }
14
+
15
+ try {
16
+ makePrivateSymbol()
17
+ } catch (e) {
18
+ try {
19
+ const v8 = require('v8')
20
+ v8.setFlagsFromString('--allow-natives-syntax')
21
+ makePrivateSymbol()
22
+ v8.setFlagsFromString('--no-allow-natives-syntax')
23
+ // eslint-disable-next-line no-empty
24
+ } catch (e) {}
25
+ }
26
+
9
27
  class AsyncResourceStorage {
10
28
  constructor () {
11
- this._ddResourceStore = Symbol('ddResourceStore')
29
+ this._ddResourceStore = PrivateSymbol('ddResourceStore')
12
30
  this._enabled = false
13
31
  this._hook = createHook(this._createHook())
14
32
  }
@@ -7,6 +7,7 @@ const runStartCh = channel('ci:cucumber:run:start')
7
7
  const runFinishCh = channel('ci:cucumber:run:finish')
8
8
  const runStepStartCh = channel('ci:cucumber:run-step:start')
9
9
  const errorCh = channel('ci:cucumber:error')
10
+ const sessionFinishCh = channel('ci:cucumber:session:finish')
10
11
 
11
12
  // TODO: remove in a later major version
12
13
  const patched = new WeakSet()
@@ -128,4 +129,18 @@ addHook({
128
129
  file: 'lib/runtime/test_case_runner.js'
129
130
  }, testCaseHook)
130
131
 
132
+ addHook({
133
+ name: '@cucumber/cucumber',
134
+ versions: ['>=7.0.0'],
135
+ file: 'lib/runtime/index.js'
136
+ }, (Runtime) => {
137
+ shimmer.wrap(Runtime.default.prototype, 'start', start => async function () {
138
+ const result = await start.apply(this, arguments)
139
+ sessionFinishCh.publish(undefined)
140
+ return result
141
+ })
142
+
143
+ return Runtime
144
+ })
145
+
131
146
  module.exports = { pickleHook, testCaseHook }
@@ -1,3 +1,7 @@
1
1
  'use strict'
2
2
 
3
- module.exports = {}
3
+ const sym = Symbol.for('_ddtrace_instrumentations')
4
+
5
+ global[sym] = global[sym] || {}
6
+
7
+ module.exports = global[sym]
@@ -1,5 +1,5 @@
1
1
  'use strict'
2
-
2
+ const istanbul = require('istanbul-lib-coverage')
3
3
  const { addHook, channel, AsyncResource } = require('./helpers/instrument')
4
4
  const shimmer = require('../../datadog-shimmer')
5
5
 
@@ -7,7 +7,8 @@ const testStartCh = channel('ci:jest:test:start')
7
7
  const testSkippedCh = channel('ci:jest:test:skip')
8
8
  const testRunFinishCh = channel('ci:jest:test:finish')
9
9
  const testErrCh = channel('ci:jest:test:err')
10
- const testSuiteFinish = channel('ci:jest:test-suite:finish')
10
+
11
+ const testCodeCoverageCh = channel('ci:jest:test:code-coverage')
11
12
 
12
13
  const {
13
14
  getTestSuitePath,
@@ -16,6 +17,24 @@ const {
16
17
 
17
18
  const { getFormattedJestTestParameters, getJestTestName } = require('../../datadog-plugin-jest/src/util')
18
19
 
20
+ // This function also resets the coverage counters
21
+ function extractCoverageInformation (coverage, rootDir) {
22
+ const coverageMap = istanbul.createCoverageMap(coverage)
23
+
24
+ return coverageMap
25
+ .files()
26
+ .filter(filename => {
27
+ const fileCoverage = coverageMap.fileCoverageFor(filename)
28
+ const lineCoverage = fileCoverage.getLineCoverage()
29
+ const isAnyLineExecuted = Object.entries(lineCoverage).some(([, numExecutions]) => !!numExecutions)
30
+
31
+ fileCoverage.resetHits()
32
+
33
+ return isAnyLineExecuted
34
+ })
35
+ .map(filename => filename.replace(`${rootDir}/`, ''))
36
+ }
37
+
19
38
  const specStatusToTestStatus = {
20
39
  'pending': 'skip',
21
40
  'disabled': 'skip',
@@ -49,15 +68,11 @@ function getWrappedEnvironment (BaseEnvironment) {
49
68
  constructor (config, context) {
50
69
  super(config, context)
51
70
  const rootDir = config.globalConfig ? config.globalConfig.rootDir : config.rootDir
71
+ this.rootDir = rootDir
52
72
  this.testSuite = getTestSuitePath(context.testPath, rootDir)
53
73
  this.nameToParams = {}
54
74
  this.global._ddtrace = global._ddtrace
55
75
  }
56
- async teardown () {
57
- super.teardown().finally(() => {
58
- testSuiteFinish.publish()
59
- })
60
- }
61
76
 
62
77
  async handleTestEvent (event, state) {
63
78
  if (super.handleTestEvent) {
@@ -99,6 +114,10 @@ function getWrappedEnvironment (BaseEnvironment) {
99
114
  if (event.name === 'test_done') {
100
115
  const asyncResource = asyncResources.get(event.test)
101
116
  asyncResource.runInAsyncScope(() => {
117
+ if (this.global.__coverage__) {
118
+ const coverageFiles = extractCoverageInformation(this.global.__coverage__, this.rootDir)
119
+ testCodeCoverageCh.publish(coverageFiles)
120
+ }
102
121
  let status = 'pass'
103
122
  if (event.test.errors && event.test.errors.length) {
104
123
  status = 'fail'
@@ -111,10 +130,13 @@ function getWrappedEnvironment (BaseEnvironment) {
111
130
  })
112
131
  }
113
132
  if (event.name === 'test_skip' || event.name === 'test_todo') {
114
- testSkippedCh.publish({
115
- name: getJestTestName(event.test),
116
- suite: this.testSuite,
117
- runner: 'jest-circus'
133
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
134
+ asyncResource.runInAsyncScope(() => {
135
+ testSkippedCh.publish({
136
+ name: getJestTestName(event.test),
137
+ suite: this.testSuite,
138
+ runner: 'jest-circus'
139
+ })
118
140
  })
119
141
  }
120
142
  }
@@ -30,6 +30,10 @@ class CucumberPlugin extends Plugin {
30
30
  const sourceRoot = process.cwd()
31
31
  const codeOwnersEntries = getCodeOwnersFileEntries(sourceRoot)
32
32
 
33
+ this.addSub('ci:cucumber:session:finish', () => {
34
+ this.tracer._exporter._writer.flush()
35
+ })
36
+
33
37
  this.addSub('ci:cucumber:run:start', ({ pickleName, pickleUri }) => {
34
38
  const store = storage.getStore()
35
39
  const childOf = store ? store.span : store
@@ -15,6 +15,9 @@ const {
15
15
  TEST_CODE_OWNERS
16
16
  } = require('../../dd-trace/src/plugins/util/test')
17
17
 
18
+ // https://github.com/facebook/jest/blob/d6ad15b0f88a05816c2fe034dd6900d28315d570/packages/jest-worker/src/types.ts#L38
19
+ const CHILD_MESSAGE_END = 2
20
+
18
21
  function getTestSpanMetadata (tracer, test) {
19
22
  const childOf = getTestParentSpan(tracer)
20
23
 
@@ -38,9 +41,31 @@ class JestPlugin extends Plugin {
38
41
  constructor (...args) {
39
42
  super(...args)
40
43
 
44
+ // Used to handle the end of a jest worker to be able to flush
45
+ const handler = ([message]) => {
46
+ if (message === CHILD_MESSAGE_END) {
47
+ this.tracer._exporter._writer.flush(() => {
48
+ // eslint-disable-next-line
49
+ // https://github.com/facebook/jest/blob/24ed3b5ecb419c023ee6fdbc838f07cc028fc007/packages/jest-worker/src/workers/processChild.ts#L118-L133
50
+ // Only after the flush is done we clean up open handles
51
+ // so the worker process can hopefully exit gracefully
52
+ process.removeListener('message', handler)
53
+ })
54
+ }
55
+ }
56
+ process.on('message', handler)
57
+
41
58
  this.testEnvironmentMetadata = getTestEnvironmentMetadata('jest', this.config)
42
59
  this.codeOwnersEntries = getCodeOwnersFileEntries()
43
60
 
61
+ this.addSub('ci:jest:test:code-coverage', (coverageFiles) => {
62
+ if (!this.config.isAgentlessEnabled || !this.config.isIntelligentTestRunnerEnabled) {
63
+ return
64
+ }
65
+ const testSpan = storage.getStore().span
66
+ this.tracer._exporter.exportCoverage({ testSpan, coverageFiles })
67
+ })
68
+
44
69
  this.addSub('ci:jest:test:start', (test) => {
45
70
  const store = storage.getStore()
46
71
  const span = this.startTestSpan(test)
@@ -55,10 +80,6 @@ class JestPlugin extends Plugin {
55
80
  finishAllTraceSpans(span)
56
81
  })
57
82
 
58
- this.addSub('ci:jest:test-suite:finish', () => {
59
- this.tracer._exporter._writer.flush()
60
- })
61
-
62
83
  this.addSub('ci:jest:test:err', (error) => {
63
84
  if (error) {
64
85
  const span = storage.getStore().span
@@ -14,7 +14,7 @@ class MongodbCorePlugin extends Plugin {
14
14
 
15
15
  this.addSub(`apm:mongodb:query:start`, ({ ns, ops, options, name }) => {
16
16
  const query = getQuery(ops)
17
- const resource = getResource(ns, query, name)
17
+ const resource = truncate(getResource(ns, query, name))
18
18
  const store = storage.getStore()
19
19
  const childOf = store ? store.span : store
20
20
  const span = this.tracer.startSpan('mongodb.query', {
@@ -55,8 +55,8 @@ class MongodbCorePlugin extends Plugin {
55
55
 
56
56
  function getQuery (cmd) {
57
57
  if (!cmd || typeof cmd !== 'object' || Array.isArray(cmd)) return
58
- if (cmd.query) return JSON.stringify(sanitize(cmd.query))
59
- if (cmd.filter) return JSON.stringify(sanitize(cmd.filter))
58
+ if (cmd.query) return JSON.stringify(limitDepth(cmd.query))
59
+ if (cmd.filter) return JSON.stringify(limitDepth(cmd.filter))
60
60
  }
61
61
 
62
62
  function getResource (ns, query, operationName) {
@@ -69,12 +69,25 @@ function getResource (ns, query, operationName) {
69
69
  return parts.join(' ')
70
70
  }
71
71
 
72
+ function truncate (input) {
73
+ return input.slice(0, Math.min(input.length, 10000))
74
+ }
75
+
76
+ function simplify (input) {
77
+ return isBSON(input) ? input.toHexString() : input
78
+ }
79
+
80
+ function shouldSimplify (input) {
81
+ return !isObject(input) || isBSON(input)
82
+ }
83
+
72
84
  function shouldHide (input) {
73
- return !isObject(input) || Buffer.isBuffer(input) || isBSON(input)
85
+ return Buffer.isBuffer(input) || typeof input === 'function'
74
86
  }
75
87
 
76
- function sanitize (input) {
88
+ function limitDepth (input) {
77
89
  if (shouldHide(input)) return '?'
90
+ if (shouldSimplify(input)) return simplify(input)
78
91
 
79
92
  const output = {}
80
93
  const queue = [{
@@ -92,8 +105,10 @@ function sanitize (input) {
92
105
  if (typeof input[key] === 'function') continue
93
106
 
94
107
  const child = input[key]
95
- if (depth >= 20 || shouldHide(child)) {
108
+ if (depth >= 10 || shouldHide(child)) {
96
109
  output[key] = '?'
110
+ } else if (shouldSimplify(child)) {
111
+ output[key] = simplify(child)
97
112
  } else {
98
113
  queue.push({
99
114
  input: child,
@@ -3,6 +3,7 @@
3
3
  const Plugin = require('../../dd-trace/src/plugins/plugin')
4
4
  const { storage } = require('../../datadog-core')
5
5
  const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
6
+ const log = require('../../dd-trace/src/log')
6
7
 
7
8
  class OracledbPlugin extends Plugin {
8
9
  static get name () {
@@ -14,18 +15,25 @@ class OracledbPlugin extends Plugin {
14
15
 
15
16
  this.addSub('apm:oracledb:execute:start', ({ query, connAttrs }) => {
16
17
  const service = getServiceName(this.tracer, this.config, connAttrs)
17
- const connectStringObj = new URL('http://' + connAttrs.connectString)
18
+ let connectStringObj
19
+ try {
20
+ connectStringObj = new URL(`http://${connAttrs.connectString}`)
21
+ } catch (e) {
22
+ log.error(e)
23
+ }
18
24
  const tags = {
19
25
  'span.kind': 'client',
20
26
  'span.type': 'sql',
21
27
  'sql.query': query,
22
- 'db.instance': connectStringObj.pathname.substring(1),
23
- 'db.hostname': connectStringObj.hostname,
24
28
  'db.user': this.config.user,
25
- 'db.port': connectStringObj.port,
26
29
  'resource.name': query,
27
30
  'service.name': service
28
31
  }
32
+ if (typeof connectStringObj !== 'undefined') {
33
+ tags['db.instance'] = connectStringObj.pathname.substring(1)
34
+ tags['db.hostname'] = connectStringObj.hostname
35
+ tags['db.port'] = connectStringObj.port
36
+ }
29
37
  const store = storage.getStore()
30
38
  const childOf = store ? store.span : store
31
39
  const span = this.tracer.startSpan('oracle.query', {
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  if (!global._ddtrace) {
4
- const TracerProxy = require('./src/proxy')
4
+ const TracerProxy = require('./src')
5
5
 
6
6
  Object.defineProperty(global, '_ddtrace', {
7
7
  value: new TracerProxy(),
@@ -0,0 +1,50 @@
1
+ 'use strict'
2
+ const request = require('../../../exporters/common/request')
3
+ const log = require('../../../log')
4
+
5
+ const { CoverageCIVisibilityEncoder } = require('../../../encode/coverage-ci-visibility')
6
+ const BaseWriter = require('../../../exporters/common/writer')
7
+
8
+ function safeJSONStringify (value) {
9
+ return JSON.stringify(value, (key, value) =>
10
+ key !== 'dd-api-key' ? value : undefined
11
+ )
12
+ }
13
+
14
+ class Writer extends BaseWriter {
15
+ constructor ({ url }) {
16
+ super(...arguments)
17
+ this._url = url
18
+ this._encoder = new CoverageCIVisibilityEncoder()
19
+ }
20
+
21
+ _sendPayload (form, _, done) {
22
+ const options = {
23
+ path: '/api/v2/citestcov',
24
+ method: 'POST',
25
+ headers: {
26
+ 'dd-api-key': process.env.DATADOG_API_KEY || process.env.DD_API_KEY,
27
+ ...form.getHeaders()
28
+ },
29
+ timeout: 15000
30
+ }
31
+
32
+ options.protocol = this._url.protocol
33
+ options.hostname = this._url.hostname
34
+ options.port = this._url.port
35
+
36
+ log.debug(() => `Request to the intake: ${safeJSONStringify(options)}`)
37
+
38
+ request(form, options, (err, res) => {
39
+ if (err) {
40
+ log.error(err)
41
+ done()
42
+ return
43
+ }
44
+ log.debug(`Response from the intake: ${res}`)
45
+ done()
46
+ })
47
+ }
48
+ }
49
+
50
+ module.exports = Writer
@@ -2,30 +2,75 @@
2
2
 
3
3
  const URL = require('url').URL
4
4
  const Writer = require('./writer')
5
- const Scheduler = require('../../../exporters/scheduler')
5
+ const CoverageWriter = require('./coverage-writer')
6
+
7
+ const log = require('../../../log')
6
8
 
7
9
  class AgentlessCiVisibilityExporter {
8
10
  constructor (config) {
9
- const { flushInterval, tags, site, url } = config
11
+ this._config = config
12
+ const { tags, site, url, isIntelligentTestRunnerEnabled } = config
13
+ this._isIntelligentTestRunnerEnabled = isIntelligentTestRunnerEnabled
10
14
  this._url = url || new URL(`https://citestcycle-intake.${site}`)
11
15
  this._writer = new Writer({ url: this._url, tags })
16
+ this._timer = undefined
17
+ this._coverageTimer = undefined
18
+
19
+ this._coverageUrl = url || new URL(`https://event-platform-intake.${site}`)
20
+ this._coverageWriter = new CoverageWriter({ url: this._coverageUrl })
21
+
22
+ process.once('beforeExit', () => {
23
+ this._writer.flush()
24
+ this._coverageWriter.flush()
25
+ })
26
+ }
12
27
 
13
- if (flushInterval > 0) {
14
- this._scheduler = new Scheduler(() => this._writer.flush(), flushInterval)
28
+ exportCoverage ({ testSpan, coverageFiles }) {
29
+ const formattedCoverage = {
30
+ traceId: testSpan.context()._traceId,
31
+ spanId: testSpan.context()._spanId,
32
+ files: coverageFiles
33
+ }
34
+ this._coverageWriter.append(formattedCoverage)
35
+
36
+ const { flushInterval } = this._config
37
+
38
+ if (flushInterval === 0) {
39
+ this._coverageWriter.flush()
40
+ } else if (flushInterval > 0 && !this._coverageTimer) {
41
+ this._coverageTimer = setTimeout(() => {
42
+ this._coverageWriter.flush()
43
+ this._coverageTimer = clearTimeout(this._coverageTimer)
44
+ }, flushInterval).unref()
15
45
  }
16
- this._scheduler && this._scheduler.start()
17
46
  }
18
47
 
19
48
  export (trace) {
20
49
  this._writer.append(trace)
21
50
 
22
- if (!this._scheduler) {
51
+ const { flushInterval } = this._config
52
+
53
+ if (flushInterval === 0) {
23
54
  this._writer.flush()
55
+ } else if (flushInterval > 0 && !this._timer) {
56
+ this._timer = setTimeout(() => {
57
+ this._writer.flush()
58
+ this._timer = clearTimeout(this._timer)
59
+ }, flushInterval).unref()
24
60
  }
25
61
  }
26
62
 
27
- flush () {
28
- this._writer.flush()
63
+ setUrl (url, coverageUrl = url) {
64
+ try {
65
+ url = new URL(url)
66
+ coverageUrl = new URL(coverageUrl)
67
+ this._url = url
68
+ this._coverageUrl = coverageUrl
69
+ this._writer.setUrl(url)
70
+ this._coverageWriter.setUrl(coverageUrl)
71
+ } catch (e) {
72
+ log.error(e)
73
+ }
29
74
  }
30
75
  }
31
76
 
@@ -5,16 +5,37 @@ const log = require('../../../log')
5
5
  const { AgentlessCiVisibilityEncoder } = require('../../../encode/agentless-ci-visibility')
6
6
  const BaseWriter = require('../../../exporters/common/writer')
7
7
 
8
+ function safeJSONStringify (value) {
9
+ return JSON.stringify(value, (key, value) =>
10
+ key !== 'dd-api-key' ? value : undefined
11
+ )
12
+ }
13
+
8
14
  class Writer extends BaseWriter {
9
15
  constructor ({ url, tags }) {
10
16
  super(...arguments)
11
17
  const { 'runtime-id': runtimeId, env, service } = tags
12
18
  this._url = url
13
- this._encoder = new AgentlessCiVisibilityEncoder({ runtimeId, env, service })
19
+ this._encoder = new AgentlessCiVisibilityEncoder(this, { runtimeId, env, service })
14
20
  }
15
21
 
16
22
  _sendPayload (data, _, done) {
17
- makeRequest(data, this._url, (err, res) => {
23
+ const options = {
24
+ path: '/api/v2/citestcycle',
25
+ method: 'POST',
26
+ headers: {
27
+ 'dd-api-key': process.env.DATADOG_API_KEY || process.env.DD_API_KEY,
28
+ 'Content-Type': 'application/msgpack'
29
+ },
30
+ timeout: 15000
31
+ }
32
+
33
+ options.protocol = this._url.protocol
34
+ options.hostname = this._url.hostname
35
+ options.port = this._url.port
36
+
37
+ log.debug(() => `Request to the intake: ${safeJSONStringify(options)}`)
38
+ request(data, options, (err, res) => {
18
39
  if (err) {
19
40
  log.error(err)
20
41
  done()
@@ -26,26 +47,4 @@ class Writer extends BaseWriter {
26
47
  }
27
48
  }
28
49
 
29
- function makeRequest (data, url, cb) {
30
- const options = {
31
- path: '/api/v2/citestcycle',
32
- method: 'POST',
33
- headers: {
34
- 'Content-Type': 'application/msgpack',
35
- 'dd-api-key': process.env.DATADOG_API_KEY || process.env.DD_API_KEY
36
- },
37
- timeout: 15000
38
- }
39
-
40
- options.protocol = url.protocol
41
- options.hostname = url.hostname
42
- options.port = url.port
43
-
44
- log.debug(() => `Request to the intake: ${JSON.stringify(options)}`)
45
-
46
- request(data, options, false, (err, res) => {
47
- cb(err, res)
48
- })
49
- }
50
-
51
50
  module.exports = Writer
@@ -70,6 +70,12 @@ class Config {
70
70
  null
71
71
  )
72
72
  const DD_CIVISIBILITY_AGENTLESS_URL = process.env.DD_CIVISIBILITY_AGENTLESS_URL
73
+
74
+ const DD_CIVISIBILITY_ITR_ENABLED = coalesce(
75
+ process.env.DD_CIVISIBILITY_ITR_ENABLED,
76
+ false
77
+ )
78
+
73
79
  const DD_SERVICE = options.service ||
74
80
  process.env.DD_SERVICE ||
75
81
  process.env.DD_SERVICE_NAME ||
@@ -262,6 +268,7 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
262
268
  obfuscatorValueRegex: DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP
263
269
  }
264
270
  this.isGitUploadEnabled = isTrue(DD_CIVISIBILITY_GIT_UPLOAD_ENABLED)
271
+ this.isIntelligentTestRunnerEnabled = isTrue(DD_CIVISIBILITY_ITR_ENABLED)
265
272
 
266
273
  tagger.add(this.tags, {
267
274
  service: this.service,