Package: logdecorator
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 myblog
post.Update: logdecorator==2.0Thanks to all dependants :) I just released a new version of
logdecorator (2.0). You can read the changes at myblogor in thechangelog.InstallationInstallation is as easy as it can get:$pipinstalllogdecoratorHow to use ittl;drimportloggingimportrequestsfromlogdecoratorimportlog_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}")defdownload(url):# some coderesponse=requests.get(url)# some more codereturnresponselogging.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.130960download("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 storyImagine a functiondownloadwith no arguments and some download code
in it.defdownload():# some download codepassif__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:importloggingfrom.exceptionsimportMyException1,MyException2logger=logging.getLogger(__name__)defdownload():logger.debug("Start downloading")# some download codelogger.debug("Downloading finished successfully")if__name__=="__main__":try:download()except(MyException1,MyException2):logger.error("Error on downloading")raiseYou 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.importloggingfromlogdecoratorimportlog_on_start,log_on_end,log_on_errorfrom.exceptionsimportMyException1,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")defdownload():# some download codeif__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 codeit should become obvious :)What logdecorator can do for youDecoratorslogdecorator provides four different built-in decorators:log_on_startlog_on_endlog_on_errorlog_exceptionwhose behaviour corresponds to their names.Additionally logdecorator supports decorating async callables with the decorators:async_log_on_startasync_log_on_endasync_log_on_errorasync_log_exceptionThese decorators are found at logdecorator.asyncioUse variables in messagesThe 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:importloggingfromlogdecoratorimportlog_on_startfrom.exceptionsimportMyException1,MyException2@log_on_start(logging.DEBUG,"Start downloading '{url:s}'")defdownload(url):# some download codeif__name__=="__main__":download("http://my.file.com/file.bin")Which results in the messageStart downloading'http://my.file.com/file.bin'gets logged.Documentationformat variablesThe following variable names can be used to construct the message:Default variable nameDescriptionlog_on_startlog_on_endlog_on_errorlog_exceptioncallableThe decorated callableYesYesYesYesargs/kwargsWhatever arguments given to the callable can be used in the logging messageYesYesYesYesresultWhatever the decorated callable returnsNoYesNoNoeThe exception raised while executing the callableNoNoYesYeslog_on_start / async_log_on_startArgumentrequired?Descriptionlog_levelyesThe log level at which the message should be sendmessageyesThe message to logloggernoAn 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 namedecorated_function.__module__handlernoA custom log handler object. Only available if no logger object is given.callable_format_variablenoThe 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_endArgumentrequired?Descriptionlog_levelyesThe log level at which the message should be sendmessageyesThe message to logloggernoAn 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 namedecorated_function.__module__handlernoA custom log handler object. Only available if no logger object is given.result_format_variablenoThe 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_variablenoThe 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_errorArgumentrequired?Descriptionlog_levelyesThe log level at which the message should be sendmessageyesThe message to logloggernoAn 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 namedecorated_function.__module__handlernoA custom log handler object. Only available if no logger object is given.on_exceptionsnoA tuple containing exception classes or a single exception, which should get caught and trigger the logging of thelog_on_errordecorator. Default: tuple() (No exceptions will get caught)reraisenoControls if caught exceptions should get reraised after logging. Default: Trueexception_format_variablenoThe 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_variablenoThe 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_exceptionArgumentrequired?Descriptionlog_levelyesThe log level at which the message should be sendmessageyesThe message to logloggernoAn 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 namedecorated_function.__module__handlernoA custom log handler object. Only available if no logger object is given.on_exceptionsnoA tuple containing exception classes or a single exception, which should get caught and trigger the logging of thelog_on_errordecorator. Default: tuple() (No exceptions will get caught)reraisenoControls if caught exceptions should get reraised after logging. Default: Falseexception_format_variablenoThe 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_variablenoThe 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”