Logging & Monitoring

Logging records what your app is doing — requests, warnings, and errors — so you can understand its behavior and diagnose problems in production.

Learn Logging & Monitoring in our free Flask course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.

Part of the free Flask course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

In this lesson you'll configure Python logging with levels and formatters, log every request, emit structured JSON logs, and learn how errors reach a monitoring service like Sentry.

A logger produces messages; a handler sends them somewhere (console, file, remote service); a formatter shapes how each line looks; and a level filters out anything below a chosen severity. Flask's app.logger is a plain Python logger, so the same setup applies everywhere.

The runnable example sends logs into a StringIO buffer so you can read exactly what was recorded. The level is INFO , so the debug line is filtered out and only INFO and above appear.

The debug message never appears because it's below the INFO threshold — that's how you keep production logs focused on what matters.

A common need is an access log: one line per request showing the method, path, and status. You attach a before_request hook to log the incoming request and an after_request hook to log the response. The runnable example routes app.logger into a buffer and exercises two requests — one that succeeds and one 404.

Each request produces an arrow-in and an arrow-out line, including the 404 — a complete trace of what hit your app and how it responded.

Plain-text logs are easy to read but hard for machines to search. Structured logs emit each entry as JSON with named fields, so a log platform can filter by user_id or count level instantly. The runnable example below uses a custom JsonFormatter that serializes the level, message, and any extra fields you pass.

Complete the setup below. Replace each ___ so only WARNING and above are recorded.

Your logger's level is too high, or it has no handler. Set setLevel(logging.INFO) and add a handler. Remember the level filters at both the logger and the handler.

You added the same handler more than once, or messages propagate to the root logger which also has a handler. Add handlers a single time, or set logger.propagate = False .

Build a custom handler that counts how many records arrive at each level.

Lesson complete — you can see inside your running app!

You configured loggers, levels, and formatters, logged every request, emitted structured JSON logs, counted records by level, and learned how errors reach a service like Sentry.

🚀 Up next: Custom CLI Commands — automate setup and maintenance from the terminal.

Practice quiz

In Python logging, what does a handler do?

  • Filters out low-severity messages
  • Sends log records somewhere, like the console or a file
  • Formats the timestamp only
  • Creates the logger object

Answer: Sends log records somewhere, like the console or a file. A handler routes log records to a destination such as a console, file, or remote service.

What is the role of a formatter?

  • It chooses the destination
  • It sets the level threshold
  • It shapes how each log line looks
  • It creates handlers

Answer: It shapes how each log line looks. A formatter controls the layout of each emitted log line.

What does a logging level control?

  • The file path
  • The color of output
  • The handler type
  • Which severities are recorded and which are filtered out

Answer: Which severities are recorded and which are filtered out. The level filters out anything below the chosen severity.

What is Flask's app.logger?

  • A plain Python logging.Logger
  • A database connection
  • A Jinja filter
  • A request hook

Answer: A plain Python logging.Logger. app.logger is an ordinary Python logger, so standard logging setup applies.

With the level set to INFO, which call is filtered out?

  • log.error(...)
  • log.debug(...)
  • log.warning(...)
  • log.info(...)

Answer: log.debug(...). DEBUG is below INFO, so debug messages are filtered out.

Which hook is ideal for logging each incoming request?

  • @app.teardown
  • @app.route
  • @app.before_request
  • @app.errorhandler

Answer: @app.before_request. @app.before_request runs before each request, perfect for an access log entry.

What handler keeps log files from growing forever?

  • StreamHandler
  • NullHandler
  • SocketHandler
  • RotatingFileHandler

Answer: RotatingFileHandler. RotatingFileHandler rotates files once they reach a size limit.

What are structured (JSON) logs good for?

  • Being searched and filtered by machines and log platforms
  • Looking prettier in a terminal
  • Reducing CPU usage
  • Encrypting messages

Answer: Being searched and filtered by machines and log platforms. JSON logs expose named fields so aggregators can filter and count them.

What does a service like Sentry capture automatically?

  • Every database row
  • All HTTP headers
  • Static files
  • Unhandled exceptions with full context

Answer: Unhandled exceptions with full context. Sentry captures unhandled exceptions with stack trace and request context.

Which level fits a normal event like 'request served'?

  • CRITICAL
  • INFO
  • ERROR
  • DEBUG

Answer: INFO. INFO is used for normal operational events.