dd-trace 5.15.0 → 5.16.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.15.0",
3
+ "version": "5.16.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -80,6 +80,10 @@ class GraphQLResolvePlugin extends TracingPlugin {
80
80
  // this will disable resolve subscribers if `config.depth` is set to 0
81
81
  super.configure(config.depth === 0 ? false : config)
82
82
  }
83
+
84
+ finish (finishTime) {
85
+ this.activeSpan.finish(finishTime)
86
+ }
83
87
  }
84
88
 
85
89
  // helpers
@@ -607,6 +607,7 @@ class Config {
607
607
 
608
608
  const tags = {}
609
609
  const env = this._env = {}
610
+ this._envUnprocessed = {}
610
611
 
611
612
  tagger.add(tags, OTEL_RESOURCE_ATTRIBUTES, true)
612
613
  tagger.add(tags, DD_TAGS)
@@ -614,16 +615,20 @@ class Config {
614
615
  tagger.add(tags, DD_TRACE_GLOBAL_TAGS)
615
616
 
616
617
  this._setValue(env, 'appsec.blockedTemplateHtml', maybeFile(DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML))
618
+ this._envUnprocessed['appsec.blockedTemplateHtml'] = DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML
617
619
  this._setValue(env, 'appsec.blockedTemplateJson', maybeFile(DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON))
620
+ this._envUnprocessed['appsec.blockedTemplateJson'] = DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON
618
621
  this._setBoolean(env, 'appsec.enabled', DD_APPSEC_ENABLED)
619
622
  this._setString(env, 'appsec.obfuscatorKeyRegex', DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP)
620
623
  this._setString(env, 'appsec.obfuscatorValueRegex', DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP)
621
624
  this._setBoolean(env, 'appsec.rasp.enabled', DD_APPSEC_RASP_ENABLED)
622
625
  this._setValue(env, 'appsec.rateLimit', maybeInt(DD_APPSEC_TRACE_RATE_LIMIT))
626
+ this._envUnprocessed['appsec.rateLimit'] = DD_APPSEC_TRACE_RATE_LIMIT
623
627
  this._setString(env, 'appsec.rules', DD_APPSEC_RULES)
624
628
  // DD_APPSEC_SCA_ENABLED is never used locally, but only sent to the backend
625
629
  this._setBoolean(env, 'appsec.sca.enabled', DD_APPSEC_SCA_ENABLED)
626
630
  this._setValue(env, 'appsec.wafTimeout', maybeInt(DD_APPSEC_WAF_TIMEOUT))
631
+ this._envUnprocessed['appsec.wafTimeout'] = DD_APPSEC_WAF_TIMEOUT
627
632
  this._setBoolean(env, 'clientIpEnabled', DD_TRACE_CLIENT_IP_ENABLED)
628
633
  this._setString(env, 'clientIpHeader', DD_TRACE_CLIENT_IP_HEADER)
629
634
  this._setString(env, 'dbmPropagationMode', DD_DBM_PROPAGATION_MODE)
@@ -636,13 +641,16 @@ class Config {
636
641
  this._setBoolean(env, 'experimental.runtimeId', DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED)
637
642
  if (AWS_LAMBDA_FUNCTION_NAME) this._setValue(env, 'flushInterval', 0)
638
643
  this._setValue(env, 'flushMinSpans', maybeInt(DD_TRACE_PARTIAL_FLUSH_MIN_SPANS))
644
+ this._envUnprocessed.flushMinSpans = DD_TRACE_PARTIAL_FLUSH_MIN_SPANS
639
645
  this._setBoolean(env, 'gitMetadataEnabled', DD_TRACE_GIT_METADATA_ENABLED)
640
646
  this._setArray(env, 'headerTags', DD_TRACE_HEADER_TAGS)
641
647
  this._setString(env, 'hostname', coalesce(DD_AGENT_HOST, DD_TRACE_AGENT_HOSTNAME))
642
648
  this._setBoolean(env, 'iast.deduplicationEnabled', DD_IAST_DEDUPLICATION_ENABLED)
643
649
  this._setBoolean(env, 'iast.enabled', DD_IAST_ENABLED)
644
650
  this._setValue(env, 'iast.maxConcurrentRequests', maybeInt(DD_IAST_MAX_CONCURRENT_REQUESTS))
651
+ this._envUnprocessed['iast.maxConcurrentRequests'] = DD_IAST_MAX_CONCURRENT_REQUESTS
645
652
  this._setValue(env, 'iast.maxContextOperations', maybeInt(DD_IAST_MAX_CONTEXT_OPERATIONS))
653
+ this._envUnprocessed['iast.maxContextOperations'] = DD_IAST_MAX_CONTEXT_OPERATIONS
646
654
  this._setBoolean(env, 'iast.redactionEnabled', DD_IAST_REDACTION_ENABLED && !isFalse(DD_IAST_REDACTION_ENABLED))
647
655
  this._setString(env, 'iast.redactionNamePattern', DD_IAST_REDACTION_NAME_PATTERN)
648
656
  this._setString(env, 'iast.redactionValuePattern', DD_IAST_REDACTION_VALUE_PATTERN)
@@ -650,15 +658,18 @@ class Config {
650
658
  if (iastRequestSampling > -1 && iastRequestSampling < 101) {
651
659
  this._setValue(env, 'iast.requestSampling', iastRequestSampling)
652
660
  }
661
+ this._envUnprocessed['iast.requestSampling'] = DD_IAST_REQUEST_SAMPLING
653
662
  this._setString(env, 'iast.telemetryVerbosity', DD_IAST_TELEMETRY_VERBOSITY)
654
663
  this._setBoolean(env, 'isGCPFunction', getIsGCPFunction())
655
664
  this._setBoolean(env, 'logInjection', DD_LOGS_INJECTION)
656
665
  this._setBoolean(env, 'openAiLogsEnabled', DD_OPENAI_LOGS_ENABLED)
657
666
  this._setValue(env, 'openaiSpanCharLimit', maybeInt(DD_OPENAI_SPAN_CHAR_LIMIT))
667
+ this._envUnprocessed.openaiSpanCharLimit = DD_OPENAI_SPAN_CHAR_LIMIT
658
668
  if (DD_TRACE_PEER_SERVICE_MAPPING) {
659
669
  this._setValue(env, 'peerServiceMapping', fromEntries(
660
- process.env.DD_TRACE_PEER_SERVICE_MAPPING.split(',').map(x => x.trim().split(':'))
670
+ DD_TRACE_PEER_SERVICE_MAPPING.split(',').map(x => x.trim().split(':'))
661
671
  ))
672
+ this._envUnprocessed.peerServiceMapping = DD_TRACE_PEER_SERVICE_MAPPING
662
673
  }
663
674
  this._setString(env, 'port', DD_TRACE_AGENT_PORT)
664
675
  this._setBoolean(env, 'profiling.enabled', coalesce(DD_EXPERIMENTAL_PROFILING_ENABLED, DD_PROFILING_ENABLED))
@@ -682,6 +693,7 @@ class Config {
682
693
  !this._isInServerlessEnvironment()
683
694
  ))
684
695
  this._setValue(env, 'remoteConfig.pollInterval', maybeFloat(DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS))
696
+ this._envUnprocessed['remoteConfig.pollInterval'] = DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS
685
697
  this._setBoolean(env, 'reportHostname', DD_TRACE_REPORT_HOSTNAME)
686
698
  // only used to explicitly set runtimeMetrics to false
687
699
  const otelSetRuntimeMetrics = String(OTEL_METRICS_EXPORTER).toLowerCase() === 'none'
@@ -699,12 +711,14 @@ class Config {
699
711
  }
700
712
  this._setUnit(env, 'sampleRate', DD_TRACE_SAMPLE_RATE || OTEL_TRACES_SAMPLER_MAPPING[OTEL_TRACES_SAMPLER])
701
713
  this._setValue(env, 'sampler.rateLimit', DD_TRACE_RATE_LIMIT)
702
- this._setSamplingRule(env, 'sampler.rules', safeJsonParse(DD_TRACE_SAMPLING_RULES)) // example
714
+ this._setSamplingRule(env, 'sampler.rules', safeJsonParse(DD_TRACE_SAMPLING_RULES))
715
+ this._envUnprocessed['sampler.rules'] = DD_TRACE_SAMPLING_RULES
703
716
  this._setString(env, 'scope', DD_TRACE_SCOPE)
704
717
  this._setString(env, 'service', DD_SERVICE || DD_SERVICE_NAME || tags.service || OTEL_SERVICE_NAME)
705
718
  this._setString(env, 'site', DD_SITE)
706
719
  if (DD_TRACE_SPAN_ATTRIBUTE_SCHEMA) {
707
720
  this._setString(env, 'spanAttributeSchema', validateNamingVersion(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA))
721
+ this._envUnprocessed.spanAttributeSchema = DD_TRACE_SPAN_ATTRIBUTE_SCHEMA
708
722
  }
709
723
  this._setBoolean(env, 'spanRemoveIntegrationFromService', DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED)
710
724
  this._setBoolean(env, 'startupLogs', DD_TRACE_STARTUP_LOGS)
@@ -719,6 +733,7 @@ class Config {
719
733
  this._setBoolean(env, 'telemetry.debug', DD_TELEMETRY_DEBUG)
720
734
  this._setBoolean(env, 'telemetry.dependencyCollection', DD_TELEMETRY_DEPENDENCY_COLLECTION_ENABLED)
721
735
  this._setValue(env, 'telemetry.heartbeatInterval', maybeInt(Math.floor(DD_TELEMETRY_HEARTBEAT_INTERVAL * 1000)))
736
+ this._envUnprocessed['telemetry.heartbeatInterval'] = DD_TELEMETRY_HEARTBEAT_INTERVAL * 1000
722
737
  const hasTelemetryLogsUsingFeatures =
723
738
  env['iast.enabled'] || env['profiling.enabled'] || env['profiling.heuristicsEnabled']
724
739
  ? true
@@ -735,20 +750,25 @@ class Config {
735
750
  _applyOptions (options) {
736
751
  const opts = this._options = this._options || {}
737
752
  const tags = {}
753
+ this._optsUnprocessed = {}
738
754
 
739
755
  options = this.options = Object.assign({ ingestion: {} }, options, opts)
740
756
 
741
757
  tagger.add(tags, options.tags)
742
758
 
743
759
  this._setValue(opts, 'appsec.blockedTemplateHtml', maybeFile(options.appsec.blockedTemplateHtml))
760
+ this._optsUnprocessed['appsec.blockedTemplateHtml'] = options.appsec.blockedTemplateHtml
744
761
  this._setValue(opts, 'appsec.blockedTemplateJson', maybeFile(options.appsec.blockedTemplateJson))
762
+ this._optsUnprocessed['appsec.blockedTemplateJson'] = options.appsec.blockedTemplateJson
745
763
  this._setBoolean(opts, 'appsec.enabled', options.appsec.enabled)
746
764
  this._setString(opts, 'appsec.obfuscatorKeyRegex', options.appsec.obfuscatorKeyRegex)
747
765
  this._setString(opts, 'appsec.obfuscatorValueRegex', options.appsec.obfuscatorValueRegex)
748
766
  this._setBoolean(opts, 'appsec.rasp.enabled', options.appsec.rasp?.enabled)
749
767
  this._setValue(opts, 'appsec.rateLimit', maybeInt(options.appsec.rateLimit))
768
+ this._optsUnprocessed['appsec.rateLimit'] = options.appsec.rateLimit
750
769
  this._setString(opts, 'appsec.rules', options.appsec.rules)
751
770
  this._setValue(opts, 'appsec.wafTimeout', maybeInt(options.appsec.wafTimeout))
771
+ this._optsUnprocessed['appsec.wafTimeout'] = options.appsec.wafTimeout
752
772
  this._setBoolean(opts, 'clientIpEnabled', options.clientIpEnabled)
753
773
  this._setString(opts, 'clientIpHeader', options.clientIpHeader)
754
774
  this._setString(opts, 'dbmPropagationMode', options.dbmPropagationMode)
@@ -763,22 +783,26 @@ class Config {
763
783
  this._setString(opts, 'experimental.exporter', options.experimental && options.experimental.exporter)
764
784
  this._setBoolean(opts, 'experimental.runtimeId', options.experimental && options.experimental.runtimeId)
765
785
  this._setValue(opts, 'flushInterval', maybeInt(options.flushInterval))
786
+ this._optsUnprocessed.flushInterval = options.flushInterval
766
787
  this._setValue(opts, 'flushMinSpans', maybeInt(options.flushMinSpans))
788
+ this._optsUnprocessed.flushMinSpans = options.flushMinSpans
767
789
  this._setArray(opts, 'headerTags', options.headerTags)
768
790
  this._setString(opts, 'hostname', options.hostname)
769
791
  this._setBoolean(opts, 'iast.deduplicationEnabled', options.iastOptions && options.iastOptions.deduplicationEnabled)
770
792
  this._setBoolean(opts, 'iast.enabled',
771
793
  options.iastOptions && (options.iastOptions === true || options.iastOptions.enabled === true))
772
- const iastRequestSampling = maybeInt(options.iastOptions?.requestSampling)
773
794
  this._setValue(opts, 'iast.maxConcurrentRequests',
774
795
  maybeInt(options.iastOptions?.maxConcurrentRequests))
775
- this._setValue(opts, 'iast.maxContextOperations',
776
- maybeInt(options.iastOptions && options.iastOptions.maxContextOperations))
777
- this._setBoolean(opts, 'iast.redactionEnabled', options.iastOptions && options.iastOptions.redactionEnabled)
796
+ this._optsUnprocessed['iast.maxConcurrentRequests'] = options.iastOptions?.maxConcurrentRequests
797
+ this._setValue(opts, 'iast.maxContextOperations', maybeInt(options.iastOptions?.maxContextOperations))
798
+ this._optsUnprocessed['iast.maxContextOperations'] = options.iastOptions?.maxContextOperations
799
+ this._setBoolean(opts, 'iast.redactionEnabled', options.iastOptions?.redactionEnabled)
778
800
  this._setString(opts, 'iast.redactionNamePattern', options.iastOptions?.redactionNamePattern)
779
801
  this._setString(opts, 'iast.redactionValuePattern', options.iastOptions?.redactionValuePattern)
802
+ const iastRequestSampling = maybeInt(options.iastOptions?.requestSampling)
780
803
  if (iastRequestSampling > -1 && iastRequestSampling < 101) {
781
804
  this._setValue(opts, 'iast.requestSampling', iastRequestSampling)
805
+ this._optsUnprocessed['iast.requestSampling'] = options.iastOptions?.requestSampling
782
806
  }
783
807
  this._setString(opts, 'iast.telemetryVerbosity', options.iastOptions && options.iastOptions.telemetryVerbosity)
784
808
  this._setBoolean(opts, 'isCiVisibility', options.isCiVisibility)
@@ -792,6 +816,7 @@ class Config {
792
816
  this._setString(opts, 'protocolVersion', options.protocolVersion)
793
817
  if (options.remoteConfig) {
794
818
  this._setValue(opts, 'remoteConfig.pollInterval', maybeFloat(options.remoteConfig.pollInterval))
819
+ this._optsUnprocessed['remoteConfig.pollInterval'] = options.remoteConfig.pollInterval
795
820
  }
796
821
  this._setBoolean(opts, 'reportHostname', options.reportHostname)
797
822
  this._setBoolean(opts, 'runtimeMetrics', options.runtimeMetrics)
@@ -803,6 +828,7 @@ class Config {
803
828
  this._setString(opts, 'site', options.site)
804
829
  if (options.spanAttributeSchema) {
805
830
  this._setString(opts, 'spanAttributeSchema', validateNamingVersion(options.spanAttributeSchema))
831
+ this._optsUnprocessed.spanAttributeSchema = options.spanAttributeSchema
806
832
  }
807
833
  this._setBoolean(opts, 'spanRemoveIntegrationFromService', options.spanRemoveIntegrationFromService)
808
834
  this._setBoolean(opts, 'startupLogs', options.startupLogs)
@@ -931,6 +957,7 @@ class Config {
931
957
 
932
958
  _applyRemote (options) {
933
959
  const opts = this._remote = this._remote || {}
960
+ this._remoteUnprocessed = {}
934
961
  const tags = {}
935
962
  const headerTags = options.tracing_header_tags
936
963
  ? options.tracing_header_tags.map(tag => {
@@ -947,7 +974,8 @@ class Config {
947
974
  this._setTags(opts, 'tags', tags)
948
975
  this._setBoolean(opts, 'tracing', options.tracing_enabled)
949
976
  // ignore tags for now since rc sampling rule tags format is not supported
950
- this._setSamplingRule(opts, 'sampler.rules', this._ignoreTags(options.trace_sample_rules))
977
+ this._setSamplingRule(opts, 'sampler.rules', this._ignoreTags(options.tracing_sampling_rules))
978
+ this._remoteUnprocessed['sampler.rules'] = options.tracing_sampling_rules
951
979
  }
952
980
 
953
981
  _ignoreTags (samplingRules) {
@@ -1040,18 +1068,21 @@ class Config {
1040
1068
  _merge () {
1041
1069
  const containers = [this._remote, this._options, this._env, this._calculated, this._defaults]
1042
1070
  const origins = ['remote_config', 'code', 'env_var', 'calculated', 'default']
1071
+ const unprocessedValues = [this._remoteUnprocessed, this._optsUnprocessed, this._envUnprocessed, {}, {}]
1043
1072
  const changes = []
1044
1073
 
1045
1074
  for (const name in this._defaults) {
1046
1075
  for (let i = 0; i < containers.length; i++) {
1047
1076
  const container = containers[i]
1048
1077
  const origin = origins[i]
1078
+ const unprocessed = unprocessedValues[i]
1049
1079
 
1050
1080
  if ((container[name] !== null && container[name] !== undefined) || container === this._defaults) {
1051
1081
  if (get(this, name) === container[name] && has(this, name)) break
1052
1082
 
1053
- const value = container[name]
1083
+ let value = container[name]
1054
1084
  set(this, name, value)
1085
+ value = unprocessed[name] || value
1055
1086
 
1056
1087
  changes.push({ name, value, origin })
1057
1088
 
@@ -1069,6 +1100,7 @@ function maybeInt (number) {
1069
1100
  const parsed = parseInt(number)
1070
1101
  return isNaN(parsed) ? undefined : parsed
1071
1102
  }
1103
+
1072
1104
  function maybeFloat (number) {
1073
1105
  const parsed = parseFloat(number)
1074
1106
  return isNaN(parsed) ? undefined : parsed
@@ -15,6 +15,8 @@ module.exports = {
15
15
  SAMPLING_MECHANISM_MANUAL: 4,
16
16
  SAMPLING_MECHANISM_APPSEC: 5,
17
17
  SAMPLING_MECHANISM_SPAN: 8,
18
+ SAMPLING_MECHANISM_REMOTE_USER: 11,
19
+ SAMPLING_MECHANISM_REMOTE_DYNAMIC: 12,
18
20
  SPAN_SAMPLING_MECHANISM: '_dd.span_sampling.mechanism',
19
21
  SPAN_SAMPLING_RULE_RATE: '_dd.span_sampling.rule_rate',
20
22
  SPAN_SAMPLING_MAX_PER_SECOND: '_dd.span_sampling.max_per_second',
@@ -179,11 +179,19 @@ class Span {
179
179
  }
180
180
 
181
181
  setAttribute (key, value) {
182
+ if (key === 'http.response.status_code') {
183
+ this._ddSpan.setTag('http.status_code', value.toString())
184
+ }
185
+
182
186
  this._ddSpan.setTag(key, value)
183
187
  return this
184
188
  }
185
189
 
186
190
  setAttributes (attributes) {
191
+ if ('http.response.status_code' in attributes) {
192
+ attributes['http.status_code'] = attributes['http.response.status_code'].toString()
193
+ }
194
+
187
195
  this._ddSpan.addTags(attributes)
188
196
  return this
189
197
  }
@@ -10,6 +10,8 @@ const {
10
10
  SAMPLING_MECHANISM_AGENT,
11
11
  SAMPLING_MECHANISM_RULE,
12
12
  SAMPLING_MECHANISM_MANUAL,
13
+ SAMPLING_MECHANISM_REMOTE_USER,
14
+ SAMPLING_MECHANISM_REMOTE_DYNAMIC,
13
15
  SAMPLING_RULE_DECISION,
14
16
  SAMPLING_LIMIT_DECISION,
15
17
  SAMPLING_AGENT_DECISION,
@@ -41,9 +43,9 @@ class PrioritySampler {
41
43
  this.update({})
42
44
  }
43
45
 
44
- configure (env, { sampleRate, rateLimit = 100, rules = [] } = {}) {
46
+ configure (env, { sampleRate, provenance = undefined, rateLimit = 100, rules = [] } = {}) {
45
47
  this._env = env
46
- this._rules = this._normalizeRules(rules, sampleRate, rateLimit)
48
+ this._rules = this._normalizeRules(rules, sampleRate, rateLimit, provenance)
47
49
  this._limiter = new RateLimiter(rateLimit)
48
50
 
49
51
  setSamplingRules(this._rules)
@@ -137,6 +139,8 @@ class PrioritySampler {
137
139
  _getPriorityByRule (context, rule) {
138
140
  context._trace[SAMPLING_RULE_DECISION] = rule.sampleRate
139
141
  context._sampling.mechanism = SAMPLING_MECHANISM_RULE
142
+ if (rule.provenance === 'customer') context._sampling.mechanism = SAMPLING_MECHANISM_REMOTE_USER
143
+ if (rule.provenance === 'dynamic') context._sampling.mechanism = SAMPLING_MECHANISM_REMOTE_DYNAMIC
140
144
 
141
145
  return rule.sample() && this._isSampledByRateLimit(context)
142
146
  ? USER_KEEP
@@ -181,11 +185,11 @@ class PrioritySampler {
181
185
  }
182
186
  }
183
187
 
184
- _normalizeRules (rules, sampleRate, rateLimit) {
188
+ _normalizeRules (rules, sampleRate, rateLimit, provenance) {
185
189
  rules = [].concat(rules || [])
186
190
 
187
191
  return rules
188
- .concat({ sampleRate, maxPerSecond: rateLimit })
192
+ .concat({ sampleRate, maxPerSecond: rateLimit, provenance })
189
193
  .map(rule => ({ ...rule, sampleRate: parseFloat(rule.sampleRate) }))
190
194
  .filter(rule => !isNaN(rule.sampleRate))
191
195
  .map(SamplingRule.from)
@@ -64,7 +64,7 @@ function serviceLocator (span) {
64
64
  }
65
65
 
66
66
  class SamplingRule {
67
- constructor ({ name, service, resource, tags, sampleRate = 1.0, maxPerSecond } = {}) {
67
+ constructor ({ name, service, resource, tags, sampleRate = 1.0, provenance = undefined, maxPerSecond } = {}) {
68
68
  this.matchers = []
69
69
 
70
70
  if (name) {
@@ -82,6 +82,7 @@ class SamplingRule {
82
82
 
83
83
  this._sampler = new Sampler(sampleRate)
84
84
  this._limiter = undefined
85
+ this.provenance = provenance
85
86
 
86
87
  if (Number.isFinite(maxPerSecond)) {
87
88
  this._limiter = new RateLimiter(maxPerSecond)