Skip to content

[AxiomHandler] Error thrown on .log() rather than initialization when dataset name is invalid #165

@kytpbs

Description

@kytpbs

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions