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.