Skip to main content

Flask: Logging Errors

Flask: Logging Errors

Logging errors in Flask is critical for diagnosing issues, monitoring application health, and ensuring reliability in production environments. By capturing detailed error information, such as stack traces, request contexts, and timestamps, developers can troubleshoot effectively. Flask integrates seamlessly with Python’s logging module and middleware for centralized error logging. This tutorial explores logging errors in Flask, covering setup, middleware for error capture, file-based logging, and best practices for robust error tracking.


01. Why Log Errors in Flask?

Errors, such as unhandled exceptions or HTTP errors (e.g., 500 Internal Server Error), can disrupt user experience and indicate underlying issues. Logging errors provides visibility into what went wrong, where, and why, enabling developers to debug efficiently, monitor application performance, and prevent future failures. Centralized error logging in Flask ensures consistency, scalability, and integration with monitoring tools.

Example: Basic Error Logging

from flask import Flask
import logging

app = Flask(__name__)

logging.basicConfig(level=logging.ERROR)
logger = logging.getLogger(__name__)

@app.errorhandler(Exception)
def handle_error(error):
    logger.error(f"Error occurred: {str(error)}", exc_info=True)
    return "Internal Server Error", 500

@app.route('/')
def index():
    return "Home Page"

@app.route('/crash')
def crash():
    raise ValueError("Test error")

if __name__ == '__main__':
    app.run(debug=True, port=5000)

Output (Console):

* Running on http://127.0.0.1:5000
ERROR:__main__:Error occurred: Test error
Traceback (most recent call last):
  ...
ValueError: Test error

Explanation:

  • logging.basicConfig - Configures logging with ERROR level.
  • logger.error - Logs the error with stack trace (exc_info=True).
  • @app.errorhandler(Exception) - Catches all unhandled exceptions.

02. Key Techniques for Logging Errors

Flask supports multiple approaches to log errors, from simple console logging to advanced file-based logging and middleware for centralized error capture. These techniques enhance observability and debugging. The table below summarizes key techniques and their applications:

Technique Description Use Case
Console Logging Log errors to console Development, quick debugging
File-Based Logging Save logs to files with rotation Production, persistent records
Middleware Logging Centralize error logging Global error capture
Request Context Log request details with errors Contextual debugging
External Integration Send logs to monitoring tools Real-time monitoring


2.1 Console-Based Error Logging

Example: Logging Errors to Console

from flask import Flask
import logging

app = Flask(__name__)

logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

@app.errorhandler(Exception)
def handle_error(error):
    logger.error(f"Error: {str(error)}", exc_info=True)
    return render_template('500.html'), 500

@app.route('/')
def index():
    return "Home Page"

@app.route('/crash')
def crash():
    raise ValueError("Test error")

if __name__ == '__main__':
    app.run(debug=True, port=5000)

Template (templates/500.html):

<!DOCTYPE html>
<html>
<head>
    <title>500 - Server Error</title>
</head>
<body>
    <h1>500 - Internal Server Error</h1>
    <p>Something went wrong. Please try again later.</p>
    <a href="/">Go Home</a>
</body>
</html>

Output (Console):

* Running on http://127.0.0.1:5000
2025-05-12 10:00:00,123 - ERROR - Error: Test error
Traceback (most recent call last):
  ...
ValueError: Test error

Explanation:

  • Logs errors with timestamps and stack traces to the console.
  • Suitable for development and quick debugging.

2.2 File-Based Error Logging

Example: Logging Errors to File with Rotation

from flask import Flask
import logging
from logging.handlers import RotatingFileHandler

app = Flask(__name__)

logger = logging.getLogger(__name__)
logger.setLevel(logging.ERROR)
handler = RotatingFileHandler('errors.log', maxBytes=1000000, backupCount=5)
handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logger.addHandler(handler)

@app.errorhandler(Exception)
def handle_error(error):
    logger.error(f"Error: {str(error)}", exc_info=True)
    return render_template('500.html'), 500

@app.route('/')
def index():
    return "Home Page"

@app.route('/crash')
def crash():
    raise ValueError("Test error")

if __name__ == '__main__':
    app.run(debug=True, port=5000)

Output (errors.log):

2025-05-12 10:00:00,123 - ERROR - Error: Test error
Traceback (most recent call last):
  ...
ValueError: Test error

Explanation:

  • RotatingFileHandler - Logs errors to a file with size-based rotation (1MB, 5 backups).
  • Ensures persistent logs for production environments.

2.3 Middleware for Centralized Error Logging

Example: Error Logging Middleware

from flask import Flask, render_template
from werkzeug.wrappers import Request, Response
import logging
from logging.handlers import RotatingFileHandler

app = Flask(__name__)

logger = logging.getLogger(__name__)
logger.setLevel(logging.ERROR)
handler = RotatingFileHandler('errors.log', maxBytes=1000000, backupCount=5)
handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logger.addHandler(handler)

class ErrorLoggingMiddleware:
    def __init__(self, wsgi_app):
        self.wsgi_app = wsgi_app

    def __call__(self, environ, start_response):
        request = Request(environ)
        try:
            return self.wsgi_app(environ, start_response)
        except Exception as e:
            logger.error(f"Error in {request.method} {request.path}: {str(e)}", exc_info=True)
            res = Response(render_template('500.html'), status=500, mimetype='text/html')
            return res(environ, start_response)

app.wsgi_app = ErrorLoggingMiddleware(app.wsgi_app)

@app.route('/')
def index():
    return "Home Page"

@app.route('/crash')
def crash():
    raise ValueError("Test error")

if __name__ == '__main__':
    app.run(debug=True, port=5000)

Output (errors.log):

2025-05-12 10:00:00,123 - ERROR - Error in GET /crash: Test error
Traceback (most recent call last):
  ...
ValueError: Test error

Output (Browser):

* Running on http://127.0.0.1:5000
(Accessing /crash: Renders 500.html)

Explanation:

  • Middleware captures all unhandled exceptions, logs them with request context, and renders an error page.
  • Centralizes error logging for consistency.

2.4 Logging with Request Context

Example: Logging Request Details

from flask import Flask, render_template
from werkzeug.wrappers import Request, Response
import logging
from logging.handlers import RotatingFileHandler

app = Flask(__name__)

logger = logging.getLogger(__name__)
logger.setLevel(logging.ERROR)
handler = RotatingFileHandler('errors.log', maxBytes=1000000, backupCount=5)
handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logger.addHandler(handler)

class RequestErrorLoggingMiddleware:
    def __init__(self, wsgi_app):
        self.wsgi_app = wsgi_app

    def __call__(self, environ, start_response):
        request = Request(environ)
        try:
            return self.wsgi_app(environ, start_response)
        except Exception as e:
            logger.error(
                f"Error in {request.method} {request.path}, "
                f"IP: {request.remote_addr}, User-Agent: {request.user_agent}: {str(e)}",
                exc_info=True
            )
            res = Response(render_template('500.html'), status=500, mimetype='text/html')
            return res(environ, start_response)

app.wsgi_app = RequestErrorLoggingMiddleware(app.wsgi_app)

@app.route('/')
def index():
    return "Home Page"

@app.route('/crash')
def crash():
    raise ValueError("Test error")

if __name__ == '__main__':
    app.run(debug=True, port=5000)

Output (errors.log):

2025-05-12 10:00:00,123 - ERROR - Error in GET /crash, IP: 127.0.0.1, User-Agent: Mozilla/5.0 ...: Test error
Traceback (most recent call last):
  ...
ValueError: Test error

Explanation:

  • Logs request details (method, path, IP, User-Agent) alongside errors.
  • Provides rich context for debugging.

2.5 Integration with External Monitoring (Sentry)

Example: Logging Errors to Sentry

from flask import Flask, render_template
import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration

app = Flask(__name__)

# Initialize Sentry
sentry_sdk.init(
    dsn="your-sentry-dsn",  # Replace with your Sentry DSN
    integrations=[FlaskIntegration()],
    traces_sample_rate=1.0
)

@app.errorhandler(Exception)
def handle_error(error):
    return render_template('500.html'), 500

@app.route('/')
def index():
    return "Home Page"

@app.route('/crash')
def crash():
    raise ValueError("Test error")

if __name__ == '__main__':
    app.run(debug=True, port=5000)

Output (Sentry Dashboard):

Error: ValueError: Test error
Path: /crash
Method: GET
IP: 127.0.0.1
User-Agent: Mozilla/5.0 ...
Stacktrace: ...

Explanation:

  • sentry_sdk - Sends errors with request context to Sentry.
  • Enables real-time monitoring and alerting in production.

2.6 Incorrect Error Logging

Example: Incomplete Error Logging

from flask import Flask
import logging

app = Flask(__name__)

logging.basicConfig(level=logging.ERROR)
logger = logging.getLogger(__name__)

@app.errorhandler(Exception)
def handle_error(error):
    logger.error("An error occurred")  # Missing details
    return "Internal Server Error", 500

@app.route('/')
def index():
    return "Home Page"

@app.route('/crash')
def crash():
    raise ValueError("Test error")

if __name__ == '__main__':
    app.run(debug=True, port=5000)

Output (Console):

* Running on http://127.0.0.1:5000
ERROR:__main__:An error occurred

Explanation:

  • Generic error message lacks context and stack trace.
  • Solution: Include str(error) and exc_info=True for detailed logging.

03. Effective Usage

3.1 Recommended Practices

  • Combine middleware with file-based logging and external monitoring for comprehensive error tracking.

Example: Comprehensive Error Logging

from flask import Flask, render_template
from werkzeug.wrappers import Request, Response
import logging
from logging.handlers import RotatingFileHandler
import sentry_sdk
from sentry_sdk.integrations.flask import FlaskIntegration

app = Flask(__name__)

# Initialize Sentry
sentry_sdk.init(
    dsn="your-sentry-dsn",  # Replace with your Sentry DSN
    integrations=[FlaskIntegration()],
    traces_sample_rate=1.0
)

# Setup file logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.ERROR)
handler = RotatingFileHandler('errors.log', maxBytes=1000000, backupCount=5)
handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logger.addHandler(handler)

# Middleware for error logging
class ComprehensiveErrorLoggingMiddleware:
    def __init__(self, wsgi_app):
        self.wsgi_app = wsgi_app

    def __call__(self, environ, start_response):
        request = Request(environ)
        try:
            return self.wsgi_app(environ, start_response)
        except Exception as e:
            error_message = (
                f"Error in {request.method} {request.path}, "
                f"IP: {request.remote_addr}, User-Agent: {request.user_agent}: {str(e)}"
            )
            logger.error(error_message, exc_info=True)
            # Sentry automatically captures the error via integration
            res = Response(render_template('500.html'), status=500, mimetype='text/html')
            return res(environ, start_response)

app.wsgi_app = ComprehensiveErrorLoggingMiddleware(app.wsgi_app)

# Error handler for specific exceptions
@app.errorhandler(ValueError)
def handle_value_error(error):
    logger.error(f"ValueError: {str(error)}", exc_info=True)
    return render_template('400.html', message=str(error)), 400

@app.route('/')
def index():
    return "Home Page"

@app.route('/crash')
def crash():
    raise ValueError("Test error")

if __name__ == '__main__':
    app.run(debug=True, port=5000)

Template (templates/500.html):

<!DOCTYPE html>
<html>
<head>
    <title>500 - Server Error</title>
</head>
<body>
    <h1>500 - Internal Server Error</h1>
    <p>Something went wrong. Please try again later.</p>
    <a href="/">Go Home</a>
</body>
</html>

Template (templates/400.html):

<!DOCTYPE html>
<html>
<head>
    <title>400 - Bad Request</title>
</head>
<body>
    <h1>400 - Bad Request</h1>
    <p>{{ message }}</p>
    <a href="/">Go Home</a>
</body>
</html>

Output (errors.log):

2025-05-12 10:00:00,123 - ERROR - Error in GET /crash, IP: 127.0.0.1, User-Agent: Mozilla/5.0 ...: Test error
Traceback (most recent call last):
  ...
ValueError: Test error

Output (Sentry Dashboard):

Error: ValueError: Test error
Path: /crash
Method: GET
IP: 127.0.0.1
User-Agent: Mozilla/5.0 ...
Stacktrace: ...
  • Logs errors to a rotating file with request context.
  • Integrates with Sentry for real-time monitoring.
  • Handles specific exceptions (e.g., ValueError) with custom responses.

3.2 Practices to Avoid

  • Avoid logging sensitive information (e.g., passwords, tokens).

Example: Logging Sensitive Data

from flask import Flask
import logging

app = Flask(__name__)

logging.basicConfig(level=logging.ERROR)
logger = logging.getLogger(__name__)

@app.errorhandler(Exception)
def handle_error(error):
    sensitive_data = "user_token=secret123"
    logger.error(f"Error with {sensitive_data}: {str(error)}", exc_info=True)
    return "Internal Server Error", 500

@app.route('/')
def index():
    return "Home Page"

@app.route('/crash')
def crash():
    raise ValueError("Test error")

if __name__ == '__main__':
    app.run(debug=True, port=5000)

Output (Console):

* Running on http://127.0.0.1:5000
ERROR:__main__:Error with user_token=secret123: Test error
  • Logging sensitive data risks security breaches.
  • Solution: Sanitize logs by excluding sensitive fields.

04. Common Use Cases

4.1 Logging Application Errors

Capture and log errors for debugging application logic.

Example: Logging Application Errors

from flask import Flask, render_template
import logging
from logging.handlers import RotatingFileHandler

app = Flask(__name__)

logger = logging.getLogger(__name__)
logger.setLevel(logging.ERROR)
handler = RotatingFileHandler('app_errors.log', maxBytes=1000000, backupCount=5)
handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logger.addHandler(handler)

@app.errorhandler(ValueError)
def handle_value_error(error):
    logger.error(f"Application error: {str(error)}", exc_info=True)
    return render_template('400.html', message=str(error)), 400

@app.route('/')
def index():
    return "Home Page"

@app.route('/process')
def process():
    raise ValueError("Invalid data format")

if __name__ == '__main__':
    app.run(debug=True, port=5000)

Output (app_errors.log):

2025-05-12 10:00:00,123 - ERROR - Application error: Invalid data format
Traceback (most recent call last):
  ...
ValueError: Invalid data format

Explanation:

  • Logs specific application errors (ValueError) to a file.
  • Provides user-friendly error responses.

4.2 Monitoring API Errors

Log errors in API endpoints for monitoring and debugging.

Example: Logging API Errors

from flask import Flask, jsonify, render_template
from werkzeug.wrappers import Request, Response
import logging
from logging.handlers import RotatingFileHandler

app = Flask(__name__)

logger = logging.getLogger(__name__)
logger.setLevel(logging.ERROR)
handler = RotatingFileHandler('api_errors.log', maxBytes=1000000, backupCount=5)
handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logger.addHandler(handler)

class APIErrorLoggingMiddleware:
    def __init__(self, wsgi_app):
        self.wsgi_app = wsgi_app

    def __call__(self, environ, start_response):
        request = Request(environ)
        if not request.path.startswith('/api/'):
            return self.wsgi_app(environ, start_response)
        try:
            return self.wsgi_app(environ, start_response)
        except Exception as e:
            logger.error(
                f"API Error in {request.method} {request.path}, "
                f"IP: {request.remote_addr}: {str(e)}",
                exc_info=True
            )
            res = Response(
                jsonify({"error": "Internal Server Error"}),
                status=500,
                mimetype='application/json'
            )
            return res(environ, start_response)

app.wsgi_app = APIErrorLoggingMiddleware(app.wsgi_app)

@app.route('/api/data')
def data():
    raise ValueError("Invalid API request")

@app.route('/')
def index():
    return "Home Page"

if __name__ == '__main__':
    app.run(debug=True, port=5000)

Output (api_errors.log):

2025-05-12 10:00:00,123 - ERROR - API Error in GET /api/data, IP: 127.0.0.1: Invalid API request
Traceback (most recent call last):
  ...
ValueError: Invalid API request

Output (Browser):

* Running on http://127.0.0.1:5000
(Accessing /api/data: Returns {"error": "Internal Server Error"})

Explanation:

  • Logs errors for API routes with request context.
  • Returns JSON error responses for API consistency.

Conclusion

Logging errors in Flask enhances debugging, monitoring, and reliability. Key takeaways:

  • Use middleware for centralized error logging with request context.
  • Implement file-based logging with rotation for persistent records.
  • Integrate with tools like Sentry for real-time monitoring.
  • Avoid logging sensitive data or incomplete error details.

With these practices, you can build Flask applications with robust error logging for effective troubleshooting and maintenance!

Comments