如何从requests.exceptions.RequestException获取异常信息及日志最佳实践
Hey there! Let's break down your questions about handling requests exceptions in Flask step by step—this is such a common scenario when working with external APIs, so great call asking about best practices.
RequestException First off, getting the core error message is straightforward: just use str(e) or e.args[0] (since most exceptions store their message in the first argument). But RequestException and its subclasses often carry extra context you’ll want to log too:
- Request details: Most subclasses have a
requestattribute that lets you access the original URL, HTTP method, headers, etc. - Response details: If the exception is tied to a server response (like
HTTPError), you’ll get aresponseattribute with status code, response body, headers, etc.
Here’s a quick example of extracting all relevant info:
import requests from requests.exceptions import RequestException try: response = requests.get("https://example.invalid/api") except RequestException as e: # Core error message error_msg = str(e) # Grab request context if available request_context = {} if hasattr(e, "request"): request_context["url"] = e.request.url request_context["method"] = e.request.method # Grab response context if available (e.g., for HTTPError) response_context = {} if hasattr(e, "response"): response_context["status_code"] = e.response.status_code response_context["response_body"] = e.response.text[:200] # Truncate long bodies # Combine everything for logging full_error = f"Request failed: {error_msg}" if request_context: full_error += f" | Request: {request_context['method']} {request_context['url']}" if response_context: full_error += f" | Status Code: {response_context['status_code']}" print(full_error) # Replace with proper logging later!
RequestException Subclasses RequestException is the parent class for all requests-specific errors, so catching it alone lumps together a bunch of different issues (timeouts, connection failures, HTTP 4xx/5xx errors, etc.). The best approach is to catch specific subclasses first, then use RequestException as a final fallback.
Here’s how to structure your try/except block to handle different error types:
try: response = requests.get("https://example.com/api") response.raise_for_status() # Triggers HTTPError for 4xx/5xx responses except requests.exceptions.HTTPError as e: # Handle HTTP errors (e.g., 401 Unauthorized, 500 Server Error) log.error(f"HTTP Error: {str(e)} | Status Code: {e.response.status_code} | URL: {e.request.url}") # Maybe add logic here like retrying for 5xx errors, or redirecting users for 401 except requests.exceptions.ConnectionError as e: # Handle connection issues (DNS failure, refused connection, network down) log.error(f"Connection Failure: {str(e)} | URL: {e.request.url if hasattr(e, 'request') else 'Unknown'}") except requests.exceptions.Timeout as e: # Handle timeouts (connect timeout or read timeout) log.error(f"Request Timed Out: {str(e)} | URL: {e.request.url if hasattr(e, 'request') else 'Unknown'}") # Maybe add retry logic here for transient timeouts except requests.exceptions.TooManyRedirects as e: # Handle excessive redirect loops log.error(f"Too Many Redirects: {str(e)} | URL: {e.request.url}") except requests.exceptions.RequestException as e: # Catch-all for any other requests-related error log.error(f"Unexpected Request Failure: {str(e)}")
This way, you can tailor your handling to each error type—like retrying timeouts, alerting on connection failures, or handling authentication for 401s.
print!) Print statements are fine for local testing, but they’re useless in production—you need structured, persistent logging. Flask integrates seamlessly with Python’s built-in logging module, which is the standard for this.
Step 1: Configure Your Logger
Set up your logger once when your Flask app starts to define where logs go (file, console, etc.) and how they’re formatted:
import logging from flask import Flask app = Flask(__name__) # Configure logging logging.basicConfig( level=logging.ERROR, # Log errors and above; adjust to INFO/WARNING if needed format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", handlers=[ logging.FileHandler("app_errors.log"), # Log to a file logging.StreamHandler() # Also log to the console for local debugging ] ) # Get a logger instance for your module log = logging.getLogger(__name__)
Step 2: Log with Context
When logging exceptions, include as much context as possible (URL, method, user ID if applicable) and use exc_info=True to capture the full stack trace—this is a lifesaver for debugging:
@app.route("/fetch-external-data") def fetch_external_data(): try: response = requests.get("https://example.com/api") response.raise_for_status() return response.json() except requests.exceptions.HTTPError as e: log.error( f"HTTP Error fetching data: {str(e)} | Status Code: {e.response.status_code} | URL: {e.request.url}", exc_info=True ) return {"error": "Failed to retrieve data"}, 500 except requests.exceptions.RequestException as e: log.error( f"Request failed: {str(e)} | URL: {e.request.url if hasattr(e, 'request') else 'Unknown'}", exc_info=True ) return {"error": "Service unavailable"}, 503
Bonus Production Tips
- Rotate Logs: Use
RotatingFileHandlerorTimedRotatingFileHandlerto prevent log files from growing indefinitely. - Structured Logging: For production, consider using libraries like
structlogto output logs in JSON format—this makes it easy to parse logs with tools like Elasticsearch or Splunk. - Log Levels: Use appropriate levels (
WARNINGfor transient issues like timeouts,ERRORfor critical failures like connection errors) to filter logs effectively.
内容的提问来源于stack exchange,提问作者Subhayan Bhattacharya

