Class: Quonfig::Client
- Inherits:
-
Object
- Object
- Quonfig::Client
- Defined in:
- lib/quonfig/client.rb
Overview
Public Quonfig SDK client.
Wires the JSON stack: Quonfig::ConfigStore + Quonfig::Evaluator + Quonfig::Resolver. Three modes are supported:
-
datadir:(offline) – load a workspace from the local filesystem. -
store:(test harness) – caller-supplied ConfigStore, no I/O. -
network mode (default) – HTTP fetch from
api_urlspopulates the ConfigStore, then (if enabled) an SSE subscription keeps it live.
Network mode is the happy path for production SDK usage. The protobuf stack was retired in qfg-dk6.32; HTTP + SSE were wired back through Client in qfg-s7h.
Constant Summary collapse
- LOG =
Quonfig::InternalLogger.new(self)
Instance Attribute Summary collapse
-
#config_loader ⇒ Object
readonly
Returns the value of attribute config_loader.
-
#evaluator ⇒ Object
readonly
Returns the value of attribute evaluator.
-
#instance_hash ⇒ Object
readonly
Returns the value of attribute instance_hash.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
-
#resolver ⇒ Object
readonly
Returns the value of attribute resolver.
-
#store ⇒ Object
readonly
Returns the value of attribute store.
-
#telemetry_reporter ⇒ Object
readonly
Returns the value of attribute telemetry_reporter.
Instance Method Summary collapse
- #defined?(key) ⇒ Boolean
- #enabled?(feature_name, jit_context = NO_DEFAULT_PROVIDED) ⇒ Boolean
- #fork ⇒ Object
-
#get(key, default = NO_DEFAULT_PROVIDED, jit_context = NO_DEFAULT_PROVIDED) ⇒ Object
—- Lookup ——————————————————–.
- #get_bool(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) ⇒ Object
-
#get_bool_details(key, context: NO_DEFAULT_PROVIDED) ⇒ Object
—- Details getters ———————————————-.
- #get_duration(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) ⇒ Object
- #get_float(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) ⇒ Object
- #get_float_details(key, context: NO_DEFAULT_PROVIDED) ⇒ Object
- #get_int(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) ⇒ Object
- #get_int_details(key, context: NO_DEFAULT_PROVIDED) ⇒ Object
- #get_json(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) ⇒ Object
- #get_json_details(key, context: NO_DEFAULT_PROVIDED) ⇒ Object
- #get_string(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) ⇒ Object
- #get_string_details(key, context: NO_DEFAULT_PROVIDED) ⇒ Object
- #get_string_list(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) ⇒ Object
- #get_string_list_details(key, context: NO_DEFAULT_PROVIDED) ⇒ Object
-
#in_context(properties) ⇒ Object
—- Context binding ———————————————-.
-
#initialize(options = nil, store: nil, **option_kwargs) ⇒ Client
constructor
A new instance of Client.
- #inspect ⇒ Object
- #keys ⇒ Object
-
#logger_key ⇒ Object
The configured
logger_keyfrom Options — the Quonfig config key the higher-levelshould_log?helper evaluates per-logger. - #on_update(&block) ⇒ Object
-
#semantic_logger_filter(config_key:) ⇒ Object
—- Filters & helpers ——————————————–.
-
#should_log?(logger_path:, desired_level:, contexts: {}) ⇒ Boolean
Higher-level log-level check — a convenience on top of the primitive
get. -
#stdlib_formatter(logger_name: nil) ⇒ Proc
Build a formatter Proc for Ruby’s built-in
::Logger. - #stop ⇒ Object
- #with_context(properties, &block) ⇒ Object
Constructor Details
#initialize(options = nil, store: nil, **option_kwargs) ⇒ Client
Returns a new instance of Client.
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/quonfig/client.rb', line 26 def initialize( = nil, store: nil, **option_kwargs) @options = if .is_a?(Quonfig::Options) elsif .is_a?(Hash) Quonfig::Options.new(.merge(option_kwargs)) else Quonfig::Options.new(option_kwargs) end Quonfig::InternalLogger.user_logger = @options.logger if @options.logger @global_context = build_initial_global_context(@options) @instance_hash = SecureRandom.uuid @store = store || Quonfig::ConfigStore.new @evaluator = Quonfig::Evaluator.new(@store, env_id: @options.environment) @resolver = Quonfig::Resolver.new(@store, @evaluator) @semantic_logger_filters = {} @sse_client = nil @poll_thread = nil @stopped = false @telemetry_reporter = nil # If the caller injected a store, we're in test/bootstrap mode; skip I/O. return if store if @options.datadir load_datadir_into_store else initialize_network_mode end initialize_telemetry end |
Instance Attribute Details
#config_loader ⇒ Object (readonly)
Returns the value of attribute config_loader.
23 24 25 |
# File 'lib/quonfig/client.rb', line 23 def config_loader @config_loader end |
#evaluator ⇒ Object (readonly)
Returns the value of attribute evaluator.
23 24 25 |
# File 'lib/quonfig/client.rb', line 23 def evaluator @evaluator end |
#instance_hash ⇒ Object (readonly)
Returns the value of attribute instance_hash.
23 24 25 |
# File 'lib/quonfig/client.rb', line 23 def instance_hash @instance_hash end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
23 24 25 |
# File 'lib/quonfig/client.rb', line 23 def @options end |
#resolver ⇒ Object (readonly)
Returns the value of attribute resolver.
23 24 25 |
# File 'lib/quonfig/client.rb', line 23 def resolver @resolver end |
#store ⇒ Object (readonly)
Returns the value of attribute store.
23 24 25 |
# File 'lib/quonfig/client.rb', line 23 def store @store end |
#telemetry_reporter ⇒ Object (readonly)
Returns the value of attribute telemetry_reporter.
23 24 25 |
# File 'lib/quonfig/client.rb', line 23 def telemetry_reporter @telemetry_reporter end |
Instance Method Details
#defined?(key) ⇒ Boolean
144 145 146 |
# File 'lib/quonfig/client.rb', line 144 def defined?(key) !@store.get(key).nil? end |
#enabled?(feature_name, jit_context = NO_DEFAULT_PROVIDED) ⇒ Boolean
139 140 141 142 |
# File 'lib/quonfig/client.rb', line 139 def enabled?(feature_name, jit_context = NO_DEFAULT_PROVIDED) value = get(feature_name, false, jit_context) [true, 'true'].include?(value) end |
#fork ⇒ Object
281 282 283 |
# File 'lib/quonfig/client.rb', line 281 def fork self.class.new(@options.for_fork) end |
#get(key, default = NO_DEFAULT_PROVIDED, jit_context = NO_DEFAULT_PROVIDED) ⇒ Object
—- Lookup ——————————————————–
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/quonfig/client.rb', line 61 def get(key, default = NO_DEFAULT_PROVIDED, jit_context = NO_DEFAULT_PROVIDED) ctx = build_context(jit_context) record_context_for_telemetry(ctx) result = begin @resolver.get(key, ctx) rescue Quonfig::Errors::MissingDefaultError # The Resolver raises (matching Quonfig.get_or_raise semantics). # The Client's get applies the caller-provided default *or* the # configured on_no_default policy via handle_missing. nil end return handle_missing(key, default) if result.nil? record_evaluation_for_telemetry(result) result.unwrapped_value end |
#get_bool(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) ⇒ Object
91 92 93 |
# File 'lib/quonfig/client.rb', line 91 def get_bool(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) typed_get(key, :bool, default: default, context: context) end |
#get_bool_details(key, context: NO_DEFAULT_PROVIDED) ⇒ Object
—- Details getters ———————————————-
Mirrors the typed getters above but returns a Quonfig::EvaluationDetails carrying the OpenFeature-aligned resolution reason (“STATIC”, “TARGETING_MATCH”, “SPLIT”, “DEFAULT”, or “ERROR”) plus an error_code/error_message on the error path. These methods never raise — exceptions are caught and rendered as ERROR details.
115 116 117 |
# File 'lib/quonfig/client.rb', line 115 def get_bool_details(key, context: NO_DEFAULT_PROVIDED) evaluate_details(key, :bool, context) end |
#get_duration(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) ⇒ Object
99 100 101 |
# File 'lib/quonfig/client.rb', line 99 def get_duration(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) typed_get(key, :duration, default: default, context: context) end |
#get_float(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) ⇒ Object
87 88 89 |
# File 'lib/quonfig/client.rb', line 87 def get_float(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) typed_get(key, Float, default: default, context: context) end |
#get_float_details(key, context: NO_DEFAULT_PROVIDED) ⇒ Object
127 128 129 |
# File 'lib/quonfig/client.rb', line 127 def get_float_details(key, context: NO_DEFAULT_PROVIDED) evaluate_details(key, Float, context) end |
#get_int(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) ⇒ Object
83 84 85 |
# File 'lib/quonfig/client.rb', line 83 def get_int(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) typed_get(key, Integer, default: default, context: context) end |
#get_int_details(key, context: NO_DEFAULT_PROVIDED) ⇒ Object
123 124 125 |
# File 'lib/quonfig/client.rb', line 123 def get_int_details(key, context: NO_DEFAULT_PROVIDED) evaluate_details(key, Integer, context) end |
#get_json(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) ⇒ Object
103 104 105 |
# File 'lib/quonfig/client.rb', line 103 def get_json(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) typed_get(key, :json, default: default, context: context) end |
#get_json_details(key, context: NO_DEFAULT_PROVIDED) ⇒ Object
135 136 137 |
# File 'lib/quonfig/client.rb', line 135 def get_json_details(key, context: NO_DEFAULT_PROVIDED) evaluate_details(key, :json, context) end |
#get_string(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) ⇒ Object
79 80 81 |
# File 'lib/quonfig/client.rb', line 79 def get_string(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) typed_get(key, String, default: default, context: context) end |
#get_string_details(key, context: NO_DEFAULT_PROVIDED) ⇒ Object
119 120 121 |
# File 'lib/quonfig/client.rb', line 119 def get_string_details(key, context: NO_DEFAULT_PROVIDED) evaluate_details(key, String, context) end |
#get_string_list(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) ⇒ Object
95 96 97 |
# File 'lib/quonfig/client.rb', line 95 def get_string_list(key, default: NO_DEFAULT_PROVIDED, context: NO_DEFAULT_PROVIDED) typed_get(key, :string_list, default: default, context: context) end |
#get_string_list_details(key, context: NO_DEFAULT_PROVIDED) ⇒ Object
131 132 133 |
# File 'lib/quonfig/client.rb', line 131 def get_string_list_details(key, context: NO_DEFAULT_PROVIDED) evaluate_details(key, :string_list, context) end |
#in_context(properties) ⇒ Object
—- Context binding ———————————————-
154 155 156 157 |
# File 'lib/quonfig/client.rb', line 154 def in_context(properties) bound = Quonfig::BoundClient.new(self, properties) block_given? ? yield(bound) : bound end |
#inspect ⇒ Object
285 286 287 |
# File 'lib/quonfig/client.rb', line 285 def inspect "#<Quonfig::Client:#{object_id} environment=#{@options.environment.inspect}>" end |
#keys ⇒ Object
148 149 150 |
# File 'lib/quonfig/client.rb', line 148 def keys @store.keys end |
#logger_key ⇒ Object
The configured logger_key from Options — the Quonfig config key the higher-level should_log? helper evaluates per-logger. nil if the client was not configured for dynamic log levels.
200 201 202 |
# File 'lib/quonfig/client.rb', line 200 def logger_key @options.logger_key end |
#on_update(&block) ⇒ Object
256 257 258 |
# File 'lib/quonfig/client.rb', line 256 def on_update(&block) @on_update = block end |
#semantic_logger_filter(config_key:) ⇒ Object
—- Filters & helpers ——————————————–
169 170 171 172 |
# File 'lib/quonfig/client.rb', line 169 def semantic_logger_filter(config_key:) @semantic_logger_filters[config_key] ||= Quonfig::SemanticLoggerFilter.new(self, config_key: config_key) end |
#should_log?(logger_path:, desired_level:, contexts: {}) ⇒ Boolean
Higher-level log-level check — a convenience on top of the primitive get. Evaluates the client’s logger_key config and returns whether a message at desired_level should be emitted for logger_path.
The SDK injects logger_path under the quonfig-sdk-logging named context with property key so a single log-level config can drive per-logger overrides via the normal rule engine (e.g. PROP_STARTS_WITH_ONE_OF “MyApp::Services::”).
logger_path is passed through verbatim — the SDK does not normalize it. Callers may pass any identifier shape their host language prefers (dotted, colon, slash, etc.) and author matching rules in the config against that exact shape.
Parallels sdk-node’s shouldLog({loggerPath}) and sdk-go’s ShouldLogPath.
Raises Quonfig::Error if logger_key was not set on the client —use semantic_logger_filter(config_key:) directly if you want to evaluate a specific key without declaring it at init time.
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/quonfig/client.rb', line 231 def should_log?(logger_path:, desired_level:, contexts: {}) unless logger_key raise Quonfig::Error, 'logger_key must be set at init to use should_log?(logger_path:, ...). ' \ 'Pass `logger_key:` to Quonfig::Options.new, or call ' \ 'semantic_logger_filter(config_key:) / get(config_key) directly.' end logger_context = { Quonfig::SemanticLoggerFilter::LOGGER_CONTEXT_NAME => { Quonfig::SemanticLoggerFilter::LOGGER_CONTEXT_KEY_PROP => logger_path } } merged = merge_contexts(normalize_context(contexts), logger_context) configured = get(logger_key, nil, merged) return true if configured.nil? desired_severity = Quonfig::SemanticLoggerFilter::LEVELS[normalize_log_level(desired_level)] || Quonfig::SemanticLoggerFilter::LEVELS[:debug] min_severity = Quonfig::SemanticLoggerFilter::LEVELS[normalize_log_level(configured)] || Quonfig::SemanticLoggerFilter::LEVELS[:debug] desired_severity >= min_severity end |
#stdlib_formatter(logger_name: nil) ⇒ Proc
Build a formatter Proc for Ruby’s built-in ::Logger. The returned proc honors dynamic log levels from the client’s logger_key config: for each log call, it evaluates should_log? and either formats the record or returns an empty string (suppressing output).
Matches ReforgeHQ’s stdlib_formatter API name (snake_case).
Usage:
logger = ::Logger.new($stdout)
logger.formatter = client.stdlib_formatter # uses progname
logger.formatter = client.stdlib_formatter(logger_name: 'MyApp') # fixed name
Raises Quonfig::Error if logger_key was not set at init — parallels should_log?‘s behavior.
193 194 195 |
# File 'lib/quonfig/client.rb', line 193 def stdlib_formatter(logger_name: nil) Quonfig::StdlibFormatter.build(self, logger_name: logger_name) end |
#stop ⇒ Object
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 |
# File 'lib/quonfig/client.rb', line 260 def stop @stopped = true begin @sse_client&.close rescue StandardError => e LOG.debug "Error closing SSE client: #{e.}" end @sse_client = nil thread = @poll_thread @poll_thread = nil thread&.kill begin @telemetry_reporter&.stop rescue StandardError => e LOG.debug "Error stopping telemetry reporter: #{e.}" end @telemetry_reporter = nil end |
#with_context(properties, &block) ⇒ Object
159 160 161 162 163 164 165 |
# File 'lib/quonfig/client.rb', line 159 def with_context(properties, &block) if block_given? in_context(properties, &block) else Quonfig::BoundClient.new(self, properties) end end |