I decided to take a look into Python Logging and start using it instead of print().

Everything is here: Logging HOWTO.

Task you want to perform

The best tool for the task

Display console output for ordinary usage of a command line script or program

print()

Report events that occur during normal operation of a program (e.g. for status monitoring or fault investigation)

logging.info() (or logging.debug() for very detailed output for diagnostic purposes)

Issue a warning regarding a particular runtime event

warnings.warn() in library code if the issue is avoidable and the client application should be modified to eliminate the warning

logging.warning() if there is nothing the client application can do about the situation, but the event should still be noted

Report an error regarding a particular runtime event

Raise an exception

Report suppression of an error without raising an exception (e.g. error handler in a long-running server process)

logging.error(), logging.exception() or logging.critical() as appropriate for the specific error and application domain

  • DEBUG - Detailed information, typically of interest only when diagnosing problems
  • INFO - Confirmation that things are working as expected
  • WARNING - An indication that something unexpected happened, or indicative of some problem in the near future (e.g. ‘disk space low’). The software is still working as expected
  • ERROR - Due to a more serious problem, the software has not been able to perform some functions
  • CRITICAL - A serious error, indicating that the program itself may be unable to continue running

Logging BasicConfig

import logging

logging.basicConfig(level=logging.DEBUG)

logging.debug("debug message")
logging.info("info message")
logging.warning("warning message")
logging.error("error message")
logging.critical("critical message")

BasicConfig does a basic configuration for the logging system by creating a StreamHandler with a default Formatter and adding it to the root logger:

Get more from Logging

Create a logger for the script itself. Level is the highest possible for the script:

import logging

logger = logging.getLogger('logger')
logger.setLevel(logging.DEBUG)

Create a StreamHandler for console output

console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
formatter = logging.Formatter("{name} - {levelname} - {message}", style="{")
console.setFormatter(formatter)

Create a StreamHandler for logfile:

logfile = logging.FileHandler("logfile.log", "a")
logfile.setLevel(logging.DEBUG)
formatter_file = logging.Formatter(
    "{asctime} - {name} - {levelname} - {message}",
    datefmt="%Y-%m-%d %H:%M:%S",
    style="{",
)
logfile.setFormatter(formatter_file)

Add Handlers to the logger:

logger.addHandler(console)
logger.addHandler(logfile)

For console we can use simpler formatter and level, but logging into the file is better with data and time. So we can use different formatters for different Handlers. More options and more flexible.

import logging

logger = logging.getLogger('logger')
logger.setLevel(logging.DEBUG)

console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
formatter = logging.Formatter("{name} - {levelname} - {message}", style="{")
console.setFormatter(formatter)

logfile = logging.FileHandler("logfile.log", "a")
logfile.setLevel(logging.DEBUG)
formatter_file = logging.Formatter(
    "{asctime} - {name} - {levelname} - {message}",
    datefmt="%Y-%m-%d %H:%M:%S",
    style="{",
)
logfile.setFormatter(formatter_file)

logger.addHandler(console)
logger.addHandler(logfile)

logger.debug("debug message")
logger.info("info message")
logger.warning("warning message")
logger.error("error message")
logger.critical("critical message")

Logging Tree

We can use logging configuration from one file while import functions from another.

As in the example, lets import functions from another file:

from test2 import test2_func

In the test2.py we have just a simple logging config. Name of the logger if “logger.test2”:

import logging

logger = logging.getLogger('logger.test2')

def test2_func():
    logger.debug("Debug message from Test2 file")

Since the logger has “logger” in the name it gets all logging configuration from the main file. logging_more.py:

logger.debug('Before function')
test2_func()
logger.debug('After function')

Logging and Exceptions

We can use logging in try/except. How we usually do this - exception message only:

>>> try:
...     2/0
... except ZeroDivisionError as err:
...     print(err)
...
division by zero

Using logging.exception: it uses ERROR level and includes the exception message itself:

>>> import logging
>>> try:
...     2/0
... except ZeroDivisionError as err:
...     logging.exception("Your exception message")
...
ERROR:root:Your exception message
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

We can also control the log level via the command line using an additional argument and argparse.

Another way to configure logging - import from another file (ini, yaml etc)