dd-trace 3.0.0-pre.1 → 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 (71) hide show
  1. package/LICENSE-3rdparty.csv +2 -2
  2. package/MIGRATING.md +119 -0
  3. package/ci/init.js +0 -1
  4. package/ext/formats.js +3 -5
  5. package/index.d.ts +1 -11
  6. package/package.json +7 -7
  7. package/packages/datadog-core/src/storage/async_resource.js +19 -1
  8. package/packages/datadog-core/src/storage/index.js +1 -1
  9. package/packages/datadog-instrumentations/index.js +1 -52
  10. package/packages/datadog-instrumentations/src/connect.js +1 -1
  11. package/packages/datadog-instrumentations/src/cucumber.js +15 -0
  12. package/packages/datadog-instrumentations/src/grpc/client.js +2 -2
  13. package/packages/datadog-instrumentations/src/grpc/server.js +1 -1
  14. package/packages/datadog-instrumentations/src/hapi.js +3 -31
  15. package/packages/datadog-instrumentations/src/helpers/hooks.js +68 -0
  16. package/packages/datadog-instrumentations/src/helpers/instrument.js +5 -34
  17. package/packages/datadog-instrumentations/src/helpers/instrumentations.js +7 -0
  18. package/packages/datadog-instrumentations/src/helpers/register.js +59 -0
  19. package/packages/datadog-instrumentations/src/jest.js +33 -11
  20. package/packages/datadog-instrumentations/src/koa.js +1 -1
  21. package/packages/datadog-instrumentations/src/mocha.js +4 -1
  22. package/packages/datadog-instrumentations/src/pg.js +2 -2
  23. package/packages/datadog-instrumentations/src/restify.js +27 -5
  24. package/packages/datadog-instrumentations/src/router.js +1 -1
  25. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -2
  26. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -2
  27. package/packages/datadog-plugin-cucumber/src/index.js +4 -0
  28. package/packages/datadog-plugin-jest/src/index.js +25 -4
  29. package/packages/datadog-plugin-mocha/src/index.js +2 -2
  30. package/packages/datadog-plugin-mongodb-core/src/index.js +21 -6
  31. package/packages/datadog-plugin-oracledb/src/index.js +12 -4
  32. package/packages/datadog-plugin-restify/src/index.js +7 -0
  33. package/packages/dd-trace/index.js +1 -1
  34. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +50 -0
  35. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +53 -8
  36. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +23 -24
  37. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +220 -0
  38. package/packages/dd-trace/src/config.js +13 -0
  39. package/packages/dd-trace/src/encode/0.4.js +51 -58
  40. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +13 -34
  41. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +84 -0
  42. package/packages/dd-trace/src/exporters/agent/index.js +13 -7
  43. package/packages/dd-trace/src/exporters/agent/writer.js +1 -1
  44. package/packages/dd-trace/src/{profiling/exporters → exporters/common}/form-data.js +0 -0
  45. package/packages/dd-trace/src/exporters/common/request.js +23 -11
  46. package/packages/dd-trace/src/exporters/common/writer.js +9 -6
  47. package/packages/dd-trace/src/id.js +16 -13
  48. package/packages/dd-trace/src/iitm.js +1 -1
  49. package/packages/dd-trace/src/index.js +10 -0
  50. package/packages/dd-trace/src/noop/proxy.js +77 -0
  51. package/packages/dd-trace/src/noop/scope.js +2 -6
  52. package/packages/dd-trace/src/noop/span.js +12 -12
  53. package/packages/dd-trace/src/noop/tracer.js +8 -5
  54. package/packages/dd-trace/src/opentracing/propagation/text_map.js +6 -6
  55. package/packages/dd-trace/src/opentracing/span.js +63 -49
  56. package/packages/dd-trace/src/opentracing/span_context.js +1 -5
  57. package/packages/dd-trace/src/opentracing/tracer.js +31 -36
  58. package/packages/dd-trace/src/plugin_manager.js +101 -68
  59. package/packages/dd-trace/src/plugins/index.js +57 -44
  60. package/packages/dd-trace/src/plugins/util/ci.js +34 -9
  61. package/packages/dd-trace/src/plugins/util/git.js +52 -2
  62. package/packages/dd-trace/src/plugins/util/tags.js +4 -1
  63. package/packages/dd-trace/src/plugins/util/web.js +1 -1
  64. package/packages/dd-trace/src/profiling/exporters/agent.js +1 -1
  65. package/packages/dd-trace/src/profiling/profilers/cpu.js +3 -3
  66. package/packages/dd-trace/src/proxy.js +18 -71
  67. package/packages/dd-trace/src/scope.js +1 -58
  68. package/packages/dd-trace/src/startup-log.js +8 -19
  69. package/packages/dd-trace/src/telemetry.js +2 -15
  70. package/scripts/install_plugin_modules.js +17 -26
  71. package/ci/jest/env.js +0 -38
@@ -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
  }
@@ -145,7 +145,7 @@ function wrapNext (req, next) {
145
145
  return function () {
146
146
  nextChannel.publish({ req })
147
147
 
148
- return next.apply(null, arguments)
148
+ return next.apply(this, arguments)
149
149
  }
150
150
  }
151
151
 
@@ -153,7 +153,10 @@ function mochaHook (Runner) {
153
153
  if (isHook && testOrHook.ctx) {
154
154
  test = testOrHook.ctx.currentTest
155
155
  }
156
- const asyncResource = getTestAsyncResource(test)
156
+ let asyncResource
157
+ if (test) {
158
+ asyncResource = getTestAsyncResource(test)
159
+ }
157
160
  if (asyncResource) {
158
161
  asyncResource.runInAsyncScope(() => {
159
162
  if (isHook) {
@@ -11,12 +11,12 @@ const startCh = channel('apm:pg:query:start')
11
11
  const finishCh = channel('apm:pg:query:finish')
12
12
  const errorCh = channel('apm:pg:query:error')
13
13
 
14
- addHook({ name: 'pg', versions: ['>=4.5.5'] }, pg => {
14
+ addHook({ name: 'pg', versions: ['>=8.0.3'] }, pg => {
15
15
  shimmer.wrap(pg.Client.prototype, 'query', query => wrapQuery(query))
16
16
  return pg
17
17
  })
18
18
 
19
- addHook({ name: 'pg', file: 'lib/native/index.js', versions: ['>=4.5.5'] }, Client => {
19
+ addHook({ name: 'pg', file: 'lib/native/index.js', versions: ['>=8.0.3'] }, Client => {
20
20
  shimmer.wrap(Client.prototype, 'query', query => wrapQuery(query))
21
21
  return Client
22
22
  })
@@ -6,7 +6,10 @@ const handlers = ['use', 'pre']
6
6
  const methods = ['del', 'get', 'head', 'opts', 'post', 'put', 'patch']
7
7
 
8
8
  const handleChannel = channel('apm:restify:request:handle')
9
- const routeChannel = channel('apm:restify:request:route')
9
+ const errorChannel = channel('apm:restify:middleware:error')
10
+ const enterChannel = channel('apm:restify:middleware:enter')
11
+ const exitChannel = channel('apm:restify:middleware:exit')
12
+ const nextChannel = channel('apm:restify:middleware:next')
10
13
 
11
14
  function wrapSetupRequest (setupRequest) {
12
15
  return function (req, res) {
@@ -37,18 +40,37 @@ function wrapFn (fn) {
37
40
  if (Array.isArray(fn)) return wrapMiddleware(fn)
38
41
 
39
42
  return function (req, res, next) {
40
- if (req.route) {
41
- routeChannel.publish({ req, route: req.route })
43
+ if (typeof next === 'function') {
44
+ arguments[2] = wrapNext(req, next)
42
45
  }
43
46
 
44
- return fn.apply(this, arguments)
47
+ const route = req.route && req.route.path
48
+
49
+ enterChannel.publish({ req, route })
50
+
51
+ try {
52
+ return fn.apply(this, arguments)
53
+ } catch (error) {
54
+ errorChannel.publish({ req, error })
55
+ nextChannel.publish({ req })
56
+ exitChannel.publish({ req })
57
+ }
58
+ }
59
+ }
60
+
61
+ function wrapNext (req, next) {
62
+ return function () {
63
+ nextChannel.publish({ req })
64
+ exitChannel.publish({ req })
65
+
66
+ next.apply(this, arguments)
45
67
  }
46
68
  }
47
69
 
48
70
  addHook({ name: 'restify', versions: ['>=3'], file: 'lib/server.js' }, Server => {
49
71
  shimmer.wrap(Server.prototype, '_setupRequest', wrapSetupRequest)
50
72
  shimmer.massWrap(Server.prototype, handlers, wrapHandler)
51
- shimmer.wrap(Server.prototype, methods, wrapMethod)
73
+ shimmer.massWrap(Server.prototype, methods, wrapMethod)
52
74
 
53
75
  return Server
54
76
  })
@@ -97,7 +97,7 @@ function createWrapRouterMethod (name) {
97
97
  nextChannel.publish({ req })
98
98
  exitChannel.publish({ req })
99
99
 
100
- next.apply(null, arguments)
100
+ next.apply(this, arguments)
101
101
  }
102
102
  }
103
103
 
@@ -1,6 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const Tags = require('opentracing').Tags
4
3
  const analyticsSampler = require('../../dd-trace/src/analytics_sampler')
5
4
  const Plugin = require('../../dd-trace/src/plugins/plugin')
6
5
  const { storage } = require('../../datadog-core')
@@ -33,7 +32,7 @@ class BaseAwsSdkPlugin extends Plugin {
33
32
  const serviceName = this.getServiceName(serviceIdentifier)
34
33
  const childOf = this.tracer.scope().active()
35
34
  const tags = {
36
- [Tags.SPAN_KIND]: 'client',
35
+ 'span.kind': 'client',
37
36
  'service.name': serviceName,
38
37
  'aws.operation': operation,
39
38
  'aws.region': awsRegion,
@@ -1,6 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const Tags = require('opentracing').Tags
4
3
  const log = require('../../../dd-trace/src/log')
5
4
  const BaseAwsSdkPlugin = require('../base')
6
5
  const { storage } = require('../../../datadog-core')
@@ -25,7 +24,7 @@ class Sqs extends BaseAwsSdkPlugin {
25
24
  tags: Object.assign(
26
25
  {},
27
26
  this.requestTags.get(request) || {},
28
- { [Tags.SPAN_KIND]: 'server' }
27
+ { 'span.kind': 'server' }
29
28
  )
30
29
  }
31
30
  const span = plugin.tracer.startSpan('aws.response', options)
@@ -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
@@ -161,12 +161,12 @@ class MochaPlugin extends Plugin {
161
161
  const testSuiteSpan = this._testSuites.get(test.parent)
162
162
 
163
163
  if (testSuiteSpan) {
164
- const testSuiteId = testSuiteSpan.context()._spanId.toString('hex')
164
+ const testSuiteId = testSuiteSpan.context()._spanId.toString(16)
165
165
  testSuiteTags[TEST_SUITE_ID] = testSuiteId
166
166
  }
167
167
 
168
168
  if (this.testSessionSpan) {
169
- const testSessionId = this.testSessionSpan.context()._traceId.toString('hex')
169
+ const testSessionId = this.testSessionSpan.context()._traceId.toString(16)
170
170
  testSuiteTags[TEST_SESSION_ID] = testSessionId
171
171
  testSuiteTags[TEST_COMMAND] = this.command
172
172
  }
@@ -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', {
@@ -19,6 +19,13 @@ class RestifyPlugin extends RouterPlugin {
19
19
  web.setRoute(req, route)
20
20
  })
21
21
  }
22
+
23
+ configure (config) {
24
+ return super.configure({
25
+ ...config,
26
+ middleware: false // not supported
27
+ })
28
+ }
22
29
  }
23
30
 
24
31
  module.exports = RestifyPlugin
@@ -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