I’ve written a piece about logging Log4j2 for Kotlin
and the motivation to use logging in Python is the same:
Logging is a common good practice in software engineering. It enables you to monitor applications in production to gather information about crashes and other malfunctions for further analysis. It is the “little brother” of debugging and often a precursor for setting up test cases which can lead to reproducing the bugs on the developers machine.
Table of Contents
Setup
The good thing about Python: batteries included!
import logging
and Bob’s your uncle!
The Most Basic Logging
import logging
logger = logging.getLogger()
if __name__ == '__main__':
# setup
sh = logging.StreamHandler()
sh.setLevel(logging.INFO)
logger.addHandler(sh)
logger.setLevel(logging.INFO)
# logging
logger.info("hello from main")
Let’s call it the Hello World of logging:
- get a logger
- setup a stream handler
- set the log level
- start logging away
The output is
hello from main
Wait, what? Could have done it with print as well.
Fair enough add some
Log output Formatting
import logging
logger = logging.getLogger()
if __name__ == '__main__':
# setup
sh = logging.StreamHandler()
sh.setLevel(logging.INFO)
FORMAT_STRING = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
formatter = logging.Formatter(FORMAT_STRING)
sh.setFormatter(formatter)
logger.addHandler(sh)
logger.setLevel(logging.INFO)
# logging
logger.info("hello from main")
adding a formatter to the stream handler we now get
2022-01-26 13:29:49,166 - root - INFO - hello from main
which definitely looks more like real logging
Other Handlers
Just printing whoops logging to the command line is not very interesting. When your software runs on a server you want to have a log file as well. Here you are:
import logging
logger = logging.getLogger()
if __name__ == '__main__':
# setup
FORMAT_STRING = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
formatter = logging.Formatter(FORMAT_STRING)
fh = logging.FileHandler('logging_tutorial.log', encoding="UTF-8")
fh.setLevel(logging.DEBUG)
fh.setFormatter(formatter)
logger.addHandler(fh)
logger.setLevel(logging.INFO)
# logging
logger.info("hello from main")
This produces a nice log file called logging_tutorial.log
You can decide if you want to have the behavior of appending (which is default) or you can erase the log with every rerun by providing mode=’w’ to the FileHandler constructor.
Logging modules and classes
Modules
import logging
logger = logging.getLogger(__name__)
def do_some_logging():
logger.info("Hello from mymodule")
Classes
import logging
logger = logging.getLogger(__name__)
class MyClass:
def do_some_logging(self):
logger.error("do_some_logging: right here - right now")
When logging modules or classes you can use the __name__ to let the logger spit out the module name into the log.
Caveat: Do not use __name__ in your main module then!
import logging
logger = logging.getLogger() # don't use __name__ here!
def setup_logging():
logger.setLevel(logging.INFO)
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
logger.addHandler(ch)
if __name__ == '__main__':
setup_logging()
logger.info("hello from main")
from my_module import do_some_logging
do_some_logging()
from MyClass import MyClass
myClass = MyClass()
myClass.do_some_logging()
Now we get:
2022-01-26 14:16:54,300 - root - INFO - hello from main
2022-01-26 14:16:54,300 - my_module - INFO - Hello from mymodule
2022-01-26 14:16:54,300 - MyClass - ERROR - do_some_logging: right here - right now
root-Logger & __name__ | named-Logger | |
---|---|---|
in main |
logger = logging.getLogger() |
logger = logging.getLogger("my_application") |
in module |
module_logger = logging.getLogger(__name__) |
module_logger = logging.getLogger("my_application.my_module") |
in class |
class MyClass: def __init__(self): self.logger = logging.getLogger(__name__) |
class MyClass: def __init__(self): self.logger = logging.getLogger("my_application.MyClass") |
Log Levels
CRITICAL = 50
FATAL = CRITICAL
ERROR = 40
WARNING = 30
WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0