dd-trace 4.36.0 → 4.38.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 (40) hide show
  1. package/README.md +8 -1
  2. package/ci/init.js +7 -0
  3. package/ext/exporters.d.ts +2 -1
  4. package/ext/exporters.js +2 -1
  5. package/index.d.ts +21 -6
  6. package/init.js +27 -3
  7. package/package.json +6 -6
  8. package/packages/datadog-esbuild/index.js +8 -2
  9. package/packages/datadog-instrumentations/src/aws-sdk.js +4 -1
  10. package/packages/datadog-instrumentations/src/cucumber.js +182 -105
  11. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  12. package/packages/datadog-instrumentations/src/lodash.js +31 -0
  13. package/packages/datadog-instrumentations/src/playwright.js +6 -1
  14. package/packages/datadog-plugin-aws-sdk/src/services/index.js +3 -0
  15. package/packages/datadog-plugin-aws-sdk/src/services/sfn.js +7 -0
  16. package/packages/datadog-plugin-aws-sdk/src/services/states.js +7 -0
  17. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +64 -0
  18. package/packages/datadog-plugin-cucumber/src/index.js +83 -11
  19. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -0
  20. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +4 -0
  21. package/packages/dd-trace/src/appsec/iast/iast-log.js +2 -33
  22. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +3 -0
  23. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +10 -1
  24. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +55 -1
  25. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/hardcoded-password-analyzer.js +13 -0
  26. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +8 -2
  27. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +6 -6
  28. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +56 -0
  29. package/packages/dd-trace/src/ci-visibility/exporters/{jest-worker → test-worker}/writer.js +7 -0
  30. package/packages/dd-trace/src/config.js +25 -8
  31. package/packages/dd-trace/src/encode/0.4.js +50 -3
  32. package/packages/dd-trace/src/exporter.js +2 -1
  33. package/packages/dd-trace/src/plugins/database.js +20 -5
  34. package/packages/dd-trace/src/plugins/util/test.js +7 -0
  35. package/packages/dd-trace/src/profiling/profiler.js +23 -7
  36. package/packages/dd-trace/src/proxy.js +7 -1
  37. package/packages/dd-trace/src/serverless.js +3 -5
  38. package/packages/dd-trace/src/telemetry/index.js +9 -3
  39. package/packages/dd-trace/src/telemetry/logs/log-collector.js +42 -1
  40. package/packages/dd-trace/src/ci-visibility/exporters/jest-worker/index.js +0 -33
@@ -0,0 +1,56 @@
1
+ 'use strict'
2
+
3
+ const Writer = require('./writer')
4
+ const {
5
+ JEST_WORKER_COVERAGE_PAYLOAD_CODE,
6
+ JEST_WORKER_TRACE_PAYLOAD_CODE,
7
+ CUCUMBER_WORKER_TRACE_PAYLOAD_CODE
8
+ } = require('../../../plugins/util/test')
9
+
10
+ function getInterprocessTraceCode () {
11
+ if (process.env.JEST_WORKER_ID) {
12
+ return JEST_WORKER_TRACE_PAYLOAD_CODE
13
+ }
14
+ if (process.env.CUCUMBER_WORKER_ID) {
15
+ return CUCUMBER_WORKER_TRACE_PAYLOAD_CODE
16
+ }
17
+ return null
18
+ }
19
+
20
+ // TODO: make it available with cucumber
21
+ function getInterprocessCoverageCode () {
22
+ if (process.env.JEST_WORKER_ID) {
23
+ return JEST_WORKER_COVERAGE_PAYLOAD_CODE
24
+ }
25
+ return null
26
+ }
27
+
28
+ /**
29
+ * Lightweight exporter whose writers only do simple JSON serialization
30
+ * of trace and coverage payloads, which they send to the test framework's main process.
31
+ * Currently used by Jest and Cucumber workers.
32
+ */
33
+ class TestWorkerCiVisibilityExporter {
34
+ constructor () {
35
+ const interprocessTraceCode = getInterprocessTraceCode()
36
+ const interprocessCoverageCode = getInterprocessCoverageCode()
37
+
38
+ this._writer = new Writer(interprocessTraceCode)
39
+ this._coverageWriter = new Writer(interprocessCoverageCode)
40
+ }
41
+
42
+ export (payload) {
43
+ this._writer.append(payload)
44
+ }
45
+
46
+ exportCoverage (formattedCoverage) {
47
+ this._coverageWriter.append(formattedCoverage)
48
+ }
49
+
50
+ flush () {
51
+ this._writer.flush()
52
+ this._coverageWriter.flush()
53
+ }
54
+ }
55
+
56
+ module.exports = TestWorkerCiVisibilityExporter
@@ -23,11 +23,18 @@ class Writer {
23
23
  }
24
24
 
25
25
  _sendPayload (data) {
26
+ // ## Jest
26
27
  // Only available when `child_process` is used for the jest worker.
27
28
  // eslint-disable-next-line
28
29
  // https://github.com/facebook/jest/blob/bb39cb2c617a3334bf18daeca66bd87b7ccab28b/packages/jest-worker/README.md#experimental-worker
29
30
  // If worker_threads is used, this will not work
30
31
  // TODO: make it compatible with worker_threads
32
+
33
+ // ## Cucumber
34
+ // This reports to the test's main process the same way test data is reported by Cucumber
35
+ // See cucumber code:
36
+ // eslint-disable-next-line
37
+ // https://github.com/cucumber/cucumber-js/blob/5ce371870b677fe3d1a14915dc535688946f734c/src/runtime/parallel/run_worker.ts#L13
31
38
  if (process.send) { // it only works if process.send is available
32
39
  process.send([this._interprocessCode, data])
33
40
  }
@@ -16,7 +16,7 @@ const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('./plugins/util/tags')
16
16
  const { getGitMetadataFromGitProperties, removeUserSensitiveInfo } = require('./git_properties')
17
17
  const { updateConfig } = require('./telemetry')
18
18
  const telemetryMetrics = require('./telemetry/metrics')
19
- const { getIsGCPFunction, getIsAzureFunctionConsumptionPlan } = require('./serverless')
19
+ const { getIsGCPFunction, getIsAzureFunction } = require('./serverless')
20
20
  const { ORIGIN_KEY } = require('./constants')
21
21
 
22
22
  const tracerMetrics = telemetryMetrics.manager.namespace('tracers')
@@ -339,7 +339,7 @@ class Config {
339
339
 
340
340
  // Requires an accompanying DD_APM_OBFUSCATION_MEMCACHED_KEEP_COMMAND=true in the agent
341
341
  this.memcachedCommandEnabled = isTrue(DD_TRACE_MEMCACHED_COMMAND_ENABLED)
342
- this.isAzureFunctionConsumptionPlan = getIsAzureFunctionConsumptionPlan()
342
+ this.isAzureFunction = getIsAzureFunction()
343
343
  this.spanLeakDebug = Number(DD_TRACE_SPAN_LEAK_DEBUG)
344
344
  this.installSignature = {
345
345
  id: DD_INSTRUMENTATION_INSTALL_ID,
@@ -417,8 +417,8 @@ class Config {
417
417
  _isInServerlessEnvironment () {
418
418
  const inAWSLambda = process.env.AWS_LAMBDA_FUNCTION_NAME !== undefined
419
419
  const isGCPFunction = getIsGCPFunction()
420
- const isAzureFunctionConsumptionPlan = getIsAzureFunctionConsumptionPlan()
421
- return inAWSLambda || isGCPFunction || isAzureFunctionConsumptionPlan
420
+ const isAzureFunction = getIsAzureFunction()
421
+ return inAWSLambda || isGCPFunction || isAzureFunction
422
422
  }
423
423
 
424
424
  // for _merge to work, every config value must have a default value
@@ -446,6 +446,7 @@ class Config {
446
446
  this._setValue(defaults, 'appsec.obfuscatorValueRegex', defaultWafObfuscatorValueRegex)
447
447
  this._setValue(defaults, 'appsec.rateLimit', 100)
448
448
  this._setValue(defaults, 'appsec.rules', undefined)
449
+ this._setValue(defaults, 'appsec.sca.enabled', null)
449
450
  this._setValue(defaults, 'appsec.wafTimeout', 5e3) // µs
450
451
  this._setValue(defaults, 'clientIpEnabled', false)
451
452
  this._setValue(defaults, 'clientIpHeader', null)
@@ -516,6 +517,7 @@ class Config {
516
517
  this._setValue(defaults, 'tracing', true)
517
518
  this._setValue(defaults, 'url', undefined)
518
519
  this._setValue(defaults, 'version', pkg.version)
520
+ this._setValue(defaults, 'instrumentation_config_id', undefined)
519
521
  }
520
522
 
521
523
  _applyEnvironment () {
@@ -528,6 +530,7 @@ class Config {
528
530
  DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP,
529
531
  DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP,
530
532
  DD_APPSEC_RULES,
533
+ DD_APPSEC_SCA_ENABLED,
531
534
  DD_APPSEC_TRACE_RATE_LIMIT,
532
535
  DD_APPSEC_WAF_TIMEOUT,
533
536
  DD_DATA_STREAMS_ENABLED,
@@ -546,7 +549,9 @@ class Config {
546
549
  DD_IAST_REDACTION_VALUE_PATTERN,
547
550
  DD_IAST_REQUEST_SAMPLING,
548
551
  DD_IAST_TELEMETRY_VERBOSITY,
552
+ DD_INJECTION_ENABLED,
549
553
  DD_INSTRUMENTATION_TELEMETRY_ENABLED,
554
+ DD_INSTRUMENTATION_CONFIG_ID,
550
555
  DD_LOGS_INJECTION,
551
556
  DD_OPENAI_LOGS_ENABLED,
552
557
  DD_OPENAI_SPAN_CHAR_LIMIT,
@@ -615,6 +620,8 @@ class Config {
615
620
  this._setString(env, 'appsec.obfuscatorValueRegex', DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP)
616
621
  this._setValue(env, 'appsec.rateLimit', maybeInt(DD_APPSEC_TRACE_RATE_LIMIT))
617
622
  this._setString(env, 'appsec.rules', DD_APPSEC_RULES)
623
+ // DD_APPSEC_SCA_ENABLED is never used locally, but only sent to the backend
624
+ this._setBoolean(env, 'appsec.sca.enabled', DD_APPSEC_SCA_ENABLED)
618
625
  this._setValue(env, 'appsec.wafTimeout', maybeInt(DD_APPSEC_WAF_TIMEOUT))
619
626
  this._setBoolean(env, 'clientIpEnabled', DD_TRACE_CLIENT_IP_ENABLED)
620
627
  this._setString(env, 'clientIpHeader', DD_TRACE_CLIENT_IP_HEADER)
@@ -695,10 +702,18 @@ class Config {
695
702
  DD_INSTRUMENTATION_TELEMETRY_ENABLED, // to comply with instrumentation telemetry specs
696
703
  !(this._isInServerlessEnvironment() || JEST_WORKER_ID)
697
704
  ))
705
+ this._setString(env, 'instrumentation_config_id', DD_INSTRUMENTATION_CONFIG_ID)
698
706
  this._setBoolean(env, 'telemetry.debug', DD_TELEMETRY_DEBUG)
699
707
  this._setBoolean(env, 'telemetry.dependencyCollection', DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED)
700
708
  this._setValue(env, 'telemetry.heartbeatInterval', maybeInt(Math.floor(DD_TELEMETRY_HEARTBEAT_INTERVAL * 1000)))
701
- this._setBoolean(env, 'telemetry.logCollection', coalesce(DD_TELEMETRY_LOG_COLLECTION_ENABLED, DD_IAST_ENABLED))
709
+ const hasTelemetryLogsUsingFeatures =
710
+ isTrue(DD_IAST_ENABLED) ||
711
+ isTrue(DD_PROFILING_ENABLED) ||
712
+ (typeof DD_INJECTION_ENABLED === 'string' && DD_INJECTION_ENABLED.split(',').includes('profiling'))
713
+ ? true
714
+ : undefined
715
+ this._setBoolean(env, 'telemetry.logCollection', coalesce(DD_TELEMETRY_LOG_COLLECTION_ENABLED,
716
+ hasTelemetryLogsUsingFeatures))
702
717
  this._setBoolean(env, 'telemetry.metrics', DD_TELEMETRY_METRICS_ENABLED)
703
718
  this._setBoolean(env, 'traceId128BitGenerationEnabled', DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED)
704
719
  this._setBoolean(env, 'traceId128BitLoggingEnabled', DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED)
@@ -779,8 +794,10 @@ class Config {
779
794
  this._setBoolean(opts, 'spanRemoveIntegrationFromService', options.spanRemoveIntegrationFromService)
780
795
  this._setBoolean(opts, 'startupLogs', options.startupLogs)
781
796
  this._setTags(opts, 'tags', tags)
782
- this._setBoolean(opts, 'telemetry.logCollection', options.iastOptions &&
783
- (options.iastOptions === true || options.iastOptions.enabled === true))
797
+ const hasTelemetryLogsUsingFeatures =
798
+ (options.iastOptions && (options.iastOptions === true || options.iastOptions?.enabled === true)) ||
799
+ (options.profiling && options.profiling === true)
800
+ this._setBoolean(opts, 'telemetry.logCollection', hasTelemetryLogsUsingFeatures)
784
801
  this._setBoolean(opts, 'traceId128BitGenerationEnabled', options.traceId128BitGenerationEnabled)
785
802
  this._setBoolean(opts, 'traceId128BitLoggingEnabled', options.traceId128BitLoggingEnabled)
786
803
  this._setString(opts, 'version', options.version || tags.version)
@@ -860,7 +877,7 @@ class Config {
860
877
  return coalesce(
861
878
  this.options.stats,
862
879
  process.env.DD_TRACE_STATS_COMPUTATION_ENABLED,
863
- getIsGCPFunction() || getIsAzureFunctionConsumptionPlan()
880
+ getIsGCPFunction() || getIsAzureFunction()
864
881
  )
865
882
  }
866
883
 
@@ -83,13 +83,17 @@ class AgentEncoder {
83
83
  span = formatSpan(span)
84
84
  bytes.reserve(1)
85
85
 
86
- if (span.type) {
86
+ if (span.type && span.meta_struct) {
87
+ bytes.buffer[bytes.length++] = 0x8d
88
+ } else if (span.type || span.meta_struct) {
87
89
  bytes.buffer[bytes.length++] = 0x8c
90
+ } else {
91
+ bytes.buffer[bytes.length++] = 0x8b
92
+ }
88
93
 
94
+ if (span.type) {
89
95
  this._encodeString(bytes, 'type')
90
96
  this._encodeString(bytes, span.type)
91
- } else {
92
- bytes.buffer[bytes.length++] = 0x8b
93
97
  }
94
98
 
95
99
  this._encodeString(bytes, 'trace_id')
@@ -114,6 +118,10 @@ class AgentEncoder {
114
118
  this._encodeMap(bytes, span.meta)
115
119
  this._encodeString(bytes, 'metrics')
116
120
  this._encodeMap(bytes, span.metrics)
121
+ if (span.meta_struct) {
122
+ this._encodeString(bytes, 'meta_struct')
123
+ this._encodeObject(bytes, span.meta_struct)
124
+ }
117
125
  }
118
126
  }
119
127
 
@@ -263,6 +271,45 @@ class AgentEncoder {
263
271
  }
264
272
  }
265
273
 
274
+ _encodeObject (bytes, value, circularReferencesDetector = new Set()) {
275
+ circularReferencesDetector.add(value)
276
+ if (Array.isArray(value)) {
277
+ return this._encodeObjectAsArray(bytes, value, circularReferencesDetector)
278
+ } else if (value !== null && typeof value === 'object') {
279
+ return this._encodeObjectAsMap(bytes, value, circularReferencesDetector)
280
+ } else if (typeof value === 'string' || typeof value === 'number') {
281
+ this._encodeValue(bytes, value)
282
+ }
283
+ }
284
+
285
+ _encodeObjectAsMap (bytes, value, circularReferencesDetector) {
286
+ const keys = Object.keys(value)
287
+ const validKeys = keys.filter(key =>
288
+ typeof value[key] === 'string' ||
289
+ typeof value[key] === 'number' ||
290
+ (value[key] !== null && typeof value[key] === 'object' && !circularReferencesDetector.has(value[key])))
291
+
292
+ this._encodeMapPrefix(bytes, validKeys.length)
293
+
294
+ for (const key of validKeys) {
295
+ this._encodeString(bytes, key)
296
+ this._encodeObject(bytes, value[key], circularReferencesDetector)
297
+ }
298
+ }
299
+
300
+ _encodeObjectAsArray (bytes, value, circularReferencesDetector) {
301
+ const validValue = value.filter(item =>
302
+ typeof item === 'string' ||
303
+ typeof item === 'number' ||
304
+ (item !== null && typeof item === 'object' && !circularReferencesDetector.has(item)))
305
+
306
+ this._encodeArrayPrefix(bytes, validValue)
307
+
308
+ for (const item of validValue) {
309
+ this._encodeObject(bytes, item, circularReferencesDetector)
310
+ }
311
+ }
312
+
266
313
  _cacheString (value) {
267
314
  if (!(value in this._stringMap)) {
268
315
  this._stringCount++
@@ -18,7 +18,8 @@ module.exports = name => {
18
18
  case exporters.AGENT_PROXY:
19
19
  return require('./ci-visibility/exporters/agent-proxy')
20
20
  case exporters.JEST_WORKER:
21
- return require('./ci-visibility/exporters/jest-worker')
21
+ case exporters.CUCUMBER_WORKER:
22
+ return require('./ci-visibility/exporters/test-worker')
22
23
  default:
23
24
  return inAWSLambda && !usingLambdaExtension ? require('./exporters/log') : require('./exporters/agent')
24
25
  }
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const StoragePlugin = require('./storage')
4
- const { PEER_SERVICE_KEY } = require('../constants')
4
+ const { PEER_SERVICE_KEY, PEER_SERVICE_SOURCE_KEY } = require('../constants')
5
5
 
6
6
  class DatabasePlugin extends StoragePlugin {
7
7
  static get operation () { return 'query' }
@@ -28,16 +28,31 @@ class DatabasePlugin extends StoragePlugin {
28
28
  }
29
29
  }
30
30
 
31
- createDBMPropagationCommentService (serviceName) {
31
+ createDBMPropagationCommentService (serviceName, span) {
32
32
  this.encodingServiceTags('dddbs', 'encodedDddbs', serviceName)
33
33
  this.encodingServiceTags('dde', 'encodedDde', this.tracer._env)
34
34
  this.encodingServiceTags('ddps', 'encodedDdps', this.tracer._service)
35
35
  this.encodingServiceTags('ddpv', 'encodedDdpv', this.tracer._version)
36
+ if (span.context()._tags['out.host']) {
37
+ this.encodingServiceTags('ddh', 'encodedDdh', span._spanContext._tags['out.host'])
38
+ }
39
+ if (span.context()._tags['db.name']) {
40
+ this.encodingServiceTags('dddb', 'encodedDddb', span._spanContext._tags['db.name'])
41
+ }
36
42
 
37
- const { encodedDddbs, encodedDde, encodedDdps, encodedDdpv } = this.serviceTags
43
+ const { encodedDddb, encodedDddbs, encodedDde, encodedDdh, encodedDdps, encodedDdpv } = this.serviceTags
38
44
 
39
- return `dddbs='${encodedDddbs}',dde='${encodedDde}',` +
45
+ let dbmComment = `dddb='${encodedDddb}',dddbs='${encodedDddbs}',dde='${encodedDde}',ddh='${encodedDdh}',` +
40
46
  `ddps='${encodedDdps}',ddpv='${encodedDdpv}'`
47
+
48
+ const peerData = this.getPeerService(span.context()._tags)
49
+ if (peerData !== undefined && peerData[PEER_SERVICE_SOURCE_KEY] === PEER_SERVICE_KEY) {
50
+ this.encodingServiceTags('ddprs', 'encodedDdprs', peerData[PEER_SERVICE_KEY])
51
+
52
+ const { encodedDdprs } = this.serviceTags
53
+ dbmComment += `,ddprs='${encodedDdprs}'`
54
+ }
55
+ return dbmComment
41
56
  }
42
57
 
43
58
  getDbmServiceName (span, tracerService) {
@@ -56,7 +71,7 @@ class DatabasePlugin extends StoragePlugin {
56
71
  return query
57
72
  }
58
73
 
59
- const servicePropagation = this.createDBMPropagationCommentService(dbmService)
74
+ const servicePropagation = this.createDBMPropagationCommentService(dbmService, span)
60
75
 
61
76
  if (isPreparedStatement || mode === 'service') {
62
77
  return `/*${servicePropagation}*/ ${query}`
@@ -61,6 +61,8 @@ const CI_APP_ORIGIN = 'ciapp-test'
61
61
  const JEST_TEST_RUNNER = 'test.jest.test_runner'
62
62
  const JEST_DISPLAY_NAME = 'test.jest.display_name'
63
63
 
64
+ const CUCUMBER_IS_PARALLEL = 'test.cucumber.is_parallel'
65
+
64
66
  const TEST_ITR_TESTS_SKIPPED = '_dd.ci.itr.tests_skipped'
65
67
  const TEST_ITR_SKIPPING_ENABLED = 'test.itr.tests_skipping.enabled'
66
68
  const TEST_ITR_SKIPPING_TYPE = 'test.itr.tests_skipping.type'
@@ -82,6 +84,9 @@ const TEST_BROWSER_VERSION = 'test.browser.version'
82
84
  const JEST_WORKER_TRACE_PAYLOAD_CODE = 60
83
85
  const JEST_WORKER_COVERAGE_PAYLOAD_CODE = 61
84
86
 
87
+ // cucumber worker variables
88
+ const CUCUMBER_WORKER_TRACE_PAYLOAD_CODE = 70
89
+
85
90
  // Early flake detection util strings
86
91
  const EFD_STRING = "Retried by Datadog's Early Flake Detection"
87
92
  const EFD_TEST_NAME_REGEX = new RegExp(EFD_STRING + ' \\(#\\d+\\): ', 'g')
@@ -92,6 +97,7 @@ module.exports = {
92
97
  TEST_FRAMEWORK_VERSION,
93
98
  JEST_TEST_RUNNER,
94
99
  JEST_DISPLAY_NAME,
100
+ CUCUMBER_IS_PARALLEL,
95
101
  TEST_TYPE,
96
102
  TEST_NAME,
97
103
  TEST_SUITE,
@@ -104,6 +110,7 @@ module.exports = {
104
110
  LIBRARY_VERSION,
105
111
  JEST_WORKER_TRACE_PAYLOAD_CODE,
106
112
  JEST_WORKER_COVERAGE_PAYLOAD_CODE,
113
+ CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
107
114
  TEST_SOURCE_START,
108
115
  TEST_SKIPPED_BY_ITR,
109
116
  TEST_CONFIGURATION_BROWSER_NAME,
@@ -5,6 +5,7 @@ const { Config } = require('./config')
5
5
  const { snapshotKinds } = require('./constants')
6
6
  const { threadNamePrefix } = require('./profilers/shared')
7
7
  const dc = require('dc-polyfill')
8
+ const telemetryLog = dc.channel('datadog:telemetry:log')
8
9
 
9
10
  const profileSubmittedChannel = dc.channel('datadog:profiling:profile-submitted')
10
11
 
@@ -15,6 +16,19 @@ function maybeSourceMap (sourceMap, SourceMapper, debug) {
15
16
  ], debug)
16
17
  }
17
18
 
19
+ function logError (logger, err) {
20
+ if (logger) {
21
+ logger.error(err)
22
+ }
23
+ if (telemetryLog.hasSubscribers) {
24
+ telemetryLog.publish({
25
+ message: err.message,
26
+ level: 'ERROR',
27
+ stack_trace: err.stack
28
+ })
29
+ }
30
+ }
31
+
18
32
  class Profiler extends EventEmitter {
19
33
  constructor () {
20
34
  super()
@@ -28,13 +42,15 @@ class Profiler extends EventEmitter {
28
42
 
29
43
  start (options) {
30
44
  return this._start(options).catch((err) => {
31
- if (options.logger) {
32
- options.logger.error(err)
33
- }
45
+ logError(options.logger, err)
34
46
  return false
35
47
  })
36
48
  }
37
49
 
50
+ _logError (err) {
51
+ logError(this._logger, err)
52
+ }
53
+
38
54
  async _start (options) {
39
55
  if (this._enabled) return true
40
56
 
@@ -61,7 +77,7 @@ class Profiler extends EventEmitter {
61
77
  })
62
78
  }
63
79
  } catch (err) {
64
- this._logger.error(err)
80
+ this._logError(err)
65
81
  }
66
82
 
67
83
  try {
@@ -78,7 +94,7 @@ class Profiler extends EventEmitter {
78
94
  this._capture(this._timeoutInterval, start)
79
95
  return true
80
96
  } catch (e) {
81
- this._logger.error(e)
97
+ this._logError(e)
82
98
  this._stop()
83
99
  return false
84
100
  }
@@ -167,7 +183,7 @@ class Profiler extends EventEmitter {
167
183
  profileSubmittedChannel.publish()
168
184
  this._logger.debug('Submitted profiles')
169
185
  } catch (err) {
170
- this._logger.error(err)
186
+ this._logError(err)
171
187
  this._stop()
172
188
  }
173
189
  }
@@ -182,7 +198,7 @@ class Profiler extends EventEmitter {
182
198
  tags.snapshot = snapshotKind
183
199
  for (const exporter of this._config.exporters) {
184
200
  const task = exporter.export({ profiles, start, end, tags })
185
- .catch(err => this._logger.error(err))
201
+ .catch(err => this._logError(err))
186
202
 
187
203
  tasks.push(task)
188
204
  }
@@ -14,6 +14,7 @@ const dogstatsd = require('./dogstatsd')
14
14
  const NoopDogStatsDClient = require('./noop/dogstatsd')
15
15
  const spanleak = require('./spanleak')
16
16
  const { SSITelemetry } = require('./profiling/ssi-telemetry')
17
+ const telemetryLog = require('dc-polyfill').channel('datadog:telemetry:log')
17
18
 
18
19
  class LazyModule {
19
20
  constructor (provider) {
@@ -91,7 +92,7 @@ class Tracer extends NoopProxy {
91
92
  })
92
93
  }
93
94
 
94
- if (config.isGCPFunction || config.isAzureFunctionConsumptionPlan) {
95
+ if (config.isGCPFunction || config.isAzureFunction) {
95
96
  require('./serverless').maybeStartServerlessMiniAgent(config)
96
97
  }
97
98
 
@@ -104,6 +105,11 @@ class Tracer extends NoopProxy {
104
105
  this._profilerStarted = profiler.start(config)
105
106
  } catch (e) {
106
107
  log.error(e)
108
+ telemetryLog.publish({
109
+ message: e.message,
110
+ level: 'ERROR',
111
+ stack_trace: e.stack
112
+ })
107
113
  }
108
114
  } else if (ssiTelemetry.enabled()) {
109
115
  require('./profiling/ssi-telemetry-mock-profiler').start(config)
@@ -53,18 +53,16 @@ function getIsGCPFunction () {
53
53
  return isDeprecatedGCPFunction || isNewerGCPFunction
54
54
  }
55
55
 
56
- function getIsAzureFunctionConsumptionPlan () {
56
+ function getIsAzureFunction () {
57
57
  const isAzureFunction =
58
58
  process.env.FUNCTIONS_EXTENSION_VERSION !== undefined && process.env.FUNCTIONS_WORKER_RUNTIME !== undefined
59
- const azureWebsiteSKU = process.env.WEBSITE_SKU
60
- const isConsumptionPlan = azureWebsiteSKU === undefined || azureWebsiteSKU === 'Dynamic'
61
59
 
62
- return isAzureFunction && isConsumptionPlan
60
+ return isAzureFunction
63
61
  }
64
62
 
65
63
  module.exports = {
66
64
  maybeStartServerlessMiniAgent,
67
65
  getIsGCPFunction,
68
- getIsAzureFunctionConsumptionPlan,
66
+ getIsAzureFunction,
69
67
  getRustBinaryPath
70
68
  }
@@ -6,7 +6,8 @@ const dependencies = require('./dependencies')
6
6
  const { sendData } = require('./send-data')
7
7
  const { errors } = require('../startup-log')
8
8
  const { manager: metricsManager } = require('./metrics')
9
- const logs = require('./logs')
9
+ const telemetryLogger = require('./logs')
10
+ const logger = require('../log')
10
11
 
11
12
  const telemetryStartChannel = dc.channel('datadog:telemetry:start')
12
13
  const telemetryStopChannel = dc.channel('datadog:telemetry:stop')
@@ -211,7 +212,7 @@ function createPayload (currReqType, currPayload = {}) {
211
212
  function heartbeat (config, application, host) {
212
213
  heartbeatTimeout = setTimeout(() => {
213
214
  metricsManager.send(config, application, host)
214
- logs.send(config, application, host)
215
+ telemetryLogger.send(config, application, host)
215
216
 
216
217
  const { reqType, payload } = createPayload('app-heartbeat')
217
218
  sendData(config, application, host, reqType, payload, updateRetryData)
@@ -235,6 +236,10 @@ function extendedHeartbeat (config) {
235
236
 
236
237
  function start (aConfig, thePluginManager) {
237
238
  if (!aConfig.telemetry.enabled) {
239
+ if (aConfig.sca?.enabled) {
240
+ logger.warn('DD_APPSEC_SCA_ENABLED requires enabling telemetry to work.')
241
+ }
242
+
238
243
  return
239
244
  }
240
245
  config = aConfig
@@ -245,7 +250,7 @@ function start (aConfig, thePluginManager) {
245
250
  integrations = getIntegrations()
246
251
 
247
252
  dependencies.start(config, application, host, getRetryData, updateRetryData)
248
- logs.start(config)
253
+ telemetryLogger.start(config)
249
254
 
250
255
  sendData(config, application, host, 'app-started', appStarted(config))
251
256
 
@@ -318,6 +323,7 @@ function updateConfig (changes, config) {
318
323
 
319
324
  for (const change of changes) {
320
325
  const name = nameMapping[change.name] || change.name
326
+
321
327
  names.push(name)
322
328
  const { origin, value } = change
323
329
  const entry = { name, value, origin }
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const log = require('../../log')
4
+ const { calculateDDBasePath } = require('../../util')
4
5
 
5
6
  const logs = new Map()
6
7
 
@@ -29,6 +30,37 @@ function isValid (logEntry) {
29
30
  return logEntry?.level && logEntry.message
30
31
  }
31
32
 
33
+ const ddBasePath = calculateDDBasePath(__dirname)
34
+ const EOL = '\n'
35
+ const STACK_FRAME_LINE_REGEX = /^\s*at\s/gm
36
+
37
+ function sanitize (logEntry) {
38
+ const stack = logEntry.stack_trace
39
+ if (!stack) return logEntry
40
+
41
+ let stackLines = stack.split(EOL)
42
+
43
+ const firstIndex = stackLines.findIndex(l => l.match(STACK_FRAME_LINE_REGEX))
44
+
45
+ const isDDCode = firstIndex > -1 && stackLines[firstIndex].includes(ddBasePath)
46
+ stackLines = stackLines
47
+ .filter((line, index) => (isDDCode && index < firstIndex) || line.includes(ddBasePath))
48
+ .map(line => line.replace(ddBasePath, ''))
49
+
50
+ logEntry.stack_trace = stackLines.join(EOL)
51
+ if (logEntry.stack_trace === '') {
52
+ // If entire stack was removed, we'd just have a message saying "omitted"
53
+ // in which case we'd rather not log it at all.
54
+ return null
55
+ }
56
+
57
+ if (!isDDCode) {
58
+ logEntry.message = 'omitted'
59
+ }
60
+
61
+ return logEntry
62
+ }
63
+
32
64
  const logCollector = {
33
65
  add (logEntry) {
34
66
  try {
@@ -37,9 +69,13 @@ const logCollector = {
37
69
  // NOTE: should errors have higher priority? and discard log entries with lower priority?
38
70
  if (logs.size >= maxEntries) {
39
71
  overflowedCount++
40
- return
72
+ return false
41
73
  }
42
74
 
75
+ logEntry = sanitize(logEntry)
76
+ if (!logEntry) {
77
+ return false
78
+ }
43
79
  const hash = createHash(logEntry)
44
80
  if (!logs.has(hash)) {
45
81
  logs.set(hash, logEntry)
@@ -51,6 +87,11 @@ const logCollector = {
51
87
  return false
52
88
  },
53
89
 
90
+ // Used for testing
91
+ hasEntry (logEntry) {
92
+ return logs.has(createHash(logEntry))
93
+ },
94
+
54
95
  drain () {
55
96
  if (logs.size === 0) return
56
97
 
@@ -1,33 +0,0 @@
1
- 'use strict'
2
-
3
- const Writer = require('./writer')
4
- const {
5
- JEST_WORKER_COVERAGE_PAYLOAD_CODE,
6
- JEST_WORKER_TRACE_PAYLOAD_CODE
7
- } = require('../../../plugins/util/test')
8
-
9
- /**
10
- * Lightweight exporter whose writers only do simple JSON serialization
11
- * of trace and coverage payloads, which they send to the jest main process.
12
- */
13
- class JestWorkerCiVisibilityExporter {
14
- constructor () {
15
- this._writer = new Writer(JEST_WORKER_TRACE_PAYLOAD_CODE)
16
- this._coverageWriter = new Writer(JEST_WORKER_COVERAGE_PAYLOAD_CODE)
17
- }
18
-
19
- export (payload) {
20
- this._writer.append(payload)
21
- }
22
-
23
- exportCoverage (formattedCoverage) {
24
- this._coverageWriter.append(formattedCoverage)
25
- }
26
-
27
- flush () {
28
- this._writer.flush()
29
- this._coverageWriter.flush()
30
- }
31
- }
32
-
33
- module.exports = JestWorkerCiVisibilityExporter