Skip to content

Logger Objects

Create and configure logger objects for modular logging throughout your application.

Mental Model

Loggers form a tree rooted at the root logger. Each module creates its own logger with logging.getLogger(__name__), and the dot-separated name mirrors the package hierarchy. A message propagates up the tree until a handler catches it, so configuring a parent logger automatically covers all its children.

Creating Loggers

Create named loggers for different modules.

```python import logging

Get loggers for different modules

logger_app = logging.getLogger('app') logger_db = logging.getLogger('app.database') logger_api = logging.getLogger('app.api')

Loggers have hierarchical names

print(f"App logger: {logger_app.name}") print(f"DB logger: {logger_db.name}") print(f"API logger: {logger_api.name}") ```

App logger: app DB logger: app.database API logger: app.api

Logger Configuration

Configure individual loggers with handlers and formatters.

```python import logging

Create logger

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

Remove existing handlers

logger.handlers.clear()

Create handler

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

Create formatter

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

Add handler to logger

logger.addHandler(handler)

Log messages

logger.info("Starting application") logger.debug("Debug info") ```

myapp - INFO - Starting application myapp - DEBUG - Debug info


Exercises

Exercise 1. Create a logger hierarchy with "app", "app.db", and "app.api". Set the "app" logger to INFO level and add a handler. Log messages from each child logger and verify they propagate to the parent handler.

Solution to Exercise 1

```python import logging

Parent logger

app_logger = logging.getLogger("app") app_logger.setLevel(logging.INFO) handler = logging.StreamHandler() handler.setFormatter(logging.Formatter("%(name)s - %(levelname)s - %(message)s")) app_logger.addHandler(handler)

Child loggers (no handlers needed — propagation)

db_logger = logging.getLogger("app.db") api_logger = logging.getLogger("app.api")

db_logger.info("Connected to database") api_logger.warning("Rate limit approaching")

app.db - INFO - Connected to database

app.api - WARNING - Rate limit approaching

```


Exercise 2. Write a function get_or_create_logger that takes a name and level, and returns a logger. If the logger already has handlers, return it as-is; otherwise, add a StreamHandler with a standard format. This prevents duplicate handler accumulation.

Solution to Exercise 2

```python import logging

def get_or_create_logger(name, level=logging.INFO): logger = logging.getLogger(name) if not logger.handlers: logger.setLevel(level) handler = logging.StreamHandler() handler.setFormatter( logging.Formatter("%(name)s - %(levelname)s - %(message)s") ) logger.addHandler(handler) return logger

Test

log1 = get_or_create_logger("myservice") log2 = get_or_create_logger("myservice") # Returns same, no new handler print(len(log1.handlers)) # 1 ```


Exercise 3. Write a function logger_info that takes a logger name and returns a dictionary with "name", "level", "handler_count", and "effective_level" (the level inherited from parents if not set explicitly). Test with both configured and unconfigured loggers.

Solution to Exercise 3

```python import logging

def logger_info(name): logger = logging.getLogger(name) return { "name": logger.name, "level": logging.getLevelName(logger.level), "handler_count": len(logger.handlers), "effective_level": logging.getLevelName(logger.getEffectiveLevel()), }

Test

logging.basicConfig(level=logging.WARNING) print(logger_info("root"))

{'name': 'root', 'level': 'WARNING', ...}

print(logger_info("unset.child"))

effective_level inherits from root: WARNING

```