← Back to Chapters

Python Logging Configuration

? Python Logging Configuration

⚡ Quick Overview

Python’s built-in logging module lets you record messages from your code. With proper configuration, you can:

  • Set the log level (DEBUG, INFO, WARNING, ERROR, CRITICAL).
  • Choose where logs go (console, files, rotating log files, etc.).
  • Customize the log format (time, level, module name, message).
  • Use different settings for different parts of your application.

? Key Concepts

  • Logger – main object you call (logger.info()).
  • Handler – decides where the log goes (console, file, etc.).
  • Formatter – decides how the log message looks.
  • Level – severity of the message (DEBUG, INFO, ...).
  • Root logger – default logger used by logging.info() etc.
  • basicConfig() – quick way to configure simple logging.
  • dictConfig() – advanced configuration using dictionaries/JSON/YAML.
  • Per-module loggers – use getLogger(__name__) in each file.

? Syntax and Theory

Typical logging setup for a small script:

  • Import logging.
  • Call logging.basicConfig(...) once at the start of your program.
  • Get a logger via logging.getLogger(__name__).
  • Log messages with logger.debug(), logger.info(), etc.

For bigger projects, you usually:

  • Create a configuration (dict/JSON/YAML).
  • Apply it using logging.config.dictConfig().
  • Use named loggers for different modules or components.

? Code Examples

? Basic Logging with basicConfig()

Configure logging once to print simple messages to the console.

? View basicConfig example
import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s"
)

logging.debug("This is a debug message")   # Will NOT show (level is INFO)
logging.info("Application started")
logging.warning("Low disk space")
logging.error("An error occurred")

? Logging to a File

Send logs to a file instead of (or in addition to) the console.

? View file logging example
import logging

logging.basicConfig(
    filename="app.log",
    filemode="a",  # append to file
    level=logging.DEBUG,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)

logger = logging.getLogger(__name__)

logger.debug("Debug details for troubleshooting")
logger.info("App started")
logger.error("Something went wrong")

? Multiple Handlers (Console + File)

Create your own logger with different handlers and formats.

? View multiple handlers example
import logging

logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)

# Console handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)

# File handler
file_handler = logging.FileHandler("myapp.log")
file_handler.setLevel(logging.DEBUG)

# Formatter
formatter = logging.Formatter(
    "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)

# Attach handlers
logger.addHandler(console_handler)
logger.addHandler(file_handler)

logger.info("This will go to console and file")
logger.debug("This will go only to file (not console)")

⚙️ Advanced Configuration with dictConfig()

Use a dictionary for complex setups (easily convertable from JSON/YAML).

? View dictConfig example
import logging
import logging.config

# Centralized logging configuration dictionary
LOGGING_CONFIG = {
    "version": 1,
    "formatters": {
        "standard": {
            "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
        }
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "level": "INFO",
            "formatter": "standard",
            "stream": "ext://sys.stdout",
        },
        "file": {
            "class": "logging.FileHandler",
            "level": "DEBUG",
            "formatter": "standard",
            "filename": "service.log",
            "mode": "a",
        },
    },
    "loggers": {
        "myservice": {
            "level": "DEBUG",
            "handlers": ["console", "file"],
            "propagate": False,
        }
    },
}

# Apply configuration and get a logger for this service
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("myservice")

# Application log messages
logger.info("Service started")
logger.debug("Debugging information")

? Live Output and Explanation

? What You Will See

  • In the basicConfig example, console output might look like:
    2025-09-11 14:30:00,123 - INFO - Application started
    2025-09-11 14:30:00,124 - WARNING - Low disk space
  • In the file logging example, these messages are written to app.log instead of the console.
  • In the multiple handlers example:
    • logger.info() appears in both console and myapp.log.
    • logger.debug() appears only in myapp.log (console level is INFO).
  • In the dictConfig example, both console and service.log receive logs with the same format, and you can adjust behavior just by changing the configuration dictionary.

Proper logging configuration makes it easy to diagnose issues in development and production without changing lots of code.

?️ Use Cases

  • Recording errors and exceptions in production applications.
  • Debugging complex flows by enabling DEBUG logs temporarily.
  • Keeping audit trails of user actions or background jobs.
  • Separating logs by module or component using different named loggers.

? Tips & Best Practices

  • Use logger = logging.getLogger(__name__) in each module for clear log origins.
  • Configure logging once, near application startup (e.g., in main.py).
  • Avoid calling basicConfig() in reusable libraries; let the main app configure logging.
  • Use the right log level: DEBUG for developers, INFO for normal events, WARNING/ERROR/CRITICAL for problems.
  • Use dictConfig() or external config files for bigger projects so you can change logging without editing code.
  • Don’t log sensitive data (passwords, tokens, personal information).

? Try It Yourself

  • Modify the basicConfig() example to include the module name using %(name)s in the format.
  • Create a script that logs to both console and a file with different levels using two handlers.
  • Build a small LOGGING_CONFIG dictionary and apply it with logging.config.dictConfig() for a pretend web service.
  • Add a logger that only logs database-related messages to db.log.
  • Experiment with switching levels (e.g., from INFO to DEBUG) and see how the output changes.