diff --git a/src/amplitude_experiment/local/client.py b/src/amplitude_experiment/local/client.py index 2a73954..99c5d5c 100644 --- a/src/amplitude_experiment/local/client.py +++ b/src/amplitude_experiment/local/client.py @@ -106,7 +106,7 @@ def evaluate_v2(self, user: User, flag_keys: Set[str] = None, options: EvaluateO flag_configs = self.flag_config_storage.get_flag_configs() if flag_configs is None or len(flag_configs) == 0: return {} - self.logger.debug(f"[Experiment] Evaluate: user={user} - Flags: {flag_configs}") + self.logger.debug("[Experiment] Evaluate: user=%s - Flags: %s", user, flag_configs) sorted_flags = topological_sort(flag_configs, flag_keys and list(flag_keys)) if not sorted_flags: return {} @@ -126,7 +126,7 @@ def evaluate_v2(self, user: User, flag_keys: Set[str] = None, options: EvaluateO metadata=v.metadata ) for k, v in result.items() } - self.logger.debug(f"[Experiment] Evaluate Result: {variants}") + self.logger.debug("[Experiment] Evaluate Result: %s", variants) if self.exposure_service is not None and options and options.tracks_exposure is True: self.exposure_service.track(Exposure(user, variants)) if self.assignment_service is not None: diff --git a/tests/local/evaluate_logging_test.py b/tests/local/evaluate_logging_test.py new file mode 100644 index 0000000..40902f1 --- /dev/null +++ b/tests/local/evaluate_logging_test.py @@ -0,0 +1,55 @@ +import logging +import unittest +from unittest.mock import patch + +from src.amplitude_experiment import LocalEvaluationClient, LocalEvaluationConfig, User +from src.amplitude_experiment.evaluation.types import EvaluationFlag + +API_KEY = 'server-api-key' + + +def always_on_flag(key: str) -> EvaluationFlag: + return EvaluationFlag.from_dict({ + 'key': key, + 'variants': {'on': {'key': 'on', 'value': 'on'}}, + 'segments': [{'variant': 'on'}], + }) + + +def forbidden_repr(self) -> str: + raise AssertionError('a flag config was rendered to text') + + +class LocalEvaluationClientEvaluateLoggingTestCase(unittest.TestCase): + + def _client(self, debug: bool) -> LocalEvaluationClient: + # Never started: no poller thread, no network call. + logger = logging.getLogger(f'evaluate-logging-test-{debug}') + logger.setLevel(logging.DEBUG if debug else logging.WARNING) + client = LocalEvaluationClient(API_KEY, LocalEvaluationConfig(logger=logger)) + client.flag_config_storage.put_flag_config(always_on_flag('flag-a')) + client.flag_config_storage.put_flag_config(always_on_flag('flag-b')) + return client + + def test_evaluate_does_not_render_flag_configs_when_debug_is_disabled(self): + client = self._client(debug=False) + + with patch.object(EvaluationFlag, '__repr__', forbidden_repr): + variants = client.evaluate_v2(User(user_id='user'), {'flag-a'}) + + self.assertEqual('on', variants['flag-a'].value) + + def test_evaluate_logs_flag_configs_and_result_when_debug_is_enabled(self): + client = self._client(debug=True) + + with self.assertLogs(client.logger, logging.DEBUG) as logs: + client.evaluate_v2(User(user_id='user'), {'flag-a'}) + + self.assertEqual(2, len(logs.output)) + self.assertIn('Evaluate: user={"device_id": null, "user_id": "user"', logs.output[0]) + self.assertIn("EvaluationFlag(key='flag-b'", logs.output[0]) + self.assertIn("Evaluate Result: {'flag-a': ", logs.output[1]) + + +if __name__ == '__main__': + unittest.main()