-
Notifications
You must be signed in to change notification settings - Fork 20
Description
When using AxiomHandler
with an incorrect dataset name but valid API credentials, the error is not detected during handler initialization. Instead, the error only occurs later when attempting to log a message, throwing: axiom_py.client.AxiomError: API error 404: dataset not found
Simple Code to reproduce:
client = axiom_py.Client(CORRECT_TOKEN)
handler = ErrorHandlingAxiomHandler(client, "INCCORECT_NAMED_DATASET")
logging.error("this line throws an error")
Possible Solutions:
1. Test flush in constructor:
def __init__(self, client: Client, dataset: str, level=NOTSET, interval=1):
super().__init__()
# Set urllib3 logging level to warning, check:
# https://github.com/axiomhq/axiom-py/issues/23
# This is a temp solution that would stop requests library from
# flooding the logs with debug messages
getLogger("urllib3").setLevel(WARNING)
self.client = client
self.dataset = dataset
self.buffer = []
self.interval = interval
self.last_flush = time.monotonic()
# Check if the dataset is valid before starting the handler, if not raise an error.
try:
self.client.datasets.get(self.dataset) # This will raise an error if the dataset does not exist
except axiom_py.client.AxiomError as e:
raise axiom_py.client.AxiomError(e.status, axiom_py.client.AxiomError.Response(f"{self.dataset}, does not exist", None))
# We use a threading.Timer to make sure we flush every second, even
# if no more logs are emitted.
self.timer = Timer(self.interval, self.flush)
# Make sure we flush before the client shuts down
def before_shutdown():
self.flush()
self.timer.cancel()
client.before_shutdown(before_shutdown)
2. Log by temporarily removing self from root
Logging to root logger as a library is bad practice, but it's way better than throwing an exception when logging fails
def flush(self):
"""Flush sends all logs in the buffer to Axiom."""
self.last_flush = time.monotonic()
if len(self.buffer) == 0:
return
local_buffer, self.buffer = self.buffer, []
try:
self.client.ingest_events(self.dataset, local_buffer)
except axiom_py.client.AxiomError as e:
# logging to root logger as a library is really bad practice, but it is possible to do it this way:
# but its way better than throwing an exception when logging fails
if self in getLogger().filters:
# This is a handler to the root logger, so we remove it temporarily to log the error
getLogger().removeHandler(self)
logging.error("Failed to log to Axiom, most probably because DB name is wrong", exc_info=e)
getLogger().addHandler(self)
else:
# we don't know which logger this handler is attached to, so we just log the error to root
# which shouldn't go back to this handler since it is not attached to the root logger
logging.error("Failed to log to Axiom, most probably because DB name is wrong", exc_info=e)
3. SilentFail:
def flush(self):
"""Flush sends all logs in the buffer to Axiom."""
self.last_flush = time.monotonic()
if len(self.buffer) == 0:
return
local_buffer, self.buffer = self.buffer, []
try:
self.client.ingest_events(self.dataset, local_buffer)
except axiom_py.client.AxiomError:
pass # dataset name is probably set wrong or some other connection error accured
Recommendation
Solution 1 is the most robust as it fails fast and provides clear error messages at configuration time rather than during logging operations. This makes it much easier to debug configuration issues.
Solution 2 is bad practice since its using the root logger, but it is still better than silent fail (solution 3) since you at least get a error message warning you that axiom is not setup correctly
Solution 3 is a very temporary solution and will cause hours if not days of debugging on some people for sure