datadog 2.39.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.
Files changed (296) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +269 -44
  3. data/README.md +3 -0
  4. data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +12 -24
  5. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +256 -193
  6. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -5
  7. data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +0 -2
  8. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +19 -20
  9. data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.h +5 -1
  10. data/ext/datadog_profiling_native_extension/collectors_stack.c +267 -171
  11. data/ext/datadog_profiling_native_extension/collectors_stack.h +17 -3
  12. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +307 -217
  13. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +10 -5
  14. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +5 -4
  15. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +19 -11
  16. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +7 -10
  17. data/ext/datadog_profiling_native_extension/extconf.rb +67 -99
  18. data/ext/datadog_profiling_native_extension/heap_recorder.c +353 -257
  19. data/ext/datadog_profiling_native_extension/heap_recorder.h +50 -31
  20. data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +3 -27
  21. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +853 -496
  22. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +42 -15
  23. data/ext/datadog_profiling_native_extension/profiling.c +30 -23
  24. data/ext/datadog_profiling_native_extension/ruby_helpers.c +4 -80
  25. data/ext/datadog_profiling_native_extension/ruby_helpers.h +4 -7
  26. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +0 -2
  27. data/ext/datadog_profiling_native_extension/stack_recorder.c +116 -89
  28. data/ext/datadog_profiling_native_extension/stack_recorder.h +12 -5
  29. data/ext/datadog_profiling_native_extension/time_helpers.c +2 -4
  30. data/ext/datadog_profiling_native_extension/time_helpers.h +15 -15
  31. data/ext/libdatadog_api/datadog_ruby_common.c +19 -11
  32. data/ext/libdatadog_api/datadog_ruby_common.h +7 -10
  33. data/ext/libdatadog_api/di.c +43 -3
  34. data/ext/libdatadog_api/extconf.rb +6 -1
  35. data/ext/libdatadog_api/feature_flags.c +8 -0
  36. data/ext/libdatadog_api/init.c +9 -1
  37. data/ext/libdatadog_api/otel_thread_context.c +274 -0
  38. data/ext/libdatadog_api/otel_thread_context.h +5 -0
  39. data/ext/libdatadog_api/process_discovery.c +15 -1
  40. data/ext/libdatadog_api/trace_exporter.c +2078 -0
  41. data/ext/libdatadog_api/trace_exporter.h +5 -0
  42. data/ext/libdatadog_extconf_helpers.rb +14 -9
  43. data/lib/datadog/ai_guard/component.rb +17 -8
  44. data/lib/datadog/ai_guard/configuration/ext.rb +2 -0
  45. data/lib/datadog/ai_guard/configuration.rb +13 -0
  46. data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +48 -55
  47. data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +1 -1
  48. data/lib/datadog/ai_guard/contrib/ruby_llm/message_adapter.rb +76 -0
  49. data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +1 -0
  50. data/lib/datadog/ai_guard/contrib/ruby_llm/redaction_change_set.rb +135 -0
  51. data/lib/datadog/ai_guard/evaluation/client.rb +50 -0
  52. data/lib/datadog/ai_guard/evaluation/content_part.rb +12 -0
  53. data/lib/datadog/ai_guard/evaluation/message.rb +25 -7
  54. data/lib/datadog/ai_guard/evaluation/message_builder.rb +31 -0
  55. data/lib/datadog/ai_guard/evaluation/no_op_result.rb +6 -6
  56. data/lib/datadog/ai_guard/evaluation/outcome.rb +24 -0
  57. data/lib/datadog/ai_guard/evaluation/request.rb +8 -64
  58. data/lib/datadog/ai_guard/evaluation/response.rb +33 -0
  59. data/lib/datadog/ai_guard/evaluation/result.rb +11 -18
  60. data/lib/datadog/ai_guard/evaluation/tool_call.rb +15 -2
  61. data/lib/datadog/ai_guard/evaluation.rb +36 -14
  62. data/lib/datadog/ai_guard/ext.rb +4 -1
  63. data/lib/datadog/ai_guard/{api_client.rb → http_client.rb} +7 -5
  64. data/lib/datadog/ai_guard/metrics/telemetry.rb +43 -0
  65. data/lib/datadog/ai_guard/redaction/replacements.rb +97 -0
  66. data/lib/datadog/ai_guard/redaction/result.rb +29 -0
  67. data/lib/datadog/ai_guard/redaction.rb +80 -0
  68. data/lib/datadog/ai_guard.rb +58 -57
  69. data/lib/datadog/appsec/actions_handler/serializable_backtrace.rb +21 -42
  70. data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +1 -1
  71. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +1 -1
  72. data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +1 -1
  73. data/lib/datadog/appsec/assets/blocked.html +1 -108
  74. data/lib/datadog/appsec/configuration.rb +11 -2
  75. data/lib/datadog/appsec/context.rb +1 -1
  76. data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +1 -1
  77. data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +2 -2
  78. data/lib/datadog/appsec/contrib/devise/configuration.rb +1 -1
  79. data/lib/datadog/appsec/contrib/devise/patcher.rb +1 -1
  80. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +2 -2
  81. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +2 -2
  82. data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +1 -1
  83. data/lib/datadog/appsec/contrib/rack/ext.rb +2 -2
  84. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +2 -2
  85. data/lib/datadog/appsec/contrib/rack/patcher.rb +1 -1
  86. data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +6 -3
  87. data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +30 -3
  88. data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +8 -7
  89. data/lib/datadog/appsec/contrib/rails/request_middleware.rb +2 -1
  90. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +2 -2
  91. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +15 -3
  92. data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +8 -7
  93. data/lib/datadog/appsec/contrib/sinatra/request_middleware.rb +2 -1
  94. data/lib/datadog/appsec/processor/rule_loader.rb +12 -12
  95. data/lib/datadog/appsec/response.rb +1 -1
  96. data/lib/datadog/appsec/route_normalizer/route_pattern.rb +3 -3
  97. data/lib/datadog/appsec/security_engine/engine.rb +5 -4
  98. data/lib/datadog/core/configuration/components.rb +50 -19
  99. data/lib/datadog/core/configuration/components_state.rb +8 -1
  100. data/lib/datadog/core/configuration/option_definition.rb +1 -1
  101. data/lib/datadog/core/configuration/options.rb +1 -1
  102. data/lib/datadog/core/configuration/settings.rb +38 -43
  103. data/lib/datadog/core/configuration/supported_configurations.rb +18 -2
  104. data/lib/datadog/core/crashtracking/component.rb +5 -1
  105. data/lib/datadog/core/diagnostics/environment_logger.rb +36 -1
  106. data/lib/datadog/core/environment/gc.rb +1 -1
  107. data/lib/datadog/core/environment/vm_cache.rb +1 -1
  108. data/lib/datadog/core/environment/yjit.rb +2 -2
  109. data/lib/datadog/core/metrics/helpers.rb +1 -1
  110. data/lib/datadog/core/metrics/options.rb +2 -2
  111. data/lib/datadog/core/process_discovery.rb +6 -2
  112. data/lib/datadog/core/remote/client/capabilities.rb +32 -21
  113. data/lib/datadog/core/remote/client.rb +45 -4
  114. data/lib/datadog/core/remote/component.rb +30 -7
  115. data/lib/datadog/core/remote/configuration/repository.rb +2 -2
  116. data/lib/datadog/core/remote/dispatcher.rb +47 -6
  117. data/lib/datadog/core/remote/worker.rb +8 -0
  118. data/lib/datadog/core/telemetry/configuration_value.rb +38 -0
  119. data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +3 -1
  120. data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +1 -1
  121. data/lib/datadog/core/telemetry/event/app_started.rb +5 -24
  122. data/lib/datadog/core/telemetry/event/generate_metrics.rb +1 -1
  123. data/lib/datadog/core/telemetry/event/log.rb +2 -2
  124. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +1 -1
  125. data/lib/datadog/core/telemetry/metric.rb +2 -2
  126. data/lib/datadog/core/telemetry/request.rb +2 -2
  127. data/lib/datadog/core/transport/http.rb +1 -1
  128. data/lib/datadog/core/transport/transport.rb +1 -1
  129. data/lib/datadog/core/utils/at_fork_monkey_patch.rb +170 -18
  130. data/lib/datadog/core/utils/sequence.rb +3 -6
  131. data/lib/datadog/core/utils/time.rb +16 -54
  132. data/lib/datadog/core/workers/runtime_metrics.rb +1 -1
  133. data/lib/datadog/data_streams/pathway_context.rb +20 -22
  134. data/lib/datadog/data_streams/processor.rb +45 -14
  135. data/lib/datadog/di/boot.rb +1 -0
  136. data/lib/datadog/di/capture_expression_evaluator/result.rb +30 -0
  137. data/lib/datadog/di/capture_expression_evaluator.rb +39 -9
  138. data/lib/datadog/di/component.rb +108 -3
  139. data/lib/datadog/di/configuration.rb +53 -2
  140. data/lib/datadog/di/context.rb +7 -2
  141. data/lib/datadog/di/contrib/active_record.rb +8 -2
  142. data/lib/datadog/di/el/compiler.rb +88 -12
  143. data/lib/datadog/di/el/evaluator.rb +82 -2
  144. data/lib/datadog/di/el/expression.rb +13 -2
  145. data/lib/datadog/di/evaluation_failure.rb +28 -0
  146. data/lib/datadog/di/guardrails_telemetry/reason.rb +87 -0
  147. data/lib/datadog/di/guardrails_telemetry.rb +203 -0
  148. data/lib/datadog/di/instrumenter.rb +106 -15
  149. data/lib/datadog/di/logger.rb +32 -10
  150. data/lib/datadog/di/probe.rb +9 -1
  151. data/lib/datadog/di/probe_builder.rb +7 -6
  152. data/lib/datadog/di/probe_manager.rb +56 -8
  153. data/lib/datadog/di/probe_notification_builder.rb +31 -28
  154. data/lib/datadog/di/probe_notifier_worker.rb +56 -6
  155. data/lib/datadog/di/proc_responder.rb +1 -1
  156. data/lib/datadog/di/remote.rb +49 -5
  157. data/lib/datadog/di/serializer.rb +172 -55
  158. data/lib/datadog/di/snapshot_event.rb +38 -0
  159. data/lib/datadog/di/transport/http.rb +13 -3
  160. data/lib/datadog/di/transport/input.rb +32 -7
  161. data/lib/datadog/error_tracking/component.rb +1 -1
  162. data/lib/datadog/kit/appsec/events/v2.rb +2 -2
  163. data/lib/datadog/open_feature/activation.rb +268 -0
  164. data/lib/datadog/open_feature/agentless/configuration_source.rb +246 -0
  165. data/lib/datadog/open_feature/agentless/parser.rb +43 -0
  166. data/lib/datadog/open_feature/agentless/response.rb +19 -0
  167. data/lib/datadog/open_feature/agentless/transport.rb +103 -0
  168. data/lib/datadog/open_feature/component.rb +85 -13
  169. data/lib/datadog/open_feature/configuration/agentless_endpoint.rb +90 -0
  170. data/lib/datadog/open_feature/configuration/source/resolution.rb +22 -0
  171. data/lib/datadog/open_feature/configuration/source.rb +43 -0
  172. data/lib/datadog/open_feature/configuration.rb +124 -0
  173. data/lib/datadog/open_feature/evaluation_engine.rb +29 -3
  174. data/lib/datadog/open_feature/exposures/batch_builder.rb +1 -1
  175. data/lib/datadog/open_feature/exposures/event.rb +15 -8
  176. data/lib/datadog/open_feature/ext.rb +19 -0
  177. data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +236 -80
  178. data/lib/datadog/open_feature/flag_evaluation/writer.rb +179 -68
  179. data/lib/datadog/open_feature/hooks/evaluation_telemetry_opt_out_hook.rb +25 -0
  180. data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +24 -21
  181. data/lib/datadog/open_feature/native_evaluator.rb +20 -6
  182. data/lib/datadog/open_feature/noop_evaluator.rb +5 -0
  183. data/lib/datadog/open_feature/provider.rb +315 -10
  184. data/lib/datadog/open_feature/remote.rb +5 -5
  185. data/lib/datadog/open_feature/transport.rb +5 -0
  186. data/lib/datadog/open_feature.rb +31 -1
  187. data/lib/datadog/opentelemetry/sdk/propagator.rb +1 -1
  188. data/lib/datadog/opentelemetry/signal_configuration.rb +13 -9
  189. data/lib/datadog/opentelemetry/trace.rb +3 -0
  190. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +10 -2
  191. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -7
  192. data/lib/datadog/profiling/collectors/info.rb +40 -13
  193. data/lib/datadog/profiling/collectors/thread_context.rb +6 -17
  194. data/lib/datadog/profiling/component.rb +33 -39
  195. data/lib/datadog/profiling/exporter.rb +10 -10
  196. data/lib/datadog/profiling/tasks/exec.rb +1 -1
  197. data/lib/datadog/profiling.rb +4 -0
  198. data/lib/datadog/symbol_database/extractor.rb +89 -107
  199. data/lib/datadog/tracing/component.rb +44 -2
  200. data/lib/datadog/tracing/configuration/dynamic/option.rb +4 -0
  201. data/lib/datadog/tracing/configuration/dynamic.rb +5 -3
  202. data/lib/datadog/tracing/configuration/ext.rb +4 -0
  203. data/lib/datadog/tracing/configuration/settings.rb +36 -0
  204. data/lib/datadog/tracing/context.rb +24 -2
  205. data/lib/datadog/tracing/context_provider.rb +17 -5
  206. data/lib/datadog/tracing/contrib/action_cable/events.rb +1 -1
  207. data/lib/datadog/tracing/contrib/action_mailer/events.rb +1 -1
  208. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +2 -2
  209. data/lib/datadog/tracing/contrib/action_view/events.rb +1 -1
  210. data/lib/datadog/tracing/contrib/active_model_serializers/events.rb +1 -1
  211. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +1 -1
  212. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -0
  213. data/lib/datadog/tracing/contrib/active_record/events.rb +1 -1
  214. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +2 -2
  215. data/lib/datadog/tracing/contrib/aws/ext.rb +1 -1
  216. data/lib/datadog/tracing/contrib/aws/services.rb +1 -1
  217. data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +1 -1
  218. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +10 -5
  219. data/lib/datadog/tracing/contrib/excon/middleware.rb +10 -3
  220. data/lib/datadog/tracing/contrib/ext.rb +3 -3
  221. data/lib/datadog/tracing/contrib/faraday/middleware.rb +10 -4
  222. data/lib/datadog/tracing/contrib/grape/endpoint.rb +32 -5
  223. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +1 -1
  224. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +1 -1
  225. data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +1 -1
  226. data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +2 -1
  227. data/lib/datadog/tracing/contrib/http/instrumentation.rb +16 -1
  228. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +8 -2
  229. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +10 -4
  230. data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +4 -0
  231. data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +7 -2
  232. data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +7 -2
  233. data/lib/datadog/tracing/contrib/kafka/events.rb +1 -1
  234. data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +1 -1
  235. data/lib/datadog/tracing/contrib/mongodb/parsers.rb +1 -1
  236. data/lib/datadog/tracing/contrib/opensearch/ext.rb +1 -1
  237. data/lib/datadog/tracing/contrib/opensearch/quantize.rb +1 -1
  238. data/lib/datadog/tracing/contrib/patchable.rb +1 -1
  239. data/lib/datadog/tracing/contrib/patcher.rb +1 -1
  240. data/lib/datadog/tracing/contrib/que/tracer.rb +1 -1
  241. data/lib/datadog/tracing/contrib/racecar/events.rb +1 -1
  242. data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +1 -1
  243. data/lib/datadog/tracing/contrib/rack/header_tagging.rb +1 -1
  244. data/lib/datadog/tracing/contrib/rack/patcher.rb +1 -1
  245. data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +1 -1
  246. data/lib/datadog/tracing/contrib/redis/configuration/resolver.rb +1 -1
  247. data/lib/datadog/tracing/contrib/redis/quantize.rb +36 -2
  248. data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +9 -3
  249. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +10 -3
  250. data/lib/datadog/tracing/contrib/sequel/database.rb +1 -5
  251. data/lib/datadog/tracing/contrib/sequel/jdbc_connection_string.rb +140 -0
  252. data/lib/datadog/tracing/contrib/sequel/utils.rb +50 -63
  253. data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +1 -1
  254. data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +1 -1
  255. data/lib/datadog/tracing/contrib/sneakers/tracer.rb +1 -1
  256. data/lib/datadog/tracing/contrib/utils/quantization/hash_formatter.rb +1 -1
  257. data/lib/datadog/tracing/contrib/utils/quantization/http.rb +108 -3
  258. data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +1 -1
  259. data/lib/datadog/tracing/correlation.rb +2 -2
  260. data/lib/datadog/tracing/diagnostics/environment_logger.rb +3 -2
  261. data/lib/datadog/tracing/distributed/baggage.rb +1 -3
  262. data/lib/datadog/tracing/distributed/datadog.rb +3 -3
  263. data/lib/datadog/tracing/distributed/propagation.rb +3 -0
  264. data/lib/datadog/tracing/distributed/propagation_policy.rb +5 -0
  265. data/lib/datadog/tracing/distributed/trace_context.rb +14 -271
  266. data/lib/datadog/tracing/distributed/trace_state/datadog.rb +233 -0
  267. data/lib/datadog/tracing/distributed/trace_state/ext.rb +44 -0
  268. data/lib/datadog/tracing/distributed/trace_state/open_telemetry.rb +156 -0
  269. data/lib/datadog/tracing/distributed/trace_state.rb +121 -0
  270. data/lib/datadog/tracing/metadata/ext.rb +3 -0
  271. data/lib/datadog/tracing/metadata/tagging.rb +1 -1
  272. data/lib/datadog/tracing/otel_thread_context.rb +97 -0
  273. data/lib/datadog/tracing/remote.rb +236 -37
  274. data/lib/datadog/tracing/sampling/rule_sampler.rb +2 -0
  275. data/lib/datadog/tracing/span.rb +1 -1
  276. data/lib/datadog/tracing/span_operation.rb +1 -1
  277. data/lib/datadog/tracing/sync_writer.rb +8 -3
  278. data/lib/datadog/tracing/trace_digest.rb +22 -4
  279. data/lib/datadog/tracing/trace_operation.rb +22 -10
  280. data/lib/datadog/tracing/tracer.rb +28 -7
  281. data/lib/datadog/tracing/transport/http/telemetry.rb +94 -0
  282. data/lib/datadog/tracing/transport/http/traces.rb +2 -0
  283. data/lib/datadog/tracing/transport/io/traces.rb +1 -1
  284. data/lib/datadog/tracing/transport/native/response.rb +71 -0
  285. data/lib/datadog/tracing/transport/native/telemetry.rb +17 -0
  286. data/lib/datadog/tracing/transport/native.rb +395 -0
  287. data/lib/datadog/tracing/transport/span_events_negotiation.rb +41 -0
  288. data/lib/datadog/tracing/transport/telemetry.rb +62 -0
  289. data/lib/datadog/tracing/transport/trace_formatter.rb +14 -4
  290. data/lib/datadog/tracing/transport/traces.rb +85 -38
  291. data/lib/datadog/tracing/writer.rb +17 -4
  292. data/lib/datadog/version.rb +1 -1
  293. metadata +51 -13
  294. data/lib/datadog/ai_guard/evaluation/content_builder.rb +0 -31
  295. data/lib/datadog/appsec/utils/hash_coercion.rb +0 -23
  296. data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +0 -69
@@ -2,6 +2,8 @@
2
2
 
3
3
  require_relative "transport"
4
4
  require_relative "evaluation_engine"
5
+ require_relative "../core/utils/time"
6
+ require_relative "configuration/source"
5
7
  require_relative "exposures/buffer"
6
8
  require_relative "exposures/worker"
7
9
  require_relative "exposures/deduplicator"
@@ -16,20 +18,18 @@ module Datadog
16
18
  module OpenFeature
17
19
  # This class is the entry point for the OpenFeature component
18
20
  class Component
19
- attr_reader :engine, :flag_eval_metrics_hook, :flag_eval_evp_hook, :span_enrichment_hook
20
-
21
- def self.build(settings, agent_settings, logger:, telemetry:)
22
- return unless settings.respond_to?(:open_feature) && settings.open_feature.enabled
21
+ CONFIGURATION_READY = :ready
22
+ CONFIGURATION_TIMEOUT = :timeout
23
+ CONFIGURATION_SHUTDOWN = :shutdown
24
+ CONFIGURATION_CHANGED = :changed
25
+ CONFIGURATION_LOST = :lost
23
26
 
24
- unless settings.respond_to?(:remote) && settings.remote.enabled
25
- message = "OpenFeature could not be enabled as Remote Configuration is currently disabled. " \
26
- "To enable Remote Configuration, see https://docs.datadoghq.com/remote_configuration/."
27
+ attr_reader :engine, :flag_eval_metrics_hook, :flag_eval_evp_hook, :span_enrichment_hook
27
28
 
28
- logger.warn(message)
29
- return
30
- end
29
+ def self.build(settings, agent_settings, resolution:, on_configuration_change:, logger:, telemetry:)
30
+ return unless resolution.enabled?
31
31
 
32
- if RUBY_ENGINE != "ruby"
32
+ unless ["ruby", "truffleruby"].include?(RUBY_ENGINE)
33
33
  message = "OpenFeature could not be enabled as MRI is required, " \
34
34
  "but running on #{RUBY_ENGINE.inspect}"
35
35
 
@@ -46,10 +46,16 @@ module Datadog
46
46
  return
47
47
  end
48
48
 
49
- 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
+ )
50
56
  end
51
57
 
52
- def initialize(settings, agent_settings, logger:, telemetry:)
58
+ def initialize(settings, agent_settings, logger:, telemetry:, on_configuration_change: nil)
53
59
  transport = Transport::HTTP.build(agent_settings: agent_settings, logger: logger)
54
60
  @worker = Exposures::Worker.new(settings: settings, transport: transport, telemetry: telemetry, logger: logger)
55
61
 
@@ -60,12 +66,74 @@ module Datadog
60
66
  @logger = logger
61
67
  @settings = settings
62
68
  @agent_settings = agent_settings
69
+ @on_configuration_change = on_configuration_change
63
70
  @flag_eval_metrics_hook = create_flag_eval_metrics_hook
64
71
  @flag_eval_evp_hook = create_flag_eval_evp_hook
65
72
  @span_enrichment_hook = create_span_enrichment_hook
73
+
74
+ @configuration_mutex = Mutex.new
75
+ @reconfiguration_mutex = Mutex.new
76
+ @configuration_condition = ConditionVariable.new
77
+ @configuration_received = false
78
+ @configuration_shutdown = false
79
+ end
80
+
81
+ def reconfigure!(configuration)
82
+ event = @reconfiguration_mutex.synchronize do
83
+ return if configuration_shutdown?
84
+
85
+ @engine.reconfigure!(configuration)
86
+
87
+ @configuration_mutex.synchronize do
88
+ return if @configuration_shutdown
89
+
90
+ previously_received = @configuration_received
91
+ @configuration_received = !configuration.nil?
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
99
+ end
100
+ end
101
+
102
+ @on_configuration_change&.call(event) if event
103
+ end
104
+
105
+ def wait_for_configuration(&cancelled)
106
+ timeout_seconds = @settings.feature_flags.initialization_timeout_ms / 1000.0
107
+ deadline = Core::Utils::Time.get_time + timeout_seconds
108
+
109
+ @configuration_mutex.synchronize do
110
+ loop do
111
+ return CONFIGURATION_SHUTDOWN if @configuration_shutdown || cancelled&.call
112
+ return CONFIGURATION_READY if @configuration_received
113
+
114
+ remaining = deadline - Core::Utils::Time.get_time
115
+ return CONFIGURATION_TIMEOUT unless remaining.positive?
116
+
117
+ @configuration_condition.wait(@configuration_mutex, remaining)
118
+ end
119
+ end
120
+ end
121
+
122
+ def wake_configuration_waiters
123
+ @configuration_mutex.synchronize { @configuration_condition.broadcast }
124
+ nil
125
+ end
126
+
127
+ def configuration_received?
128
+ @configuration_mutex.synchronize { @configuration_received }
66
129
  end
67
130
 
68
131
  def shutdown!
132
+ @configuration_mutex.synchronize do
133
+ @configuration_shutdown = true
134
+ @configuration_condition.broadcast
135
+ end
136
+
69
137
  @worker.graceful_shutdown
70
138
  @flag_eval_evp_writer&.stop
71
139
  # Symmetric teardown: drop any accumulated span-enrichment state and
@@ -75,6 +143,10 @@ module Datadog
75
143
 
76
144
  private
77
145
 
146
+ def configuration_shutdown?
147
+ @configuration_mutex.synchronize { @configuration_shutdown }
148
+ end
149
+
78
150
  def create_flag_eval_metrics_hook
79
151
  return unless Hooks::FlagEvalMetricsHook.available?
80
152
 
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module Datadog
6
+ module OpenFeature
7
+ module Configuration
8
+ # Validated endpoint for agentless Feature Flags configuration delivery.
9
+ class AgentlessEndpoint
10
+ DEFAULT_SITE = "datadoghq.com"
11
+ AGENTLESS_SUBDOMAIN = "ufc-server.ff-cdn"
12
+ CONFIGURATION_PATH = "/api/v2/feature-flagging/config/rules-based/server"
13
+ INVALID_SITE_CHARACTERS = /[\s\/?#@:]/
14
+ INTERNAL_WHITESPACE = /\s/
15
+
16
+ class << self
17
+ def build(site:, environment:, base_url: nil, logger: Datadog.logger)
18
+ if base_url
19
+ build_custom(base_url, logger)
20
+ else
21
+ build_managed(site, environment, logger)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def build_custom(base_url, logger)
28
+ unless base_url.ascii_only?
29
+ logger.warn("Feature Flags agentless base URL is invalid; agentless delivery is disabled")
30
+ return
31
+ end
32
+
33
+ if base_url.match?(INTERNAL_WHITESPACE)
34
+ logger.warn("Feature Flags agentless base URL contains whitespace; agentless delivery is disabled")
35
+ return
36
+ end
37
+
38
+ uri = URI.parse(base_url)
39
+ unless uri.is_a?(URI::HTTP) && uri.absolute? && uri.host
40
+ logger.warn("Feature Flags agentless base URL must be an absolute HTTP or HTTPS URL; agentless delivery is disabled")
41
+ return
42
+ end
43
+
44
+ uri.path = CONFIGURATION_PATH if uri.path.to_s.empty? || uri.path == "/"
45
+ new(uri, managed: false)
46
+ rescue URI::InvalidURIError
47
+ logger.warn("Feature Flags agentless base URL is invalid; agentless delivery is disabled")
48
+ nil
49
+ end
50
+
51
+ def build_managed(site, environment, logger)
52
+ string_site = site.to_s
53
+ unless string_site.ascii_only?
54
+ logger.warn("Feature Flags site is invalid; agentless delivery is disabled")
55
+ return
56
+ end
57
+
58
+ normalized_site = string_site.strip.downcase
59
+ normalized_site = DEFAULT_SITE if normalized_site.empty?
60
+ if normalized_site.match?(INVALID_SITE_CHARACTERS)
61
+ logger.warn("Feature Flags site is invalid; agentless delivery is disabled")
62
+ return
63
+ end
64
+
65
+ uri = URI::HTTPS.build(
66
+ host: "#{AGENTLESS_SUBDOMAIN}.#{normalized_site}",
67
+ path: CONFIGURATION_PATH,
68
+ query: environment.nil? ? nil : URI.encode_www_form(dd_env: environment),
69
+ )
70
+ new(uri, managed: true)
71
+ rescue URI::InvalidComponentError
72
+ logger.warn("Feature Flags site is invalid; agentless delivery is disabled")
73
+ nil
74
+ end
75
+ end
76
+
77
+ attr_reader :uri
78
+
79
+ def initialize(uri, managed:)
80
+ @uri = uri
81
+ @managed = managed
82
+ end
83
+
84
+ def managed?
85
+ @managed
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module OpenFeature
5
+ module Configuration
6
+ module Source
7
+ class Resolution
8
+ attr_reader :source
9
+
10
+ def initialize(source, enabled:)
11
+ @source = source
12
+ @enabled = enabled
13
+ end
14
+
15
+ def enabled?
16
+ @enabled
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "source/resolution"
4
+
5
+ module Datadog
6
+ module OpenFeature
7
+ module Configuration
8
+ module Source
9
+ AGENTLESS = "agentless"
10
+ REMOTE_CONFIG = "remote_config"
11
+ OFFLINE = "offline"
12
+
13
+ class << self
14
+ def resolve(settings, logger: Datadog.logger)
15
+ feature_flags = settings.feature_flags
16
+ source = resolve_source(feature_flags, legacy: settings.open_feature, logger: logger)
17
+ enabled = feature_flags.enabled && source != OFFLINE
18
+
19
+ Resolution.new(source, enabled: enabled)
20
+ end
21
+
22
+ private
23
+
24
+ def resolve_source(settings, legacy:, logger:)
25
+ configured_source = settings.configuration_source
26
+ if !settings.using_default?(:configuration_source) && !configured_source.empty?
27
+ return configured_source if configured_source == AGENTLESS ||
28
+ configured_source == REMOTE_CONFIG ||
29
+ configured_source == OFFLINE
30
+
31
+ logger.warn("Unsupported Feature Flags configuration source; Feature Flags are disabled")
32
+ OFFLINE
33
+ elsif !legacy.using_default?(:enabled)
34
+ legacy.enabled ? REMOTE_CONFIG : OFFLINE
35
+ else
36
+ AGENTLESS
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -5,6 +5,25 @@ module Datadog
5
5
  module Configuration
6
6
  # A settings class for the OpenFeature component.
7
7
  module Settings
8
+ DEFAULT_POLL_INTERVAL_SECONDS = 30
9
+ DEFAULT_REQUEST_TIMEOUT_SECONDS = 5
10
+ DEFAULT_INITIALIZATION_TIMEOUT_MS = 30_000
11
+
12
+ MAX_REQUEST_TIMEOUT_SECONDS = 300
13
+ MAX_INITIALIZATION_TIMEOUT_MS = 2_147_483_647
14
+
15
+ def self.configure_integer_option(option, environment_variable:, default:, setting_name:)
16
+ option.type :int
17
+ option.env environment_variable
18
+ option.env_parser do |value|
19
+ Integer(value, 10)
20
+ rescue ArgumentError
21
+ Datadog.logger.warn("#{setting_name} must be an integer; using the default")
22
+ default
23
+ end
24
+ option.default default
25
+ end
26
+
8
27
  def self.extended(base)
9
28
  base = base.singleton_class unless base.is_a?(Class)
10
29
  add_settings!(base)
@@ -15,10 +34,19 @@ module Datadog
15
34
  # Steep does not update `self` for this `class_eval` block.
16
35
  # @type self: Datadog::Core::Configuration::Base::_DslContext
17
36
  settings :open_feature do
37
+ # Deprecated switch retained for existing Remote Configuration adopters.
18
38
  option :enabled do |o|
19
39
  o.type :bool
20
40
  o.env "DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED"
21
41
  o.default false
42
+ o.after_set do |_, _, precedence|
43
+ next if precedence == Core::Configuration::Option::Precedence::DEFAULT
44
+
45
+ Datadog.logger.warn(
46
+ "DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED is deprecated; use " \
47
+ "DD_FEATURE_FLAGS_ENABLED and DD_FEATURE_FLAGS_CONFIGURATION_SOURCE instead"
48
+ )
49
+ end
22
50
  end
23
51
 
24
52
  # Opt-in gate for APM feature-flag span enrichment. When enabled,
@@ -42,6 +70,102 @@ module Datadog
42
70
  o.default true
43
71
  end
44
72
  end
73
+
74
+ settings :feature_flags do
75
+ option :enabled do |o|
76
+ o.type :bool
77
+ o.env "DD_FEATURE_FLAGS_ENABLED"
78
+ o.default true
79
+ end
80
+
81
+ option :evaluation_telemetry_per_subject_opt_out_enabled do |o|
82
+ o.type :bool
83
+ o.env "DD_FEATURE_FLAGS_EVALUATION_TELEMETRY_PER_SUBJECT_OPT_OUT_ENABLED"
84
+ o.default false
85
+ end
86
+
87
+ option :configuration_source do |o|
88
+ o.type :string
89
+ o.env "DD_FEATURE_FLAGS_CONFIGURATION_SOURCE"
90
+ o.default "agentless"
91
+ o.setter do |value|
92
+ value.to_s.strip.downcase
93
+ end
94
+ end
95
+
96
+ settings :agentless do
97
+ option :base_url do |o|
98
+ o.type :string, nilable: true
99
+ o.env "DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_BASE_URL"
100
+ o.skip_telemetry true
101
+ o.setter do |value|
102
+ next unless value
103
+
104
+ stripped_value = value.to_s.strip
105
+ stripped_value.empty? ? nil : stripped_value
106
+ end
107
+ end
108
+
109
+ option :poll_interval_seconds do |o|
110
+ Settings.configure_integer_option(
111
+ o,
112
+ environment_variable: "DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_POLL_INTERVAL_SECONDS",
113
+ default: Settings::DEFAULT_POLL_INTERVAL_SECONDS,
114
+ setting_name: "Feature Flags agentless poll interval",
115
+ )
116
+ o.setter do |value|
117
+ if value.is_a?(Integer) && value > 0
118
+ value
119
+ else
120
+ Datadog.logger.warn(
121
+ "Feature Flags agentless poll interval must be positive; using the default"
122
+ )
123
+ Settings::DEFAULT_POLL_INTERVAL_SECONDS
124
+ end
125
+ end
126
+ end
127
+
128
+ option :request_timeout_seconds do |o|
129
+ Settings.configure_integer_option(
130
+ o,
131
+ environment_variable: "DD_FEATURE_FLAGS_CONFIGURATION_SOURCE_AGENTLESS_REQUEST_TIMEOUT_SECONDS",
132
+ default: Settings::DEFAULT_REQUEST_TIMEOUT_SECONDS,
133
+ setting_name: "Feature Flags agentless request timeout",
134
+ )
135
+ o.setter do |value|
136
+ if value.is_a?(Integer) && value > 0 && value <= Settings::MAX_REQUEST_TIMEOUT_SECONDS
137
+ value
138
+ else
139
+ Datadog.logger.warn(
140
+ "Feature Flags agentless request timeout must be within (0, " \
141
+ "#{Settings::MAX_REQUEST_TIMEOUT_SECONDS}]; using the default"
142
+ )
143
+ Settings::DEFAULT_REQUEST_TIMEOUT_SECONDS
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ option :initialization_timeout_ms do |o|
150
+ Settings.configure_integer_option(
151
+ o,
152
+ environment_variable: "DD_EXPERIMENTAL_FLAGGING_PROVIDER_INITIALIZATION_TIMEOUT_MS",
153
+ default: Settings::DEFAULT_INITIALIZATION_TIMEOUT_MS,
154
+ setting_name: "Feature Flags provider initialization timeout",
155
+ )
156
+ o.setter do |value|
157
+ if value.is_a?(Integer) && value > 0 && value <= Settings::MAX_INITIALIZATION_TIMEOUT_MS
158
+ value
159
+ else
160
+ Datadog.logger.warn(
161
+ "Feature Flags provider initialization timeout must be within (0, " \
162
+ "#{Settings::MAX_INITIALIZATION_TIMEOUT_MS}]; using the default"
163
+ )
164
+ Settings::DEFAULT_INITIALIZATION_TIMEOUT_MS
165
+ end
166
+ end
167
+ end
168
+ end
45
169
  end
46
170
  end
47
171
  end
@@ -23,27 +23,42 @@ module Datadog
23
23
  end
24
24
 
25
25
  def fetch_value(flag_key, default_value:, expected_type:, evaluation_context: nil)
26
+ # Pre-bound to the safe default so the rescue below can still read it if the
27
+ # evaluator read itself raises.
28
+ observe_full_evaluation_data = false
29
+ # Snapshot the evaluator once: a concurrent reconfigure! must not retroactively
30
+ # apply a different environment's full-evaluation-data policy to this evaluation.
31
+ evaluator = @evaluator
32
+ observe_full_evaluation_data = evaluator.observe_full_evaluation_data == true
33
+
26
34
  unless ALLOWED_TYPES.include?(expected_type)
27
35
  message = "unknown type #{expected_type.inspect}, allowed types #{ALLOWED_TYPES.join(", ")}"
28
- return ResolutionDetails.build_error(
36
+ result = ResolutionDetails.build_error(
29
37
  value: default_value, error_code: Ext::UNKNOWN_TYPE, error_message: message
30
38
  )
39
+
40
+ return with_observe_full_evaluation_data(result, observe_full_evaluation_data)
31
41
  end
32
42
 
33
43
  context = evaluation_context&.fields.to_h
34
- result = @evaluator.get_assignment(
44
+ result = evaluator.get_assignment(
35
45
  flag_key, default_value: default_value, context: context, expected_type: expected_type
36
46
  )
37
47
 
48
+ result = with_observe_full_evaluation_data(result, observe_full_evaluation_data)
49
+
38
50
  @reporter.report(result, flag_key: flag_key, context: evaluation_context)
39
51
 
40
52
  result
41
53
  rescue => e
42
54
  @telemetry.report(e, description: "OpenFeature: Failed to fetch flag value")
43
55
 
44
- ResolutionDetails.build_error(
56
+ result = ResolutionDetails.build_error(
45
57
  value: default_value, error_code: Ext::GENERAL, error_message: "#{e.class}: #{e.message}"
46
58
  )
59
+ # `== true` is load-bearing: Steep types this local as `(nil | bool)` in a rescue
60
+ # body, and coercing at the use site is what narrows it to `bool`.
61
+ with_observe_full_evaluation_data(result, observe_full_evaluation_data == true)
47
62
  end
48
63
 
49
64
  # NOTE: In a currect implementation configuration is expected to be a raw
@@ -65,6 +80,17 @@ module Datadog
65
80
 
66
81
  raise ReconfigurationError, "#{e.class}: #{e.message}"
67
82
  end
83
+
84
+ private
85
+
86
+ # Stamps observe_full_evaluation_data onto the result metadata so the hook
87
+ # reads it from the event, not live config.
88
+ def with_observe_full_evaluation_data(result, observe_full_evaluation_data)
89
+ result = result.dup if result.frozen?
90
+ metadata = result.flag_metadata ||= {}
91
+ metadata[Ext::METADATA_OBSERVE_FULL_EVALUATION_DATA] = observe_full_evaluation_data
92
+ result
93
+ end
68
94
  end
69
95
  end
70
96
  end
@@ -12,7 +12,7 @@ module Datadog
12
12
  def payload_for(events)
13
13
  {
14
14
  context: @context,
15
- exposures: events
15
+ exposures: events,
16
16
  }
17
17
  end
18
18
 
@@ -15,27 +15,34 @@ module Datadog
15
15
  "#{flag_key}:#{context.targeting_key}"
16
16
  end
17
17
 
18
+ # NOTE: The serial id is part of the value because it can appear or change on a
19
+ # configuration refresh while the allocation and the variant stay the same.
18
20
  def cache_value(result, flag_key:, context:)
19
- "#{result.allocation_key}:#{result.variant}"
21
+ "#{result.allocation_key}:#{result.variant}:#{result.serial_id}"
20
22
  end
21
23
 
22
24
  def build(result, flag_key:, context:)
23
- {
25
+ event = {
24
26
  timestamp: current_timestamp_ms,
25
27
  allocation: {
26
- key: result.allocation_key
28
+ key: result.allocation_key,
27
29
  },
28
30
  flag: {
29
- key: flag_key
31
+ key: flag_key,
30
32
  },
31
33
  variant: {
32
- key: result.variant
34
+ key: result.variant,
33
35
  },
34
36
  subject: {
35
37
  id: context.targeting_key,
36
- attributes: extract_attributes(context)
37
- }
38
- }.freeze
38
+ attributes: extract_attributes(context),
39
+ },
40
+ }
41
+
42
+ serial_id = result.serial_id
43
+ event[:serial_id] = serial_id unless serial_id.nil?
44
+
45
+ event.freeze
39
46
  end
40
47
 
41
48
  private
@@ -11,11 +11,30 @@ module Datadog
11
11
  GENERAL = "GENERAL"
12
12
  PROVIDER_FATAL = "PROVIDER_FATAL"
13
13
  PROVIDER_NOT_READY = "PROVIDER_NOT_READY"
14
+ FLAG_NOT_FOUND = "FLAG_NOT_FOUND"
15
+ TYPE_MISMATCH = "TYPE_MISMATCH"
16
+ TARGETING_KEY_MISSING = "TARGETING_KEY_MISSING"
17
+ INVALID_CONTEXT = "INVALID_CONTEXT"
18
+
19
+ STANDARD_ERROR_CODES = [
20
+ PROVIDER_NOT_READY,
21
+ FLAG_NOT_FOUND,
22
+ PARSE_ERROR,
23
+ TYPE_MISMATCH,
24
+ TARGETING_KEY_MISSING,
25
+ INVALID_CONTEXT,
26
+ PROVIDER_FATAL,
27
+ GENERAL,
28
+ ].freeze
14
29
 
15
30
  # Flag-metadata key under which the provider threads the assignment's
16
31
  # allocation key to the flag-evaluation hooks. The wire string is the
17
32
  # value so the writer (provider) and readers (EVP/metrics hooks) can't drift.
18
33
  METADATA_ALLOCATION_KEY = "__dd_allocation_key"
34
+
35
+ # Stamped from the UFC the evaluation ran against; the hook reads only this
36
+ # key, never live config.
37
+ METADATA_OBSERVE_FULL_EVALUATION_DATA = "__dd_observe_full_evaluation_data"
19
38
  end
20
39
  end
21
40
  end