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.
- checksums.yaml +4 -4
- data/ext/datadog_profiling_native_extension/clock_id.h +9 -1
- data/ext/datadog_profiling_native_extension/clock_id_from_mach.c +73 -0
- data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +1 -1
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +5 -1
- data/ext/datadog_profiling_native_extension/extconf.rb +3 -0
- data/ext/datadog_profiling_native_extension/stack_recorder.c +3 -9
- data/ext/datadog_profiling_native_extension/time_helpers.h +1 -0
- data/ext/libdatadog_api/crashtracker.c +2 -0
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/ai_guard/autoload.rb +10 -0
- data/lib/datadog/ai_guard/component.rb +1 -1
- data/lib/datadog/ai_guard/contrib/auto_instrument.rb +24 -0
- data/lib/datadog/ai_guard/contrib/rack/integration.rb +42 -0
- data/lib/datadog/ai_guard/contrib/rack/patcher.rb +26 -0
- data/lib/datadog/ai_guard/contrib/rack/request_middleware.rb +83 -0
- data/lib/datadog/ai_guard/contrib/rails/integration.rb +41 -0
- data/lib/datadog/ai_guard/contrib/rails/patcher.rb +97 -0
- data/lib/datadog/ai_guard/evaluation.rb +1 -0
- data/lib/datadog/ai_guard/ext.rb +1 -0
- data/lib/datadog/ai_guard.rb +8 -0
- data/lib/datadog/appsec/contrib/aws_lambda/gateway/watcher.rb +75 -0
- data/lib/datadog/appsec/contrib/aws_lambda/integration.rb +39 -0
- data/lib/datadog/appsec/contrib/aws_lambda/patcher.rb +30 -0
- data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +111 -0
- data/lib/datadog/appsec.rb +1 -0
- data/lib/datadog/core/configuration/settings.rb +10 -0
- data/lib/datadog/core/configuration/supported_configurations.rb +2 -0
- data/lib/datadog/core/environment/ext.rb +1 -0
- data/lib/datadog/core/environment/socket.rb +13 -0
- data/lib/datadog/opentelemetry/metrics.rb +10 -1
- data/lib/datadog/opentelemetry/sdk/id_generator.rb +16 -10
- data/lib/datadog/profiling/component.rb +0 -1
- data/lib/datadog/profiling/stack_recorder.rb +0 -4
- data/lib/datadog/symbol_database/extractor.rb +17 -26
- data/lib/datadog/symbol_database/scope.rb +16 -12
- data/lib/datadog/symbol_database/scope_batcher.rb +280 -0
- data/lib/datadog/symbol_database/service_version.rb +4 -4
- data/lib/datadog/symbol_database/uploader.rb +3 -0
- data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/contrib/rack/ext.rb +27 -0
- data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +117 -1
- data/lib/datadog/tracing/tracer.rb +1 -3
- data/lib/datadog/version.rb +1 -1
- metadata +19 -7
- 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,
|
|
24
|
-
# @param version [String] Version (from DD_VERSION,
|
|
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
|
|
33
|
-
@version = version
|
|
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
|
-
|
|
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)
|
data/lib/datadog/version.rb
CHANGED
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.
|
|
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-
|
|
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:
|
|
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:
|
|
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.
|
|
1122
|
-
source_code_uri: https://github.com/DataDog/dd-trace-rb/tree/v2.
|
|
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
|