datadog 2.32.0 → 2.33.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/ext/datadog_profiling_native_extension/clock_id.h +9 -1
  3. data/ext/datadog_profiling_native_extension/clock_id_from_mach.c +73 -0
  4. data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +1 -1
  5. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +5 -1
  6. data/ext/datadog_profiling_native_extension/extconf.rb +3 -0
  7. data/ext/datadog_profiling_native_extension/stack_recorder.c +3 -9
  8. data/ext/datadog_profiling_native_extension/time_helpers.h +1 -0
  9. data/ext/libdatadog_api/crashtracker.c +2 -0
  10. data/ext/libdatadog_extconf_helpers.rb +1 -1
  11. data/lib/datadog/ai_guard/autoload.rb +10 -0
  12. data/lib/datadog/ai_guard/component.rb +1 -1
  13. data/lib/datadog/ai_guard/contrib/auto_instrument.rb +24 -0
  14. data/lib/datadog/ai_guard/contrib/rack/integration.rb +42 -0
  15. data/lib/datadog/ai_guard/contrib/rack/patcher.rb +26 -0
  16. data/lib/datadog/ai_guard/contrib/rack/request_middleware.rb +83 -0
  17. data/lib/datadog/ai_guard/contrib/rails/integration.rb +41 -0
  18. data/lib/datadog/ai_guard/contrib/rails/patcher.rb +97 -0
  19. data/lib/datadog/ai_guard/evaluation.rb +1 -0
  20. data/lib/datadog/ai_guard/ext.rb +1 -0
  21. data/lib/datadog/ai_guard.rb +8 -0
  22. data/lib/datadog/appsec/contrib/aws_lambda/gateway/watcher.rb +75 -0
  23. data/lib/datadog/appsec/contrib/aws_lambda/integration.rb +39 -0
  24. data/lib/datadog/appsec/contrib/aws_lambda/patcher.rb +30 -0
  25. data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +111 -0
  26. data/lib/datadog/appsec.rb +1 -0
  27. data/lib/datadog/core/configuration/settings.rb +10 -0
  28. data/lib/datadog/core/configuration/supported_configurations.rb +2 -0
  29. data/lib/datadog/core/environment/ext.rb +1 -0
  30. data/lib/datadog/core/environment/socket.rb +13 -0
  31. data/lib/datadog/opentelemetry/metrics.rb +10 -1
  32. data/lib/datadog/opentelemetry/sdk/id_generator.rb +16 -10
  33. data/lib/datadog/profiling/component.rb +0 -1
  34. data/lib/datadog/profiling/stack_recorder.rb +0 -4
  35. data/lib/datadog/symbol_database/extractor.rb +17 -26
  36. data/lib/datadog/symbol_database/scope.rb +16 -12
  37. data/lib/datadog/symbol_database/scope_batcher.rb +280 -0
  38. data/lib/datadog/symbol_database/service_version.rb +4 -4
  39. data/lib/datadog/symbol_database/uploader.rb +3 -0
  40. data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +6 -0
  41. data/lib/datadog/tracing/contrib/rack/ext.rb +27 -0
  42. data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +117 -1
  43. data/lib/datadog/tracing/tracer.rb +1 -3
  44. data/lib/datadog/version.rb +1 -1
  45. metadata +19 -7
  46. data/ext/datadog_profiling_native_extension/clock_id_noop.c +0 -21
@@ -0,0 +1,280 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module Datadog
6
+ module SymbolDatabase
7
+ # Batches extracted scopes and triggers uploads at appropriate times.
8
+ #
9
+ # Implements two upload triggers:
10
+ # 1. Size-based: Immediate upload when 400 scopes collected (MAX_SCOPES)
11
+ # 2. Time-based: Upload after 1 second of inactivity (debounce timer, not periodic)
12
+ #
13
+ # Also provides:
14
+ # - Deduplication: Tracks uploaded module names to prevent re-uploads
15
+ # - File limiting: Stops after 10,000 files to prevent runaway extraction
16
+ # - Thread safety: Mutex-protected state for concurrent access
17
+ #
18
+ # Timer implementation: A single long-lived thread waits on a ConditionVariable
19
+ # with a timeout. Each add_scope signals the CV to reset the deadline. When the
20
+ # timeout expires without a signal, the timer fires and flushes the batch.
21
+ # This avoids creating/destroying a thread per add_scope call.
22
+ #
23
+ # Flow: Extractor → add_scope → (batch or timer) → Uploader
24
+ # Created by: Component (during initialization)
25
+ # Calls: Uploader.upload_scopes when batch full or timer fires
26
+ #
27
+ # @api private
28
+ class ScopeBatcher
29
+ # Maximum scopes per batch before triggering immediate upload.
30
+ # This matches the batch size used in Java and Python tracers to ensure
31
+ # consistent upload behavior across languages.
32
+ MAX_SCOPES = 400
33
+ INACTIVITY_TIMEOUT = 1.0 # seconds
34
+ # Maximum unique files to track before stopping extraction.
35
+ # This prevents runaway memory usage in applications with very large
36
+ # numbers of loaded classes (e.g., heavily modularized Rails apps).
37
+ MAX_FILES = 10_000
38
+ # Seconds to wait for the timer thread to exit when joining during
39
+ # shutdown or reset. Bounded so a misbehaving thread cannot hang the
40
+ # caller indefinitely.
41
+ TIMER_JOIN_TIMEOUT = 5
42
+
43
+ # Initialize batching context.
44
+ # @param uploader [Uploader] Uploader instance for triggering uploads
45
+ # @param logger [Logger] Logger for diagnostics
46
+ # @param on_upload [Proc, nil] Optional callback called after upload (for testing)
47
+ # @param timer_enabled [Boolean] Enable async timer (default true, false for tests)
48
+ def initialize(uploader, logger:, on_upload: nil, timer_enabled: true)
49
+ @uploader = uploader
50
+ @logger = logger
51
+ @on_upload = on_upload
52
+ @timer_enabled = timer_enabled
53
+ @scopes = []
54
+ @mutex = Mutex.new
55
+ @file_count = 0
56
+ @uploaded_modules = Set.new
57
+
58
+ # Timer state: single long-lived thread + ConditionVariable for debounce.
59
+ # @timer_signaled is set to true on each add_scope and cleared by the timer
60
+ # thread after waking. This flag is needed because ConditionVariable#wait
61
+ # does not distinguish signal vs timeout on Ruby < 3.2 (returns self in both
62
+ # cases). The flag gives a portable way to detect whether the wakeup was a
63
+ # signal (reset deadline) or a timeout (fire the timer).
64
+ @timer_cv = ConditionVariable.new
65
+ @timer_thread = nil
66
+ @timer_stopped = false
67
+ @timer_signaled = false
68
+ end
69
+
70
+ # Add a scope to the batch.
71
+ # Triggers immediate upload if batch reaches 400 scopes.
72
+ # Resets inactivity timer if batch not full.
73
+ # @param scope [Scope] The scope to add
74
+ # @return [void]
75
+ def add_scope(scope)
76
+ # @type var scopes_to_upload: ::Array[Scope]?
77
+ scopes_to_upload = nil
78
+
79
+ @mutex.synchronize do
80
+ # Check file limit (counts only unique accepted files; duplicates are
81
+ # filtered by the dedup check below and do not consume the budget).
82
+ if @file_count >= MAX_FILES
83
+ @logger.debug { "symdb: file limit (#{MAX_FILES}) reached, ignoring scope: #{scope.name}" }
84
+ return
85
+ end
86
+
87
+ # Check if already uploaded — duplicates do not count toward MAX_FILES
88
+ # so a re-extraction scenario does not exhaust the budget for unique scopes.
89
+ if @uploaded_modules.include?(scope.name)
90
+ @logger.trace { "symdb: skipping #{scope.name}: already uploaded" }
91
+ return
92
+ end
93
+
94
+ @uploaded_modules.add(scope.name)
95
+ @file_count += 1
96
+
97
+ # Add the scope
98
+ @scopes << scope
99
+
100
+ # Check if batch size reached (AFTER adding)
101
+ if @scopes.size >= MAX_SCOPES
102
+ # Prepare for upload (clear within mutex)
103
+ scopes_to_upload = @scopes.dup
104
+ @scopes.clear
105
+ end
106
+
107
+ # Signal the timer thread to reset its inactivity deadline.
108
+ # If batch was full, this is harmless — the timer will just
109
+ # re-check and find an empty batch if it fires.
110
+ ensure_timer_running
111
+ @timer_signaled = true
112
+ @timer_cv.signal
113
+ end
114
+
115
+ # Upload outside mutex (if batch was full)
116
+ perform_upload(scopes_to_upload) if scopes_to_upload
117
+ rescue => e
118
+ @logger.debug { "symdb: failed to add scope: #{e.class}: #{e.message}" }
119
+ # Don't propagate, continue operation
120
+ end
121
+
122
+ # Force upload of current batch immediately.
123
+ # @return [void]
124
+ def flush
125
+ # @type var scopes_to_upload: ::Array[Scope]?
126
+ scopes_to_upload = nil
127
+
128
+ @mutex.synchronize do
129
+ return if @scopes.empty?
130
+
131
+ scopes_to_upload = @scopes.dup
132
+ @scopes.clear
133
+ end
134
+
135
+ perform_upload(scopes_to_upload)
136
+ end
137
+
138
+ # Shutdown and upload remaining scopes.
139
+ # @return [void]
140
+ def shutdown
141
+ # @type var scopes_to_upload: ::Array[Scope]?
142
+ scopes_to_upload = nil
143
+ # @type var thread_to_join: ::Thread?
144
+ thread_to_join = nil
145
+
146
+ @mutex.synchronize do
147
+ @timer_stopped = true
148
+ @timer_cv.signal # Wake the timer thread so it exits
149
+
150
+ # Capture the timer thread under the mutex so a concurrent add_scope
151
+ # cannot create a new thread that we'd accidentally orphan when we
152
+ # nil the field below.
153
+ thread_to_join = @timer_thread
154
+ @timer_thread = nil
155
+
156
+ scopes_to_upload = @scopes.dup
157
+ @scopes.clear
158
+ end
159
+
160
+ # Join the timer thread outside the mutex.
161
+ # The thread checks @timer_stopped and exits when signaled.
162
+ thread_to_join&.join(TIMER_JOIN_TIMEOUT)
163
+
164
+ # Upload outside mutex
165
+ perform_upload(scopes_to_upload) unless scopes_to_upload.nil? || scopes_to_upload.empty?
166
+ end
167
+
168
+ # Check if scopes are pending upload.
169
+ # @return [Boolean] true if scopes waiting in batch
170
+ def scopes_pending?
171
+ @mutex.synchronize { @scopes.any? }
172
+ end
173
+
174
+ # Get current batch size.
175
+ # @return [Integer] Number of scopes in current batch
176
+ def size
177
+ @mutex.synchronize { @scopes.size }
178
+ end
179
+
180
+ private
181
+
182
+ # Reset state. Private so production code cannot accidentally invoke it;
183
+ # tests call via +send(:reset)+.
184
+ # @return [void]
185
+ def reset
186
+ # @type var thread_to_join: ::Thread?
187
+ thread_to_join = nil
188
+
189
+ @mutex.synchronize do
190
+ @scopes.clear
191
+ @timer_stopped = true
192
+ @timer_cv.signal
193
+ @file_count = 0
194
+ @uploaded_modules.clear
195
+
196
+ # Capture under the mutex (see shutdown for rationale).
197
+ thread_to_join = @timer_thread
198
+ @timer_thread = nil
199
+ end
200
+
201
+ thread_to_join&.join(TIMER_JOIN_TIMEOUT)
202
+
203
+ # Allow timer to be restarted after reset
204
+ @mutex.synchronize do
205
+ @timer_stopped = false
206
+ @timer_signaled = false
207
+ end
208
+ end
209
+
210
+ # Start the timer thread if not already running.
211
+ # Must be called from within @mutex.synchronize.
212
+ # @return [void]
213
+ def ensure_timer_running
214
+ return unless @timer_enabled
215
+ return if @timer_thread&.alive?
216
+
217
+ @timer_stopped = false
218
+ @timer_signaled = false
219
+
220
+ @timer_thread = Thread.new do
221
+ timer_loop
222
+ end
223
+ end
224
+
225
+ # Timer thread main loop. Waits on the ConditionVariable with a timeout.
226
+ # Each signal resets the deadline (debounce). When the wait times out
227
+ # (no signal within INACTIVITY_TIMEOUT), the batch is flushed.
228
+ #
229
+ # Uses @timer_signaled flag instead of ConditionVariable#wait return value
230
+ # because Ruby < 3.2 returns self for both signal and timeout (no way to
231
+ # distinguish). The flag is set by add_scope before signaling, and cleared
232
+ # by the timer thread after waking.
233
+ # @return [void]
234
+ def timer_loop
235
+ loop do
236
+ should_flush = false
237
+
238
+ @mutex.synchronize do
239
+ return if @timer_stopped
240
+
241
+ @timer_signaled = false
242
+ @timer_cv.wait(@mutex, INACTIVITY_TIMEOUT)
243
+
244
+ return if @timer_stopped
245
+
246
+ if @timer_signaled
247
+ # Woke up because add_scope signaled — loop back to re-wait with
248
+ # a fresh timeout. This implements the debounce: the timeout resets
249
+ # on every scope addition.
250
+ next # steep:ignore BreakTypeMismatch
251
+ end
252
+
253
+ # Timed out (no signal within INACTIVITY_TIMEOUT). If there are
254
+ # scopes pending, flush them. Otherwise, loop back and wait again.
255
+ should_flush = !@scopes.empty?
256
+ end
257
+
258
+ if should_flush
259
+ flush
260
+ end
261
+ end
262
+ rescue => e
263
+ @logger.debug { "symdb: timer thread error: #{e.class}: #{e.message}" }
264
+ end
265
+
266
+ # Perform upload via uploader.
267
+ # @param scopes [Array<Scope>] Scopes to upload
268
+ # @return [void]
269
+ def perform_upload(scopes)
270
+ return if scopes.nil? || scopes.empty?
271
+
272
+ @uploader.upload_scopes(scopes)
273
+ @on_upload&.call(scopes) # Notify tests after upload
274
+ rescue => e
275
+ @logger.debug { "symdb: upload failed: #{e.class}: #{e.message}" }
276
+ # Don't propagate, uploader handles retries
277
+ end
278
+ end
279
+ end
280
+ end
@@ -20,8 +20,8 @@ module Datadog
20
20
 
21
21
  # Initialize a new ServiceVersion
22
22
  # @param service [String] Service name (required, from DD_SERVICE)
23
- # @param env [String] Environment (from DD_ENV, defaults to "none")
24
- # @param version [String] Version (from DD_VERSION, defaults to "none")
23
+ # @param env [String, nil] Environment (from DD_ENV, passed through unchanged)
24
+ # @param version [String, nil] Version (from DD_VERSION, passed through unchanged)
25
25
  # @param scopes [Array<Scope>] Top-level scopes (required)
26
26
  # @raise [ArgumentError] if service empty or scopes not an array
27
27
  def initialize(service:, env:, version:, scopes:)
@@ -29,8 +29,8 @@ module Datadog
29
29
  raise ArgumentError, 'scopes must be an array' unless scopes.is_a?(Array)
30
30
 
31
31
  @service = service
32
- @env = env.to_s.empty? ? 'none' : env.to_s
33
- @version = version.to_s.empty? ? 'none' : version.to_s
32
+ @env = env
33
+ @version = version
34
34
  @language = 'ruby'
35
35
  @scopes = scopes
36
36
  end
@@ -59,6 +59,9 @@ module Datadog
59
59
  json_data = build_symbol_payload(scopes)
60
60
  compressed_data = Zlib.gzip(json_data)
61
61
 
62
+ # Emitted unconditionally so the rare oversized case is observable.
63
+ @telemetry&.distribution('tracers', 'symbol_database.payload_size', compressed_data.bytesize)
64
+
62
65
  # Symbols for very large applications (>50MB after gzip) are dropped:
63
66
  # the upload is skipped and the customer sees no autocomplete /
64
67
  # symbol probe results for those classes. Java handles the same case
@@ -54,6 +54,12 @@ module Datadog
54
54
  o.type :string, nilable: true
55
55
  end
56
56
 
57
+ option :inferred_proxy_enabled do |o|
58
+ o.type :bool
59
+ o.env Ext::ENV_INFERRED_PROXY_ENABLED
60
+ o.default false
61
+ end
62
+
57
63
  option :web_service_name, default: Ext::DEFAULT_PEER_WEBSERVER_SERVICE_NAME, type: :string
58
64
  end
59
65
  end
@@ -9,6 +9,7 @@ module Datadog
9
9
  module Ext
10
10
  ENV_ENABLED = 'DD_TRACE_RACK_ENABLED'
11
11
  ENV_DISTRIBUTED_TRACING = 'DD_TRACE_RACK_DISTRIBUTED_TRACING'
12
+ ENV_INFERRED_PROXY_ENABLED = 'DD_TRACE_INFERRED_PROXY_SERVICES_ENABLED'
12
13
  # @!visibility private
13
14
  ENV_ANALYTICS_ENABLED = 'DD_TRACE_RACK_ANALYTICS_ENABLED'
14
15
  ENV_ANALYTICS_SAMPLE_RATE = 'DD_TRACE_RACK_ANALYTICS_SAMPLE_RATE'
@@ -24,6 +25,32 @@ module Datadog
24
25
  TAG_OPERATION_HTTP_SERVER_QUEUE = 'queue'
25
26
  WEBSERVER_APP = 'webserver'
26
27
  DEFAULT_PEER_WEBSERVER_SERVICE_NAME = 'web-server'
28
+
29
+ # @!visibility private
30
+ HEADER_X_DD_PROXY = 'HTTP_X_DD_PROXY'
31
+ HEADER_X_DD_PROXY_REQUEST_TIME_MS = 'HTTP_X_DD_PROXY_REQUEST_TIME_MS'
32
+ HEADER_X_DD_PROXY_PATH = 'HTTP_X_DD_PROXY_PATH'
33
+ HEADER_X_DD_PROXY_RESOURCE_PATH = 'HTTP_X_DD_PROXY_RESOURCE_PATH'
34
+ HEADER_X_DD_PROXY_HTTPMETHOD = 'HTTP_X_DD_PROXY_HTTPMETHOD'
35
+ HEADER_X_DD_PROXY_DOMAIN_NAME = 'HTTP_X_DD_PROXY_DOMAIN_NAME'
36
+ HEADER_X_DD_PROXY_STAGE = 'HTTP_X_DD_PROXY_STAGE'
37
+ HEADER_X_DD_PROXY_ACCOUNT_ID = 'HTTP_X_DD_PROXY_ACCOUNT_ID'
38
+ HEADER_X_DD_PROXY_API_ID = 'HTTP_X_DD_PROXY_API_ID'
39
+ HEADER_X_DD_PROXY_REGION = 'HTTP_X_DD_PROXY_REGION'
40
+ HEADER_X_DD_PROXY_USER = 'HTTP_X_DD_PROXY_USER'
41
+
42
+ PROXY_AWS_APIGATEWAY = 'aws-apigateway'
43
+ PROXY_AWS_HTTPAPI = 'aws-httpapi'
44
+
45
+ SPAN_AWS_APIGATEWAY = 'aws.apigateway'
46
+ SPAN_AWS_HTTPAPI = 'aws.httpapi'
47
+
48
+ PROXY_SPAN_NAMES = {
49
+ PROXY_AWS_APIGATEWAY => SPAN_AWS_APIGATEWAY,
50
+ PROXY_AWS_HTTPAPI => SPAN_AWS_HTTPAPI,
51
+ }.freeze
52
+
53
+ TAG_INFERRED_SPAN = '_dd.inferred_span'
27
54
  end
28
55
  end
29
56
  end
@@ -13,7 +13,11 @@ module Datadog
13
13
  module TraceProxyMiddleware
14
14
  module_function
15
15
 
16
- def call(env, configuration)
16
+ def call(env, configuration, &block)
17
+ if configuration[:inferred_proxy_enabled] &&
18
+ (proxy_type = env[Ext::HEADER_X_DD_PROXY]) && !proxy_type.empty?
19
+ return call_with_inferred_proxy(env, proxy_type, &block)
20
+ end
17
21
  return yield unless configuration[:request_queuing]
18
22
 
19
23
  # parse the request queue time
@@ -51,6 +55,118 @@ module Datadog
51
55
  queue_span&.finish
52
56
  request_span&.finish
53
57
  end
58
+
59
+ # Creates a virtual parent span representing the upstream proxy
60
+ # that wraps the actual request processing.
61
+ #
62
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
63
+ def call_with_inferred_proxy(env, proxy_type)
64
+ span_name = Ext::PROXY_SPAN_NAMES[proxy_type]
65
+ return yield unless span_name
66
+
67
+ request_time_ms = env[Ext::HEADER_X_DD_PROXY_REQUEST_TIME_MS].to_f
68
+ return yield unless request_time_ms.positive?
69
+
70
+ path = env[Ext::HEADER_X_DD_PROXY_PATH]
71
+ stage = env[Ext::HEADER_X_DD_PROXY_STAGE]
72
+ domain = env[Ext::HEADER_X_DD_PROXY_DOMAIN_NAME]
73
+ http_method = env[Ext::HEADER_X_DD_PROXY_HTTPMETHOD]
74
+ resource_path = env[Ext::HEADER_X_DD_PROXY_RESOURCE_PATH]
75
+
76
+ # NOTE: resource_path is the parameterized route (e.g. /users/{id}) vs literal path
77
+ resource = "#{http_method} #{resource_path || path}" if http_method
78
+
79
+ inferred_span = Tracing.trace(
80
+ span_name,
81
+ service: domain,
82
+ type: Tracing::Metadata::Ext::AppTypes::TYPE_WEB,
83
+ start_time: Time.at(request_time_ms / 1_000),
84
+ )
85
+ inferred_span.resource = resource if resource
86
+ inferred_span.set_tag(Tracing::Metadata::Ext::TAG_COMPONENT, proxy_type)
87
+ inferred_span.set_tag(Tracing::Metadata::Ext::TAG_KIND, Tracing::Metadata::Ext::SpanKind::TAG_SERVER)
88
+ inferred_span.set_tag('stage', stage) if stage
89
+ inferred_span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_METHOD, http_method) if http_method
90
+ inferred_span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_URL, "https://#{domain}#{path}") if domain && path
91
+ inferred_span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_ROUTE, resource_path) if resource_path
92
+ inferred_span.set_metric(Ext::TAG_INFERRED_SPAN, 1)
93
+
94
+ set_optional_tags(inferred_span, env: env, proxy_type: proxy_type)
95
+
96
+ yield
97
+ # NOTE: The underlying {Rack::TraceMiddleware} rescues {Exception}
98
+ # to tag the request span with error details.
99
+ # We must propagate errors to the inferred proxy parent span.
100
+ rescue Exception => e # rubocop:disable Lint/RescueException
101
+ inferred_span&.set_error(e)
102
+ raise
103
+ ensure
104
+ if inferred_span
105
+ propagate_request_span_tags(inferred_span, env: env)
106
+
107
+ if (trace = Tracing.active_trace) && resource
108
+ trace.resource = resource
109
+ end
110
+
111
+ inferred_span.finish
112
+ end
113
+ end
114
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
115
+
116
+ # Sets cloud provider metadata and constructs the resource ARN.
117
+ #
118
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
119
+ def set_optional_tags(span, env:, proxy_type:)
120
+ api_id = env[Ext::HEADER_X_DD_PROXY_API_ID]
121
+ region = env[Ext::HEADER_X_DD_PROXY_REGION]
122
+
123
+ # API Gateway v1 sends region as a single-quoted string
124
+ region = region.delete("'") if region
125
+
126
+ span.set_tag('apiid', api_id) if api_id
127
+ span.set_tag('region', region) if region
128
+
129
+ if (account_id = env[Ext::HEADER_X_DD_PROXY_ACCOUNT_ID])
130
+ span.set_tag('account_id', account_id)
131
+ end
132
+
133
+ if (user = env[Ext::HEADER_X_DD_PROXY_USER])
134
+ span.set_tag('aws_user', user)
135
+ end
136
+
137
+ if api_id && region
138
+ # NOTE: Update this when adding non-AWS proxy types.
139
+ restapi_prefix = (proxy_type == Ext::PROXY_AWS_APIGATEWAY) ? 'restapis' : 'apis'
140
+ span.set_tag('dd_resource_key', "arn:aws:apigateway:#{region}::/#{restapi_prefix}/#{api_id}")
141
+ end
142
+ end
143
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
144
+
145
+ # Propagates response-level and security tags from the request span to
146
+ # the inferred parent.
147
+ def propagate_request_span_tags(span, env:)
148
+ rack_span = env[Ext::RACK_ENV_REQUEST_SPAN]
149
+ return unless rack_span
150
+
151
+ if (status_code = rack_span.get_tag(Tracing::Metadata::Ext::HTTP::TAG_STATUS_CODE))
152
+ span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_STATUS_CODE, status_code)
153
+ span.status = rack_span.status
154
+ end
155
+
156
+ if (user_agent = rack_span.get_tag(Tracing::Metadata::Ext::HTTP::TAG_USER_AGENT))
157
+ span.set_tag(Tracing::Metadata::Ext::HTTP::TAG_USER_AGENT, user_agent)
158
+ end
159
+
160
+ # NOTE: Tracing shouldn't know about AppSec tags.
161
+ if (appsec_enabled = rack_span.get_metric('_dd.appsec.enabled'))
162
+ span.set_metric('_dd.appsec.enabled', appsec_enabled)
163
+ end
164
+
165
+ # NOTE: Tracing shouldn't know about AppSec tags.
166
+ if (appsec_json = rack_span.get_tag('_dd.appsec.json'))
167
+ span.set_tag('_dd.appsec.json', appsec_json)
168
+ end
169
+ end
54
170
  end
55
171
  end
56
172
  end
@@ -372,9 +372,7 @@ module Datadog
372
372
  end
373
373
 
374
374
  def build_trace(digest, auto_finish)
375
- # Resolve hostname if configured
376
- hostname = Core::Environment::Socket.hostname if Datadog.configuration.tracing.report_hostname
377
- hostname = (hostname && !hostname.empty?) ? hostname : nil
375
+ hostname = Core::Environment::Socket.resolved_hostname(Datadog.configuration)
378
376
 
379
377
  if digest
380
378
  sampling_priority = if propagate_sampling_priority?(upstream_tags: digest.trace_distributed_tags)
@@ -3,7 +3,7 @@
3
3
  module Datadog
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 32
6
+ MINOR = 33
7
7
  PATCH = 0
8
8
  PRE = nil
9
9
  BUILD = nil
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datadog
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.32.0
4
+ version: 2.33.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-08 00:00:00.000000000 Z
11
+ date: 2026-05-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -64,14 +64,14 @@ dependencies:
64
64
  requirements:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: 30.0.0.1.0
67
+ version: 33.0.0.1.0
68
68
  type: :runtime
69
69
  prerelease: false
70
70
  version_requirements: !ruby/object:Gem::Requirement
71
71
  requirements:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: 30.0.0.1.0
74
+ version: 33.0.0.1.0
75
75
  - !ruby/object:Gem::Dependency
76
76
  name: logger
77
77
  requirement: !ruby/object:Gem::Requirement
@@ -124,8 +124,8 @@ files:
124
124
  - ext/LIBDATADOG_DEVELOPMENT.md
125
125
  - ext/datadog_profiling_native_extension/NativeExtensionDesign.md
126
126
  - ext/datadog_profiling_native_extension/clock_id.h
127
+ - ext/datadog_profiling_native_extension/clock_id_from_mach.c
127
128
  - ext/datadog_profiling_native_extension/clock_id_from_pthread.c
128
- - ext/datadog_profiling_native_extension/clock_id_noop.c
129
129
  - ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c
130
130
  - ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c
131
131
  - ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.h
@@ -187,10 +187,17 @@ files:
187
187
  - lib/datadog.rb
188
188
  - lib/datadog/ai_guard.rb
189
189
  - lib/datadog/ai_guard/api_client.rb
190
+ - lib/datadog/ai_guard/autoload.rb
190
191
  - lib/datadog/ai_guard/component.rb
191
192
  - lib/datadog/ai_guard/configuration.rb
192
193
  - lib/datadog/ai_guard/configuration/ext.rb
194
+ - lib/datadog/ai_guard/contrib/auto_instrument.rb
193
195
  - lib/datadog/ai_guard/contrib/integration.rb
196
+ - lib/datadog/ai_guard/contrib/rack/integration.rb
197
+ - lib/datadog/ai_guard/contrib/rack/patcher.rb
198
+ - lib/datadog/ai_guard/contrib/rack/request_middleware.rb
199
+ - lib/datadog/ai_guard/contrib/rails/integration.rb
200
+ - lib/datadog/ai_guard/contrib/rails/patcher.rb
194
201
  - lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb
195
202
  - lib/datadog/ai_guard/contrib/ruby_llm/integration.rb
196
203
  - lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb
@@ -231,6 +238,10 @@ files:
231
238
  - lib/datadog/appsec/contrib/active_record/integration.rb
232
239
  - lib/datadog/appsec/contrib/active_record/patcher.rb
233
240
  - lib/datadog/appsec/contrib/auto_instrument.rb
241
+ - lib/datadog/appsec/contrib/aws_lambda/gateway/watcher.rb
242
+ - lib/datadog/appsec/contrib/aws_lambda/integration.rb
243
+ - lib/datadog/appsec/contrib/aws_lambda/patcher.rb
244
+ - lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb
234
245
  - lib/datadog/appsec/contrib/devise/configuration.rb
235
246
  - lib/datadog/appsec/contrib/devise/data_extractor.rb
236
247
  - lib/datadog/appsec/contrib/devise/ext.rb
@@ -613,6 +624,7 @@ files:
613
624
  - lib/datadog/symbol_database/file_hash.rb
614
625
  - lib/datadog/symbol_database/logger.rb
615
626
  - lib/datadog/symbol_database/scope.rb
627
+ - lib/datadog/symbol_database/scope_batcher.rb
616
628
  - lib/datadog/symbol_database/service_version.rb
617
629
  - lib/datadog/symbol_database/symbol.rb
618
630
  - lib/datadog/symbol_database/transport.rb
@@ -1118,8 +1130,8 @@ licenses:
1118
1130
  - Apache-2.0
1119
1131
  metadata:
1120
1132
  allowed_push_host: https://rubygems.org
1121
- changelog_uri: https://github.com/DataDog/dd-trace-rb/blob/v2.32.0/CHANGELOG.md
1122
- source_code_uri: https://github.com/DataDog/dd-trace-rb/tree/v2.32.0
1133
+ changelog_uri: https://github.com/DataDog/dd-trace-rb/blob/v2.33.0/CHANGELOG.md
1134
+ source_code_uri: https://github.com/DataDog/dd-trace-rb/tree/v2.33.0
1123
1135
  post_install_message: 'JRuby support in the datadog gem is deprecated. Details: https://dtdg.co/jruby-deprecation'
1124
1136
  rdoc_options: []
1125
1137
  require_paths:
@@ -1,21 +0,0 @@
1
- #include "extconf.h"
2
-
3
- // This file is the dual of clock_id_from_pthread.c for systems where that info
4
- // is not available.
5
- #ifndef HAVE_PTHREAD_GETCPUCLOCKID
6
-
7
- #include "clock_id.h"
8
- #include "helpers.h"
9
- #include "datadog_ruby_common.h"
10
-
11
- void self_test_clock_id(void) { } // Nothing to check
12
-
13
- thread_cpu_time_id thread_cpu_time_id_for(DDTRACE_UNUSED VALUE _thread) {
14
- return (thread_cpu_time_id) {.valid = false};
15
- }
16
-
17
- thread_cpu_time thread_cpu_time_for(DDTRACE_UNUSED thread_cpu_time_id _time_id) {
18
- return (thread_cpu_time) {.valid = false};
19
- }
20
-
21
- #endif