-
Notifications
You must be signed in to change notification settings - Fork 398
[FFL-1319] Add feature flags events exposure #5024
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Strech
merged 81 commits into
master
from
ffl-1319-add-agent-communication-for-openfeature
Nov 13, 2025
+3,169
−3
Merged
Changes from all commits
Commits
Show all changes
81 commits
Select commit
Hold shift + click to select a range
b567235
Add OpenFeature component
Strech da49ec8
Fir require of the OpenFeature component
Strech 9bad9dd
Add skeleton for evaluation API
Strech 105c754
Add guard-clause to the Provider and Evaluator
Strech 9617a35
Add comment regarding GC
Strech a381534
Update settings and component interface impl.
Strech 6c761bd
Add RBS files for OpenFeature component
Strech 4ed3540
Fix typing for Remote module
Strech 98f116c
Add openfeature-sdk stub and fix component typing
Strech da72ee2
Fix settings RBS
Strech f543d64
Wrap FFI code into a separate module
Strech d978e50
Add tests for remote config
Strech 46396ec
Add component tests
Strech 4e24ca8
Add new environment configuration definition
Strech 11209aa
Add component specs
Strech e3333a5
Add testing matrix and new group
Strech f29acf7
Adjust evaluator specs
Strech ec27207
Add some basic tests for Provider
Strech c327543
Extract Binding module
Strech 9c0a0a8
Rename Evaluator to be EvaluationEngine
Strech 894248d
Update EvaluationEngine initialization interface
Strech d8f32fa
Rework evaluation engine name in the component
Strech 73c6d55
Rework evaluation engine name in the component
Strech 1257f95
Fix naming in provider
Strech ec25611
Add extention module with constants
Strech 99966c5
Update component initialization
Strech 0a4a399
Add reconfiguration mutex to the evaluation engine
Strech 1fbc831
Make engine dynamic inside provider
Strech 4060236
Add no-op evaluator to the engine
Strech d481342
Update error and success results structure
Strech 04cf79f
Introduce Binding::ResolutionDetails object
Strech c579c33
Add transport and exposures data model
Strech 95a6041
Clean exposure events transport behavior
Strech b2a5cbe
Clean http client
Strech f9a87a7
Clean transport HTTP API
Strech 832eb5e
Clean up entire transport folder
Strech c0699c5
AI: Worker and Buffer initial implementation
Strech c9e02bb
AI: Adjustments to the worker and buffer interface
Strech 3a7c475
AI: Add Core::Queue instead of manual operations
Strech 5ceb571
Adjust buffer implementation and correct specs
Strech 5771e4c
AI: Add tests for worker class
Strech 8710948
Refactor worker and cover behavior with tests
Strech 4383006
Add RBS files for transport and worker
Strech aa7498e
Fix worker perform interface
Strech 1d31752
Create Reporter for exposures reporting
Strech 659d81c
Introduce deduplicator and reporter
Strech 42e6f5d
Rework Reporter and the data model
Strech c73be52
Refactor batching of exposure events
Strech 971c806
Fix tests after rebase
Strech 4872fca
Use Binding::ResolutionDetails everywhere
Strech 330f0d0
Add reporting to the evaluation
Strech 3e98e02
Fix tests after rebase
Strech 73f8601
Adjust code to be thread-safe
Strech f8af1a2
Update RBS definitions
Strech b4fa4b6
Address PR feedback
Strech 27a9ace
Change RC and reconfiguration flow
Strech f39e6aa
Fix standardrb complaints
Strech ea0776a
Fix duplication issue in components
Strech 62e6a0e
Fix loading specs
Strech e814c6a
Remove stale types
Strech 7b3d536
Add additional test case for RC being disabled
Strech 1ba10f1
Add missing RBS definitions
Strech a4a3ffb
Apply suggested changes from the review
Strech e2a6bd6
Update lib/datadog/open_feature/component.rb
Strech fdc8975
Reduce boilerplate and unnecessary files
Strech e2ea13a
Fix worker flushing on demand and shutdown
Strech bf101c6
Remove unnecessary methods from batch builder
Strech 057168c
Add helper methods to the resolution details
Strech ed55b41
Remove unused evaluation engine exposed methods
Strech ca515b4
Fix issues raised by standardrb
Strech d0212d8
Update provider documentation
Strech 0fcbd14
Update spec/datadog/open_feature/remote_spec.rb
Strech cf7425e
Update lib/datadog/open_feature/component.rb
Strech 8420179
Update lib/datadog/open_feature/exposures/worker.rb
Strech 0ab7091
Update lib/datadog/open_feature/evaluation_engine.rb
Strech fe83a47
Update lib/datadog/open_feature/exposures/reporter.rb
Strech b375017
Update lib/datadog/open_feature/exposures/event.rb
Strech 6025613
Update lib/datadog/open_feature/exposures/worker.rb
Strech 6609310
Update lib/datadog/open_feature/transport/exposures.rb
Strech db96bdf
Update lib/datadog/open_feature/transport/http/exposures.rb
Strech e572afc
Fix edge-cases mentioned in review
Strech File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Strech marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| require_relative 'core/configuration' | ||
| require_relative 'open_feature/configuration' | ||
|
|
||
| module Datadog | ||
| # A namespace for the OpenFeature component. | ||
| module OpenFeature | ||
| Core::Configuration::Settings.extend(Configuration::Settings) | ||
|
|
||
| def self.enabled? | ||
| Datadog.configuration.open_feature.enabled | ||
| end | ||
|
|
||
| def self.engine | ||
| Datadog.send(:components).open_feature&.engine | ||
| end | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| require_relative 'evaluation_engine' | ||
| require_relative 'exposures/buffer' | ||
| require_relative 'exposures/worker' | ||
| require_relative 'exposures/deduplicator' | ||
| require_relative 'exposures/reporter' | ||
| require_relative 'transport/http' | ||
|
|
||
| module Datadog | ||
| module OpenFeature | ||
| # This class is the entry point for the OpenFeature component | ||
| class Component | ||
| attr_reader :engine | ||
|
|
||
| def self.build(settings, agent_settings, logger:, telemetry:) | ||
| return unless settings.respond_to?(:open_feature) && settings.open_feature.enabled | ||
|
|
||
| unless settings.respond_to?(:remote) && settings.remote.enabled | ||
| message = 'OpenFeature could not be enabled as Remote Configuration is currently disabled. ' \ | ||
| 'To enable Remote Configuration, see https://docs.datadoghq.com/agent/remote_config' | ||
| logger.warn(message) | ||
|
|
||
| return | ||
| end | ||
|
|
||
| new(settings, agent_settings, logger: logger, telemetry: telemetry) | ||
| end | ||
|
|
||
| def initialize(settings, agent_settings, logger:, telemetry:) | ||
| transport = Transport::HTTP.build(agent_settings: agent_settings, logger: logger) | ||
| @worker = Exposures::Worker.new(settings: settings, transport: transport, telemetry: telemetry, logger: logger) | ||
|
|
||
| reporter = Exposures::Reporter.new(@worker, telemetry: telemetry, logger: logger) | ||
| @engine = EvaluationEngine.new(reporter, telemetry: telemetry, logger: logger) | ||
| end | ||
|
|
||
| def shutdown! | ||
Strech marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| @worker.graceful_shutdown | ||
| end | ||
| end | ||
| end | ||
| end | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| module Datadog | ||
| module OpenFeature | ||
| module Configuration | ||
| # A settings class for the OpenFeature component. | ||
| module Settings | ||
| def self.extended(base) | ||
| base = base.singleton_class unless base.is_a?(Class) | ||
| add_settings!(base) | ||
| end | ||
|
|
||
| def self.add_settings!(base) | ||
| base.class_eval do | ||
| settings :open_feature do | ||
| option :enabled do |o| | ||
| o.type :bool | ||
| o.env 'DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED' | ||
| o.default false | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| require_relative 'ext' | ||
| require_relative 'noop_evaluator' | ||
| require_relative 'resolution_details' | ||
|
|
||
| module Datadog | ||
| module OpenFeature | ||
| # This class performs the evaluation of the feature flag | ||
| class EvaluationEngine | ||
| ReconfigurationError = Class.new(StandardError) | ||
|
|
||
| ALLOWED_TYPES = %w[boolean string number float integer object].freeze | ||
|
|
||
| def initialize(reporter, telemetry:, logger:) | ||
| @reporter = reporter | ||
| @telemetry = telemetry | ||
| @logger = logger | ||
|
|
||
| @evaluator = NoopEvaluator.new(nil) | ||
| end | ||
|
|
||
| def fetch_value(flag_key:, default_value:, expected_type:, evaluation_context: nil) | ||
| unless ALLOWED_TYPES.include?(expected_type) | ||
| message = "unknown type #{expected_type.inspect}, allowed types #{ALLOWED_TYPES.join(", ")}" | ||
| return ResolutionDetails.build_error( | ||
| value: default_value, error_code: Ext::UNKNOWN_TYPE, error_message: message | ||
| ) | ||
| end | ||
|
|
||
| context = evaluation_context&.fields || {} | ||
| result = @evaluator.get_assignment(flag_key, default_value, context, expected_type) | ||
|
|
||
| @reporter.report(result, flag_key: flag_key, context: evaluation_context) | ||
|
|
||
| result | ||
| rescue => e | ||
| @telemetry.report(e, description: 'OpenFeature: Failed to fetch flag value') | ||
|
|
||
| ResolutionDetails.build_error( | ||
| value: default_value, error_code: Ext::PROVIDER_FATAL, error_message: e.message | ||
| ) | ||
| end | ||
|
|
||
| def reconfigure!(configuration) | ||
| @logger.debug('OpenFeature: Removing configuration') if configuration.nil? | ||
|
|
||
| @evaluator = NoopEvaluator.new(configuration) | ||
| rescue => e | ||
| message = 'OpenFeature: Failed to reconfigure, reverting to the previous configuration' | ||
|
|
||
| @logger.error("#{message}, error #{e.inspect}") | ||
| @telemetry.report(e, description: message) | ||
|
|
||
| raise ReconfigurationError, e.message | ||
| end | ||
| end | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| module Datadog | ||
| module OpenFeature | ||
| module Exposures | ||
| # This class builds a batch of exposures and context to be sent to the Agent | ||
| class BatchBuilder | ||
| def initialize(settings) | ||
| @context = build_context(settings) | ||
| end | ||
|
|
||
| def payload_for(events) | ||
| { | ||
| context: @context, | ||
| exposures: events | ||
| } | ||
| end | ||
|
|
||
| private | ||
|
|
||
| def build_context(settings) | ||
| context = {} | ||
| context[:env] = settings.env if settings.env | ||
| context[:service] = settings.service if settings.service | ||
| context[:version] = settings.version if settings.version | ||
|
|
||
| context | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| require_relative '../../core/buffer/cruby' | ||
|
|
||
| module Datadog | ||
| module OpenFeature | ||
| module Exposures | ||
| # This class is a buffer for exposure events that evicts at random and | ||
| # keeps track of the number of dropped events | ||
| # | ||
| # WARNING: This class does not work as intended on JRuby | ||
| class Buffer < Core::Buffer::CRuby | ||
| DEFAULT_LIMIT = 1_000 | ||
|
|
||
| attr_reader :dropped_count | ||
|
|
||
| def initialize(limit = DEFAULT_LIMIT) | ||
| @dropped = 0 | ||
| @dropped_count = 0 | ||
|
|
||
| super | ||
| end | ||
|
|
||
| protected | ||
|
|
||
| def drain! | ||
| drained = super | ||
|
|
||
| @dropped_count = @dropped | ||
| @dropped = 0 | ||
|
|
||
| drained | ||
| end | ||
|
|
||
| def replace!(item) | ||
| @dropped += 1 | ||
|
|
||
| super | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| require_relative '../../core/utils/lru_cache' | ||
|
|
||
| module Datadog | ||
| module OpenFeature | ||
| module Exposures | ||
| # This class is a deduplication buffer based on LRU cache for exposure events | ||
| class Deduplicator | ||
Strech marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| DEFAULT_CACHE_LIMIT = 1_000 | ||
|
|
||
| def initialize(limit: DEFAULT_CACHE_LIMIT) | ||
| @cache = Datadog::Core::Utils::LRUCache.new(limit) | ||
Strech marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| @mutex = Mutex.new | ||
| end | ||
|
|
||
| def duplicate?(key, value) | ||
| @mutex.synchronize do | ||
Strech marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| stored = @cache[key] | ||
| return true if stored == value | ||
|
|
||
| @cache[key] = value | ||
| end | ||
|
|
||
| false | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.