Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Courses›Python›logging Module

Standard Library

logging Module

The logging module provides a flexible framework for emitting diagnostic messages from applications and libraries. It matters because it allows developers to systematically track execution flows and errors without relying on fragile print statements. You should reach for it whenever you need to maintain a persistent, configurable record of application behavior in production or development environments.

The Basics of Logging Levels

Logging in Python operates on a hierarchy of severity levels: DEBUG, INFO, WARNING, ERROR, and CRITICAL. Understanding these levels is crucial because they allow you to filter information dynamically based on the current operational context of your application. When you define a logger, it only processes messages that are at or above the threshold defined for that logger. This structure is designed to keep noisy debug information hidden during normal production operations while allowing you to turn it on immediately when troubleshooting becomes necessary. By using distinct methods like logger.info() or logger.error(), you signal the semantic importance of the event. This prevents the output log from being cluttered with trivial details, ensuring that the most critical events, such as system crashes or authorization failures, are always visible and actionable for developers monitoring the system logs.

import logging

# Configure the basic settings to show messages of level INFO and higher
logging.basicConfig(level=logging.INFO)

# Create a logger instance
logger = logging.getLogger('app_logger')

# These will appear because they are >= INFO
logger.info('Application started successfully.')
logger.warning('High memory usage detected.')

# This will NOT appear because DEBUG < INFO
logger.debug('This is a granular debug message.')

Formatting Log Records

Log records in Python are sophisticated objects that contain much more than just a message string. The formatting system allows you to extract metadata such as the timestamp, the specific module name, the line number, and even the process ID where the log occurred. By defining a Formatter, you dictate exactly how these pieces of data are presented in your output stream. This is essential for debugging distributed or complex applications, as it provides a trail back to the precise source code location that generated the message. Without proper formatting, logs are merely unstructured text, which makes automated analysis or grepping through logs in a production environment significantly harder. By embedding context-aware attributes into every log line, you transform your logs into a structured diagnostic tool that effectively maps the internal state of your program during execution.

import logging

# Define a format string including timestamp, level name, and message
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# Create a handler to output to the console
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)

logger = logging.getLogger('formatted_logger')
logger.addHandler(console_handler)
logger.setLevel(logging.INFO)

logger.info('This log entry is now timestamped and formatted.')

Utilizing Handlers for Output Routing

Handlers determine exactly where log messages are sent once they are processed. The power of the logging module lies in the fact that a single logger can direct its output to multiple destinations simultaneously. For example, you might want to send standard INFO messages to the console for real-time monitoring while writing error-level events to a dedicated file for later inspection. This separation of concerns allows the logging infrastructure to scale with the needs of your application. Handlers are responsible for the actual delivery of the log record to a specific destination, such as the system terminal, a local text file, an email service, or even a database. By stacking multiple handlers onto a single logger, you gain granular control over your observability strategy, ensuring that the right information reaches the right destination without any manual duplication of logging calls throughout your codebase.

import logging

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

# Console handler for high-level awareness
console = logging.StreamHandler()
console.setLevel(logging.INFO)

# File handler for persistent error tracking
file_log = logging.FileHandler('errors.log')
file_log.setLevel(logging.ERROR)

logger.addHandler(console)
logger.addHandler(file_log)

logger.info('System operational.')
logger.error('Database connection failed!')

Advanced Filter Logic

Filters provide a fine-grained mechanism to decide whether a specific log record should be passed through a handler or discarded, even if it passes the initial level check. While levels provide a global filter, internal filters allow you to define custom rules based on arbitrary object attributes. You might, for instance, want to ignore logs that don't contain a specific session ID or only allow messages that originate from a specific critical component. By implementing a custom filter class with a filter() method, you gain absolute control over the logging pipeline. This is particularly useful in complex systems where you want to suppress logs during specific phases of execution or highlight events related to a particular user or transaction. Filters act as a final gatekeeper, ensuring that your logs remain relevant and manageable regardless of how many log statements exist throughout your project.

import logging

class FilterSensitiveData(logging.Filter):
    def filter(self, record):
        # Do not log records that contain the word 'password'
        return 'password' not in record.getMessage()

logger = logging.getLogger('filtered_logger')
logger.addFilter(FilterSensitiveData())

logger.warning('User attempted to enter password123') # This will be ignored
logger.warning('User attempted login.') # This will pass

Hierarchical Logger Configuration

The logging module is organized in a hierarchical tree based on logger names, typically following the dot-separated Python module structure. This hierarchy is powerful because it allows you to configure a parent logger, and have those configuration settings (such as handlers, levels, or propagation) automatically cascade down to all child loggers. If you define a logger for 'my_app', any child logger named 'my_app.database' will inherit the settings of the parent unless specifically overridden. This mechanism drastically reduces code duplication in large projects, as you can manage the logging infrastructure for entire packages from a central point. Effective use of hierarchical naming ensures that you can adjust logging verbosity for specific parts of your application architecture independently, providing a clean and organized way to manage observability as your application grows in complexity and scope over time.

import logging

# Parent logger configuration
parent = logging.getLogger('myapp')
parent.setLevel(logging.INFO)

# Child logger inherits the parent level and handlers
child = logging.getLogger('myapp.database')

# This logs at INFO because it inherits from 'myapp'
child.info('Database initialized.')

# Overriding for specific module
child.setLevel(logging.DEBUG)
child.debug('Verbose query tracing enabled for database only.')

Key points

  • Logging levels range from DEBUG to CRITICAL, allowing for systematic filtering of diagnostic messages.
  • The basicConfig method provides a quick way to set up standard logging defaults for small scripts.
  • Handlers dictate the destination of log records, supporting outputs like files, streams, and networks.
  • Formatters are essential for adding human-readable metadata, such as timestamps, to log output.
  • Logger names follow a hierarchy that allows parent configurations to propagate to child loggers.
  • Custom filters enable the suppression of specific log messages based on complex runtime conditions.
  • Proper log formatting significantly improves the ability to debug errors by providing trace context.
  • Centralized configuration of loggers reduces code clutter and simplifies observability across large projects.

Common mistakes

  • Mistake: Using print() for debugging. Why it's wrong: print() is not thread-safe, cannot be easily redirected to files or services, and lacks severity levels. Fix: Use logging.info() or logging.debug().
  • Mistake: Configuring the root logger directly in a library module. Why it's wrong: It forces global settings on the application importing the library. Fix: Use getLogger(__name__) and let the application configure the root logger.
  • Mistake: Passing strings with f-strings to log methods. Why it's wrong: It causes expensive string formatting even if the log level is disabled. Fix: Use lazy logging with format arguments like logging.info('Hello %s', name).
  • Mistake: Forgetting to set the level on the logger. Why it's wrong: The default level is WARNING, so DEBUG and INFO logs disappear silently. Fix: Call logger.setLevel(logging.DEBUG) on your specific logger instance.
  • Mistake: Catching an exception and using print(e) instead of logging. Why it's wrong: You lose the traceback information necessary for debugging. Fix: Use logger.exception('Message') inside the except block.

Interview questions

What is the logging module in Python and why should you use it instead of just using print statements?

The logging module is a built-in Python library that provides a flexible framework for tracking events that happen when software runs. While print statements are useful for quick debugging, they are unsuitable for production code. Using logging allows you to categorize messages by severity levels like DEBUG, INFO, WARNING, ERROR, and CRITICAL. It also provides built-in support for outputting to different destinations, such as files, the console, or even remote servers, simultaneously. By using logging, you gain control over the output format, timestamps, and the ability to silence or enable specific messages without modifying your core application logic, which is essential for professional, maintainable systems.

Can you explain the significance of logging levels and how they are used to control application output?

Logging levels are fundamental to managing output volume. The levels, in increasing order of severity, are DEBUG, INFO, WARNING, ERROR, and CRITICAL. When you configure a logger, you set a threshold level; the logger will only process messages that meet or exceed this threshold. For example, if you set the level to WARNING, all DEBUG and INFO messages are ignored, but errors will still be captured. This approach is highly effective because it allows developers to keep detailed diagnostic information in the code that stays hidden in production but can be quickly activated if a complex issue arises in the field.

What is the purpose of Loggers, Handlers, and Formatters in the Python logging architecture?

These three components work together to process a log record. Loggers are the entry point; they expose the interface that application code uses to log messages. Handlers then determine the destination of the logs, such as a file, a console, or an email, and can be configured with their own levels to filter specific outputs. Finally, Formatters define the layout of the log message, such as adding timestamps, the name of the logger, or the specific line number where the event occurred. By decoupling these components, the logging module achieves incredible modularity, allowing you to send error messages to an email handler while sending info logs only to a rotating file.

Compare using basic logging configuration with logging.basicConfig() versus using a complex logging configuration file or dictionary.

Using `logging.basicConfig()` is a quick, one-time approach suitable for simple scripts where you just need to set the level and format once. However, it is rigid because it can only be called successfully before any loggers are used. In contrast, using a dictionary configuration or a file-based setup is the standard for professional applications. This approach allows you to define granular hierarchies, multiple handlers, distinct filters, and complex log rotation policies. A dictionary configuration is preferred in modern Python because it is easier to maintain, can be loaded from external JSON or YAML files, and allows for dynamic configuration changes without needing to refactor the initialization code throughout your package.

How does log rotation work in Python, and why is it important for long-running applications?

Log rotation prevents disk space exhaustion by managing the size and quantity of log files over time. In Python, you use classes like `RotatingFileHandler` or `TimedRotatingFileHandler` from the `logging.handlers` module. `RotatingFileHandler` triggers a rotation when the file reaches a specific byte size, keeping a defined number of backup files, while `TimedRotatingFileHandler` rotates files based on intervals like daily or weekly. This is critical for production because, without rotation, a long-running service would continue to write to a single file until the storage medium is completely full, which would ultimately crash the application or the host operating system.

How would you implement structured logging that captures stack traces and context information during an exception?

To capture stack traces and context, you should use the `exc_info=True` parameter when logging inside an exception block. For example: `logging.exception('Error occurred')`. The `logging.exception()` method is essentially a convenience function that sets the exc_info flag to True automatically. To provide extra context, you can use the `extra` parameter to pass a dictionary containing custom metadata. For more advanced structured logging, many developers use JSON formatters to output logs in a machine-readable format, ensuring that fields like 'user_id', 'request_id', and the full traceback are captured reliably, making it much easier for log aggregation services like ELK or Datadog to index and search your application events.

All Python interview questions →

Check yourself

1. What is the primary reason for using logger.info('User %s logged in', username) instead of logger.info(f'User {username} logged in')?

  • A.It prevents syntax errors in older Python versions.
  • B.It avoids performing string formatting if the logging level is currently disabled.
  • C.It automatically adds timestamps to the message.
  • D.It ensures the username is always converted to a string.
Show answer

B. It avoids performing string formatting if the logging level is currently disabled.
The second option is correct because the logging module performs the formatting lazily only if the log record is actually emitted. The other options are incorrect because f-strings are supported in modern Python, timestamps are handled by the Formatter, and str() conversion happens in both cases.

2. If you are writing a reusable library, what is the best practice for initializing a logger?

  • A.Call logging.basicConfig() at the module level.
  • B.Use logging.getLogger('root').
  • C.Use logger = logging.getLogger(__name__).
  • D.Assign a custom StreamHandler to the base logger.
Show answer

C. Use logger = logging.getLogger(__name__).
Using __name__ ensures that the logger follows the package hierarchy, allowing developers to configure it easily. The other options involve configuring global state, which should only be done by the top-level application, not libraries.

3. When an exception occurs, why is logger.exception('An error occurred') preferred over logger.error('An error occurred: ' + str(e))?

  • A.It is faster to execute.
  • B.It automatically captures and appends the full traceback information.
  • C.It changes the logging level to CRITICAL automatically.
  • D.It disables all other loggers to isolate the error.
Show answer

B. It automatically captures and appends the full traceback information.
logger.exception is specifically designed to be used in except blocks to capture the stack trace. The other options are incorrect because speed is negligible here, it does not change the level, and it does not affect other loggers.

4. What happens if you do not define a handler for a logger, but the root logger has one defined?

  • A.The log message is lost.
  • B.The log message is sent to the handlers of the parent loggers (propagation).
  • C.The logger throws an unhandled exception.
  • D.The log message is printed to standard error only if it is a CRITICAL level.
Show answer

B. The log message is sent to the handlers of the parent loggers (propagation).
Loggers propagate messages to their parents by default. The other options are false because the message is successfully handled by the root logger, no exception is raised, and it works for all levels depending on the root's level setting.

5. How can you ensure that different parts of your application output logs to different files simultaneously?

  • A.By calling logging.basicConfig() multiple times.
  • B.By attaching different FileHandler instances to different named loggers.
  • C.By setting the log level to different values for each file.
  • D.By using multiple threads to manage a single global file object.
Show answer

B. By attaching different FileHandler instances to different named loggers.
Attaching unique handlers to specific loggers allows for fine-grained control over destinations. basicConfig only works once, setting levels doesn't change destinations, and multiple threads on one file requires locking which the logging module handles internally via handlers.

Take the full Python quiz →

← Previousre — Regular ExpressionsNext →argparse — CLI Arguments

Python

78 lessons, free to read.

All lessons →

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app