Skip to main content

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:

python
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:

  1. DEBUG - Detailed information for debugging
  2. INFO - Confirmation that things are working as expected
  3. WARNING - Something unexpected happened, but the application still works
  4. ERROR - A more serious problem prevented a function from working
  5. CRITICAL - A serious error that might prevent the program from continuing

To change the log level in Flask:

python
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:

python
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:

  1. Creates a 'logs' directory if it doesn't exist
  2. Sets up a rotating file handler that keeps log files from growing too large
  3. Formats logs with timestamp, level, message and location
  4. 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:

python
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:

python
@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:

python
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:

json
{"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:

python
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:

python
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

  1. 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
  2. Include Contextual Information:

    • Request IDs to track requests across logs
    • User IDs for user-specific actions
    • IP addresses for security monitoring
  3. 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
  4. Set Appropriate Log Rotation:

    • Prevents logs from consuming disk space
    • Maintains historical data for a reasonable period
  5. Add Structured Data When Possible:

    • Makes logs easier to parse and analyze
    • Enables better filtering and visualization
  6. Protect Sensitive Information:

    • Never log passwords, tokens, or personal information
    • Mask sensitive data in logs
  7. 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

  1. Modify the basic Flask logging example to log to both console and file.
  2. Create a custom logger format that includes the timestamp, log level, file name, and line number.
  3. Implement logging middleware that logs the execution time of each request.
  4. Configure your Flask application to use JSON logging format.
  5. 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! :)