Implementing logging in Python via decorators
Tags: python, codequality
Published: 2020-02-20 20:00:00

---
title: Implementing logging in Python via decorators
published: true
description: Implement logging with python via decorators
tags: python, codequality
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/rbykw9yvae6jytqns08n.png
---
**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](https://dev-to-uploads.s3.amazonaws.com/i/rmpjoxo4n9qa2tkwy82k.png)

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

```python
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

```

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

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

if __name__ == '__main__':
    result = divide(10, 0)
    print(result)
```

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.

```python
import log


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


if __name__ == '__main__':
    result = divide(10, 0)
    print(result)
```

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
```

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.