datadog 2.35.0 → 2.36.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -1
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +68 -31
  4. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -1
  5. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +1 -1
  6. data/ext/datadog_profiling_native_extension/collectors_stack.c +37 -18
  7. data/ext/datadog_profiling_native_extension/collectors_stack.h +8 -2
  8. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +434 -300
  9. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +9 -7
  10. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +7 -8
  11. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +0 -12
  12. data/ext/datadog_profiling_native_extension/extconf.rb +2 -2
  13. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.c +4 -43
  14. data/ext/datadog_profiling_native_extension/gvl_profiling_helper.h +15 -47
  15. data/ext/datadog_profiling_native_extension/heap_recorder.c +44 -26
  16. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +14 -35
  17. data/ext/datadog_profiling_native_extension/profiling.c +41 -4
  18. data/ext/datadog_profiling_native_extension/ruby_helpers.c +33 -34
  19. data/ext/datadog_profiling_native_extension/stack_recorder.c +24 -3
  20. data/ext/datadog_profiling_native_extension/stack_recorder.h +1 -0
  21. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +4 -2
  22. data/ext/libdatadog_api/datadog_ruby_common.c +7 -8
  23. data/ext/libdatadog_api/datadog_ruby_common.h +0 -12
  24. data/ext/libdatadog_extconf_helpers.rb +1 -1
  25. data/lib/datadog/appsec/api_security/route_extractor.rb +6 -0
  26. data/lib/datadog/appsec/component.rb +1 -1
  27. data/lib/datadog/appsec/configuration.rb +7 -0
  28. data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +37 -4
  29. data/lib/datadog/appsec/contrib/graphql/gateway/multiplex.rb +64 -19
  30. data/lib/datadog/appsec/contrib/graphql/integration.rb +1 -0
  31. data/lib/datadog/appsec/contrib/rack/buffered_input.rb +83 -0
  32. data/lib/datadog/appsec/contrib/rack/gateway/request.rb +41 -3
  33. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +20 -7
  34. data/lib/datadog/appsec/contrib/rack/input_peeker.rb +92 -0
  35. data/lib/datadog/appsec/contrib/rails/gateway/request.rb +33 -0
  36. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +17 -1
  37. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +20 -3
  38. data/lib/datadog/appsec/default_header_tags.rb +10 -6
  39. data/lib/datadog/core/configuration/components.rb +1 -0
  40. data/lib/datadog/core/configuration/settings.rb +1 -2
  41. data/lib/datadog/core/configuration/supported_configurations.rb +2 -0
  42. data/lib/datadog/core/remote/component.rb +1 -1
  43. data/lib/datadog/core/telemetry/event/app_started.rb +0 -21
  44. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +1 -1
  45. data/lib/datadog/core/utils/forking.rb +3 -1
  46. data/lib/datadog/core/utils/spawn_monkey_patch.rb +3 -1
  47. data/lib/datadog/core.rb +3 -0
  48. data/lib/datadog/di/base.rb +4 -1
  49. data/lib/datadog/di/component.rb +1 -1
  50. data/lib/datadog/error_tracking/collector.rb +2 -1
  51. data/lib/datadog/error_tracking/component.rb +2 -2
  52. data/lib/datadog/kit/tracing/method_tracer.rb +4 -1
  53. data/lib/datadog/opentelemetry/sdk/propagator.rb +9 -3
  54. data/lib/datadog/opentelemetry/sdk/span_processor.rb +4 -1
  55. data/lib/datadog/profiling/collectors/thread_context.rb +1 -0
  56. data/lib/datadog/profiling/component.rb +13 -15
  57. data/lib/datadog/profiling/ext/dir_monkey_patches.rb +3 -3
  58. data/lib/datadog/ruby_version.rb +25 -0
  59. data/lib/datadog/symbol_database/component.rb +306 -98
  60. data/lib/datadog/symbol_database/extractor.rb +223 -84
  61. data/lib/datadog/tracing/configuration/ext.rb +13 -0
  62. data/lib/datadog/tracing/configuration/settings.rb +17 -0
  63. data/lib/datadog/tracing/contrib/configuration/resolver.rb +7 -0
  64. data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +2 -0
  65. data/lib/datadog/tracing/contrib/grpc.rb +1 -0
  66. data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +2 -0
  67. data/lib/datadog/tracing/contrib/http.rb +1 -0
  68. data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +2 -0
  69. data/lib/datadog/tracing/contrib/karafka.rb +1 -0
  70. data/lib/datadog/tracing/contrib/rack/middlewares.rb +3 -1
  71. data/lib/datadog/tracing/contrib/rack/route_inference.rb +3 -1
  72. data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +2 -0
  73. data/lib/datadog/tracing/contrib/sidekiq.rb +1 -0
  74. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +2 -0
  75. data/lib/datadog/tracing/contrib/waterdrop.rb +1 -0
  76. data/lib/datadog/tracing/distributed/propagation.rb +33 -1
  77. data/lib/datadog/tracing/distributed/trace_context.rb +11 -2
  78. data/lib/datadog/tracing/trace_digest.rb +7 -0
  79. data/lib/datadog/tracing/trace_operation.rb +4 -1
  80. data/lib/datadog/tracing/tracer.rb +1 -0
  81. data/lib/datadog/version.rb +1 -1
  82. data/lib/datadog.rb +4 -1
  83. metadata +8 -5
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../input_peeker'
3
4
  require_relative '../../../instrumentation/gateway/argument'
4
5
  require_relative '../../../../core/header_collection'
5
6
  require_relative '../../../../tracing/client_ip'
@@ -75,14 +76,51 @@ module Datadog
75
76
  end
76
77
 
77
78
  def form_hash
78
- # force form data processing
79
+ # NOTE: Rack populates `rack.request.form_hash` only as a side effect
80
+ # of {::Rack::Request#POST}, which reads and parses the body.
79
81
  request.POST if request.form_data?
80
82
 
81
- # usually Hash<String,String> but can be a more complex
82
- # Hash<String,String||Array||Hash> when e.g coming from JSON
83
+ # usually Hash[String, String] but can be a more complex
84
+ # Hash[String, (String|Array|Hash)] when e.g coming from JSON
83
85
  env['rack.request.form_hash']
84
86
  end
85
87
 
88
+ # Returns the request body size in bytes using all available methods,
89
+ # or nil when the size cannot be measured within the limit
90
+ #
91
+ # NOTE: The priority of the measurement is the following:
92
+ # size if it's known, content-length if provided, and buffering
93
+ # to the limit if unknown-length
94
+ #
95
+ # WARNING: The buffering path adds overhead for streaming web-servers
96
+ # (Rack 3+) when the body length is unknown
97
+ def body_bytesize(limit)
98
+ io = request.body
99
+
100
+ return 0 unless io
101
+ return io.size if io.respond_to?(:size)
102
+
103
+ # NOTE: {::Rack::Request#content_length} is a plain `CONTENT_LENGTH`
104
+ # getter forces no body read
105
+ content_length = request.content_length
106
+ return content_length.to_i if content_length
107
+
108
+ # NOTE: A parsed-body cache without a raw byte count means the input
109
+ # stream was consumed before measurement, consider it unknown-length
110
+ return if env.key?('rack.request.form_hash')
111
+
112
+ InputPeeker.peek_bytesize(env, limit: limit)
113
+ end
114
+
115
+ # Whether a request body can be collected without forcing a parse:
116
+ # either form data parseable on demand, or a body already parsed upstream
117
+ #
118
+ # NOTE: Rack does not parse JSON itself, a body parser middleware such as
119
+ # {Rack::JSONBodyParser} populates the form hash read by {#form_hash}
120
+ def collectable_body?
121
+ request.form_data? || request.parseable_data? || env.key?('rack.request.form_hash')
122
+ end
123
+
86
124
  def client_ip
87
125
  remote_ip = remote_addr
88
126
  header_collection = Datadog::Core::HeaderCollection.from_hash(headers)
@@ -87,9 +87,26 @@ module Datadog
87
87
  gateway.watch('rack.request.body') do |stack, gateway_request|
88
88
  context = gateway_request.env[AppSec::Ext::CONTEXT_KEY]
89
89
 
90
- persistent_data = {
91
- 'server.request.body' => gateway_request.form_hash
92
- }
90
+ request = gateway_request.request
91
+ next stack.call(request) unless gateway_request.collectable_body?
92
+
93
+ # NOTE: A limit of 0 disables request body collection entirely.
94
+ limit = Datadog.configuration.appsec.body_parsing_size_limit
95
+ next stack.call(request) if limit.zero?
96
+
97
+ persistent_data = {}
98
+ byte_length = gateway_request.body_bytesize(limit)
99
+
100
+ if byte_length
101
+ persistent_data['server.request.body.byte_length'] = byte_length
102
+
103
+ if byte_length <= limit
104
+ body = gateway_request.form_hash
105
+ persistent_data['server.request.body'] = body if body
106
+ end
107
+ end
108
+
109
+ next stack.call(request) if persistent_data.empty?
93
110
 
94
111
  result = context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
95
112
 
@@ -119,10 +136,6 @@ module Datadog
119
136
  next stack.call(gateway_request.request) if context.span.nil?
120
137
 
121
138
  gateway_request.headers.each do |name, value|
122
- if Ext::COLLECTABLE_REQUEST_HEADERS.include?(name)
123
- context.span["http.request.headers.#{name}"] ||= value
124
- end
125
-
126
139
  if context.state[:has_identity_event] && Ext::IDENTITY_COLLECTABLE_REQUEST_HEADERS.include?(name)
127
140
  context.span["http.request.headers.#{name}"] ||= value
128
141
  end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stringio'
4
+ require_relative 'buffered_input'
5
+
6
+ module Datadog
7
+ module AppSec
8
+ module Contrib
9
+ module Rack
10
+ # Peeks at `rack.input` without changing what downstream Rack code can read
11
+ #
12
+ # @api private
13
+ module InputPeeker
14
+ module_function
15
+
16
+ # Peeks at `env['rack.input']` and returns the body bytesize when it
17
+ # can be measured within the given limit
18
+ #
19
+ # NOTE: Over-limit bodies cannot be fully measured, so returning `nil`
20
+ # is the tradeoff we accept
21
+ #
22
+ # WARNING: For forward-only input, replaces `env['rack.input']` with a
23
+ # replay stream over the peeked bytes, preserving the rest
24
+ # of the original stream
25
+ #
26
+ # @param env [Hash] Rack environment
27
+ # @param limit [Integer] Maximum body bytesize to measure
28
+ # @return [Integer, nil] `Integer` bytesize when measured within
29
+ # `limit`, including `0`; `nil` when input is missing, over the
30
+ # limit, or cannot be preserved for downstream reads
31
+ def peek_bytesize(env, limit:)
32
+ rack_input = env['rack.input']
33
+ return unless rack_input
34
+
35
+ rewindable = rewind? && rack_input.respond_to?(:rewind)
36
+
37
+ # NOTE: Rack 2 requires `rack.input` to be rewindable. Rewind before peeking
38
+ # in case an upstream framework already consumed part of the stream
39
+ return if rewindable && !rewind(rack_input)
40
+
41
+ buffer = peek(rack_input, limit)
42
+ over_limit = buffer.bytesize > limit
43
+
44
+ if rewindable
45
+ # NOTE: If we cannot rewind after peeking, downstream code would observe
46
+ # a partially consumed body. Treat it as not safely collectable
47
+ return unless rewind(rack_input)
48
+ else
49
+ env['rack.input'] = if over_limit
50
+ BufferedInput.new(rack_input, buffer: StringIO.new(buffer))
51
+ else
52
+ StringIO.new(buffer)
53
+ end
54
+ end
55
+
56
+ over_limit ? nil : buffer.bytesize
57
+ end
58
+
59
+ private_class_method def rewind?
60
+ return @rewind if defined?(@rewind)
61
+
62
+ @rewind = ::Gem::Version.new(::Rack.release) < ::Gem::Version.new('3')
63
+ end
64
+
65
+ private_class_method def rewind(io)
66
+ io.rewind
67
+ true
68
+ rescue => e
69
+ Datadog.logger.debug { "AppSec: Failed to rewind `rack.input`: #{e.class}: #{e.message}" }
70
+ false
71
+ end
72
+
73
+ private_class_method def peek(io, limit)
74
+ # NOTE: Read one byte past the limit to distinguish an exact-limit body
75
+ # from an over-limit body without reading the whole stream.
76
+ max = limit + 1
77
+ buffer = +''.b
78
+
79
+ while buffer.bytesize <= limit
80
+ chunk = io.read(max - buffer.bytesize)
81
+ break if chunk.nil? || chunk.empty?
82
+
83
+ buffer << chunk
84
+ end
85
+
86
+ buffer
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../rack/input_peeker'
3
4
  require_relative '../../../instrumentation/gateway/argument'
4
5
 
5
6
  module Datadog
@@ -59,6 +60,38 @@ module Datadog
59
60
  excluded.include?(k)
60
61
  end
61
62
  end
63
+
64
+ # Returns the request body size in bytes using all available methods,
65
+ # or nil when the size cannot be measured within the limit
66
+ #
67
+ # NOTE: The priority of the measurement is the following:
68
+ # raw posted data, raw form vars, size if known, raw
69
+ # Content-Length, then buffering to the limit if unknown-length
70
+ def body_bytesize(limit)
71
+ raw_body = env['RAW_POST_DATA']
72
+ return raw_body.bytesize if raw_body
73
+
74
+ form_vars = env['rack.request.form_vars']
75
+ return form_vars.bytesize if form_vars
76
+
77
+ io = request.body
78
+ return 0 unless io
79
+ return io.size if io.respond_to?(:size)
80
+
81
+ # NOTE: Read raw `CONTENT_LENGTH` as {ActionDispatch::Request#content_length}
82
+ # drains `rack.input` into `RAW_POST_DATA` on chunked Transfer-Encoding
83
+ content_length = env['CONTENT_LENGTH']
84
+ return content_length.to_i if content_length
85
+
86
+ # NOTE: An already-read body (e.g. late-parsed multipart on Rack 3+) peeks
87
+ # as 0, so we skip byte_length but still collect the parsed body
88
+ begin
89
+ Rack::InputPeeker.peek_bytesize(env, limit: limit)
90
+ rescue => e
91
+ Datadog.logger.debug { "AppSec: Failed to measure Rails request body: #{e.class}: #{e.message}" }
92
+ nil
93
+ end
94
+ end
62
95
  end
63
96
  end
64
97
  end
@@ -23,12 +23,28 @@ module Datadog
23
23
  def watch_request_action(gateway = Instrumentation.gateway)
24
24
  gateway.watch('rails.request.action') do |stack, gateway_request|
25
25
  context = gateway_request.env[AppSec::Ext::CONTEXT_KEY]
26
+ limit = Datadog.configuration.appsec.body_parsing_size_limit
26
27
 
27
28
  persistent_data = {
28
- 'server.request.body' => gateway_request.parsed_body,
29
29
  'server.request.path_params' => gateway_request.route_params
30
30
  }
31
31
 
32
+ unless limit.zero?
33
+ byte_length = gateway_request.body_bytesize(limit)
34
+
35
+ if byte_length
36
+ # NOTE: Params may be parsed before this hook, leaving the
37
+ # body stream at EOF. Keep byte_length unset for that
38
+ # case, but still inspect cached params
39
+ persistent_data['server.request.body.byte_length'] = byte_length if byte_length.positive?
40
+
41
+ if byte_length <= limit
42
+ body = gateway_request.parsed_body
43
+ persistent_data['server.request.body'] = body unless body.nil? || body.empty?
44
+ end
45
+ end
46
+ end
47
+
32
48
  result = context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
33
49
 
34
50
  if result.match?
@@ -27,9 +27,26 @@ module Datadog
27
27
 
28
28
  context.state[:web_framework] = 'sinatra'
29
29
 
30
- persistent_data = {
31
- 'server.request.body' => gateway_request.form_hash
32
- }
30
+ request = gateway_request.request
31
+ next stack.call(request) unless gateway_request.collectable_body?
32
+
33
+ # NOTE: A limit of 0 disables request body collection entirely.
34
+ limit = Datadog.configuration.appsec.body_parsing_size_limit
35
+ next stack.call(request) if limit.zero?
36
+
37
+ persistent_data = {}
38
+ byte_length = gateway_request.body_bytesize(limit)
39
+
40
+ if byte_length
41
+ persistent_data['server.request.body.byte_length'] = byte_length
42
+
43
+ if byte_length <= limit
44
+ body = gateway_request.form_hash
45
+ persistent_data['server.request.body'] = body if body
46
+ end
47
+ end
48
+
49
+ next stack.call(request) if persistent_data.empty?
33
50
 
34
51
  result = context.run_waf(persistent_data, {}, Datadog.configuration.appsec.waf_timeout)
35
52
 
@@ -10,20 +10,24 @@ module Datadog
10
10
  #
11
11
  # @api private
12
12
  module DefaultHeaderTags
13
- WAF_VENDOR_HEADERS_TAGS = %w[
13
+ # NOTE: Contains additional WAF vendor headers
14
+ REQUEST_HEADERS_TAGS = %w[
15
+ accept
16
+ content-type
17
+ user-agent
18
+ akamai-user-risk
14
19
  x-amzn-trace-id
15
- cloudfront-viewer-ja3-fingerprint
16
- cf-ray
17
20
  x-cloud-trace-context
18
21
  x-appgw-trace-id
19
22
  x-sigsci-requestid
20
23
  x-sigsci-tags
21
- akamai-user-risk
24
+ cf-ray
25
+ cloudfront-viewer-ja3-fingerprint
22
26
  ].freeze
23
27
 
24
28
  RESPONSE_HEADERS_TAGS = %w[
25
- content-length
26
29
  content-type
30
+ content-length
27
31
  content-encoding
28
32
  content-language
29
33
  ].freeze
@@ -31,7 +35,7 @@ module Datadog
31
35
  module_function
32
36
 
33
37
  def tag_request(span, headers)
34
- WAF_VENDOR_HEADERS_TAGS.each do |name|
38
+ REQUEST_HEADERS_TAGS.each do |name|
35
39
  value = headers.get(name)
36
40
  span.set_tag("http.request.headers.#{name}", value) if value
37
41
  end
@@ -194,6 +194,7 @@ module Datadog
194
194
  remote&.after_fork
195
195
  crashtracker&.update_on_fork
196
196
  ProcessDiscovery.after_fork
197
+ symbol_database&.after_fork!
197
198
  end
198
199
 
199
200
  # Hot-swaps with a new sampler.
@@ -618,8 +618,7 @@ module Datadog
618
618
  o.type :bool
619
619
  o.env 'DD_PROFILING_SIGHANDLER_SAMPLING_ENABLED'
620
620
  o.default do
621
- Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.2.5') &&
622
- !(RUBY_VERSION.start_with?('3.3.') && Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.3.4'))
621
+ RubyVersion.is?('>= 3.2.5') && !RubyVersion.is?('>= 3.3', '< 3.3.4')
623
622
  end
624
623
  end
625
624
 
@@ -25,6 +25,7 @@ module Datadog
25
25
  "DD_APM_TRACING_ENABLED",
26
26
  "DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING",
27
27
  "DD_APPSEC_AUTO_USER_INSTRUMENTATION_MODE",
28
+ "DD_APPSEC_BODY_PARSING_SIZE_LIMIT",
28
29
  "DD_APPSEC_ENABLED",
29
30
  "DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML",
30
31
  "DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON",
@@ -270,6 +271,7 @@ module Datadog
270
271
  "DD_TRACE_PRESTO_ENABLED",
271
272
  "DD_TRACE_PRESTO_PEER_SERVICE",
272
273
  "DD_TRACE_PRESTO_SERVICE_NAME",
274
+ "DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT",
273
275
  "DD_TRACE_PROPAGATION_EXTRACT_FIRST",
274
276
  "DD_TRACE_PROPAGATION_STYLE",
275
277
  "DD_TRACE_PROPAGATION_STYLE_EXTRACT",
@@ -144,7 +144,7 @@ module Datadog
144
144
  # timeout and an integer otherwise
145
145
  # - before Ruby 3.2, ConditionVariable returns itself
146
146
  # so we have to rely on @once having been set
147
- if RUBY_VERSION >= '3.2'
147
+ if RubyVersion.is?('>= 3.2')
148
148
  lifted = @condition.wait(@mutex, timeout)
149
149
  else
150
150
  @condition.wait(@mutex, timeout)
@@ -146,27 +146,6 @@ module Datadog
146
146
  )
147
147
  end
148
148
 
149
- # OpenTelemetry configuration options (using environment variable names)
150
- otel_exporter_headers_option = resolve_option(settings, 'opentelemetry.exporter.headers')
151
- otel_exporter_headers_option.values_per_precedence.each do |precedence, value|
152
- list << conf_value(
153
- option_telemetry_name(otel_exporter_headers_option),
154
- # Steep: Value is always a hash for opentelemetry.exporter.headers (ensured by o.type :hash)
155
- value&.map { |key, header_value| "#{key}=#{header_value}" }&.join(','), # steep:ignore NoMethod
156
- precedence
157
- )
158
- end
159
-
160
- otel_metrics_headers_option = resolve_option(settings, 'opentelemetry.metrics.headers')
161
- otel_metrics_headers_option.values_per_precedence.each do |precedence, value|
162
- list << conf_value(
163
- option_telemetry_name(otel_metrics_headers_option),
164
- # Steep: Value is always a hash for opentelemetry.metrics.headers (ensured by o.type :hash)
165
- value&.map { |key, header_value| "#{key}=#{header_value}" }&.join(','), # steep:ignore NoMethod
166
- precedence
167
- )
168
- end
169
-
170
149
  # Add some more custom additional payload values here
171
150
  if settings.logger.instance
172
151
  logger_instance_option = resolve_option(settings, 'logger.instance')
@@ -16,7 +16,7 @@ module Datadog
16
16
  def self.apply!
17
17
  return false unless supported?
18
18
 
19
- if RUBY_VERSION < '3.1'
19
+ if RubyVersion.is?('< 3.1')
20
20
  [
21
21
  ::Process.singleton_class, # Process.fork
22
22
  ::Kernel.singleton_class, # Kernel.fork
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../ruby_version'
4
+
3
5
  module Datadog
4
6
  module Core
5
7
  module Utils
@@ -45,7 +47,7 @@ module Datadog
45
47
  # object will cause forking to not be detected in the fork when it should have.
46
48
  #
47
49
  # This wrapper prevents this by initializing the fork PID when the object is created.
48
- if RUBY_VERSION >= '3'
50
+ if RubyVersion.is?('>= 3')
49
51
  def initialize(*args, **kwargs, &block)
50
52
  super
51
53
  update_fork_pid!
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../ruby_version'
4
+
3
5
  module Datadog
4
6
  module Core
5
7
  module Utils
@@ -19,7 +21,7 @@ module Datadog
19
21
  # Prepends `Process.spawn` to merge `env_provider` output into the child's environment hash.
20
22
  module ProcessSpawnPatch
21
23
  # The One and Only Correct Delegation Pattern
22
- if RUBY_VERSION >= '3'
24
+ if RubyVersion.is?('>= 3')
23
25
  def spawn(*args, **kwargs) # steep:ignore DifferentMethodParameterKind
24
26
  super(*SpawnMonkeyPatch.inject_envs(args), **kwargs)
25
27
  end
data/lib/datadog/core.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Keep this at the top, this is needed at require-time by some files
4
+ require_relative 'ruby_version'
5
+
3
6
  require_relative 'core/deprecations'
4
7
  require_relative 'core/configuration/config_helper'
5
8
  require_relative 'core/extensions'
@@ -10,6 +10,9 @@
10
10
 
11
11
  require_relative 'code_tracker'
12
12
 
13
+ # Needed since this file can be loaded without core
14
+ require_relative '../ruby_version'
15
+
13
16
  module Datadog
14
17
  # Namespace for Datadog dynamic instrumentation.
15
18
  #
@@ -44,7 +47,7 @@ module Datadog
44
47
  # DI code.
45
48
  def activate_tracking
46
49
  # :script_compiled trace point was added in Ruby 2.6.
47
- return unless RUBY_VERSION >= '2.6'
50
+ return unless RubyVersion.is?('>= 2.6')
48
51
 
49
52
  begin
50
53
  # Activate code tracking by default because line trace points will not work
@@ -45,7 +45,7 @@ module Datadog
45
45
  logger.warn("di: cannot enable dynamic instrumentation: MRI is required, but running on #{RUBY_ENGINE}")
46
46
  return false
47
47
  end
48
- if RUBY_VERSION < '2.6'
48
+ if RubyVersion.is?('< 2.6')
49
49
  logger.warn("di: cannot enable dynamic instrumentation: Ruby 2.6+ is required, but running on #{RUBY_VERSION}")
50
50
  return false
51
51
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'ext'
4
+ require_relative '../ruby_version'
4
5
 
5
6
  module Datadog
6
7
  module ErrorTracking
@@ -52,7 +53,7 @@ module Datadog
52
53
  end
53
54
  end
54
55
 
55
- if RUBY_VERSION >= Ext::RUBY_VERSION_WITH_RESCUE_EVENT
56
+ if RubyVersion.is?(">= #{Ext::RUBY_VERSION_WITH_RESCUE_EVENT}")
56
57
  # Starting from ruby3.3, as we are listening to :rescue event,
57
58
  # we just want to remove the span event if the error was
58
59
  # previously handled
@@ -33,7 +33,7 @@ module Datadog
33
33
  if RUBY_ENGINE != 'ruby'
34
34
  logger.warn("error tracking: cannot enable error tracking: MRI is required, but running on #{RUBY_ENGINE}")
35
35
  false
36
- elsif RUBY_VERSION < '2.7'
36
+ elsif RubyVersion.is?('< 2.7')
37
37
  logger.warn(
38
38
  "error tracking: cannot enable error tracking: Ruby 2.7+ is required, but running
39
39
  on #{RUBY_VERSION}"
@@ -63,7 +63,7 @@ module Datadog
63
63
  # Before Ruby3.3 the TracePoint listen for :raise events.
64
64
  # If an error is not handled, we will delete the according
65
65
  # span event in the collector.
66
- event = (RUBY_VERSION >= '3.3') ? :rescue : :raise
66
+ event = RubyVersion.is?('>= 3.3') ? :rescue : :raise
67
67
 
68
68
  # This TracePoint is in charge of capturing the handled exceptions
69
69
  # and of adding the corresponding span events to the collector
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Needed since this file can be loaded without core
4
+ require_relative '../../ruby_version'
5
+
3
6
  module Datadog
4
7
  module Kit
5
8
  module Tracing
@@ -72,7 +75,7 @@ module Datadog
72
75
  raise ArgumentError, 'span name is not a String'
73
76
  end
74
77
 
75
- args = (RUBY_VERSION >= '2.7.') ? '...' : '*args, &block'
78
+ args = RubyVersion.is?('>= 2.7') ? '...' : '*args, &block'
76
79
 
77
80
  hook_module = Module.new do
78
81
  define_singleton_method(:inspect) do
@@ -43,6 +43,15 @@ module Datadog
43
43
  digest = @datadog_propagator.extract(carrier)
44
44
  return context unless digest
45
45
 
46
+ # Always call continue_trace! so span links are created in restart mode
47
+ # (when digest.trace_id is nil) before we attempt to build the OTel SpanContext.
48
+ trace = Tracing.continue_trace!(digest)
49
+
50
+ # In restart mode DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=restart, the extracted
51
+ # digest carries no trace_id (fresh start). continue_trace! has already recorded
52
+ # the span link; return the current context so the caller's span starts a new root.
53
+ return context unless digest.trace_id
54
+
46
55
  # Converts the {Numeric} Datadog id object to OpenTelemetry's byte array format.
47
56
  # 128-bit unsigned, big-endian integer
48
57
  trace_id = [digest.trace_id >> 64, digest.trace_id & 0xFFFFFFFFFFFFFFFF].pack('Q>Q>')
@@ -70,9 +79,6 @@ module Datadog
70
79
  )
71
80
 
72
81
  span = ::OpenTelemetry::Trace.non_recording_span(span_context)
73
-
74
- trace = Tracing.continue_trace!(digest)
75
-
76
82
  span.datadog_trace = trace
77
83
 
78
84
  ::OpenTelemetry::Trace.context_with_span(span, parent_context: context)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'trace/span'
4
+ require_relative '../../core/utils'
4
5
  require_relative '../../tracing/span_link'
5
6
  require_relative '../../tracing/span_event'
6
7
  require_relative '../../tracing/trace_digest'
@@ -101,12 +102,14 @@ module Datadog
101
102
 
102
103
  unless span.links.nil?
103
104
  datadog_span.links = span.links.map do |link|
105
+ tracestate = link.span_context.tracestate&.to_s
106
+
104
107
  Datadog::Tracing::SpanLink.new(
105
108
  Datadog::Tracing::TraceDigest.new(
106
109
  trace_id: link.span_context.hex_trace_id.to_i(16),
107
110
  span_id: link.span_context.hex_span_id.to_i(16),
108
111
  trace_sampling_priority: (link.span_context.trace_flags&.sampled? ? 1 : 0),
109
- trace_state: link.span_context.tracestate&.to_s,
112
+ trace_state: tracestate && ::Datadog::Core::Utils.utf8_encode(tracestate, placeholder: nil),
110
113
  span_remote: link.span_context.remote?,
111
114
  ),
112
115
  attributes: link.attributes
@@ -33,6 +33,7 @@ module Datadog
33
33
  waiting_for_gvl_threshold_ns: waiting_for_gvl_threshold_ns,
34
34
  otel_context_enabled: otel_context_enabled,
35
35
  native_filenames_enabled: validate_native_filenames(native_filenames_enabled),
36
+ overhead_filename: __FILE__,
36
37
  )
37
38
  end
38
39