datadog 2.22.0 → 2.24.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 (212) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +100 -1
  3. data/ext/LIBDATADOG_DEVELOPMENT.md +1 -58
  4. data/ext/datadog_profiling_native_extension/collectors_stack.c +21 -5
  5. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +239 -0
  6. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +1 -1
  7. data/ext/datadog_profiling_native_extension/extconf.rb +9 -4
  8. data/ext/datadog_profiling_native_extension/heap_recorder.c +1 -1
  9. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +12 -0
  10. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +4 -0
  11. data/ext/datadog_profiling_native_extension/profiling.c +2 -0
  12. data/ext/libdatadog_api/datadog_ruby_common.h +1 -1
  13. data/ext/libdatadog_api/feature_flags.c +554 -0
  14. data/ext/libdatadog_api/feature_flags.h +5 -0
  15. data/ext/libdatadog_api/init.c +2 -0
  16. data/ext/libdatadog_api/library_config.c +12 -11
  17. data/ext/libdatadog_extconf_helpers.rb +1 -1
  18. data/lib/datadog/appsec/api_security/route_extractor.rb +23 -6
  19. data/lib/datadog/appsec/api_security/sampler.rb +7 -4
  20. data/lib/datadog/appsec/assets/blocked.html +8 -0
  21. data/lib/datadog/appsec/assets/blocked.json +1 -1
  22. data/lib/datadog/appsec/assets/blocked.text +3 -1
  23. data/lib/datadog/appsec/assets.rb +1 -1
  24. data/lib/datadog/appsec/context.rb +2 -1
  25. data/lib/datadog/appsec/remote.rb +5 -9
  26. data/lib/datadog/appsec/response.rb +18 -4
  27. data/lib/datadog/appsec/security_engine/result.rb +2 -1
  28. data/lib/datadog/core/configuration/components.rb +30 -3
  29. data/lib/datadog/core/configuration/config_helper.rb +2 -2
  30. data/lib/datadog/core/configuration/deprecations.rb +2 -2
  31. data/lib/datadog/core/configuration/option_definition.rb +4 -2
  32. data/lib/datadog/core/configuration/options.rb +8 -5
  33. data/lib/datadog/core/configuration/settings.rb +28 -3
  34. data/lib/datadog/core/configuration/supported_configurations.rb +332 -302
  35. data/lib/datadog/core/ddsketch.rb +0 -2
  36. data/lib/datadog/core/environment/cgroup.rb +52 -25
  37. data/lib/datadog/core/environment/container.rb +140 -46
  38. data/lib/datadog/core/environment/ext.rb +7 -0
  39. data/lib/datadog/core/environment/process.rb +87 -0
  40. data/lib/datadog/core/feature_flags.rb +61 -0
  41. data/lib/datadog/core/rate_limiter.rb +9 -1
  42. data/lib/datadog/core/remote/client/capabilities.rb +7 -0
  43. data/lib/datadog/core/remote/client.rb +14 -6
  44. data/lib/datadog/core/remote/component.rb +6 -4
  45. data/lib/datadog/core/remote/configuration/content.rb +15 -2
  46. data/lib/datadog/core/remote/configuration/digest.rb +14 -7
  47. data/lib/datadog/core/remote/configuration/repository.rb +1 -1
  48. data/lib/datadog/core/remote/configuration/target.rb +13 -6
  49. data/lib/datadog/core/remote/transport/config.rb +4 -25
  50. data/lib/datadog/core/remote/transport/http/config.rb +10 -50
  51. data/lib/datadog/core/remote/transport/http/negotiation.rb +14 -44
  52. data/lib/datadog/core/remote/transport/http.rb +15 -24
  53. data/lib/datadog/core/remote/transport/negotiation.rb +8 -33
  54. data/lib/datadog/core/remote/worker.rb +25 -37
  55. data/lib/datadog/core/tag_builder.rb +0 -4
  56. data/lib/datadog/core/tag_normalizer.rb +84 -0
  57. data/lib/datadog/core/telemetry/component.rb +59 -16
  58. data/lib/datadog/core/telemetry/event/app_started.rb +86 -49
  59. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +27 -4
  60. data/lib/datadog/core/telemetry/logger.rb +2 -2
  61. data/lib/datadog/core/telemetry/logging.rb +2 -8
  62. data/lib/datadog/core/telemetry/metrics_manager.rb +9 -0
  63. data/lib/datadog/core/telemetry/request.rb +17 -3
  64. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +3 -34
  65. data/lib/datadog/core/telemetry/transport/http.rb +21 -16
  66. data/lib/datadog/core/telemetry/transport/telemetry.rb +3 -11
  67. data/lib/datadog/core/telemetry/worker.rb +88 -32
  68. data/lib/datadog/core/transport/ext.rb +2 -0
  69. data/lib/datadog/core/transport/http/api/endpoint.rb +9 -4
  70. data/lib/datadog/core/transport/http/api/instance.rb +4 -21
  71. data/lib/datadog/core/transport/http/builder.rb +9 -5
  72. data/lib/datadog/core/transport/http/client.rb +80 -0
  73. data/lib/datadog/core/transport/http.rb +22 -19
  74. data/lib/datadog/core/transport/response.rb +9 -0
  75. data/lib/datadog/core/transport/transport.rb +90 -0
  76. data/lib/datadog/core/utils/array.rb +29 -0
  77. data/lib/datadog/{appsec/api_security → core/utils}/lru_cache.rb +10 -21
  78. data/lib/datadog/core/utils/network.rb +3 -1
  79. data/lib/datadog/core/utils/only_once_successful.rb +8 -2
  80. data/lib/datadog/core/utils/time.rb +1 -1
  81. data/lib/datadog/core/utils.rb +2 -0
  82. data/lib/datadog/core/workers/async.rb +10 -1
  83. data/lib/datadog/core/workers/interval_loop.rb +44 -3
  84. data/lib/datadog/core/workers/polling.rb +2 -0
  85. data/lib/datadog/core/workers/queue.rb +100 -1
  86. data/lib/datadog/data_streams/configuration/settings.rb +49 -0
  87. data/lib/datadog/data_streams/configuration.rb +11 -0
  88. data/lib/datadog/data_streams/ext.rb +11 -0
  89. data/lib/datadog/data_streams/extensions.rb +16 -0
  90. data/lib/datadog/data_streams/pathway_context.rb +169 -0
  91. data/lib/datadog/data_streams/processor.rb +509 -0
  92. data/lib/datadog/data_streams/transport/http/stats.rb +52 -0
  93. data/lib/datadog/data_streams/transport/http.rb +40 -0
  94. data/lib/datadog/data_streams/transport/stats.rb +46 -0
  95. data/lib/datadog/data_streams.rb +100 -0
  96. data/lib/datadog/di/component.rb +0 -16
  97. data/lib/datadog/di/contrib/active_record.rb +31 -5
  98. data/lib/datadog/di/el/compiler.rb +8 -4
  99. data/lib/datadog/di/el/evaluator.rb +1 -1
  100. data/lib/datadog/di/error.rb +9 -0
  101. data/lib/datadog/di/instrumenter.rb +93 -34
  102. data/lib/datadog/di/probe.rb +20 -0
  103. data/lib/datadog/di/probe_builder.rb +2 -1
  104. data/lib/datadog/di/probe_manager.rb +47 -33
  105. data/lib/datadog/di/probe_notification_builder.rb +77 -25
  106. data/lib/datadog/di/proc_responder.rb +32 -0
  107. data/lib/datadog/di/remote.rb +89 -84
  108. data/lib/datadog/di/transport/diagnostics.rb +8 -36
  109. data/lib/datadog/di/transport/http/diagnostics.rb +1 -33
  110. data/lib/datadog/di/transport/http/input.rb +1 -33
  111. data/lib/datadog/di/transport/http.rb +32 -17
  112. data/lib/datadog/di/transport/input.rb +67 -34
  113. data/lib/datadog/di.rb +61 -5
  114. data/lib/datadog/open_feature/component.rb +60 -0
  115. data/lib/datadog/open_feature/configuration.rb +27 -0
  116. data/lib/datadog/open_feature/evaluation_engine.rb +70 -0
  117. data/lib/datadog/open_feature/exposures/batch_builder.rb +32 -0
  118. data/lib/datadog/open_feature/exposures/buffer.rb +43 -0
  119. data/lib/datadog/open_feature/exposures/deduplicator.rb +30 -0
  120. data/lib/datadog/open_feature/exposures/event.rb +60 -0
  121. data/lib/datadog/open_feature/exposures/reporter.rb +40 -0
  122. data/lib/datadog/open_feature/exposures/worker.rb +116 -0
  123. data/lib/datadog/open_feature/ext.rb +14 -0
  124. data/lib/datadog/open_feature/native_evaluator.rb +38 -0
  125. data/lib/datadog/open_feature/noop_evaluator.rb +26 -0
  126. data/lib/datadog/open_feature/provider.rb +141 -0
  127. data/lib/datadog/open_feature/remote.rb +67 -0
  128. data/lib/datadog/open_feature/resolution_details.rb +35 -0
  129. data/lib/datadog/open_feature/transport.rb +70 -0
  130. data/lib/datadog/open_feature.rb +19 -0
  131. data/lib/datadog/opentelemetry/api/baggage.rb +1 -1
  132. data/lib/datadog/opentelemetry/configuration/settings.rb +159 -0
  133. data/lib/datadog/opentelemetry/metrics.rb +117 -0
  134. data/lib/datadog/opentelemetry/sdk/configurator.rb +25 -1
  135. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +35 -0
  136. data/lib/datadog/opentelemetry.rb +3 -0
  137. data/lib/datadog/profiling/collectors/code_provenance.rb +41 -7
  138. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +1 -1
  139. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +1 -1
  140. data/lib/datadog/profiling/collectors/info.rb +2 -1
  141. data/lib/datadog/profiling/component.rb +12 -11
  142. data/lib/datadog/profiling/http_transport.rb +4 -1
  143. data/lib/datadog/profiling/profiler.rb +4 -0
  144. data/lib/datadog/profiling/tag_builder.rb +36 -3
  145. data/lib/datadog/profiling.rb +1 -2
  146. data/lib/datadog/single_step_instrument.rb +1 -1
  147. data/lib/datadog/tracing/configuration/ext.rb +9 -0
  148. data/lib/datadog/tracing/configuration/settings.rb +74 -0
  149. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +4 -4
  150. data/lib/datadog/tracing/contrib/action_pack/utils.rb +1 -2
  151. data/lib/datadog/tracing/contrib/active_job/log_injection.rb +21 -7
  152. data/lib/datadog/tracing/contrib/active_job/patcher.rb +5 -1
  153. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +4 -2
  154. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +4 -1
  155. data/lib/datadog/tracing/contrib/excon/configuration/settings.rb +11 -3
  156. data/lib/datadog/tracing/contrib/extensions.rb +10 -2
  157. data/lib/datadog/tracing/contrib/faraday/configuration/settings.rb +11 -7
  158. data/lib/datadog/tracing/contrib/grape/configuration/settings.rb +7 -3
  159. data/lib/datadog/tracing/contrib/graphql/unified_trace.rb +22 -17
  160. data/lib/datadog/tracing/contrib/http/configuration/settings.rb +11 -3
  161. data/lib/datadog/tracing/contrib/httpclient/configuration/settings.rb +11 -3
  162. data/lib/datadog/tracing/contrib/httprb/configuration/settings.rb +11 -3
  163. data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +66 -0
  164. data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +66 -0
  165. data/lib/datadog/tracing/contrib/kafka/patcher.rb +14 -0
  166. data/lib/datadog/tracing/contrib/karafka/framework.rb +30 -0
  167. data/lib/datadog/tracing/contrib/karafka/monitor.rb +11 -0
  168. data/lib/datadog/tracing/contrib/karafka/patcher.rb +35 -4
  169. data/lib/datadog/tracing/contrib/rack/middlewares.rb +59 -27
  170. data/lib/datadog/tracing/contrib/rack/route_inference.rb +53 -0
  171. data/lib/datadog/tracing/contrib/rails/middlewares.rb +2 -2
  172. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +4 -1
  173. data/lib/datadog/tracing/contrib/roda/instrumentation.rb +3 -1
  174. data/lib/datadog/tracing/contrib/sinatra/tracer_middleware.rb +3 -1
  175. data/lib/datadog/tracing/contrib/status_range_matcher.rb +9 -1
  176. data/lib/datadog/tracing/contrib/utils/quantization/hash.rb +3 -1
  177. data/lib/datadog/tracing/contrib/waterdrop/configuration/settings.rb +27 -0
  178. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +48 -0
  179. data/lib/datadog/tracing/contrib/waterdrop/ext.rb +17 -0
  180. data/lib/datadog/tracing/contrib/waterdrop/integration.rb +43 -0
  181. data/lib/datadog/tracing/contrib/waterdrop/middleware.rb +46 -0
  182. data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +49 -0
  183. data/lib/datadog/tracing/contrib/waterdrop/producer.rb +50 -0
  184. data/lib/datadog/tracing/contrib/waterdrop.rb +37 -0
  185. data/lib/datadog/tracing/contrib.rb +1 -0
  186. data/lib/datadog/tracing/diagnostics/environment_logger.rb +1 -1
  187. data/lib/datadog/tracing/metadata/ext.rb +1 -1
  188. data/lib/datadog/tracing/remote.rb +1 -9
  189. data/lib/datadog/tracing/span_event.rb +2 -2
  190. data/lib/datadog/tracing/span_operation.rb +9 -4
  191. data/lib/datadog/tracing/trace_operation.rb +44 -6
  192. data/lib/datadog/tracing/tracer.rb +42 -16
  193. data/lib/datadog/tracing/transport/http/client.rb +12 -26
  194. data/lib/datadog/tracing/transport/http/traces.rb +2 -50
  195. data/lib/datadog/tracing/transport/http.rb +15 -9
  196. data/lib/datadog/tracing/transport/io/client.rb +1 -1
  197. data/lib/datadog/tracing/transport/trace_formatter.rb +11 -0
  198. data/lib/datadog/tracing/transport/traces.rb +9 -71
  199. data/lib/datadog/tracing/workers/trace_writer.rb +5 -0
  200. data/lib/datadog/tracing/writer.rb +1 -0
  201. data/lib/datadog/version.rb +2 -2
  202. data/lib/datadog.rb +2 -0
  203. metadata +78 -21
  204. data/lib/datadog/core/remote/transport/http/api.rb +0 -53
  205. data/lib/datadog/core/remote/transport/http/client.rb +0 -49
  206. data/lib/datadog/core/telemetry/transport/http/api.rb +0 -43
  207. data/lib/datadog/core/telemetry/transport/http/client.rb +0 -49
  208. data/lib/datadog/core/transport/http/api/spec.rb +0 -36
  209. data/lib/datadog/di/transport/http/api.rb +0 -42
  210. data/lib/datadog/di/transport/http/client.rb +0 -47
  211. data/lib/datadog/opentelemetry/api/baggage.rbs +0 -26
  212. data/lib/datadog/tracing/transport/http/api.rb +0 -44
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../core/utils/time'
4
+ require_relative '../../core/workers/queue'
5
+ require_relative '../../core/workers/polling'
6
+
7
+ require_relative 'buffer'
8
+ require_relative 'batch_builder'
9
+
10
+ module Datadog
11
+ module OpenFeature
12
+ module Exposures
13
+ # This class is responsible for sending exposures to the Agent
14
+ class Worker
15
+ include Core::Workers::Queue
16
+ include Core::Workers::Polling
17
+
18
+ GRACEFUL_SHUTDOWN_EXTRA_SECONDS = 5
19
+ GRACEFUL_SHUTDOWN_WAIT_INTERVAL_SECONDS = 0.5
20
+
21
+ DEFAULT_FLUSH_INTERVAL_SECONDS = 30
22
+ DEFAULT_BUFFER_LIMIT = Buffer::DEFAULT_LIMIT
23
+
24
+ def initialize(
25
+ settings:,
26
+ transport:,
27
+ telemetry:,
28
+ logger:,
29
+ flush_interval_seconds: DEFAULT_FLUSH_INTERVAL_SECONDS,
30
+ buffer_limit: DEFAULT_BUFFER_LIMIT
31
+ )
32
+ @logger = logger
33
+ @transport = transport
34
+ @telemetry = telemetry
35
+ @batch_builder = BatchBuilder.new(settings)
36
+ @buffer_limit = buffer_limit
37
+
38
+ self.buffer = Buffer.new(buffer_limit)
39
+ self.fork_policy = Core::Workers::Async::Thread::FORK_POLICY_RESTART
40
+ self.loop_base_interval = flush_interval_seconds
41
+ self.enabled = true
42
+ end
43
+
44
+ def start
45
+ return if !enabled? || running?
46
+
47
+ perform
48
+ end
49
+
50
+ def stop(force_stop = false, timeout = Core::Workers::Polling::DEFAULT_SHUTDOWN_TIMEOUT)
51
+ buffer.close if running?
52
+
53
+ super
54
+ end
55
+
56
+ def enqueue(event)
57
+ buffer.push(event)
58
+ start unless running?
59
+
60
+ true
61
+ end
62
+
63
+ def dequeue
64
+ [buffer.pop, buffer.dropped_count]
65
+ end
66
+
67
+ def perform(*args)
68
+ events, dropped = args
69
+ send_events(Array(events), dropped.to_i)
70
+ end
71
+
72
+ def graceful_shutdown
73
+ return false unless enabled? || !run_loop?
74
+
75
+ self.enabled = false
76
+
77
+ started = Core::Utils::Time.get_time
78
+ wait_time = loop_base_interval + GRACEFUL_SHUTDOWN_EXTRA_SECONDS
79
+
80
+ loop do
81
+ break if buffer.empty? && !in_iteration?
82
+
83
+ sleep(GRACEFUL_SHUTDOWN_WAIT_INTERVAL_SECONDS)
84
+ break if Core::Utils::Time.get_time - started > wait_time
85
+ end
86
+
87
+ stop(true)
88
+ end
89
+
90
+ private
91
+
92
+ def send_events(events, dropped)
93
+ return if events.empty?
94
+
95
+ if dropped.positive?
96
+ @logger.debug { "OpenFeature: Resolution details worker dropped #{dropped} event(s) due to full buffer" }
97
+ end
98
+
99
+ payload = @batch_builder.payload_for(events)
100
+ response = @transport.send_exposures(payload)
101
+
102
+ unless response&.ok?
103
+ @logger.debug { "OpenFeature: Resolution details upload response was not OK: #{response.inspect}" }
104
+ end
105
+
106
+ response
107
+ rescue => e
108
+ @logger.debug { "OpenFeature: Failed to flush resolution details events: #{e.class}: #{e.message}" }
109
+ @telemetry.report(e, description: 'OpenFeature: Failed to flush resolution details events')
110
+
111
+ nil
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module OpenFeature
5
+ module Ext
6
+ ERROR = 'ERROR'
7
+ INITIALIZING = 'INITIALIZING'
8
+ UNKNOWN_TYPE = 'UNKNOWN_TYPE'
9
+ GENERAL = 'GENERAL'
10
+ PROVIDER_FATAL = 'PROVIDER_FATAL'
11
+ PROVIDER_NOT_READY = 'PROVIDER_NOT_READY'
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../core/feature_flags'
4
+
5
+ module Datadog
6
+ module OpenFeature
7
+ # This class is an interface of evaluation logic using native extension
8
+ class NativeEvaluator
9
+ # NOTE: In a currect implementation configuration is expected to be a raw
10
+ # JSON string containing feature flags (straight from the remote config)
11
+ # in the format expected by `libdatadog` without any modifications
12
+ def initialize(configuration)
13
+ @configuration = Core::FeatureFlags::Configuration.new(configuration)
14
+ end
15
+
16
+ # Returns the assignment for a given flag key based on the feature flags
17
+ # configuration
18
+ #
19
+ # @param flag_key [String] The key of the feature flag
20
+ # @param default_value [Object] The default value to return if the flag is
21
+ # not found or evaluation itself fails
22
+ # @param expected_type [Symbol] The expected type of the flag
23
+ # @param context [Hash] The context of the evaluation, containing targeting key
24
+ # and other attributes
25
+ #
26
+ # @return [Core::FeatureFlags::ResolutionDetails] The assignment for the flag
27
+ def get_assignment(flag_key, default_value:, expected_type:, context:)
28
+ result = @configuration.get_assignment(flag_key, expected_type, context)
29
+
30
+ # NOTE: This is a special case when we need to fallback to the default
31
+ # value, even tho the evaluation itself doesn't produce an error
32
+ # resolution details
33
+ result.value = default_value if result.variant.nil?
34
+ result
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ext'
4
+ require_relative 'resolution_details'
5
+
6
+ module Datadog
7
+ module OpenFeature
8
+ # This class is a noop interface of evaluation logic
9
+ class NoopEvaluator
10
+ def initialize(_configuration)
11
+ # no-op
12
+ end
13
+
14
+ def get_assignment(_flag_key, default_value:, context:, expected_type:)
15
+ ResolutionDetails.new(
16
+ value: default_value,
17
+ log?: false,
18
+ error?: true,
19
+ error_code: Ext::PROVIDER_NOT_READY,
20
+ error_message: 'Waiting for flags configuration',
21
+ reason: Ext::ERROR
22
+ )
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ext'
4
+ require 'open_feature/sdk'
5
+
6
+ module Datadog
7
+ module OpenFeature
8
+ # OpenFeature feature flagging provider backed by Datadog Remote Configuration.
9
+ #
10
+ # Implementation follows the OpenFeature contract of Provider SDK.
11
+ # For details see:
12
+ # - https://github.com/open-feature/ruby-sdk/blob/v0.4.1/README.md#develop-a-provider
13
+ # - https://github.com/open-feature/ruby-sdk/blob/v0.4.1/lib/open_feature/sdk/provider/no_op_provider.rb
14
+ #
15
+ # In the example below you can see how to configure the OpenFeature SDK
16
+ # https://github.com/open-feature/ruby-sdk to use the Datadog feature flags provider.
17
+ #
18
+ # Example:
19
+ #
20
+ # Make sure to enable Remote Configuration and OpenFeature in the Datadog configuration.
21
+ #
22
+ # ```ruby
23
+ # # FILE: initializers/datadog.rb
24
+ # Datadog.configure do |config|
25
+ # config.remote.enabled = true
26
+ # config.open_feature.enabled = true
27
+ # end
28
+ # ```
29
+ #
30
+ # And configure the OpenFeature SDK to use the Datadog feature flagging provider.
31
+ #
32
+ # ```ruby
33
+ # # FILE: initializers/open_feature.rb
34
+ # require 'open_feature/sdk'
35
+ # require 'datadog/open_feature/provider'
36
+ #
37
+ # OpenFeature::SDK.configure do |config|
38
+ # config.set_provider(Datadog::OpenFeature::Provider.new)
39
+ # end
40
+ # ```
41
+ #
42
+ # Now you can create OpenFeature SDK client and use it to fetch feature flag values.
43
+ #
44
+ # ```ruby
45
+ # client = OpenFeature::SDK.build_client
46
+ # context = OpenFeature::SDK::EvaluationContext.new('email' => 'john.doe@gmail.com')
47
+ #
48
+ # client.fetch_string_value(
49
+ # flag_key: 'banner', default_value: 'Greetings!', evaluation_context: context
50
+ # )
51
+ # # => 'Welcome back!'
52
+ # ```
53
+ class Provider
54
+ NAME = 'Datadog Feature Flagging Provider'
55
+
56
+ attr_reader :metadata
57
+
58
+ def initialize
59
+ @metadata = ::OpenFeature::SDK::Provider::ProviderMetadata.new(name: NAME).freeze
60
+ end
61
+
62
+ def init
63
+ # no-op
64
+ end
65
+
66
+ def shutdown
67
+ # no-op
68
+ end
69
+
70
+ def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
71
+ evaluate(flag_key, default_value: default_value, expected_type: :boolean, evaluation_context: evaluation_context)
72
+ end
73
+
74
+ def fetch_string_value(flag_key:, default_value:, evaluation_context: nil)
75
+ evaluate(flag_key, default_value: default_value, expected_type: :string, evaluation_context: evaluation_context)
76
+ end
77
+
78
+ def fetch_number_value(flag_key:, default_value:, evaluation_context: nil)
79
+ evaluate(flag_key, default_value: default_value, expected_type: :number, evaluation_context: evaluation_context)
80
+ end
81
+
82
+ def fetch_integer_value(flag_key:, default_value:, evaluation_context: nil)
83
+ evaluate(flag_key, default_value: default_value, expected_type: :integer, evaluation_context: evaluation_context)
84
+ end
85
+
86
+ def fetch_float_value(flag_key:, default_value:, evaluation_context: nil)
87
+ evaluate(flag_key, default_value: default_value, expected_type: :float, evaluation_context: evaluation_context)
88
+ end
89
+
90
+ def fetch_object_value(flag_key:, default_value:, evaluation_context: nil)
91
+ evaluate(flag_key, default_value: default_value, expected_type: :object, evaluation_context: evaluation_context)
92
+ end
93
+
94
+ private
95
+
96
+ def evaluate(flag_key, default_value:, expected_type:, evaluation_context:)
97
+ engine = OpenFeature.engine
98
+ return component_not_configured_default(default_value) if engine.nil?
99
+
100
+ result = engine.fetch_value(
101
+ flag_key,
102
+ default_value: default_value,
103
+ expected_type: expected_type,
104
+ evaluation_context: evaluation_context
105
+ )
106
+
107
+ if result.error?
108
+ return ::OpenFeature::SDK::Provider::ResolutionDetails.new(
109
+ value: default_value,
110
+ error_code: result.error_code,
111
+ error_message: result.error_message,
112
+ reason: result.reason
113
+ )
114
+ end
115
+
116
+ ::OpenFeature::SDK::Provider::ResolutionDetails.new(
117
+ value: result.value,
118
+ variant: result.variant,
119
+ reason: result.reason,
120
+ flag_metadata: result.flag_metadata
121
+ )
122
+ rescue => e
123
+ ::OpenFeature::SDK::Provider::ResolutionDetails.new(
124
+ value: default_value,
125
+ error_code: Ext::GENERAL,
126
+ error_message: "#{e.class}: #{e.message}",
127
+ reason: Ext::ERROR
128
+ )
129
+ end
130
+
131
+ def component_not_configured_default(value)
132
+ ::OpenFeature::SDK::Provider::ResolutionDetails.new(
133
+ value: value,
134
+ error_code: Ext::PROVIDER_FATAL,
135
+ error_message: "Datadog's OpenFeature component must be configured",
136
+ reason: Ext::ERROR
137
+ )
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../core/remote/dispatcher'
4
+
5
+ module Datadog
6
+ module OpenFeature
7
+ # This module contains the remote configuration functionality for OpenFeature
8
+ module Remote
9
+ class << self
10
+ FFE_FLAG_CONFIGURATION_RULES = 1 << 46
11
+ FFE_PRODUCTS = ['FFE_FLAGS'].freeze
12
+ FFE_CAPABILITIES = [FFE_FLAG_CONFIGURATION_RULES].freeze
13
+
14
+ def capabilities
15
+ FFE_CAPABILITIES
16
+ end
17
+
18
+ def products
19
+ FFE_PRODUCTS
20
+ end
21
+
22
+ def receivers(telemetry)
23
+ matcher = Core::Remote::Dispatcher::Matcher::Product.new(FFE_PRODUCTS)
24
+ receiver = Core::Remote::Dispatcher::Receiver.new(matcher) do |repository, changes|
25
+ engine = OpenFeature.engine
26
+ next unless engine
27
+
28
+ changes.each do |change|
29
+ content = repository[change.path]
30
+
31
+ unless content || change.type == :delete
32
+ next telemetry.error("OpenFeature: Remote Configuration change is not present on #{change.type}")
33
+ end
34
+
35
+ # NOTE: In the current RC implementation we immediately apply the configuration,
36
+ # but that might change if we need to apply patches instead.
37
+ case change.type
38
+ when :insert, :update
39
+ begin
40
+ # @type var content: Core::Remote::Configuration::Content
41
+ engine.reconfigure!(read_content(content))
42
+ content.applied
43
+ rescue EvaluationEngine::ReconfigurationError => e
44
+ content.errored("Error applying OpenFeature configuration: #{e.message}")
45
+ end
46
+ when :delete
47
+ # NOTE: For now, we treat deletion as clearing the configuration
48
+ # In a multi-config scenario, we might track configs per path
49
+ engine.reconfigure!(nil)
50
+ end
51
+ end
52
+ end
53
+
54
+ [receiver]
55
+ end
56
+
57
+ private
58
+
59
+ def read_content(content)
60
+ # Unlike all of the other remotes, this one does not JSON-parse
61
+ # the data.
62
+ content.data
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ext'
4
+
5
+ module Datadog
6
+ module OpenFeature
7
+ # This class is based on the `OpenFeature::SDK::Provider::ResolutionDetails` class
8
+ #
9
+ # See: https://github.com/open-feature/ruby-sdk/blob/v0.4.1/lib/open_feature/sdk/provider/resolution_details.rb
10
+ class ResolutionDetails < Struct.new(
11
+ :value,
12
+ :reason,
13
+ :variant,
14
+ :error_code,
15
+ :error_message,
16
+ :flag_metadata,
17
+ :allocation_key,
18
+ :extra_logging,
19
+ :log?,
20
+ :error?,
21
+ keyword_init: true
22
+ )
23
+ def self.build_error(value:, error_code:, error_message:, reason: Ext::ERROR)
24
+ new(
25
+ value: value,
26
+ error_code: error_code,
27
+ error_message: error_message,
28
+ reason: reason,
29
+ error?: true,
30
+ log?: false
31
+ ).freeze
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../core/encoding'
4
+ require_relative '../core/transport/http'
5
+ require_relative '../core/transport/http/env'
6
+ require_relative '../core/transport/http/api/endpoint'
7
+ require_relative '../core/transport/http/api/instance'
8
+ require_relative '../core/transport/parcel'
9
+ require_relative '../core/transport/request'
10
+
11
+ module Datadog
12
+ module OpenFeature
13
+ module Transport
14
+ class EncodedParcel
15
+ include Core::Transport::Parcel
16
+
17
+ def encode_with(encoder)
18
+ encoder.encode(data)
19
+ end
20
+ end
21
+
22
+ class HTTP
23
+ class Spec
24
+ def initialize
25
+ @endpoint = Core::Transport::HTTP::API::Endpoint.new(
26
+ :post, '/evp_proxy/v2/api/v2/exposures'
27
+ )
28
+ end
29
+
30
+ # TODO rename to send_request?
31
+ def call(env, &block)
32
+ @endpoint.call(env) do |request_env|
33
+ request_env.headers['Content-Type'] = Core::Encoding::JSONEncoder.content_type
34
+ request_env.headers['X-Datadog-EVP-Subdomain'] = 'event-platform-intake'
35
+ request_env.body = env.request.parcel.encode_with(Core::Encoding::JSONEncoder)
36
+
37
+ block.call(request_env)
38
+ end
39
+ end
40
+ end
41
+
42
+ def self.build(agent_settings:, logger:)
43
+ Core::Transport::HTTP.build(
44
+ agent_settings: agent_settings,
45
+ logger: logger
46
+ ) { |t| t.api('exposures', HTTP::Spec.new) }.to_transport(self)
47
+ end
48
+
49
+ def initialize(apis, default_api, logger:)
50
+ @api = apis[default_api]
51
+ @logger = logger
52
+ end
53
+
54
+ def send_exposures(payload)
55
+ request = Core::Transport::Request.new(EncodedParcel.new(payload))
56
+
57
+ @api.endpoint.call(Core::Transport::HTTP::Env.new(request)) do |env|
58
+ @api.call(env)
59
+ end
60
+ rescue => e
61
+ message = "Internal error during request. Cause: #{e.class.name} #{e.message} " \
62
+ "Location: #{Array(e.backtrace).first}"
63
+ @logger.debug(message)
64
+
65
+ Core::Transport::InternalErrorResponse.new(e)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'core/configuration'
4
+ require_relative 'open_feature/configuration'
5
+
6
+ module Datadog
7
+ # A namespace for the OpenFeature component.
8
+ module OpenFeature
9
+ Core::Configuration::Settings.extend(Configuration::Settings)
10
+
11
+ def self.enabled?
12
+ Datadog.configuration.open_feature.enabled
13
+ end
14
+
15
+ def self.engine
16
+ Datadog.send(:components).open_feature&.engine
17
+ end
18
+ end
19
+ end
@@ -26,7 +26,7 @@ module Datadog
26
26
  # to ::OpenTelemetry::Context.current
27
27
  # @return [Context]
28
28
  def clear(context: ::OpenTelemetry::Context.current)
29
- context.ensure_trace.baggage.clear
29
+ context.ensure_trace&.baggage&.clear
30
30
  context
31
31
  end
32
32