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.
Files changed (39) hide show
  1. package/index.d.ts +9 -1
  2. package/package.json +4 -4
  3. package/packages/datadog-instrumentations/src/cucumber.js +5 -0
  4. package/packages/datadog-instrumentations/src/jest.js +39 -10
  5. package/packages/datadog-instrumentations/src/knex.js +24 -17
  6. package/packages/datadog-instrumentations/src/mocha.js +16 -1
  7. package/packages/datadog-instrumentations/src/next.js +58 -23
  8. package/packages/datadog-instrumentations/src/playwright.js +11 -6
  9. package/packages/datadog-plugin-http/src/client.js +2 -0
  10. package/packages/datadog-plugin-jest/src/index.js +11 -3
  11. package/packages/datadog-plugin-mocha/src/index.js +7 -1
  12. package/packages/datadog-plugin-next/src/index.js +4 -3
  13. package/packages/datadog-plugin-playwright/src/index.js +4 -1
  14. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  15. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-analyzer.js +60 -0
  16. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +269 -0
  17. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +5 -2
  18. package/packages/dd-trace/src/appsec/iast/analyzers/missing-header-analyzer.js +22 -4
  19. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +9 -2
  20. package/packages/dd-trace/src/appsec/iast/analyzers/xcontenttype-header-missing-analyzer.js +2 -2
  21. package/packages/dd-trace/src/appsec/iast/iast-log.js +9 -4
  22. package/packages/dd-trace/src/appsec/iast/path-line.js +6 -1
  23. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +13 -2
  24. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +1 -14
  25. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +19 -0
  26. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +2 -1
  27. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  28. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +5 -1
  29. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +4 -2
  30. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -0
  31. package/packages/dd-trace/src/config.js +29 -13
  32. package/packages/dd-trace/src/git_properties.js +16 -15
  33. package/packages/dd-trace/src/plugins/util/test.js +29 -1
  34. package/packages/dd-trace/src/profiling/config.js +3 -17
  35. package/packages/dd-trace/src/profiling/profilers/wall.js +44 -39
  36. package/packages/dd-trace/src/telemetry/index.js +4 -0
  37. package/packages/dd-trace/src/telemetry/logs/index.js +65 -0
  38. package/packages/dd-trace/src/{appsec/iast/telemetry/log → telemetry/logs}/log-collector.js +9 -22
  39. 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
- false
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.experimental && options.experimental.iast
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 && iastOptions.requestSampling),
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 && iastOptions.maxConcurrentRequests),
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 && iastOptions.maxContextOperations),
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 && iastOptions.deduplicationEnabled,
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 && iastOptions.redactionEnabled,
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 && iastOptions.telemetryVerbosity,
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 = coalesce(
642
- process.env.DD_GIT_REPOSITORY_URL,
643
- this.tags[GIT_REPOSITORY_URL]
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: parsedUrl
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
- function getCodeHotspotsOptionsOr (defvalue) {
132
- return coalesce(options.codeHotspotsEnabled,
133
- DD_PROFILING_CODEHOTSPOTS_ENABLED,
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._codeHotspotsEnabled,
112
+ withContexts: this._withContexts,
135
113
  lineNumbers: false,
136
114
  workaroundV8Bug: this._v8ProfilerBugWorkaroundEnabled
137
115
  })
138
116
 
139
- if (this._codeHotspotsEnabled) {
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
- if (this._lastSpan) {
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._codeHotspotsEnabled) {
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._codeHotspotsEnabled ? generateLabels : undefined)
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._codeHotspotsEnabled) {
223
+ if (this._withContexts) {
219
224
  beforeCh.unsubscribe(this._enter)
220
- enterCh.subscribe(this._enter)
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('../../../../log')
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 && logEntry.level && logEntry.message
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(newLogEntry(`Omitted ${overflowedCount} entries due to overflowing`, 'ERROR'))
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 }