datadog 2.45.0 → 2.47.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/CHANGELOG.md +49 -1
- data/ext/datadog_profiling_native_extension/collectors_stack.c +31 -14
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +5 -1
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +5 -0
- data/ext/datadog_profiling_native_extension/profiling.c +6 -0
- data/ext/libdatadog_api/di.c +30 -5
- data/ext/libdatadog_api/extconf.rb +2 -0
- data/ext/libdatadog_api/otel_thread_context.c +20 -14
- data/ext/libdatadog_api/process_discovery.c +15 -1
- data/ext/libdatadog_api/trace_exporter.c +105 -25
- data/ext/libdatadog_extconf_helpers.rb +1 -1
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +29 -2
- data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +8 -7
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +14 -2
- data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +8 -7
- data/lib/datadog/appsec/security_engine/engine.rb +2 -1
- data/lib/datadog/core/configuration/components.rb +40 -18
- data/lib/datadog/core/configuration/components_state.rb +8 -1
- data/lib/datadog/core/configuration/supported_configurations.rb +7 -2
- data/lib/datadog/core/process_discovery.rb +5 -1
- data/lib/datadog/core/remote/client/capabilities.rb +34 -29
- data/lib/datadog/core/remote/client.rb +44 -3
- data/lib/datadog/core/remote/component.rb +28 -12
- data/lib/datadog/core/remote/dispatcher.rb +47 -6
- data/lib/datadog/core/workers/runtime_metrics.rb +1 -1
- data/lib/datadog/di/boot.rb +1 -0
- data/lib/datadog/di/capture_expression_evaluator/result.rb +30 -0
- data/lib/datadog/di/capture_expression_evaluator.rb +39 -9
- data/lib/datadog/di/component.rb +108 -3
- data/lib/datadog/di/configuration.rb +53 -2
- data/lib/datadog/di/context.rb +7 -2
- data/lib/datadog/di/contrib/active_record.rb +8 -2
- data/lib/datadog/di/evaluation_failure.rb +28 -0
- data/lib/datadog/di/guardrails_telemetry/reason.rb +87 -0
- data/lib/datadog/di/guardrails_telemetry.rb +203 -0
- data/lib/datadog/di/instrumenter.rb +65 -15
- data/lib/datadog/di/logger.rb +30 -8
- data/lib/datadog/di/probe_manager.rb +48 -5
- data/lib/datadog/di/probe_notification_builder.rb +28 -26
- data/lib/datadog/di/probe_notifier_worker.rb +56 -6
- data/lib/datadog/di/remote.rb +46 -2
- data/lib/datadog/di/serializer.rb +147 -55
- data/lib/datadog/di/snapshot_event.rb +38 -0
- data/lib/datadog/di/transport/http.rb +13 -3
- data/lib/datadog/di/transport/input.rb +32 -7
- data/lib/datadog/open_feature/activation.rb +268 -0
- data/lib/datadog/open_feature/component.rb +31 -15
- data/lib/datadog/open_feature/configuration.rb +6 -0
- data/lib/datadog/open_feature/hooks/evaluation_telemetry_opt_out_hook.rb +25 -0
- data/lib/datadog/open_feature/provider.rb +304 -8
- data/lib/datadog/open_feature.rb +31 -1
- data/lib/datadog/profiling/collectors/info.rb +37 -6
- data/lib/datadog/profiling/component.rb +22 -20
- data/lib/datadog/profiling/exporter.rb +3 -5
- data/lib/datadog/tracing/component.rb +21 -2
- data/lib/datadog/tracing/configuration/ext.rb +2 -0
- data/lib/datadog/tracing/configuration/settings.rb +14 -1
- data/lib/datadog/tracing/context_provider.rb +8 -0
- data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +10 -5
- data/lib/datadog/tracing/contrib/excon/middleware.rb +10 -3
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +10 -4
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +16 -1
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +8 -2
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +10 -4
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +10 -3
- data/lib/datadog/tracing/contrib/utils/quantization/http.rb +102 -0
- data/lib/datadog/tracing/otel_thread_context.rb +3 -5
- data/lib/datadog/tracing/remote.rb +50 -19
- data/lib/datadog/tracing/transport/http/telemetry.rb +94 -0
- data/lib/datadog/tracing/transport/http/traces.rb +2 -0
- data/lib/datadog/tracing/transport/native/telemetry.rb +17 -0
- data/lib/datadog/tracing/transport/native.rb +16 -1
- data/lib/datadog/tracing/transport/telemetry.rb +62 -0
- data/lib/datadog/tracing/transport/traces.rb +82 -15
- data/lib/datadog/version.rb +1 -1
- metadata +19 -10
- data/lib/datadog/appsec/utils/hash_coercion.rb +0 -23
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "ext"
|
|
4
|
+
require_relative "../open_feature"
|
|
4
5
|
require_relative "../core/utils/time"
|
|
6
|
+
require_relative "hooks/evaluation_telemetry_opt_out_hook"
|
|
5
7
|
require "open_feature/sdk"
|
|
6
8
|
|
|
7
9
|
module Datadog
|
|
8
10
|
module OpenFeature
|
|
9
|
-
# OpenFeature feature flagging provider backed by Datadog
|
|
11
|
+
# OpenFeature feature flagging provider backed by Datadog configuration delivery.
|
|
10
12
|
#
|
|
11
13
|
# Requires openfeature-sdk >= 0.5.1 for flag evaluation metrics and EVP hook support.
|
|
12
14
|
#
|
|
@@ -24,13 +26,14 @@ module Datadog
|
|
|
24
26
|
#
|
|
25
27
|
# Example:
|
|
26
28
|
#
|
|
27
|
-
#
|
|
29
|
+
# Feature Flags use agentless configuration delivery by default. To use
|
|
30
|
+
# Remote Configuration delivery instead, configure the source explicitly.
|
|
28
31
|
#
|
|
29
32
|
# ```ruby
|
|
30
33
|
# # FILE: initializers/datadog.rb
|
|
31
34
|
# Datadog.configure do |config|
|
|
32
35
|
# config.remote.enabled = true
|
|
33
|
-
# config.
|
|
36
|
+
# config.feature_flags.configuration_source = "remote_config"
|
|
34
37
|
# end
|
|
35
38
|
# ```
|
|
36
39
|
#
|
|
@@ -58,28 +61,129 @@ module Datadog
|
|
|
58
61
|
# # => 'Welcome back!'
|
|
59
62
|
# ```
|
|
60
63
|
class Provider
|
|
64
|
+
include ::OpenFeature::SDK::Provider::EventEmitter
|
|
65
|
+
|
|
61
66
|
NAME = "Datadog Feature Flagging Provider"
|
|
67
|
+
INITIALIZATION_CANCELLED_MESSAGE = "Feature Flags provider initialization was cancelled by shutdown"
|
|
62
68
|
|
|
63
69
|
attr_reader :metadata
|
|
64
70
|
|
|
65
|
-
def initialize
|
|
71
|
+
def initialize(enable_evaluation_telemetry_opt_out_parameter: nil)
|
|
72
|
+
@enable_evaluation_telemetry_opt_out_parameter = case enable_evaluation_telemetry_opt_out_parameter
|
|
73
|
+
when nil
|
|
74
|
+
Datadog.configuration.feature_flags.evaluation_telemetry_per_subject_opt_out_enabled
|
|
75
|
+
when true, false
|
|
76
|
+
enable_evaluation_telemetry_opt_out_parameter
|
|
77
|
+
else
|
|
78
|
+
raise ArgumentError, "enable_evaluation_telemetry_opt_out_parameter expects true, false, or nil"
|
|
79
|
+
end
|
|
66
80
|
@metadata = ::OpenFeature::SDK::Provider::ProviderMetadata.new(name: NAME).freeze
|
|
81
|
+
@initialization_mutex = Mutex.new
|
|
82
|
+
@initializing = false
|
|
83
|
+
@initialization_thread = nil
|
|
84
|
+
@awaiting_ready_after_fork = false
|
|
85
|
+
@initialization_failed = false
|
|
86
|
+
@provider_error_observed = false
|
|
87
|
+
@ready_pending = false
|
|
88
|
+
@ready_dispatch_claimed = false
|
|
89
|
+
@stale_emitted = false
|
|
90
|
+
@stale_pending = false
|
|
91
|
+
@error_handler = nil
|
|
92
|
+
@initialization_ready_handler = nil
|
|
93
|
+
@waiting_component = nil
|
|
94
|
+
@shutdown = false
|
|
67
95
|
end
|
|
68
96
|
|
|
69
97
|
def init
|
|
70
|
-
|
|
98
|
+
@initialization_mutex.synchronize do
|
|
99
|
+
raise INITIALIZATION_CANCELLED_MESSAGE if @shutdown
|
|
100
|
+
|
|
101
|
+
@initializing = true
|
|
102
|
+
@initialization_thread = Thread.current
|
|
103
|
+
@awaiting_ready_after_fork = false
|
|
104
|
+
@initialization_failed = false
|
|
105
|
+
@provider_error_observed = false
|
|
106
|
+
@ready_pending = false
|
|
107
|
+
@ready_dispatch_claimed = false
|
|
108
|
+
@stale_emitted = false
|
|
109
|
+
@stale_pending = false
|
|
110
|
+
end
|
|
111
|
+
ready_handler_installed = install_initialization_ready_handler
|
|
112
|
+
|
|
113
|
+
component, failure = OpenFeature.activate_provider(self)
|
|
114
|
+
cancel_initialization_if_shutdown!
|
|
115
|
+
fail_initialization(component, failure || "Feature Flags component could not be activated") unless component
|
|
116
|
+
|
|
117
|
+
@initialization_mutex.synchronize do
|
|
118
|
+
raise INITIALIZATION_CANCELLED_MESSAGE if @shutdown
|
|
119
|
+
|
|
120
|
+
@waiting_component = component
|
|
121
|
+
end
|
|
122
|
+
# Shutdown publishes this flag through the component lock; taking our mutex here would invert lock order.
|
|
123
|
+
wait_result = component.wait_for_configuration { @shutdown }
|
|
124
|
+
cancel_initialization_if_shutdown!
|
|
125
|
+
|
|
126
|
+
case wait_result
|
|
127
|
+
when Component::CONFIGURATION_READY
|
|
128
|
+
@initialization_mutex.synchronize { @initializing = false } unless ready_handler_installed
|
|
129
|
+
when Component::CONFIGURATION_TIMEOUT
|
|
130
|
+
message = "Feature Flags provider initialization timed out while waiting for configuration"
|
|
131
|
+
Datadog.logger.error(message)
|
|
132
|
+
fail_initialization(component, message)
|
|
133
|
+
else
|
|
134
|
+
fail_initialization(component, "Feature Flags provider initialization stopped before configuration arrived")
|
|
135
|
+
end
|
|
136
|
+
rescue
|
|
137
|
+
prepare_initialization_failure(component) if @initialization_mutex.synchronize { @initializing }
|
|
138
|
+
raise
|
|
139
|
+
ensure
|
|
140
|
+
@initialization_mutex.synchronize do
|
|
141
|
+
@waiting_component = nil
|
|
142
|
+
# The SDK still owes READY after init returns; the initializing thread owns that dispatch.
|
|
143
|
+
@initialization_thread = nil unless @initializing
|
|
144
|
+
end
|
|
71
145
|
end
|
|
72
146
|
|
|
73
147
|
def shutdown
|
|
74
|
-
|
|
148
|
+
configuration = @configuration
|
|
149
|
+
# @type var error_handler: Provider::provider_event_handler?
|
|
150
|
+
error_handler = nil
|
|
151
|
+
# @type var initialization_ready_handler: Provider::provider_event_handler?
|
|
152
|
+
initialization_ready_handler = nil
|
|
153
|
+
waiting_component = nil #: Component?
|
|
154
|
+
@initialization_mutex.synchronize do
|
|
155
|
+
return if @shutdown
|
|
156
|
+
|
|
157
|
+
@shutdown = true
|
|
158
|
+
@initializing = false
|
|
159
|
+
@initialization_thread = nil
|
|
160
|
+
error_handler = @error_handler
|
|
161
|
+
initialization_ready_handler = @initialization_ready_handler
|
|
162
|
+
waiting_component = @waiting_component
|
|
163
|
+
@error_handler = nil
|
|
164
|
+
@initialization_ready_handler = nil
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
waiting_component&.wake_configuration_waiters
|
|
168
|
+
configuration&.remove_handler(::OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR, error_handler) if error_handler
|
|
169
|
+
if initialization_ready_handler
|
|
170
|
+
configuration&.remove_handler(
|
|
171
|
+
::OpenFeature::SDK::ProviderEvent::PROVIDER_READY,
|
|
172
|
+
initialization_ready_handler,
|
|
173
|
+
)
|
|
174
|
+
end
|
|
175
|
+
OpenFeature.deactivate_provider(self)
|
|
75
176
|
end
|
|
76
177
|
|
|
77
178
|
def hooks
|
|
78
179
|
component = Datadog.send(:components).open_feature
|
|
79
|
-
[
|
|
180
|
+
hooks = [
|
|
80
181
|
component&.flag_eval_metrics_hook,
|
|
81
182
|
component&.flag_eval_evp_hook,
|
|
82
183
|
].compact
|
|
184
|
+
return hooks unless @enable_evaluation_telemetry_opt_out_parameter
|
|
185
|
+
|
|
186
|
+
hooks.map { |hook| Hooks::EvaluationTelemetryOptOutHook.new(hook) }
|
|
83
187
|
end
|
|
84
188
|
|
|
85
189
|
def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
|
|
@@ -108,6 +212,191 @@ module Datadog
|
|
|
108
212
|
|
|
109
213
|
private
|
|
110
214
|
|
|
215
|
+
def after_fork(component)
|
|
216
|
+
handler = @initialization_mutex.synchronize do
|
|
217
|
+
return if @shutdown || !@initializing || @initialization_thread&.alive?
|
|
218
|
+
|
|
219
|
+
@initializing = false
|
|
220
|
+
@initialization_thread = nil
|
|
221
|
+
@waiting_component = nil
|
|
222
|
+
@awaiting_ready_after_fork = true
|
|
223
|
+
@stale_pending = false
|
|
224
|
+
previous_handler = @initialization_ready_handler
|
|
225
|
+
@initialization_ready_handler = nil
|
|
226
|
+
previous_handler
|
|
227
|
+
end
|
|
228
|
+
if handler
|
|
229
|
+
@configuration&.remove_handler(::OpenFeature::SDK::ProviderEvent::PROVIDER_READY, handler)
|
|
230
|
+
end
|
|
231
|
+
configuration_changed(Component::CONFIGURATION_READY) if component&.configuration_received?
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def fail_initialization(component, message)
|
|
235
|
+
prepare_initialization_failure(component)
|
|
236
|
+
cancel_initialization_if_shutdown!
|
|
237
|
+
raise message.to_s
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def prepare_initialization_failure(component)
|
|
241
|
+
cancelled, initialization_ready_handler = @initialization_mutex.synchronize do
|
|
242
|
+
handler = @initialization_ready_handler
|
|
243
|
+
@initialization_ready_handler = nil
|
|
244
|
+
@stale_pending = false
|
|
245
|
+
@initializing = false
|
|
246
|
+
@initialization_thread = nil
|
|
247
|
+
if @shutdown
|
|
248
|
+
[true, handler]
|
|
249
|
+
else
|
|
250
|
+
@initialization_failed = true
|
|
251
|
+
@ready_pending ||= !!component&.configuration_received?
|
|
252
|
+
[false, handler]
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
if initialization_ready_handler
|
|
256
|
+
@configuration&.remove_handler(
|
|
257
|
+
::OpenFeature::SDK::ProviderEvent::PROVIDER_READY,
|
|
258
|
+
initialization_ready_handler,
|
|
259
|
+
)
|
|
260
|
+
end
|
|
261
|
+
return if cancelled
|
|
262
|
+
|
|
263
|
+
install_error_handler
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def cancel_initialization_if_shutdown!
|
|
267
|
+
cancelled = @initialization_mutex.synchronize do
|
|
268
|
+
@initializing = false if @shutdown
|
|
269
|
+
@shutdown
|
|
270
|
+
end
|
|
271
|
+
raise INITIALIZATION_CANCELLED_MESSAGE if cancelled
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def shutdown?
|
|
275
|
+
@initialization_mutex.synchronize { @shutdown }
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def install_initialization_ready_handler
|
|
279
|
+
configuration = @configuration
|
|
280
|
+
return false unless configuration
|
|
281
|
+
|
|
282
|
+
handler = @initialization_mutex.synchronize do
|
|
283
|
+
return false if @shutdown
|
|
284
|
+
return true if @initialization_ready_handler
|
|
285
|
+
|
|
286
|
+
@initialization_ready_handler = ->(details) { provider_ready(details) }
|
|
287
|
+
end
|
|
288
|
+
configuration.add_handler(::OpenFeature::SDK::ProviderEvent::PROVIDER_READY, handler)
|
|
289
|
+
|
|
290
|
+
cancelled = @initialization_mutex.synchronize { @shutdown }
|
|
291
|
+
configuration.remove_handler(::OpenFeature::SDK::ProviderEvent::PROVIDER_READY, handler) if cancelled
|
|
292
|
+
return false if cancelled
|
|
293
|
+
|
|
294
|
+
true
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def install_error_handler
|
|
298
|
+
configuration = @configuration
|
|
299
|
+
return unless configuration
|
|
300
|
+
|
|
301
|
+
handler = @initialization_mutex.synchronize do
|
|
302
|
+
return if @shutdown
|
|
303
|
+
|
|
304
|
+
@error_handler ||= ->(details) { provider_error(details) }
|
|
305
|
+
end
|
|
306
|
+
configuration.add_handler(::OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR, handler)
|
|
307
|
+
|
|
308
|
+
cancelled = @initialization_mutex.synchronize { @shutdown }
|
|
309
|
+
configuration.remove_handler(::OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR, handler) if cancelled
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def provider_error(_details)
|
|
313
|
+
configuration = @configuration
|
|
314
|
+
return unless configuration
|
|
315
|
+
return unless configuration.send(:provider_state, self) == ::OpenFeature::SDK::ProviderState::ERROR
|
|
316
|
+
|
|
317
|
+
emit_ready = @initialization_mutex.synchronize do
|
|
318
|
+
if @shutdown
|
|
319
|
+
false
|
|
320
|
+
else
|
|
321
|
+
@provider_error_observed = true
|
|
322
|
+
if @ready_pending && !@ready_dispatch_claimed
|
|
323
|
+
@ready_dispatch_claimed = true
|
|
324
|
+
else
|
|
325
|
+
false
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
emit_event(::OpenFeature::SDK::ProviderEvent::PROVIDER_READY) if emit_ready
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def provider_ready(_details)
|
|
333
|
+
configuration = @configuration
|
|
334
|
+
return unless configuration
|
|
335
|
+
return unless configuration.send(:provider_state, self) == ::OpenFeature::SDK::ProviderState::READY
|
|
336
|
+
|
|
337
|
+
handler, emit_stale = @initialization_mutex.synchronize do
|
|
338
|
+
handler = @initialization_ready_handler
|
|
339
|
+
@initialization_ready_handler = nil
|
|
340
|
+
@initializing = false
|
|
341
|
+
@initialization_thread = nil
|
|
342
|
+
if @stale_pending && !@shutdown
|
|
343
|
+
@stale_pending = false
|
|
344
|
+
@stale_emitted = true
|
|
345
|
+
[handler, true]
|
|
346
|
+
else
|
|
347
|
+
[handler, false]
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
configuration.remove_handler(::OpenFeature::SDK::ProviderEvent::PROVIDER_READY, handler) if handler
|
|
351
|
+
emit_event(::OpenFeature::SDK::ProviderEvent::PROVIDER_STALE) if emit_stale
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def configuration_changed(event)
|
|
355
|
+
event_type = @initialization_mutex.synchronize do
|
|
356
|
+
if @shutdown
|
|
357
|
+
nil
|
|
358
|
+
elsif event == Component::CONFIGURATION_READY
|
|
359
|
+
if @initializing
|
|
360
|
+
@ready_pending = true
|
|
361
|
+
@stale_pending = false
|
|
362
|
+
nil
|
|
363
|
+
elsif @awaiting_ready_after_fork
|
|
364
|
+
@awaiting_ready_after_fork = false
|
|
365
|
+
@stale_emitted = false
|
|
366
|
+
::OpenFeature::SDK::ProviderEvent::PROVIDER_READY
|
|
367
|
+
elsif @initialization_failed && !@provider_error_observed
|
|
368
|
+
@stale_emitted = false
|
|
369
|
+
@ready_pending = true
|
|
370
|
+
nil
|
|
371
|
+
elsif @stale_emitted
|
|
372
|
+
@stale_emitted = false
|
|
373
|
+
::OpenFeature::SDK::ProviderEvent::PROVIDER_READY
|
|
374
|
+
elsif @initialization_failed
|
|
375
|
+
if @provider_error_observed && !@ready_dispatch_claimed
|
|
376
|
+
@ready_dispatch_claimed = true
|
|
377
|
+
::OpenFeature::SDK::ProviderEvent::PROVIDER_READY
|
|
378
|
+
else
|
|
379
|
+
@ready_pending = true
|
|
380
|
+
nil
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
elsif event == Component::CONFIGURATION_CHANGED && !@initializing
|
|
384
|
+
::OpenFeature::SDK::ProviderEvent::PROVIDER_CONFIGURATION_CHANGED
|
|
385
|
+
elsif event == Component::CONFIGURATION_LOST
|
|
386
|
+
@ready_pending = false
|
|
387
|
+
if @initializing
|
|
388
|
+
@stale_pending = true
|
|
389
|
+
nil
|
|
390
|
+
else
|
|
391
|
+
@stale_emitted = true
|
|
392
|
+
::OpenFeature::SDK::ProviderEvent::PROVIDER_STALE
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
emit_event(event_type) if event_type
|
|
398
|
+
end
|
|
399
|
+
|
|
111
400
|
def evaluate(flag_key, default_value:, expected_type:, evaluation_context:)
|
|
112
401
|
# Stamp evaluation entry time once, here on the eval thread. The EVP path uses this for
|
|
113
402
|
# accurate first/last_evaluation bounds instead of a later hook-fire clock read.
|
|
@@ -129,7 +418,9 @@ module Datadog
|
|
|
129
418
|
# Drive APM span enrichment directly from the evaluation path. This is the only reliable
|
|
130
419
|
# way to attach `ffe_*` tags across all supported OpenFeature SDK versions, since older
|
|
131
420
|
# SDKs do not dispatch provider hooks. Guarded so enrichment can never break evaluation.
|
|
132
|
-
|
|
421
|
+
unless evaluation_telemetry_opted_out?(evaluation_context)
|
|
422
|
+
enrich_span(flag_key, result, evaluation_context)
|
|
423
|
+
end
|
|
133
424
|
|
|
134
425
|
sdk_success_details(result, flag_meta)
|
|
135
426
|
rescue => e
|
|
@@ -218,6 +509,11 @@ module Datadog
|
|
|
218
509
|
Datadog.send(:components).open_feature&.span_enrichment_hook
|
|
219
510
|
end
|
|
220
511
|
|
|
512
|
+
def evaluation_telemetry_opted_out?(evaluation_context)
|
|
513
|
+
@enable_evaluation_telemetry_opt_out_parameter &&
|
|
514
|
+
Hooks::EvaluationTelemetryOptOutHook.opted_out?(evaluation_context)
|
|
515
|
+
end
|
|
516
|
+
|
|
221
517
|
def component_not_configured_default(value, eval_time_ms)
|
|
222
518
|
::OpenFeature::SDK::Provider::ResolutionDetails.new(
|
|
223
519
|
value: value,
|
data/lib/datadog/open_feature.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "core/configuration"
|
|
4
4
|
require_relative "open_feature/configuration"
|
|
5
|
+
require_relative "open_feature/configuration/source"
|
|
5
6
|
|
|
6
7
|
module Datadog
|
|
7
8
|
# A namespace for the OpenFeature component.
|
|
@@ -9,11 +10,40 @@ module Datadog
|
|
|
9
10
|
Core::Configuration::Settings.extend(Configuration::Settings)
|
|
10
11
|
|
|
11
12
|
def self.enabled?
|
|
12
|
-
Datadog.configuration.
|
|
13
|
+
Configuration::Source.resolve(Datadog.configuration).enabled?
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def self.engine
|
|
16
17
|
Datadog.send(:components).open_feature&.engine
|
|
17
18
|
end
|
|
19
|
+
|
|
20
|
+
def self.activate_provider(provider)
|
|
21
|
+
# Initialize the component tree before taking its reconfiguration lock.
|
|
22
|
+
Datadog.send(:components)
|
|
23
|
+
Datadog.send(:safely_synchronize) do
|
|
24
|
+
return [nil, nil] if provider.send(:shutdown?)
|
|
25
|
+
|
|
26
|
+
components = Datadog.send(:components, allow_initialization: false)
|
|
27
|
+
activation = components&.send(:open_feature_activation)
|
|
28
|
+
providers = @adopted_providers ||= {}.compare_by_identity
|
|
29
|
+
providers[provider] = true
|
|
30
|
+
[activation&.activate(provider), activation&.failure]
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.deactivate_provider(provider)
|
|
35
|
+
Datadog.send(:safely_synchronize) do
|
|
36
|
+
@adopted_providers&.delete(provider)
|
|
37
|
+
components = Datadog.send(:components, allow_initialization: false)
|
|
38
|
+
components&.send(:open_feature_activation)&.deactivate(provider)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Components startup already holds the non-reentrant reconfiguration lock.
|
|
43
|
+
def self.reattach(activation)
|
|
44
|
+
providers = @adopted_providers&.keys || []
|
|
45
|
+
providers.each { |provider| activation.activate(provider) }
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
18
48
|
end
|
|
19
49
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "json"
|
|
3
4
|
require "libdatadog"
|
|
4
5
|
require_relative "../../core/utils/time"
|
|
5
6
|
|
|
@@ -9,11 +10,11 @@ module Datadog
|
|
|
9
10
|
# Collects information of relevance for profiler. This will get sent alongside
|
|
10
11
|
# the profile and show up in the UI or potentially influence processing in some way.
|
|
11
12
|
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# gathering and easily support more flexible/dynamic info collection in the future.
|
|
13
|
+
# Most information is static: collected and frozen at construction time. The exception is
|
|
14
|
+
# what `collect_dynamic_info` collects, which can change during the process lifetime.
|
|
15
15
|
class Info
|
|
16
|
-
# @rbs @
|
|
16
|
+
# @rbs @static_info: ::Hash[::Symbol, ::Hash[::Symbol, untyped]]
|
|
17
|
+
# @rbs @info_json_cache: [::Hash[::Symbol, bool], ::String]?
|
|
17
18
|
# @rbs @platform_info: ::Hash[::Symbol, untyped]
|
|
18
19
|
# @rbs @runtime_info: ::Hash[::Symbol, untyped]
|
|
19
20
|
# @rbs @application_info: ::Hash[::Symbol, untyped]
|
|
@@ -24,18 +25,48 @@ module Datadog
|
|
|
24
25
|
def initialize(settings)
|
|
25
26
|
@profiler_info = nil
|
|
26
27
|
|
|
27
|
-
@
|
|
28
|
+
@static_info = {
|
|
28
29
|
platform: collect_platform_info,
|
|
29
30
|
runtime: collect_runtime_info,
|
|
30
31
|
application: collect_application_info(settings),
|
|
31
32
|
profiler: collect_profiler_info(settings),
|
|
32
33
|
}.freeze
|
|
34
|
+
@info_json_cache = nil
|
|
33
35
|
end
|
|
34
36
|
|
|
35
|
-
|
|
37
|
+
#: () -> ::Hash[::Symbol, ::Hash[::Symbol, untyped]]
|
|
38
|
+
def info
|
|
39
|
+
build_info(collect_dynamic_info)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Serializing the same info on every flush would be wasteful, so the latest result is
|
|
43
|
+
# cached and reused for as long as the dynamic info is unchanged.
|
|
44
|
+
#: () -> ::String
|
|
45
|
+
def info_json
|
|
46
|
+
dynamic_info = collect_dynamic_info
|
|
47
|
+
cached_dynamic_info, cached_json = @info_json_cache
|
|
48
|
+
return cached_json if cached_json && cached_dynamic_info == dynamic_info
|
|
49
|
+
|
|
50
|
+
json = JSON.generate(build_info(dynamic_info)).freeze
|
|
51
|
+
@info_json_cache = [dynamic_info, json].freeze #: [::Hash[::Symbol, bool], ::String]
|
|
52
|
+
json
|
|
53
|
+
end
|
|
36
54
|
|
|
37
55
|
private
|
|
38
56
|
|
|
57
|
+
#: (::Hash[::Symbol, bool]) -> ::Hash[::Symbol, ::Hash[::Symbol, untyped]]
|
|
58
|
+
def build_info(dynamic_info)
|
|
59
|
+
@static_info.merge(runtime: @static_info.fetch(:runtime).merge(dynamic_info))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
#: () -> ::Hash[::Symbol, bool]
|
|
63
|
+
def collect_dynamic_info
|
|
64
|
+
{
|
|
65
|
+
yjit_enabled: defined?(::RubyVM::YJIT) ? ::RubyVM::YJIT.enabled? : false,
|
|
66
|
+
zjit_enabled: defined?(::RubyVM::ZJIT) ? ::RubyVM::ZJIT.enabled? : false,
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
|
|
39
70
|
# Ruby GC tuning environment variables
|
|
40
71
|
RUBY_GC_TUNING_ENV_VARS = [
|
|
41
72
|
"RUBY_GC_HEAP_FREE_SLOTS",
|
|
@@ -427,33 +427,35 @@ module Datadog
|
|
|
427
427
|
end
|
|
428
428
|
end
|
|
429
429
|
|
|
430
|
-
#
|
|
431
|
-
#
|
|
432
|
-
# incompatible. In fact we have no reports of the mariadb variant needing the "no signals" workaround,
|
|
433
|
-
# so we flag it as compatible when it's in use.
|
|
430
|
+
# MariaDB's client has diverged from Oracle's and has no reported need for the "no signals" workaround, as
|
|
431
|
+
# reported in https://github.com/DataDog/dd-trace-rb/issues/3334, so we flag it as compatible when it's in use.
|
|
434
432
|
#
|
|
435
|
-
#
|
|
436
|
-
#
|
|
433
|
+
# There's no way to ask the mysql2 gem which client it was built against, so we infer it from the two versions
|
|
434
|
+
# that `Mysql2::Client.info` exposes:
|
|
437
435
|
#
|
|
438
|
-
#
|
|
439
|
-
#
|
|
436
|
+
# * `version` is `mysql_get_client_info()`. For MariaDB that's the version of
|
|
437
|
+
# https://github.com/mariadb-corporation/mariadb-connector-c, which is still 3.x.
|
|
438
|
+
# * `header_version` is a constant baked in when mysql2 was compiled. Up to mysql2 0.5.6 it was the MariaDB
|
|
439
|
+
# server version, which is what people usually call "the mariadb version"; 0.5.7 switched it to the
|
|
440
|
+
# Connector/C version (https://github.com/brianmario/mysql2/pull/1406) and that broke our detection, as
|
|
441
|
+
# reported in https://github.com/DataDog/dd-trace-rb/issues/6536.
|
|
440
442
|
#
|
|
441
|
-
#
|
|
442
|
-
#
|
|
443
|
-
#
|
|
443
|
+
# So MariaDB shows up as `{version: "3.3.8", header_version: "11.2.2"}` with mysql2 < 0.5.7 and as
|
|
444
|
+
# `{version: "3.4.9", header_version: "3.4.9"}` from 0.5.7 on. We accept both shapes: a header that is a server
|
|
445
|
+
# version (10 or newer, a sane range for modern releases per https://endoflife.date/mariadb) or one that is a
|
|
446
|
+
# Connector/C version (3.x, matching the client version).
|
|
444
447
|
#
|
|
445
|
-
#
|
|
446
|
-
#
|
|
447
|
-
#
|
|
448
|
-
# Thus our detection is version 4 or older, because libmysqlclient 4 is almost 20 years old so it's most probably
|
|
449
|
-
# not that one + header_version being 10 or newer, since according to https://endoflife.date/mariadb that's a
|
|
450
|
-
# sane range for modern mariadb releases.
|
|
448
|
+
# Oracle's client repeats one version in both fields (`{version: "8.0.35", header_version: "8.0.35"}`), and our
|
|
449
|
+
# caller already considers 8.0.0 and above compatible, so only a client older than 5.0.0 gets this far -- and
|
|
450
|
+
# no Oracle release has been one since libmysqlclient 4.1, which is from 2004.
|
|
451
451
|
private_class_method def self.looks_like_mariadb?(info, libmysqlclient_version)
|
|
452
452
|
header_version = Gem::Version.new(info[:header_version]) if info[:header_version]
|
|
453
453
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
454
|
+
return false unless header_version && libmysqlclient_version < Gem::Version.new("5.0.0")
|
|
455
|
+
return true if header_version >= Gem::Version.new("10.0.0")
|
|
456
|
+
|
|
457
|
+
connector_c_versions = Gem::Requirement.new(">= 3.0.0", "< 4.0.0")
|
|
458
|
+
connector_c_versions.satisfied_by?(libmysqlclient_version) && connector_c_versions.satisfied_by?(header_version)
|
|
457
459
|
end
|
|
458
460
|
|
|
459
461
|
private_class_method def self.dir_interruption_workaround_enabled?(settings, no_signals_workaround_enabled)
|
|
@@ -27,7 +27,7 @@ module Datadog
|
|
|
27
27
|
attr_reader :last_flush_finish_at #: ::Time?
|
|
28
28
|
attr_reader :created_at #: ::Time
|
|
29
29
|
attr_reader :internal_metadata #: ::Hash[::Symbol, untyped]
|
|
30
|
-
attr_reader :
|
|
30
|
+
attr_reader :info_collector #: Datadog::Profiling::Collectors::Info
|
|
31
31
|
attr_reader :sequence_tracker #: singleton(Datadog::Profiling::SequenceTracker)
|
|
32
32
|
|
|
33
33
|
public
|
|
@@ -56,9 +56,7 @@ module Datadog
|
|
|
56
56
|
@last_flush_finish_at = nil
|
|
57
57
|
@created_at = time_provider.now.utc
|
|
58
58
|
@internal_metadata = internal_metadata
|
|
59
|
-
|
|
60
|
-
# it on startup to prevent serializing the same info on every flush.
|
|
61
|
-
@info_json = JSON.generate(info_collector.info).freeze
|
|
59
|
+
@info_collector = info_collector
|
|
62
60
|
@sequence_tracker = sequence_tracker
|
|
63
61
|
end
|
|
64
62
|
|
|
@@ -111,7 +109,7 @@ module Datadog
|
|
|
111
109
|
gc: gc_stats,
|
|
112
110
|
}
|
|
113
111
|
),
|
|
114
|
-
info_json: info_json,
|
|
112
|
+
info_json: info_collector.info_json,
|
|
115
113
|
)
|
|
116
114
|
end
|
|
117
115
|
|
|
@@ -21,14 +21,14 @@ module Datadog
|
|
|
21
21
|
tracer = settings.tracing.instance
|
|
22
22
|
return tracer unless tracer.nil?
|
|
23
23
|
|
|
24
|
+
sampler = build_sampler(settings)
|
|
25
|
+
|
|
24
26
|
# Apply test mode settings if test mode is activated
|
|
25
27
|
if settings.tracing.test_mode.enabled
|
|
26
28
|
trace_flush = build_test_mode_trace_flush(settings)
|
|
27
|
-
sampler = build_test_mode_sampler
|
|
28
29
|
writer = build_test_mode_writer(settings, agent_settings)
|
|
29
30
|
else
|
|
30
31
|
trace_flush = build_trace_flush(settings)
|
|
31
|
-
sampler = build_sampler(settings)
|
|
32
32
|
writer = build_writer(settings, agent_settings)
|
|
33
33
|
end
|
|
34
34
|
|
|
@@ -62,7 +62,26 @@ module Datadog
|
|
|
62
62
|
end
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
def bind_transport_telemetry(tracer, telemetry)
|
|
66
|
+
return unless tracer.is_a?(Tracing::Tracer)
|
|
67
|
+
|
|
68
|
+
writer = tracer.writer
|
|
69
|
+
return unless writer.respond_to?(:transport)
|
|
70
|
+
|
|
71
|
+
transport = writer.transport
|
|
72
|
+
if transport.is_a?(Transport::Traces::Transport) ||
|
|
73
|
+
(defined?(Transport::Native::Transport) && transport.is_a?(Transport::Native::Transport))
|
|
74
|
+
transport.telemetry = telemetry
|
|
75
|
+
end
|
|
76
|
+
nil
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Builds the sampler for the given settings: the sampler that keeps
|
|
80
|
+
# every trace when test mode is enabled, otherwise the sampler built
|
|
81
|
+
# from the custom sampler, sampling rules and rates.
|
|
65
82
|
def build_sampler(settings)
|
|
83
|
+
return build_test_mode_sampler if settings.tracing.test_mode.enabled
|
|
84
|
+
|
|
66
85
|
# A custom sampler is provided
|
|
67
86
|
if (sampler = settings.tracing.sampler)
|
|
68
87
|
return sampler
|
|
@@ -128,6 +128,8 @@ module Datadog
|
|
|
128
128
|
ENV_SERVER_ERROR_STATUSES = "DD_TRACE_HTTP_SERVER_ERROR_STATUSES"
|
|
129
129
|
ENV_CLIENT_ERROR_STATUSES = "DD_TRACE_HTTP_CLIENT_ERROR_STATUSES"
|
|
130
130
|
end
|
|
131
|
+
|
|
132
|
+
ENV_HTTP_CLIENT_RESOURCE_NAME_QUANTIZE = "DD_TRACE_HTTP_CLIENT_RESOURCE_NAME_QUANTIZE"
|
|
131
133
|
end
|
|
132
134
|
end
|
|
133
135
|
end
|
|
@@ -258,7 +258,9 @@ module Datadog
|
|
|
258
258
|
|
|
259
259
|
option :otel_thread_context_enabled do |o|
|
|
260
260
|
o.env Tracing::Configuration::Ext::ENV_OTEL_CONTEXT_ENABLED
|
|
261
|
-
o.default
|
|
261
|
+
o.default do
|
|
262
|
+
RUBY_ENGINE == "ruby" && RUBY_PLATFORM.include?("linux")
|
|
263
|
+
end
|
|
262
264
|
o.type :bool
|
|
263
265
|
end
|
|
264
266
|
|
|
@@ -566,6 +568,17 @@ module Datadog
|
|
|
566
568
|
o.default 512
|
|
567
569
|
end
|
|
568
570
|
|
|
571
|
+
# Whether outbound HTTP client resource names include a quantized request path.
|
|
572
|
+
# URL tags and outgoing requests are unchanged.
|
|
573
|
+
#
|
|
574
|
+
# @default `DD_TRACE_HTTP_CLIENT_RESOURCE_NAME_QUANTIZE` environment variable, otherwise `false`.
|
|
575
|
+
# @return [Boolean]
|
|
576
|
+
option :http_client_resource_name_quantize do |o|
|
|
577
|
+
o.env Tracing::Configuration::Ext::ENV_HTTP_CLIENT_RESOURCE_NAME_QUANTIZE
|
|
578
|
+
o.default false
|
|
579
|
+
o.type :bool
|
|
580
|
+
end
|
|
581
|
+
|
|
569
582
|
# HTTP error statuses configuration
|
|
570
583
|
# @public_api
|
|
571
584
|
settings :http_error_statuses do
|