datadog 2.45.0 → 2.46.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 +25 -2
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +4 -0
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +2 -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/core/configuration/components.rb +29 -8
- data/lib/datadog/core/configuration/supported_configurations.rb +5 -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/component.rb +28 -12
- data/lib/datadog/core/remote/dispatcher.rb +13 -3
- 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 +38 -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/serializer.rb +130 -54
- 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/provider.rb +282 -5
- data/lib/datadog/open_feature.rb +31 -1
- data/lib/datadog/tracing/component.rb +14 -0
- data/lib/datadog/tracing/configuration/settings.rb +3 -1
- data/lib/datadog/tracing/otel_thread_context.rb +3 -5
- 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 +16 -7
|
@@ -1,12 +1,13 @@
|
|
|
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"
|
|
5
6
|
require "open_feature/sdk"
|
|
6
7
|
|
|
7
8
|
module Datadog
|
|
8
9
|
module OpenFeature
|
|
9
|
-
# OpenFeature feature flagging provider backed by Datadog
|
|
10
|
+
# OpenFeature feature flagging provider backed by Datadog configuration delivery.
|
|
10
11
|
#
|
|
11
12
|
# Requires openfeature-sdk >= 0.5.1 for flag evaluation metrics and EVP hook support.
|
|
12
13
|
#
|
|
@@ -24,13 +25,14 @@ module Datadog
|
|
|
24
25
|
#
|
|
25
26
|
# Example:
|
|
26
27
|
#
|
|
27
|
-
#
|
|
28
|
+
# Feature Flags use agentless configuration delivery by default. To use
|
|
29
|
+
# Remote Configuration delivery instead, configure the source explicitly.
|
|
28
30
|
#
|
|
29
31
|
# ```ruby
|
|
30
32
|
# # FILE: initializers/datadog.rb
|
|
31
33
|
# Datadog.configure do |config|
|
|
32
34
|
# config.remote.enabled = true
|
|
33
|
-
# config.
|
|
35
|
+
# config.feature_flags.configuration_source = "remote_config"
|
|
34
36
|
# end
|
|
35
37
|
# ```
|
|
36
38
|
#
|
|
@@ -58,20 +60,110 @@ module Datadog
|
|
|
58
60
|
# # => 'Welcome back!'
|
|
59
61
|
# ```
|
|
60
62
|
class Provider
|
|
63
|
+
include ::OpenFeature::SDK::Provider::EventEmitter
|
|
64
|
+
|
|
61
65
|
NAME = "Datadog Feature Flagging Provider"
|
|
66
|
+
INITIALIZATION_CANCELLED_MESSAGE = "Feature Flags provider initialization was cancelled by shutdown"
|
|
62
67
|
|
|
63
68
|
attr_reader :metadata
|
|
64
69
|
|
|
65
70
|
def initialize
|
|
66
71
|
@metadata = ::OpenFeature::SDK::Provider::ProviderMetadata.new(name: NAME).freeze
|
|
72
|
+
@initialization_mutex = Mutex.new
|
|
73
|
+
@initializing = false
|
|
74
|
+
@initialization_thread = nil
|
|
75
|
+
@awaiting_ready_after_fork = false
|
|
76
|
+
@initialization_failed = false
|
|
77
|
+
@provider_error_observed = false
|
|
78
|
+
@ready_pending = false
|
|
79
|
+
@ready_dispatch_claimed = false
|
|
80
|
+
@stale_emitted = false
|
|
81
|
+
@stale_pending = false
|
|
82
|
+
@error_handler = nil
|
|
83
|
+
@initialization_ready_handler = nil
|
|
84
|
+
@waiting_component = nil
|
|
85
|
+
@shutdown = false
|
|
67
86
|
end
|
|
68
87
|
|
|
69
88
|
def init
|
|
70
|
-
|
|
89
|
+
@initialization_mutex.synchronize do
|
|
90
|
+
raise INITIALIZATION_CANCELLED_MESSAGE if @shutdown
|
|
91
|
+
|
|
92
|
+
@initializing = true
|
|
93
|
+
@initialization_thread = Thread.current
|
|
94
|
+
@awaiting_ready_after_fork = false
|
|
95
|
+
@initialization_failed = false
|
|
96
|
+
@provider_error_observed = false
|
|
97
|
+
@ready_pending = false
|
|
98
|
+
@ready_dispatch_claimed = false
|
|
99
|
+
@stale_emitted = false
|
|
100
|
+
@stale_pending = false
|
|
101
|
+
end
|
|
102
|
+
ready_handler_installed = install_initialization_ready_handler
|
|
103
|
+
|
|
104
|
+
component, failure = OpenFeature.activate_provider(self)
|
|
105
|
+
cancel_initialization_if_shutdown!
|
|
106
|
+
fail_initialization(component, failure || "Feature Flags component could not be activated") unless component
|
|
107
|
+
|
|
108
|
+
@initialization_mutex.synchronize do
|
|
109
|
+
raise INITIALIZATION_CANCELLED_MESSAGE if @shutdown
|
|
110
|
+
|
|
111
|
+
@waiting_component = component
|
|
112
|
+
end
|
|
113
|
+
# Shutdown publishes this flag through the component lock; taking our mutex here would invert lock order.
|
|
114
|
+
wait_result = component.wait_for_configuration { @shutdown }
|
|
115
|
+
cancel_initialization_if_shutdown!
|
|
116
|
+
|
|
117
|
+
case wait_result
|
|
118
|
+
when Component::CONFIGURATION_READY
|
|
119
|
+
@initialization_mutex.synchronize { @initializing = false } unless ready_handler_installed
|
|
120
|
+
when Component::CONFIGURATION_TIMEOUT
|
|
121
|
+
message = "Feature Flags provider initialization timed out while waiting for configuration"
|
|
122
|
+
Datadog.logger.error(message)
|
|
123
|
+
fail_initialization(component, message)
|
|
124
|
+
else
|
|
125
|
+
fail_initialization(component, "Feature Flags provider initialization stopped before configuration arrived")
|
|
126
|
+
end
|
|
127
|
+
rescue
|
|
128
|
+
prepare_initialization_failure(component) if @initialization_mutex.synchronize { @initializing }
|
|
129
|
+
raise
|
|
130
|
+
ensure
|
|
131
|
+
@initialization_mutex.synchronize do
|
|
132
|
+
@waiting_component = nil
|
|
133
|
+
# The SDK still owes READY after init returns; the initializing thread owns that dispatch.
|
|
134
|
+
@initialization_thread = nil unless @initializing
|
|
135
|
+
end
|
|
71
136
|
end
|
|
72
137
|
|
|
73
138
|
def shutdown
|
|
74
|
-
|
|
139
|
+
configuration = @configuration
|
|
140
|
+
# @type var error_handler: Provider::provider_event_handler?
|
|
141
|
+
error_handler = nil
|
|
142
|
+
# @type var initialization_ready_handler: Provider::provider_event_handler?
|
|
143
|
+
initialization_ready_handler = nil
|
|
144
|
+
waiting_component = nil #: Component?
|
|
145
|
+
@initialization_mutex.synchronize do
|
|
146
|
+
return if @shutdown
|
|
147
|
+
|
|
148
|
+
@shutdown = true
|
|
149
|
+
@initializing = false
|
|
150
|
+
@initialization_thread = nil
|
|
151
|
+
error_handler = @error_handler
|
|
152
|
+
initialization_ready_handler = @initialization_ready_handler
|
|
153
|
+
waiting_component = @waiting_component
|
|
154
|
+
@error_handler = nil
|
|
155
|
+
@initialization_ready_handler = nil
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
waiting_component&.wake_configuration_waiters
|
|
159
|
+
configuration&.remove_handler(::OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR, error_handler) if error_handler
|
|
160
|
+
if initialization_ready_handler
|
|
161
|
+
configuration&.remove_handler(
|
|
162
|
+
::OpenFeature::SDK::ProviderEvent::PROVIDER_READY,
|
|
163
|
+
initialization_ready_handler,
|
|
164
|
+
)
|
|
165
|
+
end
|
|
166
|
+
OpenFeature.deactivate_provider(self)
|
|
75
167
|
end
|
|
76
168
|
|
|
77
169
|
def hooks
|
|
@@ -108,6 +200,191 @@ module Datadog
|
|
|
108
200
|
|
|
109
201
|
private
|
|
110
202
|
|
|
203
|
+
def after_fork(component)
|
|
204
|
+
handler = @initialization_mutex.synchronize do
|
|
205
|
+
return if @shutdown || !@initializing || @initialization_thread&.alive?
|
|
206
|
+
|
|
207
|
+
@initializing = false
|
|
208
|
+
@initialization_thread = nil
|
|
209
|
+
@waiting_component = nil
|
|
210
|
+
@awaiting_ready_after_fork = true
|
|
211
|
+
@stale_pending = false
|
|
212
|
+
previous_handler = @initialization_ready_handler
|
|
213
|
+
@initialization_ready_handler = nil
|
|
214
|
+
previous_handler
|
|
215
|
+
end
|
|
216
|
+
if handler
|
|
217
|
+
@configuration&.remove_handler(::OpenFeature::SDK::ProviderEvent::PROVIDER_READY, handler)
|
|
218
|
+
end
|
|
219
|
+
configuration_changed(Component::CONFIGURATION_READY) if component&.configuration_received?
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def fail_initialization(component, message)
|
|
223
|
+
prepare_initialization_failure(component)
|
|
224
|
+
cancel_initialization_if_shutdown!
|
|
225
|
+
raise message.to_s
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def prepare_initialization_failure(component)
|
|
229
|
+
cancelled, initialization_ready_handler = @initialization_mutex.synchronize do
|
|
230
|
+
handler = @initialization_ready_handler
|
|
231
|
+
@initialization_ready_handler = nil
|
|
232
|
+
@stale_pending = false
|
|
233
|
+
@initializing = false
|
|
234
|
+
@initialization_thread = nil
|
|
235
|
+
if @shutdown
|
|
236
|
+
[true, handler]
|
|
237
|
+
else
|
|
238
|
+
@initialization_failed = true
|
|
239
|
+
@ready_pending ||= !!component&.configuration_received?
|
|
240
|
+
[false, handler]
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
if initialization_ready_handler
|
|
244
|
+
@configuration&.remove_handler(
|
|
245
|
+
::OpenFeature::SDK::ProviderEvent::PROVIDER_READY,
|
|
246
|
+
initialization_ready_handler,
|
|
247
|
+
)
|
|
248
|
+
end
|
|
249
|
+
return if cancelled
|
|
250
|
+
|
|
251
|
+
install_error_handler
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def cancel_initialization_if_shutdown!
|
|
255
|
+
cancelled = @initialization_mutex.synchronize do
|
|
256
|
+
@initializing = false if @shutdown
|
|
257
|
+
@shutdown
|
|
258
|
+
end
|
|
259
|
+
raise INITIALIZATION_CANCELLED_MESSAGE if cancelled
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def shutdown?
|
|
263
|
+
@initialization_mutex.synchronize { @shutdown }
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def install_initialization_ready_handler
|
|
267
|
+
configuration = @configuration
|
|
268
|
+
return false unless configuration
|
|
269
|
+
|
|
270
|
+
handler = @initialization_mutex.synchronize do
|
|
271
|
+
return false if @shutdown
|
|
272
|
+
return true if @initialization_ready_handler
|
|
273
|
+
|
|
274
|
+
@initialization_ready_handler = ->(details) { provider_ready(details) }
|
|
275
|
+
end
|
|
276
|
+
configuration.add_handler(::OpenFeature::SDK::ProviderEvent::PROVIDER_READY, handler)
|
|
277
|
+
|
|
278
|
+
cancelled = @initialization_mutex.synchronize { @shutdown }
|
|
279
|
+
configuration.remove_handler(::OpenFeature::SDK::ProviderEvent::PROVIDER_READY, handler) if cancelled
|
|
280
|
+
return false if cancelled
|
|
281
|
+
|
|
282
|
+
true
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def install_error_handler
|
|
286
|
+
configuration = @configuration
|
|
287
|
+
return unless configuration
|
|
288
|
+
|
|
289
|
+
handler = @initialization_mutex.synchronize do
|
|
290
|
+
return if @shutdown
|
|
291
|
+
|
|
292
|
+
@error_handler ||= ->(details) { provider_error(details) }
|
|
293
|
+
end
|
|
294
|
+
configuration.add_handler(::OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR, handler)
|
|
295
|
+
|
|
296
|
+
cancelled = @initialization_mutex.synchronize { @shutdown }
|
|
297
|
+
configuration.remove_handler(::OpenFeature::SDK::ProviderEvent::PROVIDER_ERROR, handler) if cancelled
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def provider_error(_details)
|
|
301
|
+
configuration = @configuration
|
|
302
|
+
return unless configuration
|
|
303
|
+
return unless configuration.send(:provider_state, self) == ::OpenFeature::SDK::ProviderState::ERROR
|
|
304
|
+
|
|
305
|
+
emit_ready = @initialization_mutex.synchronize do
|
|
306
|
+
if @shutdown
|
|
307
|
+
false
|
|
308
|
+
else
|
|
309
|
+
@provider_error_observed = true
|
|
310
|
+
if @ready_pending && !@ready_dispatch_claimed
|
|
311
|
+
@ready_dispatch_claimed = true
|
|
312
|
+
else
|
|
313
|
+
false
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
emit_event(::OpenFeature::SDK::ProviderEvent::PROVIDER_READY) if emit_ready
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def provider_ready(_details)
|
|
321
|
+
configuration = @configuration
|
|
322
|
+
return unless configuration
|
|
323
|
+
return unless configuration.send(:provider_state, self) == ::OpenFeature::SDK::ProviderState::READY
|
|
324
|
+
|
|
325
|
+
handler, emit_stale = @initialization_mutex.synchronize do
|
|
326
|
+
handler = @initialization_ready_handler
|
|
327
|
+
@initialization_ready_handler = nil
|
|
328
|
+
@initializing = false
|
|
329
|
+
@initialization_thread = nil
|
|
330
|
+
if @stale_pending && !@shutdown
|
|
331
|
+
@stale_pending = false
|
|
332
|
+
@stale_emitted = true
|
|
333
|
+
[handler, true]
|
|
334
|
+
else
|
|
335
|
+
[handler, false]
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
configuration.remove_handler(::OpenFeature::SDK::ProviderEvent::PROVIDER_READY, handler) if handler
|
|
339
|
+
emit_event(::OpenFeature::SDK::ProviderEvent::PROVIDER_STALE) if emit_stale
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def configuration_changed(event)
|
|
343
|
+
event_type = @initialization_mutex.synchronize do
|
|
344
|
+
if @shutdown
|
|
345
|
+
nil
|
|
346
|
+
elsif event == Component::CONFIGURATION_READY
|
|
347
|
+
if @initializing
|
|
348
|
+
@ready_pending = true
|
|
349
|
+
@stale_pending = false
|
|
350
|
+
nil
|
|
351
|
+
elsif @awaiting_ready_after_fork
|
|
352
|
+
@awaiting_ready_after_fork = false
|
|
353
|
+
@stale_emitted = false
|
|
354
|
+
::OpenFeature::SDK::ProviderEvent::PROVIDER_READY
|
|
355
|
+
elsif @initialization_failed && !@provider_error_observed
|
|
356
|
+
@stale_emitted = false
|
|
357
|
+
@ready_pending = true
|
|
358
|
+
nil
|
|
359
|
+
elsif @stale_emitted
|
|
360
|
+
@stale_emitted = false
|
|
361
|
+
::OpenFeature::SDK::ProviderEvent::PROVIDER_READY
|
|
362
|
+
elsif @initialization_failed
|
|
363
|
+
if @provider_error_observed && !@ready_dispatch_claimed
|
|
364
|
+
@ready_dispatch_claimed = true
|
|
365
|
+
::OpenFeature::SDK::ProviderEvent::PROVIDER_READY
|
|
366
|
+
else
|
|
367
|
+
@ready_pending = true
|
|
368
|
+
nil
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
elsif event == Component::CONFIGURATION_CHANGED && !@initializing
|
|
372
|
+
::OpenFeature::SDK::ProviderEvent::PROVIDER_CONFIGURATION_CHANGED
|
|
373
|
+
elsif event == Component::CONFIGURATION_LOST
|
|
374
|
+
@ready_pending = false
|
|
375
|
+
if @initializing
|
|
376
|
+
@stale_pending = true
|
|
377
|
+
nil
|
|
378
|
+
else
|
|
379
|
+
@stale_emitted = true
|
|
380
|
+
::OpenFeature::SDK::ProviderEvent::PROVIDER_STALE
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
emit_event(event_type) if event_type
|
|
386
|
+
end
|
|
387
|
+
|
|
111
388
|
def evaluate(flag_key, default_value:, expected_type:, evaluation_context:)
|
|
112
389
|
# Stamp evaluation entry time once, here on the eval thread. The EVP path uses this for
|
|
113
390
|
# accurate first/last_evaluation bounds instead of a later hook-fire clock read.
|
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
|
|
@@ -62,6 +62,20 @@ 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
|
+
|
|
65
79
|
def build_sampler(settings)
|
|
66
80
|
# A custom sampler is provided
|
|
67
81
|
if (sampler = settings.tracing.sampler)
|
|
@@ -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
|
|
|
@@ -14,9 +14,11 @@ module Datadog
|
|
|
14
14
|
|
|
15
15
|
def self.build(tracing_settings)
|
|
16
16
|
return unless tracing_settings.otel_thread_context_enabled
|
|
17
|
+
return unless Datadog::Core::LIBDATADOG_API_FAILURE.nil?
|
|
17
18
|
|
|
18
19
|
instance = new
|
|
19
|
-
instance
|
|
20
|
+
instance._native_enable
|
|
21
|
+
instance
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def initialize
|
|
@@ -65,10 +67,6 @@ module Datadog
|
|
|
65
67
|
false
|
|
66
68
|
end
|
|
67
69
|
|
|
68
|
-
def supported?
|
|
69
|
-
Datadog::Core::LIBDATADOG_API_FAILURE.nil? && _native_supported?
|
|
70
|
-
end
|
|
71
|
-
|
|
72
70
|
def update_from_trace_op(trace_op)
|
|
73
71
|
active_span = trace_op.active_span
|
|
74
72
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require_relative "../telemetry"
|
|
5
|
+
|
|
6
|
+
module Datadog
|
|
7
|
+
module Tracing
|
|
8
|
+
module Transport
|
|
9
|
+
module HTTP
|
|
10
|
+
class Telemetry < Datadog::Tracing::Transport::Telemetry
|
|
11
|
+
def initialize(client)
|
|
12
|
+
super(client, source: "ruby")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def batch(traces)
|
|
16
|
+
return unless @client.enabled? && @client.metrics_manager.enabled
|
|
17
|
+
|
|
18
|
+
Batch.new(self, traces)
|
|
19
|
+
rescue
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Batch
|
|
24
|
+
def initialize(reporter, traces)
|
|
25
|
+
@reporter = reporter
|
|
26
|
+
@remaining_chunks = traces.length
|
|
27
|
+
@remaining_spans = traces.sum(&:length)
|
|
28
|
+
@reporter.record(spans_enqueued_for_serialization: @remaining_spans)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def record_oversized_drop(span_count)
|
|
32
|
+
@remaining_chunks -= 1
|
|
33
|
+
@remaining_spans -= span_count
|
|
34
|
+
@reporter.record(chunks_dropped_payload_too_large: 1, spans_dropped_payload_too_large: span_count)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def serialization_failed
|
|
38
|
+
@reporter.record(chunks_dropped_serialization_error: @remaining_chunks,
|
|
39
|
+
spans_dropped_serialization_error: @remaining_spans,)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def sent(request, response, chunks, spans, bytes, fallback:)
|
|
43
|
+
@reporter.record(requests_count: 1)
|
|
44
|
+
return if fallback
|
|
45
|
+
|
|
46
|
+
@remaining_chunks -= chunks
|
|
47
|
+
@remaining_spans -= spans
|
|
48
|
+
observations = {} #: Hash[Symbol, Integer]
|
|
49
|
+
if (status = request.http_status)
|
|
50
|
+
observations[:responses_count] = 1
|
|
51
|
+
observations[:status_code] = status
|
|
52
|
+
end
|
|
53
|
+
success = status ? status.between?(200, 299) : request.http_success
|
|
54
|
+
if success
|
|
55
|
+
observations[:chunks_sent] = chunks
|
|
56
|
+
observations[:bytes_sent] = bytes
|
|
57
|
+
elsif status || success == false
|
|
58
|
+
observations[:errors_status_code] = 1 if status
|
|
59
|
+
observations[:chunks_dropped_send_failure] = chunks
|
|
60
|
+
observations[:spans_dropped_api_error] = spans
|
|
61
|
+
elsif response.is_a?(Core::Transport::InternalErrorResponse)
|
|
62
|
+
error = response.error
|
|
63
|
+
if error.is_a?(ArgumentError) || error.is_a?(TypeError)
|
|
64
|
+
observations[:chunks_dropped_serialization_error] = chunks
|
|
65
|
+
observations[:spans_dropped_serialization_error] = spans
|
|
66
|
+
else
|
|
67
|
+
observations[:chunks_dropped_send_failure] = chunks
|
|
68
|
+
observations[:spans_dropped_api_error] = spans
|
|
69
|
+
if error.is_a?(::Timeout::Error) || error.is_a?(Errno::ETIMEDOUT)
|
|
70
|
+
observations[:errors_timeout] = 1
|
|
71
|
+
elsif network_error?(error)
|
|
72
|
+
observations[:errors_network] = 1
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
@reporter.record(observations)
|
|
77
|
+
rescue
|
|
78
|
+
nil
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def network_error?(error)
|
|
84
|
+
error.is_a?(SystemCallError) || error.is_a?(IOError) || error.is_a?(SocketError) ||
|
|
85
|
+
error.is_a?(::Net::ProtocolError) || error.is_a?(::Net::HTTPBadResponse) ||
|
|
86
|
+
error.is_a?(::Net::HTTPHeaderSyntaxError) ||
|
|
87
|
+
(defined?(::OpenSSL::SSL::SSLError) && error.is_a?(::OpenSSL::SSL::SSLError))
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -51,6 +51,8 @@ module Datadog
|
|
|
51
51
|
|
|
52
52
|
# Query for response
|
|
53
53
|
http_response = super
|
|
54
|
+
# Delivery succeeded or failed before service-rate parsing can raise.
|
|
55
|
+
env.request.capture_status(http_response)
|
|
54
56
|
|
|
55
57
|
# Process the response
|
|
56
58
|
response_options = {trace_count: env.request.parcel.trace_count}.tap do |options|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../telemetry"
|
|
4
|
+
|
|
5
|
+
module Datadog
|
|
6
|
+
module Tracing
|
|
7
|
+
module Transport
|
|
8
|
+
module Native
|
|
9
|
+
class Telemetry < Datadog::Tracing::Transport::Telemetry
|
|
10
|
+
def initialize(client)
|
|
11
|
+
super(client, source: "libdatadog")
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -4,6 +4,7 @@ require "json"
|
|
|
4
4
|
require_relative "trace_formatter"
|
|
5
5
|
require_relative "statistics"
|
|
6
6
|
require_relative "span_events_negotiation"
|
|
7
|
+
require_relative "native/telemetry"
|
|
7
8
|
|
|
8
9
|
module Datadog
|
|
9
10
|
module Tracing
|
|
@@ -49,6 +50,16 @@ module Datadog
|
|
|
49
50
|
|
|
50
51
|
attr_reader :logger, :url
|
|
51
52
|
|
|
53
|
+
def telemetry=(client)
|
|
54
|
+
@send_mutex.synchronize do
|
|
55
|
+
if (reporter = @telemetry)
|
|
56
|
+
reporter.client = client
|
|
57
|
+
elsif @exporter
|
|
58
|
+
@telemetry = Telemetry.new(client)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
52
63
|
# @param agent_settings [Datadog::Core::Configuration::AgentSettingsResolver::AgentSettings]
|
|
53
64
|
# Agent connection settings, including the request timeout.
|
|
54
65
|
# @param logger [Logger]
|
|
@@ -58,6 +69,7 @@ module Datadog
|
|
|
58
69
|
end
|
|
59
70
|
|
|
60
71
|
@logger = logger
|
|
72
|
+
@telemetry = nil
|
|
61
73
|
|
|
62
74
|
# Serializes native sends and is held across a fork. See the
|
|
63
75
|
# fork-safety note below.
|
|
@@ -226,6 +238,7 @@ module Datadog
|
|
|
226
238
|
@exporter&._native_close
|
|
227
239
|
ensure
|
|
228
240
|
@fork_hooks = nil
|
|
241
|
+
@telemetry = nil
|
|
229
242
|
@exporter = nil
|
|
230
243
|
hooks.each do |stage, block|
|
|
231
244
|
Core::Utils::AtForkMonkeyPatch.remove_at_fork(stage, block)
|
|
@@ -285,7 +298,9 @@ module Datadog
|
|
|
285
298
|
exporter = @exporter
|
|
286
299
|
raise "Native transport has been closed" if exporter.nil?
|
|
287
300
|
|
|
288
|
-
exporter._native_send_traces(chunks, native_events_supported)
|
|
301
|
+
exporter._native_send_traces(chunks, native_events_supported) do |observations|
|
|
302
|
+
@telemetry&.record(observations)
|
|
303
|
+
end
|
|
289
304
|
end
|
|
290
305
|
|
|
291
306
|
# Update statistics from the response
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module Tracing
|
|
5
|
+
module Transport
|
|
6
|
+
class Telemetry
|
|
7
|
+
# Observation schema: Symbol keys with non-negative Integer values.
|
|
8
|
+
# COUNTS maps per-send count deltas to [metric name, additional tags].
|
|
9
|
+
# :bytes_sent is one successful payload's byte-size distribution sample;
|
|
10
|
+
# record each payload separately rather than summing its samples.
|
|
11
|
+
# :responses_count requires :status_code; omit both when the code is unknown.
|
|
12
|
+
# Missing and zero measurements emit nothing. All metrics use namespace
|
|
13
|
+
# "tracers" and common: true; trace_* metrics gain src_library:<source>, while
|
|
14
|
+
# span metrics keep their source-free tags.
|
|
15
|
+
COUNTS = {
|
|
16
|
+
requests_count: ["trace_api.requests", []],
|
|
17
|
+
errors_network: ["trace_api.errors", ["type:network"]],
|
|
18
|
+
errors_timeout: ["trace_api.errors", ["type:timeout"]],
|
|
19
|
+
errors_status_code: ["trace_api.errors", ["type:status_code"]],
|
|
20
|
+
chunks_sent: ["trace_chunks_sent", []],
|
|
21
|
+
chunks_dropped_serialization_error: ["trace_chunks_dropped", ["reason:serialization_error"]],
|
|
22
|
+
chunks_dropped_send_failure: ["trace_chunks_dropped", ["reason:send_failure"]],
|
|
23
|
+
chunks_dropped_payload_too_large: ["trace_chunks_dropped", ["reason:payload_too_large"]],
|
|
24
|
+
spans_enqueued_for_serialization: ["spans_enqueued_for_serialization", []],
|
|
25
|
+
spans_dropped_serialization_error: ["spans_dropped", ["reason:serialization_error"]],
|
|
26
|
+
spans_dropped_api_error: ["spans_dropped", ["reason:api_error"]],
|
|
27
|
+
spans_dropped_payload_too_large: ["spans_dropped", ["reason:payload_too_large"]],
|
|
28
|
+
}.freeze #: Hash[Symbol, [String, Array[String]]]
|
|
29
|
+
|
|
30
|
+
attr_writer :client
|
|
31
|
+
|
|
32
|
+
def initialize(client, source:)
|
|
33
|
+
@client = client
|
|
34
|
+
@source_tags = ["src_library:#{source}"]
|
|
35
|
+
@counts = COUNTS.map do |key, (name, tags)|
|
|
36
|
+
[key, name, name.start_with?("trace_") ? @source_tags + tags : tags]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def record(observations)
|
|
41
|
+
client = @client
|
|
42
|
+
return unless client.enabled?
|
|
43
|
+
|
|
44
|
+
@counts.each do |key, name, tags|
|
|
45
|
+
value = observations.fetch(key, 0)
|
|
46
|
+
client.inc("tracers", name, value, tags: tags) if value > 0
|
|
47
|
+
end
|
|
48
|
+
bytes = observations.fetch(:bytes_sent, 0)
|
|
49
|
+
client.distribution("tracers", "trace_api.bytes", bytes, tags: @source_tags) if bytes > 0
|
|
50
|
+
responses = observations.fetch(:responses_count, 0)
|
|
51
|
+
if responses > 0
|
|
52
|
+
client.inc("tracers", "trace_api.responses", responses,
|
|
53
|
+
tags: @source_tags + ["status_code:#{observations.fetch(:status_code)}"],)
|
|
54
|
+
end
|
|
55
|
+
nil
|
|
56
|
+
rescue
|
|
57
|
+
nil
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|