Search code examples
pythonprometheus

Measure execution time, but also in case of exceptions


I am writing some code that I would like to add prometheus metrics. In the metrics we want to measure the time it took, but also in case a exception was raised.

I ended up with this code, but it feels as if it can be written a bit better, but i dont know how to achieve it:

def async_load_one(self, result, **fields):
    start = time()
    end = 0
    status = ""
    try:
        result.set(self.table.query(**fields))
        end = time()
        status = "ok"
    except Exception as e:
        result.set_exception(e)
        end = time()
        status = "error"
    finally:
        elapsed = end - start
        DYNAMO_ACCESS_DURATION.labels(
            operation="load_one",
            status=status
            ).observe(elapsed)


Solution

  • The finally clause runs whether or not the try statement produces an exception. Meaning it will execute in any event.

    So you can do:

    def func():
        start = time()
        try:
            print("do something")
            status = "ok"
        except Exception as e:
            print("an error has occurred", e)
            status = "error"
        finally:
            end = time()
            elapsed = end - start
            print(f"{elapsed=}")
            print(f"{status=}")
    

    Which prints something like this when no errors are raised:

    do something
    elapsed=1.3828277587890625e-05
    status='ok'
    

    Or this if an error were to be raised in the try block:

    do something
    an error has occurred 
    elapsed=2.09808349609375e-05
    status='error'
    

    If you need to do this in more than one method/function, you can consider using a decorator:

    def timing_decorator(func):
        def wrapper(*args, **kwargs):
            start = time.time()
            try:
                result = func(*args, **kwargs)
                status = "ok"
            except Exception as e:
                print("an error has occurred", e)
                status = "error"
                result = None
            finally:
                end = time.time()
                elapsed = end - start
                print(f"{elapsed=}")
                print(f"{status=}")
            return result
    
        return wrapper
    
    
    @timing_decorator
    def func():
        print("do something")