Practical Guide to FastAPI Middleware: Implementing Request Logging and Response Time Statistics

In FastAPI, middleware is a very useful feature that allows us to uniformly handle logic such as authentication, logging, and error handling before a request reaches the view function and after the response is returned to the client. This article will walk you through a practical case to implement a simple middleware for recording request logs and statistics on response times, making your API development more standardized and easier to debug.

1. Environment Preparation

First, ensure you have FastAPI and Uvicorn (the server for running FastAPI applications) installed. If not, install them using the following command:

pip install fastapi uvicorn

2. Role of Middleware

Why use middleware for request logging and response time statistics? Imagine if you manually recorded this information in each view function—this would result in a lot of repeated code, and changes (e.g., adding new fields) would be cumbersome. Middleware helps you centralize this logic, ensuring all requests automatically pass through the middleware, which is both concise and easy to maintain.

3. Implementing Middleware for Request Logging and Response Time Statistics

We need to create a middleware class that inherits from BaseHTTPMiddleware provided by Starlette (since FastAPI is built on Starlette, middleware logic is managed by Starlette) and implement the logic for logging and time statistics in the dispatch method.

1. Define the Middleware Class

from fastapi import FastAPI
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
import time

class LoggingMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        # Record the start timestamp of the request
        start_time = time.time()

        # Call the next middleware or view function to get the response
        response = await call_next(request)

        # Calculate response time (end time - start time)
        duration = time.time() - start_time

        # Construct log information
        log_info = (
            f"[Request Log] Method: {request.method} | "
            f"Path: {request.url.path} | "
            f"Client IP: {request.client.host} | "
            f"Duration: {duration:.4f}s | "
            f"Status Code: {response.status_code}"
        )

        # Print log (replace with logging module to output to file in production)
        print(log_info)

        return response

2. Explain Middleware Logic

  • Inherit BaseHTTPMiddleware: This is the base class for middleware provided by Starlette, encapsulating the basic processing flow. You only need to implement the dispatch method.
  • dispatch Method: The core method of the middleware, which takes two parameters:
  • request: The current request object (contains method, path, client info, etc.).
  • call_next: An async function to call the next middleware or view function and return the processed response.
  • Time Statistics: Record the start time before processing the request, then the end time after processing, and calculate the duration.
  • Log Construction: Combine request method, path, client IP, duration, and response status code into a single log for easy debugging.

4. Add Middleware to the FastAPI Application

After creating the FastAPI app, add the middleware using the add_middleware method:

# Create a FastAPI app instance
app = FastAPI()

# Add the middleware (applies globally)
app.add_middleware(LoggingMiddleware)

5. Test the Middleware Effect

Define a few simple route functions to test if the middleware correctly records logs and response times.

1. Define Test Routes

@app.get("/")
async def root():
    return {"message": "Hello, FastAPI!"}

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    return {"item_id": item_id, "item_name": f"Item {item_id}"}

@app.post("/users")
async def create_user(name: str):
    return {"user": {"name": name, "status": "created"}}

2. Run and Test the Application

Start the app using Uvicorn:

uvicorn main:app --reload

Then visit different endpoints (e.g., http://localhost:8000/, http://localhost:8000/items/123, http://localhost:8000/users?name=test) and check the console for log output.

3. Example Log Output

After accessing http://localhost:8000/, you might see output like:

[Request Log] Method: GET | Path: / | Client IP: 127.0.0.1 | Duration: 0.0002s | Status Code: 200

6. Extensions and Optimizations

The basic implementation above can be extended based on your needs:

  1. Use the logging Module: Replace print with Python’s logging module to output logs to files with different levels (INFO/WARNING/ERROR).
  2. Log Request Body: For POST/PUT requests, use await request.json() to capture request data (remember to sanitize sensitive information).
  3. Error Handling: Add exception catching in the middleware to log error stacks.
  4. CORS Support: Include the CORS middleware if cross-origin resource sharing is needed.

7. Conclusion

Through this article, you’ve learned to use FastAPI middleware to centrally handle request logging and response time statistics, avoiding repetitive code and improving maintainability. Middleware is a powerful tool for managing request/response logic in FastAPI (and Starlette). You can further encapsulate common logic (e.g., authentication, CORS) into middleware to make API development more efficient and standardized.

Xiaoye