Logs are how you learn what a production Django app is doing. This lesson builds a practical setup: a LOGGING dict, request logs with an id, structured JSON and a different configuration per environment.

From print to logging

print has no level, time, source or traceback. The standard logging module gives levels, named loggers, several destinations and formats. Django itself logs through it (django.request, django.db.backends).

How a log record travels
How a log record travels
BlockRole
LoggerThe object you call; named with getLogger(__name__)
HandlerWhere a record goes: console, file, email, collector
FormatterWhat it looks like: text or JSON
FilterDrops records or adds fields such as request_id

Logging in your code

python
import logging
import sys

logging.basicConfig(
    stream=sys.stdout,
    level=logging.INFO,
    format='%(levelname)s %(name)s %(message)s',
)
log = logging.getLogger('shop.orders')
log.debug('hidden')
log.info('order %s created', 42)
log.warning('stock low: %d left', 3)

It prints:

text
INFO shop.orders order 42 created
WARNING shop.orders stock low: 3 left

The DEBUG line is dropped because the threshold is INFO. Pass values as arguments (%s), not through an f-string.

LevelWhen
DEBUGDetails, development only
INFONormal events: order created
WARNINGUnexpected, still working
ERRORAn operation failed
CRITICALThe system is down
python
# shop/services.py
import logging

logger = logging.getLogger(__name__)   # -> 'shop.services'

def pay(order_id):
    logger.info('paying order %s', order_id)
    try:
        charge(order_id)
    except TimeoutError:
        logger.exception('payment gateway timeout, order %s', order_id)
        raise