dd-trace 4.19.0 → 4.21.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/LICENSE-3rdparty.csv +1 -0
- package/index.d.ts +24 -0
- package/package.json +6 -5
- package/packages/datadog-instrumentations/src/aerospike.js +47 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/http/client.js +22 -0
- package/packages/datadog-instrumentations/src/jest.js +11 -5
- package/packages/datadog-instrumentations/src/kafkajs.js +27 -0
- package/packages/datadog-instrumentations/src/next.js +3 -1
- package/packages/datadog-instrumentations/src/restify.js +1 -1
- package/packages/datadog-plugin-aerospike/src/index.js +113 -0
- package/packages/datadog-plugin-http/src/client.js +19 -2
- package/packages/datadog-plugin-kafkajs/src/consumer.js +51 -0
- package/packages/datadog-plugin-kafkajs/src/producer.js +55 -0
- package/packages/datadog-plugin-next/src/index.js +7 -7
- package/packages/dd-trace/src/appsec/addresses.js +2 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +105 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/constants.js +7 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +12 -19
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js +20 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +6 -10
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +18 -25
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +79 -85
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +27 -36
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +14 -11
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
- package/packages/dd-trace/src/appsec/index.js +14 -2
- package/packages/dd-trace/src/appsec/recommended.json +1395 -2
- package/packages/dd-trace/src/appsec/reporter.js +19 -0
- package/packages/dd-trace/src/appsec/rule_manager.js +9 -6
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +6 -3
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +0 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +17 -1
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +75 -56
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +15 -9
- package/packages/dd-trace/src/config.js +37 -3
- package/packages/dd-trace/src/datastreams/processor.js +107 -12
- package/packages/dd-trace/src/id.js +12 -0
- package/packages/dd-trace/src/noop/proxy.js +4 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +14 -5
- package/packages/dd-trace/src/opentracing/span.js +2 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +2 -1
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/git.js +2 -2
- package/packages/dd-trace/src/plugins/util/test.js +3 -2
- package/packages/dd-trace/src/profiler.js +5 -3
- package/packages/dd-trace/src/profiling/config.js +17 -10
- package/packages/dd-trace/src/profiling/profiler.js +10 -4
- package/packages/dd-trace/src/profiling/profilers/events.js +171 -73
- package/packages/dd-trace/src/profiling/profilers/wall.js +93 -67
- package/packages/dd-trace/src/proxy.js +21 -1
- package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +5 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
- package/packages/dd-trace/src/spanleak.js +98 -0
- package/packages/dd-trace/src/startup-log.js +7 -1
- package/packages/dd-trace/src/telemetry/dependencies.js +55 -9
- package/packages/dd-trace/src/telemetry/index.js +135 -43
- package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
- package/packages/dd-trace/src/telemetry/send-data.js +47 -5
- package/packages/dd-trace/src/tracer.js +4 -0
- package/scripts/install_plugin_modules.js +11 -3
|
@@ -15,7 +15,7 @@ const spanFinishCh = dc.channel('dd-trace:span:finish')
|
|
|
15
15
|
const profilerTelemetryMetrics = telemetryMetrics.manager.namespace('profilers')
|
|
16
16
|
const threadName = `${threadNamePrefix} Event Loop`
|
|
17
17
|
|
|
18
|
-
const
|
|
18
|
+
const MemoizedWebTags = Symbol('NativeWallProfiler.MemoizedWebTags')
|
|
19
19
|
|
|
20
20
|
let kSampleCount
|
|
21
21
|
|
|
@@ -28,28 +28,6 @@ function getStartedSpans (context) {
|
|
|
28
28
|
return context._trace.started
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function generateLabels ({ context: { spanId, rootSpanId, webTags, endpoint }, timestamp }) {
|
|
32
|
-
const labels = {
|
|
33
|
-
[THREAD_NAME]: threadName,
|
|
34
|
-
// Incoming timestamps are in microseconds, we emit nanos.
|
|
35
|
-
[END_TIMESTAMP]: timestamp * 1000n
|
|
36
|
-
}
|
|
37
|
-
if (spanId) {
|
|
38
|
-
labels['span id'] = spanId
|
|
39
|
-
}
|
|
40
|
-
if (rootSpanId) {
|
|
41
|
-
labels['local root span id'] = rootSpanId
|
|
42
|
-
}
|
|
43
|
-
if (webTags && Object.keys(webTags).length !== 0) {
|
|
44
|
-
labels['trace endpoint'] = endpointNameFromTags(webTags)
|
|
45
|
-
} else if (endpoint) {
|
|
46
|
-
// fallback to endpoint computed when sample was taken
|
|
47
|
-
labels['trace endpoint'] = endpoint
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return labels
|
|
51
|
-
}
|
|
52
|
-
|
|
53
31
|
function isWebServerSpan (tags) {
|
|
54
32
|
return tags[SPAN_TYPE] === WEB
|
|
55
33
|
}
|
|
@@ -61,6 +39,38 @@ function endpointNameFromTags (tags) {
|
|
|
61
39
|
].filter(v => v).join(' ')
|
|
62
40
|
}
|
|
63
41
|
|
|
42
|
+
function getWebTags (startedSpans, i, span) {
|
|
43
|
+
// Are web tags for this span already memoized?
|
|
44
|
+
const memoizedWebTags = span[MemoizedWebTags]
|
|
45
|
+
if (memoizedWebTags !== undefined) {
|
|
46
|
+
return memoizedWebTags
|
|
47
|
+
}
|
|
48
|
+
// No, we'll have to memoize a new value
|
|
49
|
+
function memoize (tags) {
|
|
50
|
+
span[MemoizedWebTags] = tags
|
|
51
|
+
return tags
|
|
52
|
+
}
|
|
53
|
+
// Is this span itself a web span?
|
|
54
|
+
const context = span.context()
|
|
55
|
+
const tags = context._tags
|
|
56
|
+
if (isWebServerSpan(tags)) {
|
|
57
|
+
return memoize(tags)
|
|
58
|
+
}
|
|
59
|
+
// It isn't. Get parent's web tags (memoize them too recursively.)
|
|
60
|
+
// There might be several webspans, for example with next.js, http plugin creates the first span
|
|
61
|
+
// and then next.js plugin creates a child span, and this child span has the correct endpoint
|
|
62
|
+
// information. That's why we always use the tags of the closest ancestor web span.
|
|
63
|
+
const parentId = context._parentId
|
|
64
|
+
while (--i >= 0) {
|
|
65
|
+
const ispan = startedSpans[i]
|
|
66
|
+
if (ispan.context()._spanId === parentId) {
|
|
67
|
+
return memoize(getWebTags(startedSpans, i, ispan))
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Local root span with no web span
|
|
71
|
+
return memoize(null)
|
|
72
|
+
}
|
|
73
|
+
|
|
64
74
|
class NativeWallProfiler {
|
|
65
75
|
constructor (options = {}) {
|
|
66
76
|
this.type = 'wall'
|
|
@@ -68,14 +78,30 @@ class NativeWallProfiler {
|
|
|
68
78
|
this._flushIntervalMillis = options.flushInterval || 60 * 1e3 // 60 seconds
|
|
69
79
|
this._codeHotspotsEnabled = !!options.codeHotspotsEnabled
|
|
70
80
|
this._endpointCollectionEnabled = !!options.endpointCollectionEnabled
|
|
71
|
-
this.
|
|
81
|
+
this._timelineEnabled = !!options.timelineEnabled
|
|
82
|
+
// We need to capture span data into the sample context for either code hotspots
|
|
83
|
+
// or endpoint collection.
|
|
84
|
+
this._captureSpanData = this._codeHotspotsEnabled || this._endpointCollectionEnabled
|
|
85
|
+
// We need to run the pprof wall profiler with sample contexts if we're either
|
|
86
|
+
// capturing span data or timeline is enabled (so we need sample timestamps, and for now
|
|
87
|
+
// timestamps require the sample contexts feature in the pprof wall profiler.)
|
|
88
|
+
this._withContexts = this._captureSpanData || this._timelineEnabled
|
|
72
89
|
this._v8ProfilerBugWorkaroundEnabled = !!options.v8ProfilerBugWorkaroundEnabled
|
|
73
90
|
this._mapper = undefined
|
|
74
91
|
this._pprof = undefined
|
|
75
92
|
|
|
76
|
-
// Bind to this so
|
|
77
|
-
|
|
78
|
-
|
|
93
|
+
// Bind these to this so they can be used as callbacks
|
|
94
|
+
if (this._withContexts) {
|
|
95
|
+
if (this._captureSpanData) {
|
|
96
|
+
this._enter = this._enter.bind(this)
|
|
97
|
+
this._spanFinished = this._spanFinished.bind(this)
|
|
98
|
+
}
|
|
99
|
+
this._generateLabels = this._generateLabels.bind(this)
|
|
100
|
+
} else {
|
|
101
|
+
// Explicitly assigning, to express the intent that this is meant to be
|
|
102
|
+
// undefined when passed to pprof.time.stop() when not using sample contexts.
|
|
103
|
+
this._generateLabels = undefined
|
|
104
|
+
}
|
|
79
105
|
this._logger = options.logger
|
|
80
106
|
this._started = false
|
|
81
107
|
}
|
|
@@ -113,17 +139,20 @@ class NativeWallProfiler {
|
|
|
113
139
|
})
|
|
114
140
|
|
|
115
141
|
if (this._withContexts) {
|
|
116
|
-
this._profilerState = this._pprof.time.getState()
|
|
117
142
|
this._currentContext = {}
|
|
118
143
|
this._pprof.time.setContext(this._currentContext)
|
|
119
|
-
this._lastSpan = undefined
|
|
120
|
-
this._lastStartedSpans = undefined
|
|
121
|
-
this._lastWebTags = undefined
|
|
122
|
-
this._lastSampleCount = 0
|
|
123
144
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
145
|
+
if (this._captureSpanData) {
|
|
146
|
+
this._profilerState = this._pprof.time.getState()
|
|
147
|
+
this._lastSpan = undefined
|
|
148
|
+
this._lastStartedSpans = undefined
|
|
149
|
+
this._lastWebTags = undefined
|
|
150
|
+
this._lastSampleCount = 0
|
|
151
|
+
|
|
152
|
+
beforeCh.subscribe(this._enter)
|
|
153
|
+
enterCh.subscribe(this._enter)
|
|
154
|
+
spanFinishCh.subscribe(this._spanFinished)
|
|
155
|
+
}
|
|
127
156
|
}
|
|
128
157
|
|
|
129
158
|
this._started = true
|
|
@@ -149,33 +178,7 @@ class NativeWallProfiler {
|
|
|
149
178
|
const startedSpans = getStartedSpans(context)
|
|
150
179
|
this._lastStartedSpans = startedSpans
|
|
151
180
|
if (this._endpointCollectionEnabled) {
|
|
152
|
-
|
|
153
|
-
if (cachedWebTags === undefined) {
|
|
154
|
-
let found = false
|
|
155
|
-
// Find the first webspan starting from the end:
|
|
156
|
-
// There might be several webspans, for example with next.js, http plugin creates a first span
|
|
157
|
-
// and then next.js plugin creates a child span, and this child span has the correct endpoint information.
|
|
158
|
-
let nextSpanId = context._spanId
|
|
159
|
-
for (let i = startedSpans.length - 1; i >= 0; i--) {
|
|
160
|
-
const nextContext = startedSpans[i].context()
|
|
161
|
-
if (nextContext._spanId === nextSpanId) {
|
|
162
|
-
const tags = nextContext._tags
|
|
163
|
-
if (isWebServerSpan(tags)) {
|
|
164
|
-
this._lastWebTags = tags
|
|
165
|
-
span[CachedWebTags] = tags
|
|
166
|
-
found = true
|
|
167
|
-
break
|
|
168
|
-
}
|
|
169
|
-
nextSpanId = nextContext._parentId
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
if (!found) {
|
|
173
|
-
this._lastWebTags = undefined
|
|
174
|
-
span[CachedWebTags] = null // cache negative lookup result
|
|
175
|
-
}
|
|
176
|
-
} else {
|
|
177
|
-
this._lastWebTags = cachedWebTags
|
|
178
|
-
}
|
|
181
|
+
this._lastWebTags = getWebTags(startedSpans, startedSpans.length, span)
|
|
179
182
|
}
|
|
180
183
|
} else {
|
|
181
184
|
this._lastStartedSpans = undefined
|
|
@@ -204,8 +207,8 @@ class NativeWallProfiler {
|
|
|
204
207
|
}
|
|
205
208
|
|
|
206
209
|
_spanFinished (span) {
|
|
207
|
-
if (span[
|
|
208
|
-
span[
|
|
210
|
+
if (span[MemoizedWebTags]) {
|
|
211
|
+
span[MemoizedWebTags] = undefined
|
|
209
212
|
}
|
|
210
213
|
}
|
|
211
214
|
|
|
@@ -221,12 +224,12 @@ class NativeWallProfiler {
|
|
|
221
224
|
|
|
222
225
|
_stop (restart) {
|
|
223
226
|
if (!this._started) return
|
|
224
|
-
if (this.
|
|
227
|
+
if (this._captureSpanData) {
|
|
225
228
|
// update last sample context if needed
|
|
226
229
|
this._enter()
|
|
227
230
|
this._lastSampleCount = 0
|
|
228
231
|
}
|
|
229
|
-
const profile = this._pprof.time.stop(restart, this.
|
|
232
|
+
const profile = this._pprof.time.stop(restart, this._generateLabels)
|
|
230
233
|
if (restart) {
|
|
231
234
|
const v8BugDetected = this._pprof.time.v8ProfilerStuckEventLoopDetected()
|
|
232
235
|
if (v8BugDetected !== 0) {
|
|
@@ -236,6 +239,29 @@ class NativeWallProfiler {
|
|
|
236
239
|
return profile
|
|
237
240
|
}
|
|
238
241
|
|
|
242
|
+
_generateLabels ({ context: { spanId, rootSpanId, webTags, endpoint }, timestamp }) {
|
|
243
|
+
const labels = this._timelineEnabled ? {
|
|
244
|
+
[THREAD_NAME]: threadName,
|
|
245
|
+
// Incoming timestamps are in microseconds, we emit nanos.
|
|
246
|
+
[END_TIMESTAMP]: timestamp * 1000n
|
|
247
|
+
} : {}
|
|
248
|
+
|
|
249
|
+
if (spanId) {
|
|
250
|
+
labels['span id'] = spanId
|
|
251
|
+
}
|
|
252
|
+
if (rootSpanId) {
|
|
253
|
+
labels['local root span id'] = rootSpanId
|
|
254
|
+
}
|
|
255
|
+
if (webTags && Object.keys(webTags).length !== 0) {
|
|
256
|
+
labels['trace endpoint'] = endpointNameFromTags(webTags)
|
|
257
|
+
} else if (endpoint) {
|
|
258
|
+
// fallback to endpoint computed when sample was taken
|
|
259
|
+
labels['trace endpoint'] = endpoint
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return labels
|
|
263
|
+
}
|
|
264
|
+
|
|
239
265
|
profile () {
|
|
240
266
|
return this._stop(true)
|
|
241
267
|
}
|
|
@@ -248,7 +274,7 @@ class NativeWallProfiler {
|
|
|
248
274
|
if (!this._started) return
|
|
249
275
|
|
|
250
276
|
const profile = this._stop(false)
|
|
251
|
-
if (this.
|
|
277
|
+
if (this._captureSpanData) {
|
|
252
278
|
beforeCh.unsubscribe(this._enter)
|
|
253
279
|
enterCh.unsubscribe(this._enter)
|
|
254
280
|
spanFinishCh.unsubscribe(this._spanFinished)
|
|
@@ -10,6 +10,7 @@ const PluginManager = require('./plugin_manager')
|
|
|
10
10
|
const remoteConfig = require('./appsec/remote_config')
|
|
11
11
|
const AppsecSdk = require('./appsec/sdk')
|
|
12
12
|
const dogstatsd = require('./dogstatsd')
|
|
13
|
+
const spanleak = require('./spanleak')
|
|
13
14
|
|
|
14
15
|
class Tracer extends NoopProxy {
|
|
15
16
|
constructor () {
|
|
@@ -37,6 +38,15 @@ class Tracer extends NoopProxy {
|
|
|
37
38
|
}, 10 * 1000).unref()
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
if (config.spanLeakDebug > 0) {
|
|
42
|
+
if (config.spanLeakDebug === spanleak.MODES.LOG) {
|
|
43
|
+
spanleak.enableLogging()
|
|
44
|
+
} else if (config.spanLeakDebug === spanleak.MODES.GC_AND_LOG) {
|
|
45
|
+
spanleak.enableGarbageCollection()
|
|
46
|
+
}
|
|
47
|
+
spanleak.startScrubber()
|
|
48
|
+
}
|
|
49
|
+
|
|
40
50
|
if (config.remoteConfig.enabled && !config.isCiVisibility) {
|
|
41
51
|
const rc = remoteConfig.enable(config)
|
|
42
52
|
|
|
@@ -62,11 +72,14 @@ class Tracer extends NoopProxy {
|
|
|
62
72
|
// do not stop tracer initialization if the profiler fails to be imported
|
|
63
73
|
try {
|
|
64
74
|
const profiler = require('./profiler')
|
|
65
|
-
profiler.start(config)
|
|
75
|
+
this._profilerStarted = profiler.start(config)
|
|
66
76
|
} catch (e) {
|
|
67
77
|
log.error(e)
|
|
68
78
|
}
|
|
69
79
|
}
|
|
80
|
+
if (!this._profilerStarted) {
|
|
81
|
+
this._profilerStarted = Promise.resolve(false)
|
|
82
|
+
}
|
|
70
83
|
|
|
71
84
|
if (config.runtimeMetrics) {
|
|
72
85
|
runtimeMetrics.start(config)
|
|
@@ -104,6 +117,13 @@ class Tracer extends NoopProxy {
|
|
|
104
117
|
return this
|
|
105
118
|
}
|
|
106
119
|
|
|
120
|
+
profilerStarted () {
|
|
121
|
+
if (!this._profilerStarted) {
|
|
122
|
+
throw new Error('profilerStarted() must be called after init()')
|
|
123
|
+
}
|
|
124
|
+
return this._profilerStarted
|
|
125
|
+
}
|
|
126
|
+
|
|
107
127
|
use () {
|
|
108
128
|
this._pluginManager.configurePlugin(...arguments)
|
|
109
129
|
return this
|
|
@@ -37,6 +37,11 @@ const redisConfig = {
|
|
|
37
37
|
|
|
38
38
|
const storage = {
|
|
39
39
|
client: {
|
|
40
|
+
aerospike: {
|
|
41
|
+
opName: () => 'aerospike.command',
|
|
42
|
+
serviceName: ({ tracerService, pluginConfig }) =>
|
|
43
|
+
pluginConfig.service || `${tracerService}-aerospike`
|
|
44
|
+
},
|
|
40
45
|
'cassandra-driver': {
|
|
41
46
|
opName: () => 'cassandra.query',
|
|
42
47
|
serviceName: ({ tracerService, pluginConfig, system }) =>
|
|
@@ -22,6 +22,10 @@ function withFunction ({ tracerService, pluginConfig, params }) {
|
|
|
22
22
|
|
|
23
23
|
const storage = {
|
|
24
24
|
client: {
|
|
25
|
+
aerospike: {
|
|
26
|
+
opName: () => 'aerospike.command',
|
|
27
|
+
serviceName: configWithFallback
|
|
28
|
+
},
|
|
25
29
|
'cassandra-driver': {
|
|
26
30
|
opName: () => 'cassandra.query',
|
|
27
31
|
serviceName: configWithFallback
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/* eslint-disable no-console */
|
|
4
|
+
|
|
5
|
+
const SortedSet = require('tlhunter-sorted-set')
|
|
6
|
+
|
|
7
|
+
const INTERVAL = 1000 // look for expired spans every 1s
|
|
8
|
+
const LIFETIME = 60 * 1000 // all spans have a max lifetime of 1m
|
|
9
|
+
|
|
10
|
+
const MODES = {
|
|
11
|
+
DISABLED: 0,
|
|
12
|
+
// METRICS_ONLY
|
|
13
|
+
LOG: 1,
|
|
14
|
+
GC_AND_LOG: 2
|
|
15
|
+
// GC
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports.MODES = MODES
|
|
19
|
+
|
|
20
|
+
const spans = new SortedSet()
|
|
21
|
+
|
|
22
|
+
// TODO: should these also be delivered as runtime metrics?
|
|
23
|
+
|
|
24
|
+
// const registry = new FinalizationRegistry(name => {
|
|
25
|
+
// spans.del(span) // there is no span
|
|
26
|
+
// })
|
|
27
|
+
|
|
28
|
+
let interval
|
|
29
|
+
let mode = MODES.DISABLED
|
|
30
|
+
|
|
31
|
+
module.exports.disable = function () {
|
|
32
|
+
mode = MODES.DISABLED
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports.enableLogging = function () {
|
|
36
|
+
mode = MODES.LOG
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports.enableGarbageCollection = function () {
|
|
40
|
+
mode = MODES.GC_AND_LOG
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports.startScrubber = function () {
|
|
44
|
+
if (!isEnabled()) return
|
|
45
|
+
|
|
46
|
+
interval = setInterval(() => {
|
|
47
|
+
const now = Date.now()
|
|
48
|
+
const expired = spans.rangeByScore(0, now)
|
|
49
|
+
|
|
50
|
+
if (!expired.length) return
|
|
51
|
+
|
|
52
|
+
const gc = isGarbageCollecting()
|
|
53
|
+
|
|
54
|
+
const expirationsByType = Object.create(null) // { [spanType]: count }
|
|
55
|
+
|
|
56
|
+
for (const wrapped of expired) {
|
|
57
|
+
spans.del(wrapped)
|
|
58
|
+
const span = wrapped.deref()
|
|
59
|
+
|
|
60
|
+
if (!span) continue // span has already been garbage collected
|
|
61
|
+
|
|
62
|
+
// TODO: Should we also do things like record the route to help users debug leaks?
|
|
63
|
+
if (!expirationsByType[span._name]) expirationsByType[span._name] = 0
|
|
64
|
+
expirationsByType[span._name]++
|
|
65
|
+
|
|
66
|
+
if (!gc) continue // everything after this point is related to manual GC
|
|
67
|
+
|
|
68
|
+
// TODO: what else can we do to alleviate memory usage
|
|
69
|
+
span.context()._tags = Object.create(null)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log('expired spans:' +
|
|
73
|
+
Object.keys(expirationsByType).reduce((a, c) => `${a} ${c}: ${expirationsByType[c]}`, ''))
|
|
74
|
+
}, INTERVAL)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports.stopScrubber = function () {
|
|
78
|
+
clearInterval(interval)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports.addSpan = function (span) {
|
|
82
|
+
if (!isEnabled()) return
|
|
83
|
+
|
|
84
|
+
const now = Date.now()
|
|
85
|
+
const expiration = now + LIFETIME
|
|
86
|
+
// eslint-disable-next-line no-undef
|
|
87
|
+
const wrapped = new WeakRef(span)
|
|
88
|
+
spans.add(wrapped, expiration)
|
|
89
|
+
// registry.register(span, span._name)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isEnabled () {
|
|
93
|
+
return mode > MODES.DISABLED
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function isGarbageCollecting () {
|
|
97
|
+
return mode >= MODES.GC_AND_LOG
|
|
98
|
+
}
|
|
@@ -6,6 +6,7 @@ const os = require('os')
|
|
|
6
6
|
const { inspect } = require('util')
|
|
7
7
|
const tracerVersion = require('../../../package.json').version
|
|
8
8
|
|
|
9
|
+
const errors = {}
|
|
9
10
|
let config
|
|
10
11
|
let pluginManager
|
|
11
12
|
let samplingRules = []
|
|
@@ -89,6 +90,10 @@ function startupLog ({ agentError } = {}) {
|
|
|
89
90
|
info('DATADOG TRACER CONFIGURATION - ' + out)
|
|
90
91
|
if (agentError) {
|
|
91
92
|
warn('DATADOG TRACER DIAGNOSTIC - Agent Error: ' + agentError.message)
|
|
93
|
+
errors.agentError = {
|
|
94
|
+
code: agentError.code ? agentError.code : '',
|
|
95
|
+
message: `Agent Error:${agentError.message}`
|
|
96
|
+
}
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
config = undefined
|
|
@@ -112,5 +117,6 @@ module.exports = {
|
|
|
112
117
|
startupLog,
|
|
113
118
|
setStartupLogConfig,
|
|
114
119
|
setStartupLogPluginManager,
|
|
115
|
-
setSamplingRules
|
|
120
|
+
setSamplingRules,
|
|
121
|
+
errors
|
|
116
122
|
}
|
|
@@ -6,6 +6,7 @@ const requirePackageJson = require('../require-package-json')
|
|
|
6
6
|
const { sendData } = require('./send-data')
|
|
7
7
|
const dc = require('dc-polyfill')
|
|
8
8
|
const { fileURLToPath } = require('url')
|
|
9
|
+
const { isTrue } = require('../../src/util')
|
|
9
10
|
|
|
10
11
|
const savedDependenciesToSend = new Set()
|
|
11
12
|
const detectedDependencyKeys = new Set()
|
|
@@ -14,20 +15,57 @@ const detectedDependencyVersions = new Set()
|
|
|
14
15
|
const FILE_URI_START = `file://`
|
|
15
16
|
const moduleLoadStartChannel = dc.channel('dd-trace:moduleLoadStart')
|
|
16
17
|
|
|
17
|
-
let immediate, config, application, host
|
|
18
|
+
let immediate, config, application, host, initialLoad
|
|
18
19
|
let isFirstModule = true
|
|
20
|
+
let getRetryData
|
|
21
|
+
let updateRetryData
|
|
19
22
|
|
|
23
|
+
function createBatchPayload (payload) {
|
|
24
|
+
const batchPayload = []
|
|
25
|
+
payload.map(item => {
|
|
26
|
+
batchPayload.push({
|
|
27
|
+
request_type: item.reqType,
|
|
28
|
+
payload: item.payload
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
return batchPayload
|
|
33
|
+
}
|
|
20
34
|
function waitAndSend (config, application, host) {
|
|
21
35
|
if (!immediate) {
|
|
22
36
|
immediate = setImmediate(() => {
|
|
23
37
|
immediate = null
|
|
24
38
|
if (savedDependenciesToSend.size > 0) {
|
|
25
|
-
const dependencies = Array.from(savedDependenciesToSend.values())
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
const dependencies = Array.from(savedDependenciesToSend.values())
|
|
40
|
+
// if a depencdency is from the initial load, *always* send the event
|
|
41
|
+
// Otherwise, only send if dependencyCollection is enabled
|
|
42
|
+
.filter(dep => {
|
|
43
|
+
const initialLoadModule = isTrue(dep.split(' ')[2])
|
|
44
|
+
const sendModule = initialLoadModule || (config.telemetry?.dependencyCollection)
|
|
45
|
+
|
|
46
|
+
if (!sendModule) savedDependenciesToSend.delete(dep) // we'll never send it
|
|
47
|
+
return sendModule
|
|
48
|
+
})
|
|
49
|
+
.splice(0, 2000) // v2 documentation specifies up to 2000 dependencies can be sent at once
|
|
50
|
+
.map(pair => {
|
|
51
|
+
savedDependenciesToSend.delete(pair)
|
|
52
|
+
const [name, version] = pair.split(' ')
|
|
53
|
+
return { name, version }
|
|
54
|
+
})
|
|
55
|
+
let currPayload
|
|
56
|
+
const retryData = getRetryData()
|
|
57
|
+
if (retryData) {
|
|
58
|
+
currPayload = { reqType: 'app-dependencies-loaded', payload: { dependencies } }
|
|
59
|
+
} else {
|
|
60
|
+
if (!dependencies.length) return // no retry data and no dependencies, nothing to send
|
|
61
|
+
currPayload = { dependencies }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const payload = retryData ? createBatchPayload([currPayload, retryData]) : currPayload
|
|
65
|
+
const reqType = retryData ? 'message-batch' : 'app-dependencies-loaded'
|
|
66
|
+
|
|
67
|
+
sendData(config, application, host, reqType, payload, updateRetryData)
|
|
68
|
+
|
|
31
69
|
if (savedDependenciesToSend.size > 0) {
|
|
32
70
|
waitAndSend(config, application, host)
|
|
33
71
|
}
|
|
@@ -76,7 +114,7 @@ function onModuleLoad (data) {
|
|
|
76
114
|
const dependencyAndVersion = `${name} ${version}`
|
|
77
115
|
|
|
78
116
|
if (!detectedDependencyVersions.has(dependencyAndVersion)) {
|
|
79
|
-
savedDependenciesToSend.add(dependencyAndVersion)
|
|
117
|
+
savedDependenciesToSend.add(`${dependencyAndVersion} ${initialLoad}`)
|
|
80
118
|
detectedDependencyVersions.add(dependencyAndVersion)
|
|
81
119
|
|
|
82
120
|
waitAndSend(config, application, host)
|
|
@@ -89,11 +127,19 @@ function onModuleLoad (data) {
|
|
|
89
127
|
}
|
|
90
128
|
}
|
|
91
129
|
}
|
|
92
|
-
function start (_config, _application, _host) {
|
|
130
|
+
function start (_config = {}, _application, _host, getRetryDataFunction, updateRetryDatafunction) {
|
|
93
131
|
config = _config
|
|
94
132
|
application = _application
|
|
95
133
|
host = _host
|
|
134
|
+
initialLoad = true
|
|
135
|
+
getRetryData = getRetryDataFunction
|
|
136
|
+
updateRetryData = updateRetryDatafunction
|
|
96
137
|
moduleLoadStartChannel.subscribe(onModuleLoad)
|
|
138
|
+
|
|
139
|
+
// try and capture intially loaded modules in the first tick
|
|
140
|
+
// since, ideally, the tracer (and this module) should be loaded first,
|
|
141
|
+
// this should capture any first-tick dependencies
|
|
142
|
+
queueMicrotask(() => { initialLoad = false })
|
|
97
143
|
}
|
|
98
144
|
|
|
99
145
|
function isDependency (filename, request) {
|