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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -2
  3. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +4 -0
  4. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +2 -0
  5. data/ext/datadog_profiling_native_extension/profiling.c +6 -0
  6. data/ext/libdatadog_api/di.c +30 -5
  7. data/ext/libdatadog_api/extconf.rb +2 -0
  8. data/ext/libdatadog_api/otel_thread_context.c +20 -14
  9. data/ext/libdatadog_api/process_discovery.c +15 -1
  10. data/ext/libdatadog_api/trace_exporter.c +105 -25
  11. data/ext/libdatadog_extconf_helpers.rb +1 -1
  12. data/lib/datadog/core/configuration/components.rb +29 -8
  13. data/lib/datadog/core/configuration/supported_configurations.rb +5 -2
  14. data/lib/datadog/core/process_discovery.rb +5 -1
  15. data/lib/datadog/core/remote/client/capabilities.rb +34 -29
  16. data/lib/datadog/core/remote/component.rb +28 -12
  17. data/lib/datadog/core/remote/dispatcher.rb +13 -3
  18. data/lib/datadog/di/boot.rb +1 -0
  19. data/lib/datadog/di/capture_expression_evaluator/result.rb +30 -0
  20. data/lib/datadog/di/capture_expression_evaluator.rb +39 -9
  21. data/lib/datadog/di/component.rb +38 -3
  22. data/lib/datadog/di/configuration.rb +53 -2
  23. data/lib/datadog/di/context.rb +7 -2
  24. data/lib/datadog/di/contrib/active_record.rb +8 -2
  25. data/lib/datadog/di/evaluation_failure.rb +28 -0
  26. data/lib/datadog/di/guardrails_telemetry/reason.rb +87 -0
  27. data/lib/datadog/di/guardrails_telemetry.rb +203 -0
  28. data/lib/datadog/di/instrumenter.rb +65 -15
  29. data/lib/datadog/di/logger.rb +30 -8
  30. data/lib/datadog/di/probe_manager.rb +48 -5
  31. data/lib/datadog/di/probe_notification_builder.rb +28 -26
  32. data/lib/datadog/di/probe_notifier_worker.rb +56 -6
  33. data/lib/datadog/di/serializer.rb +130 -54
  34. data/lib/datadog/di/snapshot_event.rb +38 -0
  35. data/lib/datadog/di/transport/http.rb +13 -3
  36. data/lib/datadog/di/transport/input.rb +32 -7
  37. data/lib/datadog/open_feature/activation.rb +268 -0
  38. data/lib/datadog/open_feature/component.rb +31 -15
  39. data/lib/datadog/open_feature/provider.rb +282 -5
  40. data/lib/datadog/open_feature.rb +31 -1
  41. data/lib/datadog/tracing/component.rb +14 -0
  42. data/lib/datadog/tracing/configuration/settings.rb +3 -1
  43. data/lib/datadog/tracing/otel_thread_context.rb +3 -5
  44. data/lib/datadog/tracing/transport/http/telemetry.rb +94 -0
  45. data/lib/datadog/tracing/transport/http/traces.rb +2 -0
  46. data/lib/datadog/tracing/transport/native/telemetry.rb +17 -0
  47. data/lib/datadog/tracing/transport/native.rb +16 -1
  48. data/lib/datadog/tracing/transport/telemetry.rb +62 -0
  49. data/lib/datadog/tracing/transport/traces.rb +82 -15
  50. data/lib/datadog/version.rb +1 -1
  51. metadata +16 -7
@@ -0,0 +1,268 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "agentless/configuration_source"
4
+ require_relative "component"
5
+ require_relative "configuration/agentless_endpoint"
6
+ require_relative "configuration/source"
7
+ require_relative "remote"
8
+
9
+ module Datadog
10
+ module OpenFeature
11
+ # Owns eager Remote Configuration and lazy agentless delivery activation.
12
+ class Activation
13
+ attr_reader :component, :failure
14
+
15
+ def initialize(settings, agent_settings, remote, logger:, telemetry:)
16
+ @settings = settings
17
+ @agent_settings = agent_settings
18
+ @remote = remote
19
+ @logger = logger
20
+ @telemetry = telemetry
21
+ @component = nil
22
+ @configuration_source = nil
23
+ @delivery_source = nil
24
+ @remote_receivers = nil
25
+ @failure = nil
26
+ @providers = {}.compare_by_identity
27
+ @activated = false
28
+ @delivery_started = false
29
+ @shutdown = false
30
+ @mutex = Mutex.new
31
+ end
32
+
33
+ def activate(provider)
34
+ @mutex.synchronize do
35
+ return if @shutdown
36
+ return unless open_feature_available?
37
+
38
+ @providers[provider] = true
39
+ return @component if @activated && @delivery_started
40
+ return if @activated
41
+
42
+ resolution = Configuration::Source.resolve(@settings, logger: @logger)
43
+ activate_delivery(resolution)
44
+ end
45
+ end
46
+
47
+ def startup!
48
+ start!
49
+ OpenFeature.reattach(self)
50
+ rescue => e
51
+ # Feature Flags is optional and must never interrupt library startup.
52
+ report_failure(e, "Feature Flags delivery failed to start")
53
+ end
54
+
55
+ def start!
56
+ @mutex.synchronize do
57
+ return if @shutdown || @activated
58
+ return unless open_feature_available?
59
+
60
+ resolution = Configuration::Source.resolve(@settings, logger: @logger)
61
+ return unless resolution.enabled? && resolution.source == Configuration::Source::REMOTE_CONFIG
62
+
63
+ activate_delivery(resolution)
64
+ end
65
+ end
66
+
67
+ def activated?
68
+ @mutex.synchronize { @activated }
69
+ end
70
+
71
+ def providers
72
+ @mutex.synchronize { @providers.keys }
73
+ end
74
+
75
+ def after_fork
76
+ inherited_providers, component = @mutex.synchronize do
77
+ @shutdown ? [[], nil] : [@providers.keys, @component]
78
+ end
79
+ inherited_providers.each { |provider| provider.send(:after_fork, component) }
80
+
81
+ configuration_source = @mutex.synchronize do
82
+ if @shutdown || !@delivery_started
83
+ nil
84
+ else
85
+ (@delivery_source == Configuration::Source::REMOTE_CONFIG) ? @remote : @configuration_source
86
+ end
87
+ end
88
+
89
+ configuration_source&.start
90
+ nil
91
+ rescue => e
92
+ # Feature Flags is optional and must never interrupt other post-fork handlers.
93
+ report_failure(e, "Feature Flags delivery failed to restart after fork")
94
+ end
95
+
96
+ def deactivate(provider)
97
+ configuration_source, component = @mutex.synchronize do
98
+ return if @shutdown
99
+ return unless @providers.delete(provider)
100
+ return unless @providers.empty?
101
+ if @delivery_source == Configuration::Source::REMOTE_CONFIG && @delivery_started
102
+ # Remote Configuration is process-scoped and may already hold configuration needed by the next provider.
103
+ [nil, nil]
104
+ else
105
+ previous_source = @configuration_source
106
+ previous_component = @component
107
+ @component = nil
108
+ @configuration_source = nil
109
+ @delivery_source = nil
110
+ @activated = false
111
+ @delivery_started = false
112
+ @failure = nil
113
+ [previous_source, previous_component]
114
+ end
115
+ end
116
+
117
+ configuration_source&.stop
118
+ component&.shutdown!
119
+ end
120
+
121
+ def shutdown!
122
+ configuration_source, component = @mutex.synchronize do
123
+ return if @shutdown
124
+
125
+ @shutdown = true
126
+ [@configuration_source, @component]
127
+ end
128
+
129
+ configuration_source&.stop
130
+ configuration_received = @mutex.synchronize do
131
+ !@providers.empty? && !!component&.configuration_received?
132
+ end
133
+ configuration_changed(Component::CONFIGURATION_LOST) if configuration_received
134
+ component&.shutdown!
135
+ end
136
+
137
+ private
138
+
139
+ def report_failure(error, description)
140
+ @logger.error("#{description}: #{error.class}: #{error.message}")
141
+ @telemetry.report(error, description: description)
142
+ end
143
+
144
+ def open_feature_available?
145
+ @settings.respond_to?(:open_feature) && @settings.respond_to?(:feature_flags)
146
+ end
147
+
148
+ def activate_delivery(resolution)
149
+ @activated = true
150
+ unless resolution.enabled?
151
+ @failure = "Feature Flags are disabled"
152
+ return
153
+ end
154
+
155
+ component = Component.build(
156
+ @settings,
157
+ @agent_settings,
158
+ resolution: resolution,
159
+ on_configuration_change: ->(event) { configuration_changed(event) },
160
+ logger: @logger,
161
+ telemetry: @telemetry,
162
+ )
163
+ unless component
164
+ @failure = "Feature Flags are unavailable on this runtime"
165
+ return
166
+ end
167
+
168
+ @component = component
169
+ @delivery_source = resolution.source
170
+ @delivery_started = start_delivery(resolution.source)
171
+ return component if @delivery_started
172
+
173
+ component.shutdown!
174
+ @component = nil
175
+ @delivery_source = nil
176
+ nil
177
+ rescue
178
+ configuration_source = @configuration_source
179
+ failed_component = @component
180
+ @component = nil
181
+ @configuration_source = nil
182
+ @delivery_source = nil
183
+ @activated = false
184
+ @delivery_started = false
185
+ @failure = nil
186
+ begin
187
+ configuration_source&.stop
188
+ ensure
189
+ failed_component&.shutdown!
190
+ end
191
+ raise
192
+ end
193
+
194
+ def start_delivery(source)
195
+ case source
196
+ when Configuration::Source::AGENTLESS
197
+ start_agentless
198
+ when Configuration::Source::REMOTE_CONFIG
199
+ start_remote_configuration
200
+ else
201
+ @failure = "Feature Flags configuration source is unavailable"
202
+ false
203
+ end
204
+ end
205
+
206
+ def start_agentless
207
+ component = @component
208
+ return false unless component
209
+
210
+ endpoint = Configuration::AgentlessEndpoint.build(
211
+ site: @settings.site,
212
+ environment: @settings.env,
213
+ base_url: @settings.feature_flags.agentless.base_url,
214
+ logger: @logger,
215
+ )
216
+ unless endpoint
217
+ @failure = "Feature Flags agentless endpoint is unavailable"
218
+ return false
219
+ end
220
+
221
+ configuration_source = Agentless::ConfigurationSource.build(
222
+ @settings,
223
+ endpoint: endpoint,
224
+ apply: ->(configuration) { component.reconfigure!(configuration) },
225
+ logger: @logger,
226
+ )
227
+ unless configuration_source
228
+ @failure = "Feature Flags agentless delivery could not start"
229
+ return false
230
+ end
231
+
232
+ @configuration_source = configuration_source
233
+ started = configuration_source.start
234
+ @failure = "Feature Flags agentless delivery could not start" unless started
235
+ started
236
+ end
237
+
238
+ def start_remote_configuration
239
+ remote = @remote
240
+ unless remote
241
+ @failure = "Feature Flags Remote Configuration is unavailable"
242
+ @logger.warn(
243
+ "#{@failure}. To enable Remote Configuration, " \
244
+ "see https://docs.datadoghq.com/remote_configuration/."
245
+ )
246
+ return false
247
+ end
248
+
249
+ receivers = @remote_receivers ||= Remote.receivers(
250
+ @telemetry,
251
+ component_provider: -> { @mutex.synchronize { @component } },
252
+ )
253
+ remote.register(
254
+ capabilities: Remote.capabilities,
255
+ products: Remote.products,
256
+ receivers: receivers,
257
+ )
258
+ remote.start
259
+ true
260
+ end
261
+
262
+ def configuration_changed(event)
263
+ providers = @mutex.synchronize { @providers.keys }
264
+ providers.each { |provider| provider.send(:configuration_changed, event) }
265
+ end
266
+ end
267
+ end
268
+ end
@@ -3,6 +3,7 @@
3
3
  require_relative "transport"
4
4
  require_relative "evaluation_engine"
5
5
  require_relative "../core/utils/time"
6
+ require_relative "configuration/source"
6
7
  require_relative "exposures/buffer"
7
8
  require_relative "exposures/worker"
8
9
  require_relative "exposures/deduplicator"
@@ -20,19 +21,13 @@ module Datadog
20
21
  CONFIGURATION_READY = :ready
21
22
  CONFIGURATION_TIMEOUT = :timeout
22
23
  CONFIGURATION_SHUTDOWN = :shutdown
24
+ CONFIGURATION_CHANGED = :changed
25
+ CONFIGURATION_LOST = :lost
23
26
 
24
27
  attr_reader :engine, :flag_eval_metrics_hook, :flag_eval_evp_hook, :span_enrichment_hook
25
28
 
26
- def self.build(settings, agent_settings, logger:, telemetry:)
27
- return unless settings.respond_to?(:open_feature) && settings.open_feature.enabled
28
-
29
- unless settings.respond_to?(:remote) && settings.remote.enabled
30
- message = "OpenFeature could not be enabled as Remote Configuration is currently disabled. " \
31
- "To enable Remote Configuration, see https://docs.datadoghq.com/remote_configuration/."
32
-
33
- logger.warn(message)
34
- return
35
- end
29
+ def self.build(settings, agent_settings, resolution:, on_configuration_change:, logger:, telemetry:)
30
+ return unless resolution.enabled?
36
31
 
37
32
  unless ["ruby", "truffleruby"].include?(RUBY_ENGINE)
38
33
  message = "OpenFeature could not be enabled as MRI is required, " \
@@ -51,10 +46,16 @@ module Datadog
51
46
  return
52
47
  end
53
48
 
54
- new(settings, agent_settings, logger: logger, telemetry: telemetry)
49
+ new(
50
+ settings,
51
+ agent_settings,
52
+ on_configuration_change: on_configuration_change,
53
+ logger: logger,
54
+ telemetry: telemetry,
55
+ )
55
56
  end
56
57
 
57
- def initialize(settings, agent_settings, logger:, telemetry:)
58
+ def initialize(settings, agent_settings, logger:, telemetry:, on_configuration_change: nil)
58
59
  transport = Transport::HTTP.build(agent_settings: agent_settings, logger: logger)
59
60
  @worker = Exposures::Worker.new(settings: settings, transport: transport, telemetry: telemetry, logger: logger)
60
61
 
@@ -65,6 +66,7 @@ module Datadog
65
66
  @logger = logger
66
67
  @settings = settings
67
68
  @agent_settings = agent_settings
69
+ @on_configuration_change = on_configuration_change
68
70
  @flag_eval_metrics_hook = create_flag_eval_metrics_hook
69
71
  @flag_eval_evp_hook = create_flag_eval_evp_hook
70
72
  @span_enrichment_hook = create_span_enrichment_hook
@@ -77,7 +79,7 @@ module Datadog
77
79
  end
78
80
 
79
81
  def reconfigure!(configuration)
80
- @reconfiguration_mutex.synchronize do
82
+ event = @reconfiguration_mutex.synchronize do
81
83
  return if configuration_shutdown?
82
84
 
83
85
  @engine.reconfigure!(configuration)
@@ -85,19 +87,28 @@ module Datadog
85
87
  @configuration_mutex.synchronize do
86
88
  return if @configuration_shutdown
87
89
 
90
+ previously_received = @configuration_received
88
91
  @configuration_received = !configuration.nil?
89
92
  @configuration_condition.broadcast
93
+
94
+ if @configuration_received
95
+ previously_received ? CONFIGURATION_CHANGED : CONFIGURATION_READY
96
+ elsif previously_received
97
+ CONFIGURATION_LOST
98
+ end
90
99
  end
91
100
  end
101
+
102
+ @on_configuration_change&.call(event) if event
92
103
  end
93
104
 
94
- def wait_for_configuration
105
+ def wait_for_configuration(&cancelled)
95
106
  timeout_seconds = @settings.feature_flags.initialization_timeout_ms / 1000.0
96
107
  deadline = Core::Utils::Time.get_time + timeout_seconds
97
108
 
98
109
  @configuration_mutex.synchronize do
99
110
  loop do
100
- return CONFIGURATION_SHUTDOWN if @configuration_shutdown
111
+ return CONFIGURATION_SHUTDOWN if @configuration_shutdown || cancelled&.call
101
112
  return CONFIGURATION_READY if @configuration_received
102
113
 
103
114
  remaining = deadline - Core::Utils::Time.get_time
@@ -108,6 +119,11 @@ module Datadog
108
119
  end
109
120
  end
110
121
 
122
+ def wake_configuration_waiters
123
+ @configuration_mutex.synchronize { @configuration_condition.broadcast }
124
+ nil
125
+ end
126
+
111
127
  def configuration_received?
112
128
  @configuration_mutex.synchronize { @configuration_received }
113
129
  end