dd-trace 4.17.0 → 4.18.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 +9 -1
- package/package.json +4 -4
- package/packages/datadog-instrumentations/src/cucumber.js +5 -0
- package/packages/datadog-instrumentations/src/jest.js +39 -10
- package/packages/datadog-instrumentations/src/knex.js +24 -17
- package/packages/datadog-instrumentations/src/mocha.js +16 -1
- package/packages/datadog-instrumentations/src/next.js +58 -23
- package/packages/datadog-instrumentations/src/playwright.js +11 -6
- package/packages/datadog-plugin-http/src/client.js +2 -0
- package/packages/datadog-plugin-jest/src/index.js +11 -3
- package/packages/datadog-plugin-mocha/src/index.js +7 -1
- package/packages/datadog-plugin-next/src/index.js +4 -3
- package/packages/datadog-plugin-playwright/src/index.js +4 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-analyzer.js +60 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +269 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +5 -2
- package/packages/dd-trace/src/appsec/iast/analyzers/missing-header-analyzer.js +22 -4
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +9 -2
- package/packages/dd-trace/src/appsec/iast/analyzers/xcontenttype-header-missing-analyzer.js +2 -2
- package/packages/dd-trace/src/appsec/iast/iast-log.js +9 -4
- package/packages/dd-trace/src/appsec/iast/path-line.js +6 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +13 -2
- package/packages/dd-trace/src/appsec/iast/telemetry/index.js +1 -14
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +19 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +2 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +5 -1
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +4 -2
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -0
- package/packages/dd-trace/src/config.js +29 -13
- package/packages/dd-trace/src/git_properties.js +16 -15
- package/packages/dd-trace/src/plugins/util/test.js +29 -1
- package/packages/dd-trace/src/profiling/config.js +3 -17
- package/packages/dd-trace/src/profiling/profilers/wall.js +44 -39
- package/packages/dd-trace/src/telemetry/index.js +4 -0
- package/packages/dd-trace/src/telemetry/logs/index.js +65 -0
- package/packages/dd-trace/src/{appsec/iast/telemetry/log → telemetry/logs}/log-collector.js +9 -22
- package/packages/dd-trace/src/appsec/iast/telemetry/log/index.js +0 -87
|
@@ -10,7 +10,7 @@ const coalesce = require('koalas')
|
|
|
10
10
|
const tagger = require('./tagger')
|
|
11
11
|
const { isTrue, isFalse } = require('./util')
|
|
12
12
|
const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('./plugins/util/tags')
|
|
13
|
-
const { getGitMetadataFromGitProperties } = require('./git_properties')
|
|
13
|
+
const { getGitMetadataFromGitProperties, removeUserSensitiveInfo } = require('./git_properties')
|
|
14
14
|
const { updateConfig } = require('./telemetry')
|
|
15
15
|
const { getIsGCPFunction, getIsAzureFunctionConsumptionPlan } = require('./serverless')
|
|
16
16
|
|
|
@@ -251,7 +251,7 @@ class Config {
|
|
|
251
251
|
)
|
|
252
252
|
const DD_TELEMETRY_METRICS_ENABLED = coalesce(
|
|
253
253
|
process.env.DD_TELEMETRY_METRICS_ENABLED,
|
|
254
|
-
|
|
254
|
+
true
|
|
255
255
|
)
|
|
256
256
|
const DD_TRACE_AGENT_PROTOCOL_VERSION = coalesce(
|
|
257
257
|
options.protocolVersion,
|
|
@@ -447,7 +447,7 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
447
447
|
5 // seconds
|
|
448
448
|
)
|
|
449
449
|
|
|
450
|
-
const iastOptions = options
|
|
450
|
+
const iastOptions = options?.experimental?.iast
|
|
451
451
|
const DD_IAST_ENABLED = coalesce(
|
|
452
452
|
iastOptions &&
|
|
453
453
|
(iastOptions === true || iastOptions.enabled === true),
|
|
@@ -461,7 +461,7 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
461
461
|
|
|
462
462
|
const defaultIastRequestSampling = 30
|
|
463
463
|
const iastRequestSampling = coalesce(
|
|
464
|
-
parseInt(iastOptions
|
|
464
|
+
parseInt(iastOptions?.requestSampling),
|
|
465
465
|
parseInt(process.env.DD_IAST_REQUEST_SAMPLING),
|
|
466
466
|
defaultIastRequestSampling
|
|
467
467
|
)
|
|
@@ -469,31 +469,43 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
469
469
|
iastRequestSampling > 100 ? defaultIastRequestSampling : iastRequestSampling
|
|
470
470
|
|
|
471
471
|
const DD_IAST_MAX_CONCURRENT_REQUESTS = coalesce(
|
|
472
|
-
parseInt(iastOptions
|
|
472
|
+
parseInt(iastOptions?.maxConcurrentRequests),
|
|
473
473
|
parseInt(process.env.DD_IAST_MAX_CONCURRENT_REQUESTS),
|
|
474
474
|
2
|
|
475
475
|
)
|
|
476
476
|
|
|
477
477
|
const DD_IAST_MAX_CONTEXT_OPERATIONS = coalesce(
|
|
478
|
-
parseInt(iastOptions
|
|
478
|
+
parseInt(iastOptions?.maxContextOperations),
|
|
479
479
|
parseInt(process.env.DD_IAST_MAX_CONTEXT_OPERATIONS),
|
|
480
480
|
2
|
|
481
481
|
)
|
|
482
482
|
|
|
483
483
|
const DD_IAST_DEDUPLICATION_ENABLED = coalesce(
|
|
484
|
-
iastOptions
|
|
484
|
+
iastOptions?.deduplicationEnabled,
|
|
485
485
|
process.env.DD_IAST_DEDUPLICATION_ENABLED && isTrue(process.env.DD_IAST_DEDUPLICATION_ENABLED),
|
|
486
486
|
true
|
|
487
487
|
)
|
|
488
488
|
|
|
489
489
|
const DD_IAST_REDACTION_ENABLED = coalesce(
|
|
490
|
-
iastOptions
|
|
490
|
+
iastOptions?.redactionEnabled,
|
|
491
491
|
!isFalse(process.env.DD_IAST_REDACTION_ENABLED),
|
|
492
492
|
true
|
|
493
493
|
)
|
|
494
494
|
|
|
495
|
+
const DD_IAST_REDACTION_NAME_PATTERN = coalesce(
|
|
496
|
+
iastOptions?.redactionNamePattern,
|
|
497
|
+
process.env.DD_IAST_REDACTION_NAME_PATTERN,
|
|
498
|
+
null
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
const DD_IAST_REDACTION_VALUE_PATTERN = coalesce(
|
|
502
|
+
iastOptions?.redactionValuePattern,
|
|
503
|
+
process.env.DD_IAST_REDACTION_VALUE_PATTERN,
|
|
504
|
+
null
|
|
505
|
+
)
|
|
506
|
+
|
|
495
507
|
const DD_IAST_TELEMETRY_VERBOSITY = coalesce(
|
|
496
|
-
iastOptions
|
|
508
|
+
iastOptions?.telemetryVerbosity,
|
|
497
509
|
process.env.DD_IAST_TELEMETRY_VERBOSITY,
|
|
498
510
|
'INFORMATION'
|
|
499
511
|
)
|
|
@@ -588,8 +600,8 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
588
600
|
this.telemetry = {
|
|
589
601
|
enabled: DD_TRACE_EXPORTER !== 'datadog' && isTrue(DD_TRACE_TELEMETRY_ENABLED),
|
|
590
602
|
heartbeatInterval: DD_TELEMETRY_HEARTBEAT_INTERVAL,
|
|
591
|
-
logCollection: isTrue(DD_TELEMETRY_LOG_COLLECTION_ENABLED),
|
|
592
603
|
debug: isTrue(DD_TELEMETRY_DEBUG),
|
|
604
|
+
logCollection: isTrue(DD_TELEMETRY_LOG_COLLECTION_ENABLED),
|
|
593
605
|
metrics: isTrue(DD_TELEMETRY_METRICS_ENABLED)
|
|
594
606
|
}
|
|
595
607
|
this.protocolVersion = DD_TRACE_AGENT_PROTOCOL_VERSION
|
|
@@ -620,6 +632,8 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
620
632
|
maxContextOperations: DD_IAST_MAX_CONTEXT_OPERATIONS,
|
|
621
633
|
deduplicationEnabled: DD_IAST_DEDUPLICATION_ENABLED,
|
|
622
634
|
redactionEnabled: DD_IAST_REDACTION_ENABLED,
|
|
635
|
+
redactionNamePattern: DD_IAST_REDACTION_NAME_PATTERN,
|
|
636
|
+
redactionValuePattern: DD_IAST_REDACTION_VALUE_PATTERN,
|
|
623
637
|
telemetryVerbosity: DD_IAST_TELEMETRY_VERBOSITY
|
|
624
638
|
}
|
|
625
639
|
|
|
@@ -638,9 +652,11 @@ ken|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)
|
|
|
638
652
|
this.memcachedCommandEnabled = isTrue(DD_TRACE_MEMCACHED_COMMAND_ENABLED)
|
|
639
653
|
|
|
640
654
|
if (this.gitMetadataEnabled) {
|
|
641
|
-
this.repositoryUrl =
|
|
642
|
-
|
|
643
|
-
|
|
655
|
+
this.repositoryUrl = removeUserSensitiveInfo(
|
|
656
|
+
coalesce(
|
|
657
|
+
process.env.DD_GIT_REPOSITORY_URL,
|
|
658
|
+
this.tags[GIT_REPOSITORY_URL]
|
|
659
|
+
)
|
|
644
660
|
)
|
|
645
661
|
this.commitSHA = coalesce(
|
|
646
662
|
process.env.DD_GIT_COMMIT_SHA,
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
const commitSHARegex = /git\.commit\.sha=([a-f\d]{40})/
|
|
2
2
|
const repositoryUrlRegex = /git\.repository_url=([\w\d:@/.-]+)/
|
|
3
3
|
|
|
4
|
+
function removeUserSensitiveInfo (repositoryUrl) {
|
|
5
|
+
try {
|
|
6
|
+
// repository URLs can contain username and password, so we want to filter those out
|
|
7
|
+
const parsedUrl = new URL(repositoryUrl)
|
|
8
|
+
if (parsedUrl.username || parsedUrl.password) {
|
|
9
|
+
return `${parsedUrl.origin}${parsedUrl.pathname}`
|
|
10
|
+
}
|
|
11
|
+
return repositoryUrl
|
|
12
|
+
} catch (e) {
|
|
13
|
+
// if protocol isn't https, no password will be used
|
|
14
|
+
return repositoryUrl
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
4
18
|
function getGitMetadataFromGitProperties (gitPropertiesString) {
|
|
5
19
|
if (!gitPropertiesString) {
|
|
6
20
|
return {}
|
|
@@ -9,24 +23,11 @@ function getGitMetadataFromGitProperties (gitPropertiesString) {
|
|
|
9
23
|
const repositoryUrlMatch = gitPropertiesString.match(repositoryUrlRegex)
|
|
10
24
|
|
|
11
25
|
const repositoryUrl = repositoryUrlMatch ? repositoryUrlMatch[1] : undefined
|
|
12
|
-
let parsedUrl = repositoryUrl
|
|
13
|
-
|
|
14
|
-
if (repositoryUrl) {
|
|
15
|
-
try {
|
|
16
|
-
// repository URLs can contain username and password, so we want to filter those out
|
|
17
|
-
parsedUrl = new URL(repositoryUrl)
|
|
18
|
-
if (parsedUrl.password) {
|
|
19
|
-
parsedUrl = `${parsedUrl.origin}${parsedUrl.pathname}`
|
|
20
|
-
}
|
|
21
|
-
} catch (e) {
|
|
22
|
-
// if protocol isn't https, no password will be used
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
26
|
|
|
26
27
|
return {
|
|
27
28
|
commitSHA: commitSHAMatch ? commitSHAMatch[1] : undefined,
|
|
28
|
-
repositoryUrl:
|
|
29
|
+
repositoryUrl: removeUserSensitiveInfo(repositoryUrl)
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
module.exports = { getGitMetadataFromGitProperties }
|
|
33
|
+
module.exports = { getGitMetadataFromGitProperties, removeUserSensitiveInfo }
|
|
@@ -118,7 +118,8 @@ module.exports = {
|
|
|
118
118
|
fromCoverageMapToCoverage,
|
|
119
119
|
getTestLineStart,
|
|
120
120
|
getCallSites,
|
|
121
|
-
removeInvalidMetadata
|
|
121
|
+
removeInvalidMetadata,
|
|
122
|
+
parseAnnotations
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
// Returns pkg manager and its version, separated by '-', e.g. npm-8.15.0 or yarn-1.22.19
|
|
@@ -492,3 +493,30 @@ function getCallSites () {
|
|
|
492
493
|
|
|
493
494
|
return v8StackTrace
|
|
494
495
|
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Gets an object of test tags from an Playwright annotations array.
|
|
499
|
+
* @param {Object[]} annotations - Annotations from a Playwright test.
|
|
500
|
+
* @param {string} annotations[].type - Type of annotation. A string of the shape DD_TAGS[$tag_name].
|
|
501
|
+
* @param {string} annotations[].description - Value of the tag.
|
|
502
|
+
*/
|
|
503
|
+
function parseAnnotations (annotations) {
|
|
504
|
+
return annotations.reduce((tags, annotation) => {
|
|
505
|
+
if (!annotation?.type) {
|
|
506
|
+
return tags
|
|
507
|
+
}
|
|
508
|
+
const { type, description } = annotation
|
|
509
|
+
if (type.startsWith('DD_TAGS')) {
|
|
510
|
+
const regex = /\[(.*?)\]/
|
|
511
|
+
const match = regex.exec(type)
|
|
512
|
+
let tagValue = ''
|
|
513
|
+
if (match) {
|
|
514
|
+
tagValue = match[1]
|
|
515
|
+
}
|
|
516
|
+
if (tagValue) {
|
|
517
|
+
tags[tagValue] = description
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return tags
|
|
521
|
+
}, {})
|
|
522
|
+
}
|
|
@@ -128,24 +128,10 @@ class Config {
|
|
|
128
128
|
? options.profilers
|
|
129
129
|
: getProfilers({ DD_PROFILING_HEAP_ENABLED, DD_PROFILING_WALLTIME_ENABLED, DD_PROFILING_PROFILERS })
|
|
130
130
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED, defvalue)
|
|
135
|
-
}
|
|
136
|
-
this.codeHotspotsEnabled = isTrue(getCodeHotspotsOptionsOr(false))
|
|
131
|
+
this.codeHotspotsEnabled = isTrue(coalesce(options.codeHotspotsEnabled,
|
|
132
|
+
DD_PROFILING_CODEHOTSPOTS_ENABLED,
|
|
133
|
+
DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED, false))
|
|
137
134
|
logExperimentalVarDeprecation('CODEHOTSPOTS_ENABLED')
|
|
138
|
-
if (this.endpointCollectionEnabled && !this.codeHotspotsEnabled) {
|
|
139
|
-
if (getCodeHotspotsOptionsOr(undefined) !== undefined) {
|
|
140
|
-
this.logger.warn(
|
|
141
|
-
'Endpoint collection is enabled, but Code Hotspots are disabled. ' +
|
|
142
|
-
'Enable Code Hotspots too for endpoint collection to work.')
|
|
143
|
-
this.endpointCollectionEnabled = false
|
|
144
|
-
} else {
|
|
145
|
-
this.logger.info('Code Hotspots are implicitly enabled by endpoint collection.')
|
|
146
|
-
this.codeHotspotsEnabled = true
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
135
|
|
|
150
136
|
this.profilers = ensureProfilers(profilers, this)
|
|
151
137
|
}
|
|
@@ -12,6 +12,12 @@ const beforeCh = dc.channel('dd-trace:storage:before')
|
|
|
12
12
|
const enterCh = dc.channel('dd-trace:storage:enter')
|
|
13
13
|
const profilerTelemetryMetrics = telemetryMetrics.manager.namespace('profilers')
|
|
14
14
|
|
|
15
|
+
const threadName = (function () {
|
|
16
|
+
const { isMainThread, threadId } = require('node:worker_threads')
|
|
17
|
+
const name = isMainThread ? 'Main' : `Worker #${threadId}`
|
|
18
|
+
return `${name} Event Loop`
|
|
19
|
+
})()
|
|
20
|
+
|
|
15
21
|
let kSampleCount
|
|
16
22
|
|
|
17
23
|
function getActiveSpan () {
|
|
@@ -24,7 +30,7 @@ function getStartedSpans (context) {
|
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
function generateLabels ({ context: { spanId, rootSpanId, webTags, endpoint }, timestamp }) {
|
|
27
|
-
const labels = {}
|
|
33
|
+
const labels = { 'thread name': threadName }
|
|
28
34
|
if (spanId) {
|
|
29
35
|
labels['span id'] = spanId
|
|
30
36
|
}
|
|
@@ -58,29 +64,6 @@ function endpointNameFromTags (tags) {
|
|
|
58
64
|
].filter(v => v).join(' ')
|
|
59
65
|
}
|
|
60
66
|
|
|
61
|
-
function updateContext (context, span, startedSpans, endpointCollectionEnabled) {
|
|
62
|
-
context.spanId = span.context().toSpanId()
|
|
63
|
-
const rootSpan = startedSpans[0]
|
|
64
|
-
if (rootSpan) {
|
|
65
|
-
context.rootSpanId = rootSpan.context().toSpanId()
|
|
66
|
-
if (endpointCollectionEnabled) {
|
|
67
|
-
// Find the first webspan starting from the end:
|
|
68
|
-
// There might be several webspans, for example with next.js, http plugin creates a first span
|
|
69
|
-
// and then next.js plugin creates a child span, and this child span haves the correct endpoint information.
|
|
70
|
-
for (let i = startedSpans.length - 1; i >= 0; i--) {
|
|
71
|
-
const tags = getSpanContextTags(startedSpans[i])
|
|
72
|
-
if (isWebServerSpan(tags)) {
|
|
73
|
-
context.webTags = tags
|
|
74
|
-
// endpoint may not be determined yet, but keep it as fallback
|
|
75
|
-
// if tags are not available anymore during serialization
|
|
76
|
-
context.endpoint = endpointNameFromTags(tags)
|
|
77
|
-
break
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
67
|
class NativeWallProfiler {
|
|
85
68
|
constructor (options = {}) {
|
|
86
69
|
this.type = 'wall'
|
|
@@ -88,6 +71,7 @@ class NativeWallProfiler {
|
|
|
88
71
|
this._flushIntervalMillis = options.flushInterval || 60 * 1e3 // 60 seconds
|
|
89
72
|
this._codeHotspotsEnabled = !!options.codeHotspotsEnabled
|
|
90
73
|
this._endpointCollectionEnabled = !!options.endpointCollectionEnabled
|
|
74
|
+
this._withContexts = this._codeHotspotsEnabled || this._endpointCollectionEnabled
|
|
91
75
|
this._v8ProfilerBugWorkaroundEnabled = !!options.v8ProfilerBugWorkaroundEnabled
|
|
92
76
|
this._mapper = undefined
|
|
93
77
|
this._pprof = undefined
|
|
@@ -109,12 +93,6 @@ class NativeWallProfiler {
|
|
|
109
93
|
start ({ mapper } = {}) {
|
|
110
94
|
if (this._started) return
|
|
111
95
|
|
|
112
|
-
if (this._codeHotspotsEnabled && !this._emittedFFMessage && this._logger) {
|
|
113
|
-
this._logger.debug(
|
|
114
|
-
`Wall profiler: Enable trace_show_breakdown_profiling_for_node feature flag to see code hotspots.`)
|
|
115
|
-
this._emittedFFMessage = true
|
|
116
|
-
}
|
|
117
|
-
|
|
118
96
|
this._mapper = mapper
|
|
119
97
|
this._pprof = require('@datadog/pprof')
|
|
120
98
|
kSampleCount = this._pprof.time.constants.kSampleCount
|
|
@@ -131,12 +109,12 @@ class NativeWallProfiler {
|
|
|
131
109
|
intervalMicros: this._samplingIntervalMicros,
|
|
132
110
|
durationMillis: this._flushIntervalMillis,
|
|
133
111
|
sourceMapper: this._mapper,
|
|
134
|
-
withContexts: this.
|
|
112
|
+
withContexts: this._withContexts,
|
|
135
113
|
lineNumbers: false,
|
|
136
114
|
workaroundV8Bug: this._v8ProfilerBugWorkaroundEnabled
|
|
137
115
|
})
|
|
138
116
|
|
|
139
|
-
if (this.
|
|
117
|
+
if (this._withContexts) {
|
|
140
118
|
this._profilerState = this._pprof.time.getState()
|
|
141
119
|
this._currentContext = {}
|
|
142
120
|
this._pprof.time.setContext(this._currentContext)
|
|
@@ -161,9 +139,7 @@ class NativeWallProfiler {
|
|
|
161
139
|
this._currentContext = {}
|
|
162
140
|
this._pprof.time.setContext(this._currentContext)
|
|
163
141
|
|
|
164
|
-
|
|
165
|
-
updateContext(context, this._lastSpan, this._lastStartedSpans, this._endpointCollectionEnabled)
|
|
166
|
-
}
|
|
142
|
+
this._updateContext(context)
|
|
167
143
|
}
|
|
168
144
|
|
|
169
145
|
const span = getActiveSpan()
|
|
@@ -176,6 +152,35 @@ class NativeWallProfiler {
|
|
|
176
152
|
}
|
|
177
153
|
}
|
|
178
154
|
|
|
155
|
+
_updateContext (context) {
|
|
156
|
+
if (!this._lastSpan) {
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
if (this._codeHotspotsEnabled) {
|
|
160
|
+
context.spanId = this._lastSpan.context().toSpanId()
|
|
161
|
+
const rootSpan = this._lastStartedSpans[0]
|
|
162
|
+
if (rootSpan) {
|
|
163
|
+
context.rootSpanId = rootSpan.context().toSpanId()
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (this._endpointCollectionEnabled) {
|
|
167
|
+
const startedSpans = this._lastStartedSpans
|
|
168
|
+
// Find the first webspan starting from the end:
|
|
169
|
+
// There might be several webspans, for example with next.js, http plugin creates a first span
|
|
170
|
+
// and then next.js plugin creates a child span, and this child span haves the correct endpoint information.
|
|
171
|
+
for (let i = startedSpans.length - 1; i >= 0; i--) {
|
|
172
|
+
const tags = getSpanContextTags(startedSpans[i])
|
|
173
|
+
if (isWebServerSpan(tags)) {
|
|
174
|
+
context.webTags = tags
|
|
175
|
+
// endpoint may not be determined yet, but keep it as fallback
|
|
176
|
+
// if tags are not available anymore during serialization
|
|
177
|
+
context.endpoint = endpointNameFromTags(tags)
|
|
178
|
+
break
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
179
184
|
_reportV8bug (maybeBug) {
|
|
180
185
|
const tag = `v8_profiler_bug_workaround_enabled:${this._v8ProfilerBugWorkaroundEnabled}`
|
|
181
186
|
const metric = `v8_cpu_profiler${maybeBug ? '_maybe' : ''}_stuck_event_loop`
|
|
@@ -188,12 +193,12 @@ class NativeWallProfiler {
|
|
|
188
193
|
|
|
189
194
|
_stop (restart) {
|
|
190
195
|
if (!this._started) return
|
|
191
|
-
if (this.
|
|
196
|
+
if (this._withContexts) {
|
|
192
197
|
// update last sample context if needed
|
|
193
198
|
this._enter()
|
|
194
199
|
this._lastSampleCount = 0
|
|
195
200
|
}
|
|
196
|
-
const profile = this._pprof.time.stop(restart, this.
|
|
201
|
+
const profile = this._pprof.time.stop(restart, this._withContexts ? generateLabels : undefined)
|
|
197
202
|
if (restart) {
|
|
198
203
|
const v8BugDetected = this._pprof.time.v8ProfilerStuckEventLoopDetected()
|
|
199
204
|
if (v8BugDetected !== 0) {
|
|
@@ -215,9 +220,9 @@ class NativeWallProfiler {
|
|
|
215
220
|
if (!this._started) return
|
|
216
221
|
|
|
217
222
|
const profile = this._stop(false)
|
|
218
|
-
if (this.
|
|
223
|
+
if (this._withContexts) {
|
|
219
224
|
beforeCh.unsubscribe(this._enter)
|
|
220
|
-
enterCh.
|
|
225
|
+
enterCh.unsubscribe(this._enter)
|
|
221
226
|
this._profilerState = undefined
|
|
222
227
|
}
|
|
223
228
|
|
|
@@ -7,6 +7,7 @@ const dependencies = require('./dependencies')
|
|
|
7
7
|
const { sendData } = require('./send-data')
|
|
8
8
|
|
|
9
9
|
const { manager: metricsManager } = require('./metrics')
|
|
10
|
+
const logs = require('./logs')
|
|
10
11
|
|
|
11
12
|
const telemetryStartChannel = dc.channel('datadog:telemetry:start')
|
|
12
13
|
const telemetryStopChannel = dc.channel('datadog:telemetry:stop')
|
|
@@ -139,10 +140,13 @@ function start (aConfig, thePluginManager) {
|
|
|
139
140
|
heartbeatInterval = config.telemetry.heartbeatInterval
|
|
140
141
|
|
|
141
142
|
dependencies.start(config, application, host)
|
|
143
|
+
logs.start(config)
|
|
144
|
+
|
|
142
145
|
sendData(config, application, host, 'app-started', appStarted())
|
|
143
146
|
heartbeat(config, application, host)
|
|
144
147
|
interval = setInterval(() => {
|
|
145
148
|
metricsManager.send(config, application, host)
|
|
149
|
+
logs.send(config, application, host)
|
|
146
150
|
}, heartbeatInterval)
|
|
147
151
|
interval.unref()
|
|
148
152
|
process.on('beforeExit', onBeforeExit)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const dc = require('../../../../diagnostics_channel')
|
|
4
|
+
const logCollector = require('./log-collector')
|
|
5
|
+
const { sendData } = require('../send-data')
|
|
6
|
+
|
|
7
|
+
const telemetryLog = dc.channel('datadog:telemetry:log')
|
|
8
|
+
|
|
9
|
+
let enabled = false
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Telemetry logs api defines only ERROR, WARN and DEBUG levels:
|
|
13
|
+
* - WARN level is enabled by default
|
|
14
|
+
* - DEBUG level will be possible to activate with an env var or telemetry config property
|
|
15
|
+
*/
|
|
16
|
+
function isLevelEnabled (level) {
|
|
17
|
+
return isValidLevel(level)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isValidLevel (level) {
|
|
21
|
+
switch (level) {
|
|
22
|
+
case 'ERROR':
|
|
23
|
+
case 'WARN':
|
|
24
|
+
return true
|
|
25
|
+
default:
|
|
26
|
+
return false
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function onLog (log) {
|
|
31
|
+
if (isLevelEnabled(log?.level?.toUpperCase())) {
|
|
32
|
+
logCollector.add(log)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function start (config) {
|
|
37
|
+
if (!config.telemetry.logCollection || enabled) return
|
|
38
|
+
|
|
39
|
+
enabled = true
|
|
40
|
+
|
|
41
|
+
telemetryLog.subscribe(onLog)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function stop () {
|
|
45
|
+
enabled = false
|
|
46
|
+
|
|
47
|
+
if (telemetryLog.hasSubscribers) {
|
|
48
|
+
telemetryLog.unsubscribe(onLog)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function send (config, application, host) {
|
|
53
|
+
if (!enabled) return
|
|
54
|
+
|
|
55
|
+
const logs = logCollector.drain()
|
|
56
|
+
if (logs) {
|
|
57
|
+
sendData(config, application, host, 'logs', logs)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
start,
|
|
63
|
+
stop,
|
|
64
|
+
send
|
|
65
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const log = require('
|
|
3
|
+
const log = require('../../log')
|
|
4
4
|
|
|
5
5
|
const logs = new Map()
|
|
6
6
|
|
|
@@ -18,37 +18,21 @@ function hashCode (hashSource) {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
function createHash (logEntry) {
|
|
21
|
-
if (!logEntry) return 0
|
|
22
|
-
|
|
23
21
|
const prime = 31
|
|
24
22
|
let result = ((!logEntry.level) ? 0 : hashCode(logEntry.level))
|
|
25
23
|
result = (((prime * result) | 0) + ((!logEntry.message) ? 0 : hashCode(logEntry.message))) | 0
|
|
26
|
-
|
|
27
|
-
// NOTE: tags are not used at the moment
|
|
28
|
-
// result = (((prime * result) | 0) + ((!logEntry.tags) ? 0 : hashCode(logEntry.tags))) | 0
|
|
29
24
|
result = (((prime * result) | 0) + ((!logEntry.stack_trace) ? 0 : hashCode(logEntry.stack_trace))) | 0
|
|
30
25
|
return result
|
|
31
26
|
}
|
|
32
27
|
|
|
33
|
-
function newLogEntry (message, level, tags) {
|
|
34
|
-
return {
|
|
35
|
-
message,
|
|
36
|
-
level,
|
|
37
|
-
tags
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
28
|
function isValid (logEntry) {
|
|
42
|
-
return logEntry
|
|
29
|
+
return logEntry?.level && logEntry.message
|
|
43
30
|
}
|
|
44
31
|
|
|
45
32
|
const logCollector = {
|
|
46
33
|
add (logEntry) {
|
|
47
34
|
try {
|
|
48
|
-
if (!isValid(logEntry))
|
|
49
|
-
log.info('IAST log collector discarding invalid log')
|
|
50
|
-
return
|
|
51
|
-
}
|
|
35
|
+
if (!isValid(logEntry)) return false
|
|
52
36
|
|
|
53
37
|
// NOTE: should errors have higher priority? and discard log entries with lower priority?
|
|
54
38
|
if (logs.size >= maxEntries) {
|
|
@@ -70,11 +54,13 @@ const logCollector = {
|
|
|
70
54
|
drain () {
|
|
71
55
|
if (logs.size === 0) return
|
|
72
56
|
|
|
73
|
-
const drained = []
|
|
74
|
-
drained.push(...logs.values())
|
|
57
|
+
const drained = [...logs.values()]
|
|
75
58
|
|
|
76
59
|
if (overflowedCount > 0) {
|
|
77
|
-
drained.push(
|
|
60
|
+
drained.push({
|
|
61
|
+
message: `Omitted ${overflowedCount} entries due to overflowing`,
|
|
62
|
+
level: 'ERROR'
|
|
63
|
+
})
|
|
78
64
|
}
|
|
79
65
|
|
|
80
66
|
this.reset()
|
|
@@ -85,6 +71,7 @@ const logCollector = {
|
|
|
85
71
|
reset (max) {
|
|
86
72
|
logs.clear()
|
|
87
73
|
overflowedCount = 0
|
|
74
|
+
|
|
88
75
|
if (max) {
|
|
89
76
|
maxEntries = max
|
|
90
77
|
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const dc = require('../../../../../../diagnostics_channel')
|
|
4
|
-
const logCollector = require('./log-collector')
|
|
5
|
-
const { sendData } = require('../../../../telemetry/send-data')
|
|
6
|
-
const log = require('../../../../log')
|
|
7
|
-
|
|
8
|
-
const telemetryStartChannel = dc.channel('datadog:telemetry:start')
|
|
9
|
-
const telemetryStopChannel = dc.channel('datadog:telemetry:stop')
|
|
10
|
-
|
|
11
|
-
let config, application, host, interval
|
|
12
|
-
|
|
13
|
-
function publish (log) {
|
|
14
|
-
if (log && isLevelEnabled(log.level)) {
|
|
15
|
-
logCollector.add(log)
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function sendLogs () {
|
|
20
|
-
try {
|
|
21
|
-
const logs = logCollector.drain()
|
|
22
|
-
if (logs) {
|
|
23
|
-
sendData(config, application, host, 'logs', logs)
|
|
24
|
-
}
|
|
25
|
-
} catch (e) {
|
|
26
|
-
log.error(e)
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function isLevelEnabled (level) {
|
|
31
|
-
return isLogCollectionEnabled(config) && level !== 'DEBUG'
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function isLogCollectionEnabled (config) {
|
|
35
|
-
return config && config.telemetry && config.telemetry.logCollection
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function onTelemetryStart (msg) {
|
|
39
|
-
if (!msg || !isLogCollectionEnabled(msg.config)) {
|
|
40
|
-
log.info('IAST telemetry logs start event received but log collection is not enabled or configuration is incorrect')
|
|
41
|
-
return false
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
log.info('IAST telemetry logs starting')
|
|
45
|
-
|
|
46
|
-
config = msg.config
|
|
47
|
-
application = msg.application
|
|
48
|
-
host = msg.host
|
|
49
|
-
|
|
50
|
-
if (msg.heartbeatInterval) {
|
|
51
|
-
interval = setInterval(sendLogs, msg.heartbeatInterval)
|
|
52
|
-
interval.unref()
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return true
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function onTelemetryStop () {
|
|
59
|
-
stop()
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function start () {
|
|
63
|
-
telemetryStartChannel.subscribe(onTelemetryStart)
|
|
64
|
-
telemetryStopChannel.subscribe(onTelemetryStop)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function stop () {
|
|
68
|
-
if (!isLogCollectionEnabled(config)) return
|
|
69
|
-
|
|
70
|
-
log.info('IAST telemetry logs stopping')
|
|
71
|
-
|
|
72
|
-
config = null
|
|
73
|
-
application = null
|
|
74
|
-
host = null
|
|
75
|
-
|
|
76
|
-
if (telemetryStartChannel.hasSubscribers) {
|
|
77
|
-
telemetryStartChannel.unsubscribe(onTelemetryStart)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (telemetryStopChannel.hasSubscribers) {
|
|
81
|
-
telemetryStopChannel.unsubscribe(onTelemetryStop)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
clearInterval(interval)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
module.exports = { start, stop, publish, isLevelEnabled }
|