Skip to main content

Move logging code out of your business logic with decorators

Project description

Downloads

Move logging code out of your business logic with python decorators.

Logging is a nice tool in your toolbox for tracing bugs and getting a better sense how your application is working in production. But if you are like me, you often omit logging code, so it will not hide business logic or feature your code with complexity.

Fortunately pythons decorator now came to our rescue and provides us with a nice library to add logging to our code without stealing readability and understandability.

If you want to know more about the intentions behind logdecorator check out my blog post.

Update: logdecorator==2.0

Thanks to all dependants :) I just released a new version of logdecorator (2.0). You can read the changes at my blog or in the changelog.

Installation

Installation is as easy as it can get:

$ pip install logdecorator

How to use it

tl;dr

import logging
import requests
from logdecorator import log_on_start, log_on_end, log_on_error, log_exception


@log_on_start(logging.DEBUG, "Start downloading {url:s}...")
@log_on_error(logging.ERROR, "Error on downloading {url:s}: {e!r}",
              on_exceptions=IOError,
              reraise=True)
@log_on_end(logging.DEBUG, "Downloading {url:s} finished successfully within {result.elapsed!s}")
def download(url):
    # some code
    response = requests.get(url)
    # some more code
    return response


logging.basicConfig(level=logging.DEBUG)

download("https://www.sighalt.de")
# DEBUG:__main__:Start downloading https://www.sighalt.de...
# DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): www.sighalt.de
# DEBUG:urllib3.connectionpool:https://www.sighalt.de:443 "GET / HTTP/1.1" 200 None
# DEBUG:__main__:Downloading https://www.sighalt.de finished successfully within 0:00:00.130960

download("https://www.sighalt.der")
# DEBUG:__main__:Start downloading https://www.sighalt.der...
# DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): www.sighalt.der
# ERROR:__main__:Error on downloading https://www.sighalt.der: ConnectionError(MaxRetryError("
# HTTPSConnectionPool(host='www.sighalt.der', port=443): Max retries exceeded with url: /
# (Caused by NewConnectionError('<urllib3.connection.VerifiedHTTPSConnection object at 0x7fe3fc4b5320>:
# Failed to establish a new connection: [Errno -2] Name or service not known',))",),)

Long story

Imagine a function download with no arguments and some download code in it.

def download():
    # some download code
    pass

if __name__ == "__main__":
    download()

Say you are going to launch your new tool but want to add some logging before releasing the kraken. Your code will probably look something like the following:

import logging
from .exceptions import MyException1, MyException2

logger = logging.getLogger(__name__)


def download():
    logger.debug("Start downloading")
    # some download code
    logger.debug("Downloading finished successfully")


if __name__ == "__main__":
    try:
        download()
    except (MyException1, MyException2):
        logger.error("Error on downloading")
        raise

You just added at least a couple lines of code which are eventually stumbling in your way when you are trying to understand your business logic to find a bug. But what’s even worse is, that you added an additional indentation (try:… except: …) just for the sake of logging.

With logdecorator you can leave your code nearly as it was and reach the same goals.

import logging
from logdecorator import log_on_start, log_on_end, log_on_error
from .exceptions import MyException1, MyException2


@log_on_start(logging.DEBUG, "Start downloading")
@log_on_error(logging.ERROR, "Error on downloading",
              on_exceptions=(MyException1, MyException2),
              reraise=True)
@log_on_end(logging.DEBUG, "Downloading finished successfully")
def download():
    # some download code


if __name__ == "__main__":
    download()

Maybe the improvement to the previous snippet does not seem great for you but if you actually fill in business logic into # some download code it should become obvious :)

What logdecorator can do for you

Decorators

logdecorator provides four different built-in decorators:

  • log_on_start

  • log_on_end

  • log_on_error

  • log_exception

whose behaviour corresponds to their names.

Additionally logdecorator supports decorating async callables with the decorators:

  • async_log_on_start

  • async_log_on_end

  • async_log_on_error

  • async_log_exception

These decorators are found at logdecorator.asyncio

Use variables in messages

The message, given to the decorator, is treated as a python format string which takes the functions arguments as format arguments.

Sticking to the previous example one could write:

import logging
from logdecorator import log_on_start
from .exceptions import MyException1, MyException2


@log_on_start(logging.DEBUG, "Start downloading '{url:s}'")
def download(url):
    # some download code


if __name__ == "__main__":
    download("http://my.file.com/file.bin")

Which results in the message Start downloading 'http://my.file.com/file.bin' gets logged.

Documentation

format variables

The following variable names can be used to construct the message:

Default variable name

Description

log_on_start

log_on_end

log_on_error

log_exception

callable

The decorated callable

Yes

Yes

Yes

Yes

args/kwargs

Whatever arguments given to the callable can be used in the logging message

Yes

Yes

Yes

Yes

result

Whatever the decorated callable returns

No

Yes

No

No

e

The exception raised while executing the callable

No

No

Yes

Yes

log_on_start / async_log_on_start

Argument

required?

Description

log_level

yes

The log level at which the message should be send

message

yes

The message to log

logger

no

An alternative logger object. If no logger is given logdecorator creates a logger object with the name of the module the decorated function is in (decorated_function.__module__)Default: Creates a new logger with the name decorated_function.__module__

handler

no

A custom log handler object. Only available if no logger object is given.

callable_format_variable

no

The variable name one can use in the message to reference the decorated callable. e.g. @log_on_start(ERROR, “Called {callable.__name__:s}”, …) Default: “callable”

log_on_end / async_log_on_end

Argument

required?

Description

log_level

yes

The log level at which the message should be send

message

yes

The message to log

logger

no

An alternative logger object. If no logger is given logdecorator creates a logger object with the name of the module the decorated function is in (decorated_function.__module__) Default: Creates a new logger with the name decorated_function.__module__

handler

no

A custom log handler object. Only available if no logger object is given.

result_format_variable

no

The variable name one can use in the message to reference the result of the > decorated function e.g. @log_on_end(DEBUG, “Result was: {result!r}”) Default: “result”

callable_format_variable

no

The variable name one can use in the message to reference the decorated callable. e.g. @log_on_start(ERROR, “Called {callable.__name__:s}”, …) Default: “callable”

log_on_error / async_log_on_error

Argument

required?

Description

log_level

yes

The log level at which the message should be send

message

yes

The message to log

logger

no

An alternative logger object. If no logger is given logdecorator creates a logger object with the name of the module the decorated function is in (decorated_function.__module__) Default: Creates a new logger with the name decorated_function.__module__

handler

no

A custom log handler object. Only available if no logger object is given.

on_exceptions

no

A tuple containing exception classes or a single exception, which should get caught and trigger the logging of the log_on_error decorator. Default: tuple() (No exceptions will get caught)

reraise

no

Controls if caught exceptions should get reraised after logging. Default: True

exception_format_variable

no

The variable name one can use in the message to reference the caught exception raised in the decorated function > e.g. @log_on_error(ERROR, “Error was: {e!r}”, …) Default: “e”

callable_format_variable

no

The variable name one can use in the message to reference the decorated callable. e.g. @log_on_start(ERROR, “Called {callable.__name__:s}”, …) Default: “callable”

log_exception / async_log_exception

Argument

required?

Description

log_level

yes

The log level at which the message should be send

message

yes

The message to log

logger

no

An alternative logger object. If no logger is given logdecorator creates a logger object with the name of the module the decorated function is in (decorated_function.__module__) Default: Creates a new logger with the name decorated_function.__module__

handler

no

A custom log handler object. Only available if no logger object is given.

on_exceptions

no

A tuple containing exception classes or a single exception, which should get caught and trigger the logging of the log_on_error decorator. Default: tuple() (No exceptions will get caught)

reraise

no

Controls if caught exceptions should get reraised after logging. Default: False

exception_format_variable

no

The variable name one can use in the message to reference the caught exception raised in the decorated function e.g. @log_on_error(ERROR, “Error was: {e!r}”, …) Default: “e”

callable_format_variable

no

The variable name one can use in the message to reference the decorated callable. e.g. @log_on_start(ERROR, “Called {callable.__name__:s}”, …) Default: “callable”

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

logdecorator-2.4.tar.gz (6.9 kB view details)

Uploaded Source

Built Distribution

logdecorator-2.4-py3-none-any.whl (7.2 kB view details)

Uploaded Python 3

File details

Details for the file logdecorator-2.4.tar.gz.

File metadata

  • Download URL: logdecorator-2.4.tar.gz
  • Upload date:
  • Size: 6.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.2 importlib_metadata/4.8.1 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.9.7

File hashes

Hashes for logdecorator-2.4.tar.gz
Algorithm Hash digest
SHA256 7735260e579c50af5f3aee4ee75dab886ae427b1960eb805d8eafe84b75c41b6
MD5 de17eacc18a34cbd017da28c8a5892f6
BLAKE2b-256 378e67489bd6cb26b8cf0749ab64773a8ee501690c5c9c12531850c9371be137

See more details on using hashes here.

File details

Details for the file logdecorator-2.4-py3-none-any.whl.

File metadata

  • Download URL: logdecorator-2.4-py3-none-any.whl
  • Upload date:
  • Size: 7.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.2 importlib_metadata/4.8.1 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.9.7

File hashes

Hashes for logdecorator-2.4-py3-none-any.whl
Algorithm Hash digest
SHA256 34bf769dafaf8b53a94484036b401b651f50fca830e03a93837a68d7180afd55
MD5 f9d1ec6204f22632f5ba89fa62b8ddae
BLAKE2b-256 e30277db6d63b20225308fef00687edfc019e21e3957222d384a6577943e16ce

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page