Say Goodbye to Print Statements: How to Use Logger for Effective Debugging

Pikho
4 min readMar 4, 2023

To ensure robust debugging and error tracking, I extensively utilize logging throughout our codebases, including for local runs. This is because logging provides superior privileges and benefits compared to traditional printing methods. Let’s take a look at how I define the logger in my code and then we will explore its privileges.

Before we begin, it’s important to note that there is a hierarchy in logging called the logger tree or logger hierarchy. The hierarchy consists of several levels, each representing a different level of severity for log messages. The most common levels are:

CRITICAL #A critical error occurred, the program may not be able to continue running.
ERROR #An error occurred that should be investigated.
WARNING #An indication that something unexpected happened or indicative of some problem in the near future.
INFO #General information about the program's execution.
DEBUG #Detailed information for debugging purposes.

Let’s create a python module called set_logging.py

import logging
logger = logging.getLogger()

def set_logger():
logger.setLevel(logging.INFO)

handler = logging.StreamHandler()
handler.setLevel(logger_level)

formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)

logger.addHandler(handler)

To clarify the code, we create a logger instance with the getLogger function and use setLevel to set the logging level (DEBUG, INFO, etc.). The setLevel method for the logger acts as a filter that determines whether a log message should be processed and sent to the handler or not. For example, if we set the logger level to INFO, then the logger will not send messages with a level of DEBUG to the handler because they are of lower severity than the minimum level set on the logger. It sends only log messages with a level of INFO or higher (i.e., WARNING, ERROR, or CRITICAL) to the handler for processing.

We create a StreamHandler which sends log messages to a stream, such as console or terminal. It is used to output log messages for debugging purposes. We also set the level for the handler. WHY?

We do this because when a handler receives a message from the logger, it will compare those messages with its level and filter out messages with lower severity before they are emitted. This is useful when we have different handlers such as below.

logger.setLevel(logging.INFO)

file_handler = logging.FileHandler()
file_handler.setLevel(logging.ERROR)

console_handler = logging.StreamHandler()
console_handler.setLevel(logging.WARNING)

As the logger level is set to INFO, it sends only log messages with a level of INFO or higher to both handlers, but each handler will only process messages that are at or above its specified logging level.

Back to our main example, we then create a formatter and add it to the handler. The formatter specifies the format of the log messages, including the timestamp, logger name, log level, and message. Finally, we add the handler to the logger.”

Now in your code, you need to call the set_logger as follows:

import logging
from set_logging import set_logger
set_logger()

logger = logging.getLogger()

def roman_number(s: str) -> int:
dic = {"I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000}

res = 0
pre = None
for char in s:
res += dic.get(char)
if dic.get(pre) and dic.get(pre) < dic.get(char):
res -= 2 * dic.get(pre)
pre = char
logger.info("logging is awesome")
return res

roman_number("IV")

running this code will result

2023-03-04 02:26:57,619 - root - INFO - logging is awesome

Note that the root logger is used when we do not set the name while creating an instance of the logger using getLogger(). To include the name of the file in the log messages, you can use __file__, which tells the logger to use the name of the current module (in this case, set_logging.py) as the name in the log message.

logger = logging.getLogger(__file__)

will result

2023-03-04 02:46:34,589 - ./roman_to_integer.py - INFO - logging is awesome

A bit late but What are the privileges?

  1. Levels: A logger provides a way to set different log levels for different types of messages, such as DEBUG, INFO, WARNING, ERROR, and CRITICAL. This makes it easier to filter and prioritize log messages based on their severity. Of course, printing can mimic the same behavior as logging, but it requires more hard-coded efforts and is not as flexible as logging.
  2. Performance: Printing log messages can be slower than using a logger, especially when dealing with large amounts of data or when logging is performed frequently.
  3. Configurability: A logger provides a way to configure the logging behavior of an application, such as the log level, log destination, and log format, without having to modify the source code. This makes it easier to manage and maintain logging behavior over time.
  4. Flexibility: A logger allows you to send log messages to multiple destinations, such as the console, a file, or a database. This flexibility makes it easier to manage logs and analyze them.

--

--