Flask Logging
Logging is a critical aspect of any production-ready web application. In this tutorial, we'll explore how to implement effective logging in your Flask applications to help diagnose problems, track application behavior, and monitor performance.
Why Logging Matters
Imagine deploying a Flask application and suddenly users report errors. Without proper logging, you're left guessing what went wrong. Logging gives you visibility into your application's behavior and helps you:
- Debug issues in development and production
- Track application performance
- Monitor user behavior and usage patterns
- Get notified of critical errors
- Maintain an audit trail for security purposes
Flask's Built-in Logging
Flask comes with logging capabilities built-in, as it uses Python's standard logging
module. By default, Flask configures a basic logger that outputs to the standard error stream.
Basic Flask Logging Example
Here's how Flask logs messages by default:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
app.logger.debug('This is a debug log')
app.logger.info('This is an info log')
app.logger.warning('This is a warning log')
app.logger.error('This is an error log')
app.logger.critical('This is a critical log')
return "Check your console for logs!"
if __name__ == '__main__':
app.run(debug=True)
When you run this application and visit the root URL, you'll see log messages in your console:
WARNING:app:This is a warning log
ERROR:app:This is an error log
CRITICAL:app:This is a critical log
Notice that debug and info logs don't appear by default. This is because Flask sets the default log level to WARNING
in production mode.
Configuring Log Levels
The logging module defines several log levels, in increasing order of severity:
- DEBUG - Detailed information for debugging
- INFO - Confirmation that things are working as expected
- WARNING - Something unexpected happened, but the application still works
- ERROR - A more serious problem prevented a function from working
- CRITICAL - A serious error that might prevent the program from continuing
To change the log level in Flask:
import logging
from flask import Flask
app = Flask(__name__)
app.logger.setLevel(logging.INFO)
@app.route('/')
def hello():
app.logger.info('This info message will now be displayed')
return "Check your console!"
if __name__ == '__main__':
app.run(debug=True)
Creating a More Robust Logging Configuration
For a production application, you'll want a more robust logging configuration. Here's an example that logs to both console and a file:
import logging
from logging.handlers import RotatingFileHandler
import os
from flask import Flask, request
app = Flask(__name__)
# Ensure log directory exists
if not os.path.exists('logs'):
os.mkdir('logs')
# Configure logging
handler = RotatingFileHandler('logs/app.log', maxBytes=10000, backupCount=3)
handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
handler.setLevel(logging.INFO)
app.logger.addHandler(handler)
app.logger.setLevel(logging.INFO)
app.logger.info('Application startup')
@app.route('/')
def hello():
app.logger.info('Homepage requested')
return "Hello, World!"
@app.route('/user/<username>')
def show_user(username):
app.logger.info(f'User {username} profile page requested')
return f"User: {username}"
@app.errorhandler(404)
def page_not_found(e):
app.logger.error(f'Page not found: {request.path}')
return "Page not found", 404
@app.errorhandler(500)
def internal_server_error(e):
app.logger.error(f'Server error: {str(e)}')
return "Server error", 500
if __name__ == '__main__':
app.run(debug=True)
This configuration:
- Creates a 'logs' directory if it doesn't exist
- Sets up a rotating file handler that keeps log files from growing too large
- Formats logs with timestamp, level, message and location
- Logs application startup, page visits, and errors
Logs will be written to logs/app.log
and might look like:
2023-08-17 14:30:22,345 INFO: Application startup [in app.py:20]
2023-08-17 14:30:30,123 INFO: Homepage requested [in app.py:25]
2023-08-17 14:30:45,678 INFO: User johndoe profile page requested [in app.py:30]
2023-08-17 14:31:10,987 ERROR: Page not found: /nonexistent [in app.py:35]
Logging User Activities and Requests
For more detailed logging, you can use Flask's before_request
and after_request
hooks to log information about every request:
import time
from flask import g
@app.before_request
def start_timer():
g.start = time.time()
@app.after_request
def log_request(response):
if request.path != '/favicon.ico':
now = time.time()
duration = round(now - g.start, 2)
ip = request.headers.get('X-Forwarded-For', request.remote_addr)
host = request.host.split(':', 1)[0]
args = dict(request.args)
log_params = [
('method', request.method),
('path', request.path),
('status', response.status_code),
('duration', duration),
('ip', ip),
('host', host),
('params', args)
]
request_id = request.headers.get('X-Request-ID')
if request_id:
log_params.append(('request_id', request_id))
parts = []
for name, value in log_params:
part = f"{name}={value}"
parts.append(part)
app.logger.info(" ".join(parts))
return response
This code logs:
- HTTP method
- Request path
- Response status code
- Request duration
- Client IP address
- Host
- URL parameters
- Request ID (if available)
A sample log entry might look like:
2023-08-17 15:45:12,345 INFO: method=GET path=/user/johndoe status=200 duration=0.05 ip=192.168.1.1 host=example.com params={} [in app.py:65]
Handling Exceptions with Logging
It's important to log exceptions when they occur. Here's an example of how to do that:
@app.route('/divide/<int:a>/<int:b>')
def divide(a, b):
try:
result = a / b
app.logger.info(f'Successfully divided {a} by {b}')
return f"Result: {result}"
except ZeroDivisionError:
app.logger.error(f'Attempted to divide {a} by zero')
return "Cannot divide by zero", 400
except Exception as e:
app.logger.exception(f'Unexpected error dividing {a} by {b}')
return "An unexpected error occurred", 500
Note the use of logger.exception()
which automatically includes the full exception traceback in the log.
Structuring Logs with JSON
For applications that will be monitored by systems like ELK (Elasticsearch, Logstash, Kibana) or other log analysis tools, structured JSON logging can be more effective:
import json
from datetime import datetime
class JSONFormatter(logging.Formatter):
def format(self, record):
log_record = {
'timestamp': datetime.utcnow().isoformat(),
'level': record.levelname,
'message': record.getMessage(),
'module': record.module,
'function': record.funcName,
'line': record.lineno
}
if hasattr(record, 'request_id'):
log_record['request_id'] = record.request_id
if record.exc_info:
log_record['exception'] = self.formatException(record.exc_info)
return json.dumps(log_record)
# Setup JSON handler
json_handler = RotatingFileHandler('logs/app.json.log', maxBytes=10000, backupCount=3)
json_handler.setFormatter(JSONFormatter())
app.logger.addHandler(json_handler)
The resulting JSON log entries would look like:
{"timestamp": "2023-08-17T15:48:23.456789", "level": "INFO", "message": "Homepage requested", "module": "app", "function": "hello", "line": 25}
{"timestamp": "2023-08-17T15:49:10.123456", "level": "ERROR", "message": "Page not found: /nonexistent", "module": "app", "function": "page_not_found", "line": 35}
Using Third-Party Logging Tools
For more advanced logging needs, consider using third-party packages:
Flask-LogConfig
Flask-LogConfig is an extension that simplifies logging configuration:
pip install Flask-LogConfig
Here's how to use it:
from flask import Flask
from flask_logconfig import LogConfig
app = Flask(__name__)
logconfig = LogConfig(app)
# Load logging configuration from a file
app.config['LOGCONFIG'] = 'logging.yml'
app.config['LOGCONFIG_DICT'] = {
'version': 1,
'formatters': {
'default': {
'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s',
}
},
'handlers': {
'wsgi': {
'class': 'logging.StreamHandler',
'stream': 'ext://flask.logging.wsgi_errors_stream',
'formatter': 'default'
},
'file': {
'class': 'logging.handlers.RotatingFileHandler',
'formatter': 'default',
'filename': 'logs/app.log',
'maxBytes': 10000,
'backupCount': 3
}
},
'root': {
'level': 'INFO',
'handlers': ['wsgi', 'file']
}
}
@app.route('/')
def hello():
app.logger.info('Hello logged')
return "Hello World!"
Sentry Integration
For production applications, you might want to use a service like Sentry to track errors:
pip install sentry-sdk[flask]
Here's how to integrate it:
import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration
sentry_sdk.init(
dsn="your_sentry_dsn_here",
integrations=[FlaskIntegration()],
traces_sample_rate=1.0 # Capture 100% of transactions
)
app = Flask(__name__)
@app.route('/error')
def trigger_error():
division_by_zero = 1 / 0
Best Practices for Flask Logging
-
Use Different Log Levels Appropriately:
- DEBUG: Detailed information for debugging
- INFO: Notable events, but normal operation
- WARNING: Unexpected behavior that should be investigated
- ERROR: Failed operations that need immediate attention
- CRITICAL: System-level failures
-
Include Contextual Information:
- Request IDs to track requests across logs
- User IDs for user-specific actions
- IP addresses for security monitoring
-
Configure Different Handlers for Different Environments:
- Development: Console logs for immediate feedback
- Testing: File logs for test runs analysis
- Production: File logs and possibly remote logging services
-
Set Appropriate Log Rotation:
- Prevents logs from consuming disk space
- Maintains historical data for a reasonable period
-
Add Structured Data When Possible:
- Makes logs easier to parse and analyze
- Enables better filtering and visualization
-
Protect Sensitive Information:
- Never log passwords, tokens, or personal information
- Mask sensitive data in logs
-
Use Try/Except Blocks with Logging:
- Log exceptions with full context
- Use
logger.exception()
to include tracebacks
Summary
Effective logging is an essential part of any Flask application, providing visibility into its behavior and helping diagnose issues quickly. By following the examples and best practices outlined in this tutorial, you can implement a robust logging system that will serve you well in development and production environments.
Remember that logging is not just for debugging—it's a crucial part of application monitoring, security, and maintenance. A well-designed logging system can save you hours of troubleshooting and provide valuable insights into how your application is being used.
Additional Resources
Exercises
- Modify the basic Flask logging example to log to both console and file.
- Create a custom logger format that includes the timestamp, log level, file name, and line number.
- Implement logging middleware that logs the execution time of each request.
- Configure your Flask application to use JSON logging format.
- Set up different logging configurations for development and production environments.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)