Skip to content

Commit e11446c

Browse files
authored
FFM-7363 Standardise Lifecycle messages (#72)
* FFM-7363 Add SDK codes functionality * FFM-7363 Add SDK codes test * FFM-7363 Add SDK codes test * FFM-7363 Add SDK codes test * FFM-7363 Add SDK codes test * FFM-7363 Call logger * FFM-7363 Update retry message * FFM-7363 Implement auth messages * FFM-7363 Implement polling messages * FFM-7363 Implement streaming messages * FFM-7363 Implement metrics messages * FFM-7363 Implement stop messages * FFM-7363 Implement eval success * FFM-7363 comment * FFM-7363 Call init code from polling * FFM-7363 Tweak init code, and make missing or empty API key exception * FFM-7363 Fix code * FFM-7363 Fix code * FFM-7363 Fix code * FFM-7363 1.1.15 Release prep * FFM-7363 Linting * FFM-7363 Remove unused call * FFM-7363 Log whole target * FFM-7363 Add close codes * FFM-7363 Remove unused call * FFM-7363 Fix edge case bug with closing sdk if a stream event has ocurred, plus add metrics stop messages * FFM-7363 Fix edge case bug with closing sdk if a stream event has ocurred, plus add metrics stop messages * FFM-7363 Lint
1 parent 013e864 commit e11446c

File tree

11 files changed

+274
-59
lines changed

11 files changed

+274
-59
lines changed

featureflags/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
__author__ = """Enver Bisevac"""
44
__email__ = "enver.bisevac@harness.io"
5-
__version__ = '1.1.14'
5+
__version__ = '1.1.15'

featureflags/analytics.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
from .models.metrics_data import MetricsData
1919
from .models.target_data import TargetData
2020
from .models.unset import Unset
21+
from .sdk_logging_codes import info_metrics_thread_started, \
22+
info_metrics_success, warn_post_metrics_failed, info_metrics_thread_existed
2123
from .util import log
2224

2325
FF_METRIC_TYPE = 'FFMETRICS'
@@ -118,8 +120,8 @@ def get_target_key(self, event: AnalyticsEvent) -> str:
118120

119121
def _sync(self) -> None:
120122
if not self._running:
121-
log.info("Starting AnalyticsService with request interval: %d",
122-
self._config.events_sync_interval)
123+
info_metrics_thread_started(
124+
f'{self._config.events_sync_interval}s')
123125
self._running = True
124126
while self._running:
125127
time.sleep(self._config.events_sync_interval)
@@ -179,18 +181,16 @@ def _send_data(self) -> None:
179181
environment=self._environment, json_body=body)
180182
log.debug('Metrics server returns: %d', response.status_code)
181183
if response.status_code >= 400:
182-
log.error(
183-
'Error while sending metrics data with status code: %d',
184-
response.status_code
185-
)
184+
warn_post_metrics_failed(response.status_code)
186185
return
187-
log.info('Metrics data sent to server')
186+
info_metrics_success()
188187
return
189188

190189
def close(self) -> None:
191190
self._running = False
192191
if len(self._data) > 0:
193192
self._send_data()
193+
info_metrics_thread_existed()
194194

195195
def __enter__(self):
196196
return self

featureflags/api/default/authenticate.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
from typing import Any, Dict, Optional, Union
2+
3+
from featureflags.sdk_logging_codes import warn_auth_retying
24
from featureflags.util import log
35

46
import httpx
@@ -77,7 +79,7 @@ def handle_http_result(response):
7779
return True
7880
else:
7981
log.error(
80-
f'SDK_AUTH_2001: Authentication failed with HTTP code #{code} and '
82+
f'Authentication received HTTP code #{code} and '
8183
'will not attempt to reconnect')
8284
return False
8385

@@ -89,10 +91,9 @@ def _post_request(kwargs, max_auth_retries):
8991
retry_if_result(lambda response: response.status_code != 200),
9092
retry_if_result(handle_http_result)
9193
),
92-
before_sleep=lambda retry_state: log.warning(
93-
f'SDK_AUTH_2002: Authentication attempt #'
94-
f'{retry_state.attempt_number} '
95-
f'got {retry_state.outcome.result()} Retrying...'),
94+
before_sleep=lambda retry_state: warn_auth_retying(
95+
retry_state.attempt_number,
96+
retry_state.retry_state.outcome.result()),
9697
stop=stop_after_attempt(max_auth_retries),
9798
)
9899
return retryer(httpx.post, **kwargs)

featureflags/client.py

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,15 @@
1818
from .evaluations.auth_target import Target
1919
from .polling import PollingProcessor
2020
from .streaming import StreamProcessor
21-
from .util import log
21+
import featureflags.sdk_logging_codes as sdk_codes
2222

2323
VERSION: str = "1.0"
2424

2525

26+
class MissingOrEmptyAPIKeyException(Exception):
27+
pass
28+
29+
2630
class CfClient(object):
2731
def __init__(
2832
self, sdk_key: str,
@@ -33,6 +37,9 @@ def __init__(
3337
# The Client is considered initialized when flags and groups
3438
# are loaded into cache.
3539
self._initialized = threading.Event()
40+
# Keep track if initialization has failed due to authentication
41+
# or a missing/empty API key.
42+
self._initialized_failed = False
3643
self._auth_token: Optional[str] = None
3744
self._environment_id: Optional[str] = None
3845
self._sdk_key: Optional[str] = sdk_key
@@ -56,7 +63,10 @@ def __init__(
5663

5764
def run(self):
5865
try:
66+
if self._sdk_key is None or self._sdk_key == "":
67+
raise MissingOrEmptyAPIKeyException()
5968
self.authenticate()
69+
sdk_codes.info_sdk_auth_ok()
6070
streaming_event = threading.Event()
6171
polling_event = threading.Event()
6272

@@ -97,24 +107,31 @@ def run(self):
97107
)
98108

99109
except RetryError:
100-
log.error(
101-
"Authentication failed and max retries have been exceeded - "
102-
"defaults will be served.")
103-
# Mark the client as initialized in case wait_for_initialization
104-
# is called. The SDK has already logged that authentication
105-
# failed and defaults will be returned.
110+
sdk_codes.warn_auth_failed_exceed_retries()
111+
sdk_codes.warn_failed_init_auth_error()
112+
self._initialized_failed = True
113+
# We just need to unblock the thread here
114+
# in case wait_for_intialization was called. The SDK has already
115+
# logged that authentication failed and defaults will be returned.
106116
self._initialized.set()
107117
except UnrecoverableAuthenticationException:
108-
log.error(
109-
"Authentication failed - defaults will be served.")
110-
# Same again, just mark the client as initailized.
118+
self._initialized_failed = True
119+
sdk_codes.warn_auth_failed_srv_defaults()
120+
sdk_codes.warn_failed_init_auth_error()
121+
# Same again, unblock the thread.
122+
self._initialized.set()
123+
except MissingOrEmptyAPIKeyException:
124+
self._initialized_failed = True
125+
sdk_codes.wan_missing_sdk_key()
126+
# And again, unblock the thread.
111127
self._initialized.set()
112128

113129
def wait_for_initialization(self):
114-
log.debug("Waiting for initialization to finish")
115130
self._initialized.wait()
116131

117132
def is_initialized(self):
133+
if self._initialized_failed:
134+
return False
118135
return self._initialized.is_set()
119136

120137
def get_environment_id(self):
@@ -211,14 +228,14 @@ def json_variation(self, identifier: str, target: Target,
211228
return variation.json(target, identifier, default)
212229

213230
def close(self):
214-
log.info('closing sdk client')
231+
sdk_codes.info_sdk_start_close()
215232
self._polling_processor.stop()
216233
if self._config.enable_stream:
217234
self._stream.stop()
218235

219236
if self._config.enable_analytics:
220237
self._analytics.close()
221-
log.info('All threads finished')
238+
sdk_codes.info_sdk_close_success()
222239

223240
def __enter__(self):
224241
return self

featureflags/evaluations/variation.py

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,51 +22,76 @@ class Variation(object):
2222
def bool(self, target: Target, flag_identifier: str,
2323
default: bool = False) -> bool:
2424
if self.value:
25-
return self.value.lower() == "true"
25+
result = self.value.lower() == "true"
26+
log.info(
27+
"SDKCODE:6000: Evaluated bool variation successfully:"
28+
"%s", {"result": result, "flag identifier": flag_identifier,
29+
"target": target})
30+
return result
2631
log.error(
27-
"SDK_EVAL_6001: Failed to evaluate bool variation for %s and the "
32+
"SDKCODE:6001: Failed to evaluate bool variation for %s and the "
2833
"default variation '%s' is being returned",
29-
{"target": target.identifier, "flag": flag_identifier}, default)
34+
{"target": target, "flag": flag_identifier}, default)
3035
return default
3136

3237
def string(self, target: Target, flag_identifier: str,
3338
default: str) -> str:
3439
if self.value:
35-
return self.value
40+
result = self.value
41+
log.info(
42+
"SDKCODE:6000: Evaluated string variation successfully:"
43+
"%s", {"result": result, "flag identifier": flag_identifier,
44+
"target": target})
45+
return result
3646
log.error(
37-
"SDK_EVAL_6001: Failed to evaluate string variation for %s and the"
47+
"SDKCODE:6001: Failed to evaluate string variation for %s and the"
3848
" default variation '%s' is being returned",
39-
{"target": target.identifier, "flag": flag_identifier}, default)
49+
{"target": target, "flag": flag_identifier}, default)
4050
return default
4151

4252
def number(self, target: Target, flag_identifier: str,
4353
default: float) -> float:
4454
if self.value:
45-
return float(self.value)
55+
result = float(self.value)
56+
log.info(
57+
"SDKCODE:6000: Evaluated number variation successfully:"
58+
"%s", {"result": result, "flag identifier": flag_identifier,
59+
"target": target})
60+
return result
4661
log.error(
47-
"SDK_EVAL_6001: Failed to evaluate number variation for %s and the"
62+
"SDKCODE:6001: Failed to evaluate number variation for %s and the"
4863
" default variation '%s' is being returned",
49-
{"target": target.identifier, "flag": flag_identifier}, default)
64+
{"target": target, "flag": flag_identifier}, default)
5065
return default
5166

5267
def int(self, target: Target, flag_identifier: str,
5368
default: int) -> int:
5469
if self.value:
55-
return int(self.value)
70+
result = int(self.value)
71+
log.info(
72+
"SDKCODE:6000: Evaluated number variation successfully:"
73+
"%s", {"result": result, "flag identifier": flag_identifier,
74+
"target": target})
75+
return result
5676
log.error(
57-
"SDK_EVAL_6001: Failed to evaluate int variation for %s and the "
77+
"SDKCODE:6001: Failed to evaluate int variation for %s and the "
5878
"default variation '%s' is being returned",
59-
{"target": target.identifier, "flag": flag_identifier}, default)
79+
{"target": target, "flag": flag_identifier}, default)
6080
return default
6181

6282
def json(self, target: Target, flag_identifier: str,
6383
default: dict) -> dict:
6484
if self.value:
65-
return json.loads(self.value)
85+
result = json.loads(self.value)
86+
log.info(
87+
"SDKCODE:6000: Evaluated json variation successfully:"
88+
"%s", {"result": result, "flag identifier": flag_identifier,
89+
"target": target})
90+
return result
6691
log.error(
67-
"SDK_EVAL_6001: Failed to evaluate json variation for %s and the "
92+
"SDKCODE:6001: Failed to evaluate json variation for %s and the "
6893
"default variation '%s' is being returned",
69-
{"target": target.identifier, "flag": flag_identifier}, default)
94+
{"target": target, "flag": flag_identifier}, default)
7095
return default
7196

7297
def to_dict(self) -> Dict[str, Any]:

featureflags/polling.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from .api.default.get_all_segments import sync as retrieve_segments
88
from .api.default.get_feature_config import sync as retrieve_flags
99
from .config import Config
10+
from .sdk_logging_codes import info_poll_started, info_polling_stopped, \
11+
info_sdk_init_ok, info_sdk_init_waiting
1012
from .util import log
1113

1214

@@ -39,6 +41,7 @@ def run(self):
3941
self.__running = True
4042
# Get initial flags and groups
4143
try:
44+
info_sdk_init_waiting()
4245
log.info("Fetching initial target segments and flags")
4346
self.retrieve_flags_and_segments()
4447
log.info("Initial target segments and flags fetched. "
@@ -47,7 +50,7 @@ def run(self):
4750
# Segments and flags have been cached so
4851
# mark the Client as initialised.
4952
self.__wait_for_initialization.set()
50-
log.debug("CfClient initialized")
53+
info_sdk_init_ok()
5154
except Exception as ex:
5255
log.exception(
5356
'Error: Exception encountered when '
@@ -56,15 +59,13 @@ def run(self):
5659
)
5760
# Sleep for an interval before going into the polling loop.
5861
time.sleep(self.__config.pull_interval)
59-
log.info("Starting PollingProcessor with request interval: " +
60-
str(self.__config.pull_interval))
62+
info_poll_started(self.__config.pull_interval)
6163
while self.__running:
6264
start_time = time.time()
6365
try:
6466
if self.__config.enable_stream and \
6567
self.__stream_ready.is_set():
66-
log.debug('Poller will be paused because' +
67-
' streaming mode is active')
68+
info_polling_stopped('streaming mode is active')
6869
# on stream disconnect, make sure flags are in sync
6970
self.retrieve_flags_and_segments()
7071
# Block until ready.set() is called
@@ -87,8 +88,9 @@ def run(self):
8788
time.sleep(self.__config.pull_interval - elapsed)
8889

8990
def stop(self):
90-
log.info("Stopping PollingProcessor")
9191
self.__running = False
92+
info_polling_stopped("Client was closed")
93+
9294

9395
def retrieve_flags_and_segments(self):
9496
t1 = Thread(target=self.__retrieve_segments)

0 commit comments

Comments
 (0)