The Python Standard Logging Library
Learn the basics of python logging library
As a seasoned senior web developer with a wealth of experience in Python, Javascript, web development, MySQL, MongoDB, and React, I am passionate about crafting exceptional digital experiences that delight users and drive business success.
Python has an inbuilt library called logging. Purpose of this module is to provide all the functions and classes for implementing event logging mechanism within the code. As python has this standard library, application and the packages it is depended on can log data. If needed developer can disable logging of different packages as well.
Import logging Library
Importing and using logging library is very simple.
import logging
logging.warning("My first log")
By default logging library starts logging WARNING or upper level logs, like ERROR or CRITICAL. In order to log INFO or DEBUG developer needs to set the log level to desired level.
import logging
logging.basicConfig(level=logging.DEBUG)
logging.debug("My second log")
logging.info("My third log")
The logging will function for the log level you've set, as well as for all higher levels. So, if the level was set to INFO, logging.debug("My first log") will not work.
Log Format
By default the logging module logs record in following format.
DEBUG:root:My second log
INFO:root:My third log
What if a developer needs to log the time of event, name of the function where logging occurred or name of the logger.
Log format is the structure that makes sure application is logging all the necessary information needed to understand and analyze the application state or flow. Python logging library provides a standard way of configuring the log format.
import logging
logging.basicConfig(
format='%(asctime)s - %(levelname)s - %(message)s - %(name)s',
level=logging.DEBUG
)
logging.debug("My second log")
logging.info("My third log")
# Log record
# 2025-09-18 17:26:57,247 - DEBUG - My second log - root
# 2025-09-18 17:26:57,247 - INFO - My third log - root
| Log Record Attribute | Description |
| asctime | Human readable datetime when log was created |
| levelname | Logging level (DEBUG, INFO, WARNING, ERROR and CRITICAL) |
| message | Actual text passed for logging |
| name | Name of the logger (default root) |
getLogger Function
getLogger function is used to create logger object. When developer uses logging.info('learning to log’), logging library actually calling logging.getLogger().info(‘learning to log’)
logging.debug("My second log")
logging.info("My third log")
# And
logging.getLogger().debug("My second log")
logging.getLogger().info("My third log")
# Are same
To create a logging object developer need to call the getLogger function and store the object in a variable. Remember, calling getLogger with same name parameter will result in getting the same object. If the name parameter is not provided, getLogger will return the root logger.
import logging
logging.basicConfig(
format='%(asctime)s - %(levelname)s - %(message)s - %(name)s',
level=logging.DEBUG
)
logger_1 = logging.getLogger("learning")
logger_1.debug("My second log")
logger_2 = logging.getLogger("learning")
logger_2.info("My third log")
assert logger_1 is logger_2 # test passed ✅
# Log record
# 2025-09-18 17:41:54,416 - DEBUG - My second log - learning
# 2025-09-18 17:41:54,416 - INFO - My third log - learning
When to Use root Logger?
If application is using same logger object throughout the system, root logger is sufficient. As root logger is also a logger object that has same capability of a custom logger object. Moreover, developer can use the standard logging module to log record in the same configuration throughout the application.
# main.py
import logging
from extra import greet
logging.basicConfig(
format='%(asctime)s - %(levelname)s - %(message)s - %(name)s',
level=logging.DEBUG
)
logging.info("Hello")
greet()
# extra.py
import logging
def greet():
logging.info("Hello Developers!")
# Log record
# 2025-09-18 17:51:08,212 - INFO - Hello - root
# 2025-09-18 17:51:08,212 - INFO - Hello Developers! - root
When root Logger is not enough?
- A developer needs to implement loggers with different names. Example, db logger for database related activities, api logger for api request and response related events and auth logger for authentication and authorization related events.
db_logger = logging.getLogger('database')
api_logger = logging.getLogger('api')
auth_logger = logging.getLogger('auth')
- Need to implement different handlers for different purposes. Suppose, db handler related logs will be stored in a file. Api logs will be sent to Splunk in JSON format.
# API logs go to file, DB logs go to console
api_logger = logging.getLogger('db')
api_logger.addHandler(file_handler)
db_logger = logging.getLogger('api')
db_logger.addHandler(hec_handler) # Splunk HEC or HTTP Event Collector
- Need hierarchical logging. The configuration in the parent logger object will be inherited by child object.
import logging
logging.basicConfig(
format='%(asctime)s - %(levelname)s - %(message)s - %(name)s'
)
# Parent logger
app_logger = logging.getLogger('myapp')
app_logger.setLevel(logging.INFO)
# Child loggers inherit from parent
db_logger = logging.getLogger('myapp.database') # Inherits from 'myapp'
api_logger = logging.getLogger('myapp.api') # Inherits from 'myapp'
api_logger.info("Hello")
db_logger.info("Hello")
# Log record
# 2025-09-18 18:11:41,233 - INFO - Hello - myapp.api
# 2025-09-18 18:11:41,233 - INFO - Hello - myapp.database
print(app_logger.getChildren())
# prints
# {<Logger myapp.database (INFO)>, <Logger myapp.api (INFO)>}
Next Blog
Next blog will show how developers can use basicConfig to configure loggers.