dd-trace 4.16.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/LICENSE-3rdparty.csv +1 -0
- package/index.d.ts +9 -1
- package/package.json +5 -4
- package/packages/datadog-instrumentations/src/body-parser.js +2 -1
- package/packages/datadog-instrumentations/src/cucumber.js +29 -4
- package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +45 -0
- package/packages/datadog-instrumentations/src/express.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
- package/packages/datadog-instrumentations/src/jest.js +58 -20
- package/packages/datadog-instrumentations/src/knex.js +69 -1
- package/packages/datadog-instrumentations/src/mocha.js +34 -4
- package/packages/datadog-instrumentations/src/mongodb.js +63 -0
- package/packages/datadog-instrumentations/src/mongoose.js +140 -1
- package/packages/datadog-instrumentations/src/next.js +98 -23
- package/packages/datadog-instrumentations/src/playwright.js +22 -8
- package/packages/datadog-plugin-cucumber/src/index.js +17 -5
- package/packages/datadog-plugin-cypress/src/plugin.js +38 -8
- package/packages/datadog-plugin-http/src/client.js +2 -0
- package/packages/datadog-plugin-jest/src/index.js +29 -6
- package/packages/datadog-plugin-jest/src/util.js +45 -2
- package/packages/datadog-plugin-memcached/src/index.js +10 -5
- package/packages/datadog-plugin-mocha/src/index.js +25 -6
- 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/channels.js +3 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +2 -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 +173 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +21 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +3 -3
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +1 -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/iast-plugin.js +4 -0
- package/packages/dd-trace/src/appsec/iast/path-line.js +6 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +25 -12
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +4 -4
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +13 -2
- package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +13 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +2 -1
- package/packages/dd-trace/src/appsec/iast/telemetry/index.js +1 -14
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +16 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +22 -4
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +9 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +15 -2
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +169 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +2 -0
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +5 -1
- package/packages/dd-trace/src/appsec/index.js +31 -13
- package/packages/dd-trace/src/appsec/remote_config/manager.js +11 -3
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +14 -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 +37 -13
- package/packages/dd-trace/src/format.js +3 -0
- package/packages/dd-trace/src/git_properties.js +16 -15
- package/packages/dd-trace/src/plugin_manager.js +3 -1
- package/packages/dd-trace/src/plugins/util/ci.js +17 -0
- package/packages/dd-trace/src/plugins/util/git.js +26 -4
- package/packages/dd-trace/src/plugins/util/test.js +45 -2
- package/packages/dd-trace/src/profiling/config.js +20 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +51 -40
- package/packages/dd-trace/src/service-naming/extra-services.js +24 -0
- 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/telemetry/metrics.js +0 -5
- package/packages/dd-trace/src/appsec/iast/telemetry/log/index.js +0 -87
|
@@ -602,6 +602,23 @@ module.exports = {
|
|
|
602
602
|
tags[refKey] = ref
|
|
603
603
|
}
|
|
604
604
|
|
|
605
|
+
if (env.CODEBUILD_INITIATOR?.startsWith('codepipeline/')) {
|
|
606
|
+
const {
|
|
607
|
+
CODEBUILD_BUILD_ARN,
|
|
608
|
+
DD_ACTION_EXECUTION_ID,
|
|
609
|
+
DD_PIPELINE_EXECUTION_ID
|
|
610
|
+
} = env
|
|
611
|
+
tags = {
|
|
612
|
+
[CI_PROVIDER_NAME]: 'awscodepipeline',
|
|
613
|
+
[CI_PIPELINE_ID]: DD_PIPELINE_EXECUTION_ID,
|
|
614
|
+
[CI_ENV_VARS]: JSON.stringify({
|
|
615
|
+
CODEBUILD_BUILD_ARN,
|
|
616
|
+
DD_PIPELINE_EXECUTION_ID,
|
|
617
|
+
DD_ACTION_EXECUTION_ID
|
|
618
|
+
})
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
605
622
|
normalizeTag(tags, CI_WORKSPACE_PATH, resolveTilde)
|
|
606
623
|
normalizeTag(tags, GIT_REPOSITORY_URL, filterSensitiveInfoFromRepository)
|
|
607
624
|
normalizeTag(tags, GIT_BRANCH, normalizeRef)
|
|
@@ -61,15 +61,37 @@ function unshallowRepository () {
|
|
|
61
61
|
}
|
|
62
62
|
const defaultRemoteName = sanitizedExec('git', ['config', '--default', 'origin', '--get', 'clone.defaultRemoteName'])
|
|
63
63
|
const revParseHead = sanitizedExec('git', ['rev-parse', 'HEAD'])
|
|
64
|
-
|
|
64
|
+
|
|
65
|
+
const baseGitOptions = [
|
|
65
66
|
'fetch',
|
|
66
67
|
'--shallow-since="1 month ago"',
|
|
67
68
|
'--update-shallow',
|
|
68
69
|
'--filter=blob:none',
|
|
69
70
|
'--recurse-submodules=no',
|
|
70
|
-
defaultRemoteName
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
defaultRemoteName
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
execFileSync('git', [
|
|
76
|
+
...baseGitOptions,
|
|
77
|
+
revParseHead
|
|
78
|
+
], { stdio: 'pipe' })
|
|
79
|
+
} catch (e) {
|
|
80
|
+
// If the local HEAD is a commit that has not been pushed to the remote, the above command will fail.
|
|
81
|
+
log.error(e)
|
|
82
|
+
const upstreamRemote = sanitizedExec('git', ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}'])
|
|
83
|
+
try {
|
|
84
|
+
execFileSync('git', [
|
|
85
|
+
...baseGitOptions,
|
|
86
|
+
upstreamRemote
|
|
87
|
+
], { stdio: 'pipe' })
|
|
88
|
+
} catch (e) {
|
|
89
|
+
// If the CI is working on a detached HEAD or branch tracking hasn’t been set up, the above command will fail.
|
|
90
|
+
log.error(e)
|
|
91
|
+
// We use sanitizedExec here because if this last option fails, we'll give up.
|
|
92
|
+
sanitizedExec('git', baseGitOptions)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
73
95
|
}
|
|
74
96
|
|
|
75
97
|
function getRepositoryUrl () {
|
|
@@ -58,6 +58,8 @@ const TEST_ITR_SKIPPING_ENABLED = 'test.itr.tests_skipping.enabled'
|
|
|
58
58
|
const TEST_ITR_SKIPPING_TYPE = 'test.itr.tests_skipping.type'
|
|
59
59
|
const TEST_ITR_SKIPPING_COUNT = 'test.itr.tests_skipping.count'
|
|
60
60
|
const TEST_CODE_COVERAGE_ENABLED = 'test.code_coverage.enabled'
|
|
61
|
+
const TEST_ITR_UNSKIPPABLE = 'test.itr.unskippable'
|
|
62
|
+
const TEST_ITR_FORCED_RUN = 'test.itr.forced_run'
|
|
61
63
|
|
|
62
64
|
const TEST_CODE_COVERAGE_LINES_PCT = 'test.code_coverage.lines_pct'
|
|
63
65
|
|
|
@@ -107,6 +109,8 @@ module.exports = {
|
|
|
107
109
|
TEST_ITR_SKIPPING_COUNT,
|
|
108
110
|
TEST_CODE_COVERAGE_ENABLED,
|
|
109
111
|
TEST_CODE_COVERAGE_LINES_PCT,
|
|
112
|
+
TEST_ITR_UNSKIPPABLE,
|
|
113
|
+
TEST_ITR_FORCED_RUN,
|
|
110
114
|
addIntelligentTestRunnerSpanTags,
|
|
111
115
|
getCoveredFilenamesFromCoverage,
|
|
112
116
|
resetCoverage,
|
|
@@ -114,7 +118,8 @@ module.exports = {
|
|
|
114
118
|
fromCoverageMapToCoverage,
|
|
115
119
|
getTestLineStart,
|
|
116
120
|
getCallSites,
|
|
117
|
-
removeInvalidMetadata
|
|
121
|
+
removeInvalidMetadata,
|
|
122
|
+
parseAnnotations
|
|
118
123
|
}
|
|
119
124
|
|
|
120
125
|
// Returns pkg manager and its version, separated by '-', e.g. npm-8.15.0 or yarn-1.22.19
|
|
@@ -366,7 +371,9 @@ function addIntelligentTestRunnerSpanTags (
|
|
|
366
371
|
isCodeCoverageEnabled,
|
|
367
372
|
testCodeCoverageLinesTotal,
|
|
368
373
|
skippingCount,
|
|
369
|
-
skippingType = 'suite'
|
|
374
|
+
skippingType = 'suite',
|
|
375
|
+
hasUnskippableSuites,
|
|
376
|
+
hasForcedToRunSuites
|
|
370
377
|
}
|
|
371
378
|
) {
|
|
372
379
|
testSessionSpan.setTag(TEST_ITR_TESTS_SKIPPED, isSuitesSkipped ? 'true' : 'false')
|
|
@@ -381,6 +388,15 @@ function addIntelligentTestRunnerSpanTags (
|
|
|
381
388
|
testModuleSpan.setTag(TEST_ITR_SKIPPING_COUNT, skippingCount)
|
|
382
389
|
testModuleSpan.setTag(TEST_CODE_COVERAGE_ENABLED, isCodeCoverageEnabled ? 'true' : 'false')
|
|
383
390
|
|
|
391
|
+
if (hasUnskippableSuites) {
|
|
392
|
+
testSessionSpan.setTag(TEST_ITR_UNSKIPPABLE, 'true')
|
|
393
|
+
testModuleSpan.setTag(TEST_ITR_UNSKIPPABLE, 'true')
|
|
394
|
+
}
|
|
395
|
+
if (hasForcedToRunSuites) {
|
|
396
|
+
testSessionSpan.setTag(TEST_ITR_FORCED_RUN, 'true')
|
|
397
|
+
testModuleSpan.setTag(TEST_ITR_FORCED_RUN, 'true')
|
|
398
|
+
}
|
|
399
|
+
|
|
384
400
|
// If suites have been skipped we don't want to report the total coverage, as it will be wrong
|
|
385
401
|
if (testCodeCoverageLinesTotal !== undefined && !isSuitesSkipped) {
|
|
386
402
|
testSessionSpan.setTag(TEST_CODE_COVERAGE_LINES_PCT, testCodeCoverageLinesTotal)
|
|
@@ -477,3 +493,30 @@ function getCallSites () {
|
|
|
477
493
|
|
|
478
494
|
return v8StackTrace
|
|
479
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
|
+
}
|
|
@@ -37,6 +37,8 @@ class Config {
|
|
|
37
37
|
DD_PROFILING_EXPERIMENTAL_OOM_HEAP_LIMIT_EXTENSION_SIZE,
|
|
38
38
|
DD_PROFILING_EXPERIMENTAL_OOM_MAX_HEAP_EXTENSION_COUNT,
|
|
39
39
|
DD_PROFILING_EXPERIMENTAL_OOM_EXPORT_STRATEGIES,
|
|
40
|
+
DD_PROFILING_CODEHOTSPOTS_ENABLED,
|
|
41
|
+
DD_PROFILING_ENDPOINT_COLLECTION_ENABLED,
|
|
40
42
|
DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED,
|
|
41
43
|
DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED
|
|
42
44
|
} = process.env
|
|
@@ -53,8 +55,6 @@ class Config {
|
|
|
53
55
|
Number(DD_PROFILING_UPLOAD_TIMEOUT), 60 * 1000)
|
|
54
56
|
const sourceMap = coalesce(options.sourceMap,
|
|
55
57
|
DD_PROFILING_SOURCE_MAP, true)
|
|
56
|
-
const endpointCollectionEnabled = coalesce(options.endpointCollection,
|
|
57
|
-
DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED, false)
|
|
58
58
|
const pprofPrefix = coalesce(options.pprofPrefix,
|
|
59
59
|
DD_PROFILING_PPROF_PREFIX, '')
|
|
60
60
|
|
|
@@ -71,11 +71,25 @@ class Config {
|
|
|
71
71
|
tagger.parse({ env, host, service, version, functionname })
|
|
72
72
|
)
|
|
73
73
|
this.logger = ensureLogger(options.logger)
|
|
74
|
+
const logger = this.logger
|
|
75
|
+
function logExperimentalVarDeprecation (shortVarName) {
|
|
76
|
+
const deprecatedEnvVarName = `DD_PROFILING_EXPERIMENTAL_${shortVarName}`
|
|
77
|
+
const v = process.env[deprecatedEnvVarName]
|
|
78
|
+
// not null, undefined, or NaN -- same logic as koalas.hasValue
|
|
79
|
+
// eslint-disable-next-line no-self-compare
|
|
80
|
+
if (v != null && v === v) {
|
|
81
|
+
logger.warn(`${deprecatedEnvVarName} is deprecated. Use DD_PROFILING_${shortVarName} instead.`)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
74
84
|
this.flushInterval = flushInterval
|
|
75
85
|
this.uploadTimeout = uploadTimeout
|
|
76
86
|
this.sourceMap = sourceMap
|
|
77
87
|
this.debugSourceMaps = isTrue(coalesce(options.debugSourceMaps, DD_PROFILING_DEBUG_SOURCE_MAPS, false))
|
|
78
|
-
this.endpointCollectionEnabled =
|
|
88
|
+
this.endpointCollectionEnabled = isTrue(coalesce(options.endpointCollection,
|
|
89
|
+
DD_PROFILING_ENDPOINT_COLLECTION_ENABLED,
|
|
90
|
+
DD_PROFILING_EXPERIMENTAL_ENDPOINT_COLLECTION_ENABLED, false))
|
|
91
|
+
logExperimentalVarDeprecation('ENDPOINT_COLLECTION_ENABLED')
|
|
92
|
+
|
|
79
93
|
this.pprofPrefix = pprofPrefix
|
|
80
94
|
this.v8ProfilerBugWorkaroundEnabled = isTrue(coalesce(options.v8ProfilerBugWorkaround,
|
|
81
95
|
DD_PROFILING_V8_PROFILER_BUG_WORKAROUND, true))
|
|
@@ -113,8 +127,11 @@ class Config {
|
|
|
113
127
|
const profilers = options.profilers
|
|
114
128
|
? options.profilers
|
|
115
129
|
: getProfilers({ DD_PROFILING_HEAP_ENABLED, DD_PROFILING_WALLTIME_ENABLED, DD_PROFILING_PROFILERS })
|
|
130
|
+
|
|
116
131
|
this.codeHotspotsEnabled = isTrue(coalesce(options.codeHotspotsEnabled,
|
|
132
|
+
DD_PROFILING_CODEHOTSPOTS_ENABLED,
|
|
117
133
|
DD_PROFILING_EXPERIMENTAL_CODEHOTSPOTS_ENABLED, false))
|
|
134
|
+
logExperimentalVarDeprecation('CODEHOTSPOTS_ENABLED')
|
|
118
135
|
|
|
119
136
|
this.profilers = ensureProfilers(profilers, this)
|
|
120
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 () {
|
|
@@ -23,8 +29,8 @@ function getStartedSpans (context) {
|
|
|
23
29
|
return context._trace.started
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
function generateLabels ({ spanId, rootSpanId, webTags, endpoint }) {
|
|
27
|
-
const labels = {}
|
|
32
|
+
function generateLabels ({ context: { spanId, rootSpanId, webTags, endpoint }, timestamp }) {
|
|
33
|
+
const labels = { 'thread name': threadName }
|
|
28
34
|
if (spanId) {
|
|
29
35
|
labels['span id'] = spanId
|
|
30
36
|
}
|
|
@@ -37,6 +43,8 @@ function generateLabels ({ spanId, rootSpanId, webTags, endpoint }) {
|
|
|
37
43
|
// fallback to endpoint computed when sample was taken
|
|
38
44
|
labels['trace endpoint'] = endpoint
|
|
39
45
|
}
|
|
46
|
+
// Incoming timestamps are in microseconds, we emit nanos.
|
|
47
|
+
labels['end_timestamp_ns'] = timestamp * 1000n
|
|
40
48
|
|
|
41
49
|
return labels
|
|
42
50
|
}
|
|
@@ -56,29 +64,6 @@ function endpointNameFromTags (tags) {
|
|
|
56
64
|
].filter(v => v).join(' ')
|
|
57
65
|
}
|
|
58
66
|
|
|
59
|
-
function updateContext (context, span, startedSpans, endpointCollectionEnabled) {
|
|
60
|
-
context.spanId = span.context().toSpanId()
|
|
61
|
-
const rootSpan = startedSpans[0]
|
|
62
|
-
if (rootSpan) {
|
|
63
|
-
context.rootSpanId = rootSpan.context().toSpanId()
|
|
64
|
-
if (endpointCollectionEnabled) {
|
|
65
|
-
// Find the first webspan starting from the end:
|
|
66
|
-
// There might be several webspans, for example with next.js, http plugin creates a first span
|
|
67
|
-
// and then next.js plugin creates a child span, and this child span haves the correct endpoint information.
|
|
68
|
-
for (let i = startedSpans.length - 1; i >= 0; i--) {
|
|
69
|
-
const tags = getSpanContextTags(startedSpans[i])
|
|
70
|
-
if (isWebServerSpan(tags)) {
|
|
71
|
-
context.webTags = tags
|
|
72
|
-
// endpoint may not be determined yet, but keep it as fallback
|
|
73
|
-
// if tags are not available anymore during serialization
|
|
74
|
-
context.endpoint = endpointNameFromTags(tags)
|
|
75
|
-
break
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
67
|
class NativeWallProfiler {
|
|
83
68
|
constructor (options = {}) {
|
|
84
69
|
this.type = 'wall'
|
|
@@ -86,6 +71,7 @@ class NativeWallProfiler {
|
|
|
86
71
|
this._flushIntervalMillis = options.flushInterval || 60 * 1e3 // 60 seconds
|
|
87
72
|
this._codeHotspotsEnabled = !!options.codeHotspotsEnabled
|
|
88
73
|
this._endpointCollectionEnabled = !!options.endpointCollectionEnabled
|
|
74
|
+
this._withContexts = this._codeHotspotsEnabled || this._endpointCollectionEnabled
|
|
89
75
|
this._v8ProfilerBugWorkaroundEnabled = !!options.v8ProfilerBugWorkaroundEnabled
|
|
90
76
|
this._mapper = undefined
|
|
91
77
|
this._pprof = undefined
|
|
@@ -100,15 +86,13 @@ class NativeWallProfiler {
|
|
|
100
86
|
return this._codeHotspotsEnabled
|
|
101
87
|
}
|
|
102
88
|
|
|
89
|
+
endpointCollectionEnabled () {
|
|
90
|
+
return this._endpointCollectionEnabled
|
|
91
|
+
}
|
|
92
|
+
|
|
103
93
|
start ({ mapper } = {}) {
|
|
104
94
|
if (this._started) return
|
|
105
95
|
|
|
106
|
-
if (this._codeHotspotsEnabled && !this._emittedFFMessage && this._logger) {
|
|
107
|
-
this._logger.debug(
|
|
108
|
-
`Wall profiler: Enable trace_show_breakdown_profiling_for_node feature flag to see code hotspots.`)
|
|
109
|
-
this._emittedFFMessage = true
|
|
110
|
-
}
|
|
111
|
-
|
|
112
96
|
this._mapper = mapper
|
|
113
97
|
this._pprof = require('@datadog/pprof')
|
|
114
98
|
kSampleCount = this._pprof.time.constants.kSampleCount
|
|
@@ -125,12 +109,12 @@ class NativeWallProfiler {
|
|
|
125
109
|
intervalMicros: this._samplingIntervalMicros,
|
|
126
110
|
durationMillis: this._flushIntervalMillis,
|
|
127
111
|
sourceMapper: this._mapper,
|
|
128
|
-
withContexts: this.
|
|
112
|
+
withContexts: this._withContexts,
|
|
129
113
|
lineNumbers: false,
|
|
130
114
|
workaroundV8Bug: this._v8ProfilerBugWorkaroundEnabled
|
|
131
115
|
})
|
|
132
116
|
|
|
133
|
-
if (this.
|
|
117
|
+
if (this._withContexts) {
|
|
134
118
|
this._profilerState = this._pprof.time.getState()
|
|
135
119
|
this._currentContext = {}
|
|
136
120
|
this._pprof.time.setContext(this._currentContext)
|
|
@@ -155,9 +139,7 @@ class NativeWallProfiler {
|
|
|
155
139
|
this._currentContext = {}
|
|
156
140
|
this._pprof.time.setContext(this._currentContext)
|
|
157
141
|
|
|
158
|
-
|
|
159
|
-
updateContext(context, this._lastSpan, this._lastStartedSpans, this._endpointCollectionEnabled)
|
|
160
|
-
}
|
|
142
|
+
this._updateContext(context)
|
|
161
143
|
}
|
|
162
144
|
|
|
163
145
|
const span = getActiveSpan()
|
|
@@ -170,6 +152,35 @@ class NativeWallProfiler {
|
|
|
170
152
|
}
|
|
171
153
|
}
|
|
172
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
|
+
|
|
173
184
|
_reportV8bug (maybeBug) {
|
|
174
185
|
const tag = `v8_profiler_bug_workaround_enabled:${this._v8ProfilerBugWorkaroundEnabled}`
|
|
175
186
|
const metric = `v8_cpu_profiler${maybeBug ? '_maybe' : ''}_stuck_event_loop`
|
|
@@ -182,12 +193,12 @@ class NativeWallProfiler {
|
|
|
182
193
|
|
|
183
194
|
_stop (restart) {
|
|
184
195
|
if (!this._started) return
|
|
185
|
-
if (this.
|
|
196
|
+
if (this._withContexts) {
|
|
186
197
|
// update last sample context if needed
|
|
187
198
|
this._enter()
|
|
188
199
|
this._lastSampleCount = 0
|
|
189
200
|
}
|
|
190
|
-
const profile = this._pprof.time.stop(restart, this.
|
|
201
|
+
const profile = this._pprof.time.stop(restart, this._withContexts ? generateLabels : undefined)
|
|
191
202
|
if (restart) {
|
|
192
203
|
const v8BugDetected = this._pprof.time.v8ProfilerStuckEventLoopDetected()
|
|
193
204
|
if (v8BugDetected !== 0) {
|
|
@@ -209,9 +220,9 @@ class NativeWallProfiler {
|
|
|
209
220
|
if (!this._started) return
|
|
210
221
|
|
|
211
222
|
const profile = this._stop(false)
|
|
212
|
-
if (this.
|
|
223
|
+
if (this._withContexts) {
|
|
213
224
|
beforeCh.unsubscribe(this._enter)
|
|
214
|
-
enterCh.
|
|
225
|
+
enterCh.unsubscribe(this._enter)
|
|
215
226
|
this._profilerState = undefined
|
|
216
227
|
}
|
|
217
228
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const maxExtraServices = 64
|
|
4
|
+
const extraServices = new Set()
|
|
5
|
+
|
|
6
|
+
function getExtraServices () {
|
|
7
|
+
return [...extraServices]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function registerExtraService (serviceName) {
|
|
11
|
+
if (serviceName && extraServices.size < maxExtraServices) {
|
|
12
|
+
extraServices.add(serviceName)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function clear () {
|
|
17
|
+
extraServices.clear()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
registerExtraService,
|
|
22
|
+
getExtraServices,
|
|
23
|
+
clear
|
|
24
|
+
}
|
|
@@ -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,7 +1,5 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { version } = require('../../../../package.json')
|
|
4
|
-
|
|
5
3
|
const { sendData } = require('./send-data')
|
|
6
4
|
|
|
7
5
|
function getId (type, namespace, name, tags) {
|
|
@@ -35,10 +33,7 @@ class Metric {
|
|
|
35
33
|
this.metric = common ? metric : `nodejs.${metric}`
|
|
36
34
|
this.tags = tagArray(tags)
|
|
37
35
|
if (common) {
|
|
38
|
-
this.tags.push('lib_language:nodejs')
|
|
39
36
|
this.tags.push(`version:${process.version}`)
|
|
40
|
-
} else {
|
|
41
|
-
this.tags.push(`lib_version:${version}`)
|
|
42
37
|
}
|
|
43
38
|
this.common = common
|
|
44
39
|
|
|
@@ -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 }
|