dd-trace 2.2.1 → 2.3.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 CHANGED
@@ -266,6 +266,12 @@ export declare interface TracerOptions {
266
266
  */
267
267
  flushInterval?: number;
268
268
 
269
+ /**
270
+ * Number of spans before partially exporting a trace. This prevents keeping all the spans in memory for very large traces.
271
+ * @default 1000
272
+ */
273
+ flushMinSpans?: number;
274
+
269
275
  /**
270
276
  * Whether to enable runtime metrics.
271
277
  * @default false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "2.2.1",
3
+ "version": "2.3.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -3,6 +3,7 @@
3
3
  require('./src/bluebird')
4
4
  require('./src/bunyan')
5
5
  require('./src/couchbase')
6
+ require('./src/cucumber')
6
7
  require('./src/dns')
7
8
  require('./src/elasticsearch')
8
9
  require('./src/generic-pool')
@@ -0,0 +1,116 @@
1
+ 'use strict'
2
+
3
+ const { addHook, channel } = require('./helpers/instrument')
4
+ const shimmer = require('../../datadog-shimmer')
5
+
6
+ const runStartCh = channel('ci:cucumber:run:start')
7
+ const runEndCh = channel('ci:cucumber:run:end')
8
+ const runAsyncEndCh = channel('ci:cucumber:run:async-end')
9
+ const runStepStartCh = channel('ci:cucumber:run-step:start')
10
+ const runStepEndCh = channel('ci:cucumber:run-step:end')
11
+ const errorCh = channel('ci:cucumber:error')
12
+
13
+ function getStatusFromResult (result) {
14
+ if (result.status === 1) {
15
+ return { status: 'pass' }
16
+ }
17
+ if (result.status === 2) {
18
+ return { status: 'skip' }
19
+ }
20
+ if (result.status === 4) {
21
+ return { status: 'skip', skipReason: 'not implemented' }
22
+ }
23
+ return { status: 'fail', errorMessage: result.message }
24
+ }
25
+
26
+ function getStatusFromResultLatest (result) {
27
+ if (result.status === 'PASSED') {
28
+ return { status: 'pass' }
29
+ }
30
+ if (result.status === 'SKIPPED' || result.status === 'PENDING') {
31
+ return { status: 'skip' }
32
+ }
33
+ if (result.status === 'UNDEFINED') {
34
+ return { status: 'skip', skipReason: 'not implemented' }
35
+ }
36
+ return { status: 'fail', errorMessage: result.message }
37
+ }
38
+
39
+ function wrapRun (pl, isLatestVersion) {
40
+ shimmer.wrap(pl.prototype, 'run', run => function () {
41
+ if (!runStartCh.hasSubscribers) {
42
+ return run.apply(this, arguments)
43
+ }
44
+
45
+ runStartCh.publish({ pickleName: this.pickle.name, pickleUri: this.pickle.uri })
46
+ try {
47
+ const promise = run.apply(this, arguments)
48
+ promise.finally(() => {
49
+ const result = this.getWorstStepResult()
50
+ const { status, skipReason, errorMessage } = isLatestVersion
51
+ ? getStatusFromResultLatest(result) : getStatusFromResult(result)
52
+
53
+ runAsyncEndCh.publish({ status, skipReason, errorMessage })
54
+ })
55
+ return promise
56
+ } catch (err) {
57
+ errorCh.publish(err)
58
+ } finally {
59
+ runEndCh.publish(undefined)
60
+ }
61
+ })
62
+ shimmer.wrap(pl.prototype, 'runStep', runStep => function () {
63
+ if (!runStepStartCh.hasSubscribers) {
64
+ return runStep.apply(this, arguments)
65
+ }
66
+ const testStep = arguments[0]
67
+ let resource
68
+
69
+ if (isLatestVersion) {
70
+ resource = testStep.text
71
+ } else {
72
+ resource = testStep.isHook ? 'hook' : testStep.pickleStep.text
73
+ }
74
+
75
+ runStepStartCh.publish({ resource })
76
+ try {
77
+ const promise = runStep.apply(this, arguments)
78
+
79
+ promise.then((result) => {
80
+ const { status, skipReason, errorMessage } = isLatestVersion
81
+ ? getStatusFromResultLatest(result) : getStatusFromResult(result)
82
+
83
+ runAsyncEndCh.publish({ isStep: true, status, skipReason, errorMessage })
84
+ })
85
+ return promise
86
+ } catch (err) {
87
+ errorCh.publish(err)
88
+ } finally {
89
+ runStepEndCh.publish(undefined)
90
+ }
91
+ })
92
+ }
93
+
94
+ addHook({
95
+ name: '@cucumber/cucumber',
96
+ versions: ['7.0.0 - 7.2.1'],
97
+ file: 'lib/runtime/pickle_runner.js'
98
+ }, (PickleRunner) => {
99
+ const pl = PickleRunner.default
100
+
101
+ wrapRun(pl, false)
102
+
103
+ return PickleRunner
104
+ })
105
+
106
+ addHook({
107
+ name: '@cucumber/cucumber',
108
+ versions: ['>=7.3.0'],
109
+ file: 'lib/runtime/test_case_runner.js'
110
+ }, (TestCaseRunner) => {
111
+ const pl = TestCaseRunner.default
112
+
113
+ wrapRun(pl, true)
114
+
115
+ return TestCaseRunner
116
+ })
@@ -35,18 +35,20 @@ function wrapRequest (request) {
35
35
 
36
36
  if (!params) return request.apply(this, arguments)
37
37
 
38
- const asyncResource = new AsyncResource('bound-anonymous-fn')
38
+ const parentResource = new AsyncResource('bound-anonymous-fn')
39
39
 
40
40
  startCh.publish({ params })
41
41
 
42
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
43
+
42
44
  try {
43
45
  const lastIndex = arguments.length - 1
44
46
  cb = arguments[lastIndex]
45
47
 
46
48
  if (typeof cb === 'function') {
47
- cb = asyncResource.bind(cb)
49
+ cb = parentResource.bind(cb)
48
50
 
49
- arguments[lastIndex] = AsyncResource.bind(function (error) {
51
+ arguments[lastIndex] = asyncResource.bind(function (error) {
50
52
  finish(params, error)
51
53
  return cb.apply(null, arguments)
52
54
  })
@@ -54,7 +56,10 @@ function wrapRequest (request) {
54
56
  } else {
55
57
  const promise = request.apply(this, arguments)
56
58
  if (promise && typeof promise.then === 'function') {
57
- promise.then(() => finish(params), e => finish(params, e))
59
+ const onResolve = asyncResource.bind(() => finish(params))
60
+ const onReject = asyncResource.bind(e => finish(params, e))
61
+
62
+ promise.then(onResolve, onReject)
58
63
  } else {
59
64
  finish(params)
60
65
  }
@@ -2,7 +2,8 @@
2
2
 
3
3
  const {
4
4
  channel,
5
- addHook
5
+ addHook,
6
+ AsyncResource
6
7
  } = require('./helpers/instrument')
7
8
  const shimmer = require('../../datadog-shimmer')
8
9
 
@@ -21,12 +22,14 @@ addHook({ name: 'ioredis', versions: ['>=2'] }, Redis => {
21
22
  const connectionName = options.connectionName
22
23
  const db = options.db
23
24
  const connectionOptions = { host: options.host, port: options.port }
25
+
24
26
  startCh.publish({ db, command: command.name, args: command.args, connectionOptions, connectionName })
25
27
 
26
- command.promise.then(
27
- () => finish(asyncEndCh, errorCh),
28
- err => finish(asyncEndCh, errorCh, err)
29
- )
28
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
29
+ const onResolve = asyncResource.bind(() => finish(asyncEndCh, errorCh))
30
+ const onReject = asyncResource.bind(err => finish(asyncEndCh, errorCh, err))
31
+
32
+ command.promise.then(onResolve, onReject)
30
33
 
31
34
  try {
32
35
  return sendCommand.apply(this, arguments)
@@ -21,14 +21,14 @@ addHook({ name: '@node-redis/client', file: 'dist/lib/client/commands-queue.js',
21
21
  const name = command[0]
22
22
  const args = command.slice(1)
23
23
 
24
- startSpan(this, name, args)
24
+ start(this, name, args)
25
25
 
26
26
  const res = addCommand.apply(this, arguments)
27
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
28
+ const onResolve = asyncResource.bind(() => finish(asyncEndCh, errorCh))
29
+ const onReject = asyncResource.bind(err => finish(asyncEndCh, errorCh, err))
27
30
 
28
- res.then(
29
- () => finish(asyncEndCh, errorCh),
30
- err => finish(asyncEndCh, errorCh, err)
31
- )
31
+ res.then(onResolve, onReject)
32
32
  endCh.publish(undefined)
33
33
  return res
34
34
  })
@@ -45,7 +45,7 @@ addHook({ name: 'redis', versions: ['>=2.6 <4'] }, redis => {
45
45
 
46
46
  const cb = asyncResource.bind(options.callback)
47
47
 
48
- startSpan(this, options.command, options.args)
48
+ start(this, options.command, options.args)
49
49
 
50
50
  options.callback = AsyncResource.bind(wrapCallback(asyncEndCh, errorCh, cb))
51
51
 
@@ -70,7 +70,7 @@ addHook({ name: 'redis', versions: ['>=0.12 <2.6'] }, redis => {
70
70
 
71
71
  const asyncResource = new AsyncResource('bound-anonymous-fn')
72
72
 
73
- startSpan(this, command, args)
73
+ start(this, command, args)
74
74
 
75
75
  if (typeof callback === 'function') {
76
76
  const cb = asyncResource.bind(callback)
@@ -95,7 +95,7 @@ addHook({ name: 'redis', versions: ['>=0.12 <2.6'] }, redis => {
95
95
  return redis
96
96
  })
97
97
 
98
- function startSpan (client, command, args) {
98
+ function start (client, command, args) {
99
99
  const db = client.selected_db
100
100
  const connectionOptions = client.connection_options || client.connection_option || client.connectionOption || {}
101
101
  startCh.publish({ db, command, args, connectionOptions })
@@ -1,150 +1,105 @@
1
- const { SAMPLING_RULE_DECISION } = require('../../dd-trace/src/constants')
1
+ 'use strict'
2
+
3
+ const Plugin = require('../../dd-trace/src/plugins/plugin')
4
+ const { storage } = require('../../datadog-core')
2
5
 
3
6
  const {
7
+ CI_APP_ORIGIN,
4
8
  TEST_TYPE,
5
9
  TEST_NAME,
6
10
  TEST_SUITE,
7
- TEST_STATUS,
8
- TEST_FRAMEWORK_VERSION,
9
11
  TEST_SKIP_REASON,
10
- CI_APP_ORIGIN,
12
+ TEST_FRAMEWORK_VERSION,
11
13
  ERROR_MESSAGE,
12
- getTestEnvironmentMetadata,
14
+ TEST_STATUS,
13
15
  finishAllTraceSpans,
16
+ getTestEnvironmentMetadata,
14
17
  getTestSuitePath
15
18
  } = require('../../dd-trace/src/plugins/util/test')
19
+ const { SPAN_TYPE, RESOURCE_NAME } = require('../../../ext/tags')
20
+ const { SAMPLING_RULE_DECISION } = require('../../dd-trace/src/constants')
16
21
 
17
- function setStatusFromResult (span, result, tag) {
18
- if (result.status === 1) {
19
- span.setTag(tag, 'pass')
20
- } else if (result.status === 2) {
21
- span.setTag(tag, 'skip')
22
- } else if (result.status === 4) {
23
- span.setTag(tag, 'skip')
24
- span.setTag(TEST_SKIP_REASON, 'not implemented')
25
- } else {
26
- span.setTag(tag, 'fail')
27
- span.setTag(ERROR_MESSAGE, result.message)
22
+ class CucumberPlugin extends Plugin {
23
+ static get name () {
24
+ return 'cucumber'
28
25
  }
29
- }
30
26
 
31
- function setStatusFromResultLatest (span, result, tag) {
32
- if (result.status === 'PASSED') {
33
- span.setTag(tag, 'pass')
34
- } else if (result.status === 'SKIPPED' || result.status === 'PENDING') {
35
- span.setTag(tag, 'skip')
36
- } else if (result.status === 'UNDEFINED') {
37
- span.setTag(tag, 'skip')
38
- span.setTag(TEST_SKIP_REASON, 'not implemented')
39
- } else {
40
- span.setTag(tag, 'fail')
41
- span.setTag(ERROR_MESSAGE, result.message)
42
- }
43
- }
27
+ constructor (...args) {
28
+ super(...args)
44
29
 
45
- function createWrapRun (tracer, testEnvironmentMetadata, sourceRoot, setStatus) {
46
- return function wrapRun (run) {
47
- return function handleRun () {
48
- const testName = this.pickle.name
49
- const testSuite = getTestSuitePath(this.pickle.uri, sourceRoot)
50
-
51
- const commonSpanTags = {
52
- [TEST_TYPE]: 'test',
53
- [TEST_NAME]: testName,
54
- [TEST_SUITE]: testSuite,
55
- [SAMPLING_RULE_DECISION]: 1,
56
- [TEST_FRAMEWORK_VERSION]: tracer._version,
57
- ...testEnvironmentMetadata
58
- }
30
+ const testEnvironmentMetadata = getTestEnvironmentMetadata('cucumber', this.config)
31
+ const sourceRoot = process.cwd()
32
+
33
+ this.addSub('ci:cucumber:run:start', ({ pickleName, pickleUri }) => {
34
+ const store = storage.getStore()
35
+ const childOf = store ? store.span : store
36
+ const testSuite = getTestSuitePath(pickleUri, sourceRoot)
59
37
 
60
- return tracer.trace(
61
- 'cucumber.test',
62
- {
63
- type: 'test',
64
- resource: testName,
65
- tags: commonSpanTags
66
- },
67
- (testSpan) => {
68
- testSpan.context()._trace.origin = CI_APP_ORIGIN
69
- const promise = run.apply(this, arguments)
70
- promise.then(() => {
71
- setStatus(testSpan, this.getWorstStepResult(), TEST_STATUS)
72
- }).finally(() => {
73
- finishAllTraceSpans(testSpan)
74
- })
75
- return promise
38
+ const span = this.tracer.startSpan('cucumber.test', {
39
+ childOf,
40
+ tags: {
41
+ [SPAN_TYPE]: 'test',
42
+ [RESOURCE_NAME]: pickleName,
43
+ [TEST_TYPE]: 'test',
44
+ [TEST_NAME]: pickleName,
45
+ [TEST_SUITE]: testSuite,
46
+ [SAMPLING_RULE_DECISION]: 1,
47
+ [TEST_FRAMEWORK_VERSION]: this.tracer._version,
48
+ ...testEnvironmentMetadata
76
49
  }
77
- )
78
- }
79
- }
80
- }
50
+ })
51
+ span.context()._trace.origin = CI_APP_ORIGIN
52
+ this.enter(span, store)
53
+ })
81
54
 
82
- function createWrapRunStep (tracer, getResourceName, setStatus) {
83
- return function wrapRunStep (runStep) {
84
- return function handleRunStep () {
85
- const resource = getResourceName(arguments[0])
86
- return tracer.trace(
87
- 'cucumber.step',
88
- { resource, tags: { 'cucumber.step': resource } },
89
- (span) => {
90
- const promise = runStep.apply(this, arguments)
91
- promise.then((result) => {
92
- setStatus(span, result, 'step.status')
93
- })
94
- return promise
55
+ this.addSub('ci:cucumber:run:end', () => {
56
+ this.exit()
57
+ })
58
+
59
+ this.addSub('ci:cucumber:run-step:start', ({ resource }) => {
60
+ const store = storage.getStore()
61
+ const childOf = store ? store.span : store
62
+ const span = this.tracer.startSpan('cucumber.step', {
63
+ childOf,
64
+ tags: {
65
+ 'cucumber.step': resource,
66
+ [RESOURCE_NAME]: resource
95
67
  }
96
- )
97
- }
98
- }
99
- }
68
+ })
69
+ this.enter(span, store)
70
+ })
71
+
72
+ this.addSub('ci:cucumber:run-step:end', () => {
73
+ this.exit()
74
+ })
75
+
76
+ this.addSub('ci:cucumber:run:async-end', ({ isStep, status, skipReason, errorMessage }) => {
77
+ const span = storage.getStore().span
78
+ const statusTag = isStep ? 'step.status' : TEST_STATUS
79
+
80
+ span.setTag(statusTag, status)
100
81
 
101
- module.exports = [
102
- {
103
- name: '@cucumber/cucumber',
104
- versions: ['7.0.0 - 7.2.1'],
105
- file: 'lib/runtime/pickle_runner.js',
106
- patch (PickleRunner, tracer, config) {
107
- const testEnvironmentMetadata = getTestEnvironmentMetadata('cucumber', config)
108
- const sourceRoot = process.cwd()
109
- const pl = PickleRunner.default
110
- this.wrap(
111
- pl.prototype,
112
- 'run',
113
- createWrapRun(tracer, testEnvironmentMetadata, sourceRoot, setStatusFromResult)
114
- )
115
- const getResourceName = (testStep) => {
116
- return testStep.isHook ? 'hook' : testStep.pickleStep.text
82
+ if (skipReason) {
83
+ span.setTag(TEST_SKIP_REASON, skipReason)
117
84
  }
118
- this.wrap(pl.prototype, 'runStep', createWrapRunStep(tracer, getResourceName, setStatusFromResult))
119
- },
120
- unpatch (PickleRunner) {
121
- const pl = PickleRunner.default
122
- this.unwrap(pl.prototype, 'run')
123
- this.unwrap(pl.prototype, 'runStep')
124
- }
125
- },
126
- {
127
- name: '@cucumber/cucumber',
128
- versions: ['>=7.3.0'],
129
- file: 'lib/runtime/test_case_runner.js',
130
- patch (TestCaseRunner, tracer, config) {
131
- const testEnvironmentMetadata = getTestEnvironmentMetadata('cucumber', config)
132
- const sourceRoot = process.cwd()
133
- const pl = TestCaseRunner.default
134
- this.wrap(
135
- pl.prototype,
136
- 'run',
137
- createWrapRun(tracer, testEnvironmentMetadata, sourceRoot, setStatusFromResultLatest)
138
- )
139
- const getResourceName = (testStep) => {
140
- return testStep.text
85
+
86
+ if (errorMessage) {
87
+ span.setTag(ERROR_MESSAGE, errorMessage)
88
+ }
89
+
90
+ span.finish()
91
+ if (!isStep) {
92
+ finishAllTraceSpans(span)
141
93
  }
142
- this.wrap(pl.prototype, 'runStep', createWrapRunStep(tracer, getResourceName, setStatusFromResultLatest))
143
- },
144
- unpatch (TestCaseRunner) {
145
- const pl = TestCaseRunner.default
146
- this.unwrap(pl.prototype, 'run')
147
- this.unwrap(pl.prototype, 'runStep')
148
- }
94
+ })
95
+
96
+ this.addSub('ci:cucumber:error', (err) => {
97
+ if (err) {
98
+ const span = storage.getStore().span
99
+ span.setTag('error', err)
100
+ }
101
+ })
149
102
  }
150
- ]
103
+ }
104
+
105
+ module.exports = CucumberPlugin
@@ -1 +1 @@
1
- module.exports = '2.2.1'
1
+ module.exports = '2.3.0'
@@ -99,6 +99,11 @@ class Config {
99
99
  process.env.DD_TRACE_AGENT_PROTOCOL_VERSION,
100
100
  '0.4'
101
101
  )
102
+ const DD_TRACE_PARTIAL_FLUSH_MIN_SPANS = coalesce(
103
+ parseInt(options.flushMinSpans),
104
+ parseInt(process.env.DD_TRACE_PARTIAL_FLUSH_MIN_SPANS),
105
+ 1000
106
+ )
102
107
  const DD_TRACE_B3_ENABLED = coalesce(
103
108
  options.experimental && options.experimental.b3,
104
109
  process.env.DD_TRACE_EXPERIMENTAL_B3_ENABLED,
@@ -166,6 +171,7 @@ class Config {
166
171
  this.hostname = DD_AGENT_HOST || (this.url && this.url.hostname)
167
172
  this.port = String(DD_TRACE_AGENT_PORT || (this.url && this.url.port))
168
173
  this.flushInterval = coalesce(parseInt(options.flushInterval, 10), defaultFlushInterval)
174
+ this.flushMinSpans = DD_TRACE_PARTIAL_FLUSH_MIN_SPANS
169
175
  this.sampleRate = coalesce(Math.min(Math.max(sampler.sampleRate, 0), 1), 1)
170
176
  this.logger = options.logger
171
177
  this.plugins = !!coalesce(options.plugins, true)
@@ -229,7 +235,7 @@ function getAgentUrl (url, options) {
229
235
  !process.env.DD_TRACE_AGENT_PORT &&
230
236
  fs.existsSync('/var/run/datadog/apm.socket')
231
237
  ) {
232
- return new URL('file:///var/run/datadog/apm.socket')
238
+ return new URL('unix:///var/run/datadog/apm.socket')
233
239
  }
234
240
  }
235
241
 
@@ -35,7 +35,7 @@ class DatadogTracer extends Tracer {
35
35
  this._debug = config.debug
36
36
  this._prioritySampler = new PrioritySampler(config.env, config.experimental.sampler)
37
37
  this._exporter = new Exporter(config, this._prioritySampler)
38
- this._processor = new SpanProcessor(this._exporter, this._prioritySampler)
38
+ this._processor = new SpanProcessor(this._exporter, this._prioritySampler, config)
39
39
  this._url = this._exporter._url
40
40
  this._enableGetRumData = config.experimental.enableGetRumData
41
41
  this._propagators = {
@@ -34,11 +34,16 @@ module.exports = class LogPlugin extends Plugin {
34
34
  this.addSub(`apm:${this.constructor.name}:log`, (arg) => {
35
35
  // TODO rather than checking this every time, setting it ought to enable/disable any plugin
36
36
  // extending from this one
37
- if (this.tracer._logInjection) {
38
- const holder = {}
39
- this.tracer.inject(storage.getStore().span, LOG, holder)
40
- arg.message = messageProxy(arg.message, holder)
41
- }
37
+ if (!this.tracer._logInjection) return
38
+
39
+ const store = storage.getStore()
40
+ const span = store && store.span
41
+
42
+ if (!span) return
43
+
44
+ const holder = {}
45
+ this.tracer.inject(span, LOG, holder)
46
+ arg.message = messageProxy(arg.message, holder)
42
47
  })
43
48
  }
44
49
  }
@@ -37,7 +37,7 @@ function getGitMetadata (ciMetadata) {
37
37
  committerName,
38
38
  committerEmail,
39
39
  committerDate
40
- ] = sanitizedExec('git show -s --format=%an,%ae,%ad,%cn,%ce,%cd', { stdio: 'pipe' }).split(',')
40
+ ] = sanitizedExec('git show -s --format=%an,%ae,%aI,%cn,%ce,%cI', { stdio: 'pipe' }).split(',')
41
41
 
42
42
  return {
43
43
  [GIT_REPOSITORY_URL]:
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const log = require('./log')
2
4
  const format = require('./format')
3
5
 
@@ -5,24 +7,37 @@ const startedSpans = new WeakSet()
5
7
  const finishedSpans = new WeakSet()
6
8
 
7
9
  class SpanProcessor {
8
- constructor (exporter, prioritySampler) {
10
+ constructor (exporter, prioritySampler, config) {
9
11
  this._exporter = exporter
10
12
  this._prioritySampler = prioritySampler
13
+ this._config = config
11
14
  }
12
15
 
13
16
  process (span) {
14
17
  const spanContext = span.context()
18
+ const active = []
19
+ const formatted = []
15
20
  const trace = spanContext._trace
21
+ const { flushMinSpans } = this._config
22
+ const { started, finished } = trace
16
23
 
17
- if (trace.started.length === trace.finished.length) {
24
+ if (started.length === finished.length || finished.length >= flushMinSpans) {
18
25
  this._prioritySampler.sample(spanContext)
19
- const formattedSpans = trace.finished.map(format)
20
- this._exporter.export(formattedSpans)
21
- this._erase(trace)
26
+
27
+ for (const span of started) {
28
+ if (span._duration !== undefined) {
29
+ formatted.push(format(span))
30
+ } else {
31
+ active.push(span)
32
+ }
33
+ }
34
+
35
+ this._exporter.export(formatted)
36
+ this._erase(trace, active)
22
37
  }
23
38
  }
24
39
 
25
- _erase (trace) {
40
+ _erase (trace, active) {
26
41
  if (process.env.DD_TRACE_EXPERIMENTAL_STATE_TRACKING === 'true') {
27
42
  const started = new Set()
28
43
  const startedIds = new Set()
@@ -98,7 +113,7 @@ class SpanProcessor {
98
113
  span.context()._tags = {}
99
114
  }
100
115
 
101
- trace.started = []
116
+ trace.started = active
102
117
  trace.finished = []
103
118
  }
104
119
  }