DEV Community

Cover image for Implementing logging in Python via decorators
Aldo
Aldo

Posted on

Implementing logging in Python via decorators

Disclaimer: This is my first article. I will try to post once in a while if I think any article might help someone.
Personally, I hate buzzwords so I will try to keep it simple. Suggestions are welcome.

Writing logs for your program is one of the most crucial thing in software development. It helps to find bugs, write analytics, keep a track of traffic, verify if there is any DDoS attack in your server, etc.

Personally, as a person who never had a mentor and learned myself to code, I have always struggled how to maintain a clean code in my projects. So, in order to avoid writing many try... catch... clauses in every function I though if I write a decorator where I catch the error and log it in a .log file.

UPDATE: There is a typo in the flowchart. It should be Throw Exception. My apologies.

Python Log

First we create a file named log.py (or whatever name you want). Here we will write the log decorator.

import logging
import functools


def _generate_log(path):
    """
    Create a logger object
    :param path: Path of the log file.
    :return: Logger object.
    """
    # Create a logger and set the level.
    logger = logging.getLogger('LogError')
    logger.setLevel(logging.ERROR)

    # Create file handler, log format and add the format to file handler
    file_handler = logging.FileHandler(path)

    # See https://docs.python.org/3/library/logging.html#logrecord-attributes
    # for log format attributes.
    log_format = '%(levelname)s %(asctime)s %(message)s'
    formatter = logging.Formatter(log_format)
    file_handler.setFormatter(formatter)

    logger.addHandler(file_handler)
    return logger


def log_error(path='<path>/log.error.log'):
    """
    We create a parent function to take arguments
    :param path:
    :return:
    """

    def error_log(func):

        @functools.wraps(func)
        def wrapper(*args, **kwargs):

            try:
                # Execute the called function, in this case `divide()`.
                # If it throws an error `Exception` will be called.
                # Otherwise it will be execute successfully.
                return func(*args, **kwargs)
            except Exception as e:
                logger = _generate_log(path)
                error_msg = 'And error has occurred at /' + func.__name__ + '\n'
                logger.exception(error_msg)

                return e  # Or whatever message you want.

        return wrapper

    return error_log

Enter fullscreen mode Exit fullscreen mode

Now, we create another Python file. Let's call it divive_num.py. Here we create a function called divide() which divides two numbers.

def divide(num1, num2):
    return num1 / num2

if __name__ == '__main__':
    result = divide(10, 0)
    print(result)
Enter fullscreen mode Exit fullscreen mode

But when we execute this code, the error message will be shown in console and not in a log file. In order to add logging in this function we import log.py module and call log_error() function as a decorator.

import log


@log.log_error()
def divide(num1, num2):
    return num1 / num2


if __name__ == '__main__':
    result = divide(10, 0)
    print(result)
Enter fullscreen mode Exit fullscreen mode

After we run this code, a log file will be created based on the path that you added and inside that file the error log will be appended.

ERROR 2020-02-20 14:39:56,233 And error has occurred at /divide
Traceback (most recent call last):
  File "<path>/log.py", line 44, in wrapper
    return func(*args, **kwargs)
  File "<path>/divide_num.py", line 6, in divide
    return num1 / num2
ZeroDivisionError: division by zero
Enter fullscreen mode Exit fullscreen mode

Now, every time we create a function, we can add @log.log_error() decorator and all the error logs will be appended in that file.

Top comments (0)