11import logging
2- from typing import Any , Dict , List , Optional , cast
2+ from typing import Any , Dict , List , Optional , Union , cast
33
4+ from ... import Logger
45from . import schema
56from .base import StoreProvider
67from .exceptions import ConfigurationStoreError
78
8- logger = logging .getLogger (__name__ )
9-
109
1110class FeatureFlags :
12- def __init__ (self , store : StoreProvider ):
11+ def __init__ (self , store : StoreProvider , logger : Optional [ Union [ logging . Logger , Logger ]] = None ):
1312 """Evaluates whether feature flags should be enabled based on a given context.
1413
1514 It uses the provided store to fetch feature flag rules before evaluating them.
@@ -35,11 +34,13 @@ def __init__(self, store: StoreProvider):
3534 ----------
3635 store: StoreProvider
3736 Store to use to fetch feature flag schema configuration.
37+ logger: A logging object
38+ Used to log messages. If None is supplied, one will be created.
3839 """
3940 self .store = store
41+ self .logger = logger or logging .getLogger (__name__ )
4042
41- @staticmethod
42- def _match_by_action (action : str , condition_value : Any , context_value : Any ) -> bool :
43+ def _match_by_action (self , action : str , condition_value : Any , context_value : Any ) -> bool :
4344 if not context_value :
4445 return False
4546 mapping_by_action = {
@@ -58,7 +59,7 @@ def _match_by_action(action: str, condition_value: Any, context_value: Any) -> b
5859 func = mapping_by_action .get (action , lambda a , b : False )
5960 return func (context_value , condition_value )
6061 except Exception as exc :
61- logger .debug (f"caught exception while matching action: action={ action } , exception={ str (exc )} " )
62+ self . logger .debug (f"caught exception while matching action: action={ action } , exception={ str (exc )} " )
6263 return False
6364
6465 def _evaluate_conditions (
@@ -69,7 +70,7 @@ def _evaluate_conditions(
6970 conditions = cast (List [Dict ], rule .get (schema .CONDITIONS_KEY ))
7071
7172 if not conditions :
72- logger .debug (
73+ self . logger .debug (
7374 f"rule did not match, no conditions to match, rule_name={ rule_name } , rule_value={ rule_match_value } , "
7475 f"name={ feature_name } "
7576 )
@@ -81,13 +82,13 @@ def _evaluate_conditions(
8182 cond_value = condition .get (schema .CONDITION_VALUE )
8283
8384 if not self ._match_by_action (action = cond_action , condition_value = cond_value , context_value = context_value ):
84- logger .debug (
85+ self . logger .debug (
8586 f"rule did not match action, rule_name={ rule_name } , rule_value={ rule_match_value } , "
8687 f"name={ feature_name } , context_value={ str (context_value )} "
8788 )
8889 return False # context doesn't match condition
8990
90- logger .debug (f"rule matched, rule_name={ rule_name } , rule_value={ rule_match_value } , name={ feature_name } " )
91+ self . logger .debug (f"rule matched, rule_name={ rule_name } , rule_value={ rule_match_value } , name={ feature_name } " )
9192 return True
9293
9394 def _evaluate_rules (
@@ -98,12 +99,16 @@ def _evaluate_rules(
9899 rule_match_value = rule .get (schema .RULE_MATCH_VALUE )
99100
100101 # Context might contain PII data; do not log its value
101- logger .debug (f"Evaluating rule matching, rule={ rule_name } , feature={ feature_name } , default={ feat_default } " )
102+ self .logger .debug (
103+ f"Evaluating rule matching, rule={ rule_name } , feature={ feature_name } , default={ feat_default } "
104+ )
102105 if self ._evaluate_conditions (rule_name = rule_name , feature_name = feature_name , rule = rule , context = context ):
103106 return bool (rule_match_value )
104107
105108 # no rule matched, return default value of feature
106- logger .debug (f"no rule matched, returning feature default, default={ feat_default } , name={ feature_name } " )
109+ self .logger .debug (
110+ f"no rule matched, returning feature default, default={ feat_default } , name={ feature_name } "
111+ )
107112 return feat_default
108113 return False
109114
@@ -150,7 +155,7 @@ def get_configuration(self) -> Dict:
150155 ```
151156 """
152157 # parse result conf as JSON, keep in cache for max age defined in store
153- logger .debug (f"Fetching schema from registered store, store={ self .store } " )
158+ self . logger .debug (f"Fetching schema from registered store, store={ self .store } " )
154159 config : Dict = self .store .get_configuration ()
155160 validator = schema .SchemaValidator (schema = config )
156161 validator .validate ()
@@ -194,21 +199,21 @@ def evaluate(self, *, name: str, context: Optional[Dict[str, Any]] = None, defau
194199 try :
195200 features = self .get_configuration ()
196201 except ConfigurationStoreError as err :
197- logger .debug (f"Failed to fetch feature flags from store, returning default provided, reason={ err } " )
202+ self . logger .debug (f"Failed to fetch feature flags from store, returning default provided, reason={ err } " )
198203 return default
199204
200205 feature = features .get (name )
201206 if feature is None :
202- logger .debug (f"Feature not found; returning default provided, name={ name } , default={ default } " )
207+ self . logger .debug (f"Feature not found; returning default provided, name={ name } , default={ default } " )
203208 return default
204209
205210 rules = feature .get (schema .RULES_KEY )
206211 feat_default = feature .get (schema .FEATURE_DEFAULT_VAL_KEY )
207212 if not rules :
208- logger .debug (f"no rules found, returning feature default, name={ name } , default={ feat_default } " )
213+ self . logger .debug (f"no rules found, returning feature default, name={ name } , default={ feat_default } " )
209214 return bool (feat_default )
210215
211- logger .debug (f"looking for rule match, name={ name } , default={ feat_default } " )
216+ self . logger .debug (f"looking for rule match, name={ name } , default={ feat_default } " )
212217 return self ._evaluate_rules (feature_name = name , context = context , feat_default = bool (feat_default ), rules = rules )
213218
214219 def get_enabled_features (self , * , context : Optional [Dict [str , Any ]] = None ) -> List [str ]:
@@ -245,20 +250,20 @@ def get_enabled_features(self, *, context: Optional[Dict[str, Any]] = None) -> L
245250 try :
246251 features : Dict [str , Any ] = self .get_configuration ()
247252 except ConfigurationStoreError as err :
248- logger .debug (f"Failed to fetch feature flags from store, returning empty list, reason={ err } " )
253+ self . logger .debug (f"Failed to fetch feature flags from store, returning empty list, reason={ err } " )
249254 return features_enabled
250255
251- logger .debug ("Evaluating all features" )
256+ self . logger .debug ("Evaluating all features" )
252257 for name , feature in features .items ():
253258 rules = feature .get (schema .RULES_KEY , {})
254259 feature_default_value = feature .get (schema .FEATURE_DEFAULT_VAL_KEY )
255260 if feature_default_value and not rules :
256- logger .debug (f"feature is enabled by default and has no defined rules, name={ name } " )
261+ self . logger .debug (f"feature is enabled by default and has no defined rules, name={ name } " )
257262 features_enabled .append (name )
258263 elif self ._evaluate_rules (
259264 feature_name = name , context = context , feat_default = feature_default_value , rules = rules
260265 ):
261- logger .debug (f"feature's calculated value is True, name={ name } " )
266+ self . logger .debug (f"feature's calculated value is True, name={ name } " )
262267 features_enabled .append (name )
263268
264269 return features_enabled
0 commit comments