Building a Request Logging Middleware in Python
Web applications often need to perform actions on incoming requests before they reach the main application logic, or on outgoing responses before they are sent back to the client. This is the core concept of middleware. This challenge asks you to create a simple request logging middleware for a Python web application framework. This middleware will log details about each incoming request, which is a common and essential task for debugging and monitoring.
Problem Description
Your task is to implement a middleware function in Python that intercepts incoming HTTP requests. This middleware should log specific details about each request to the console before the request is processed by the main application.
Requirements:
- Create a Middleware Function: Design a Python function that acts as middleware. This function should accept a callable (representing the next handler in the chain) and return a new callable that will be executed for each request.
- Log Request Details: When the middleware is invoked, it must log the following information about the incoming request:
- The HTTP method (e.g., GET, POST).
- The request path (e.g.,
/users,/items/123). - Any query parameters present in the request.
- Pass Control: After logging, the middleware must call the next handler in the chain, passing along the original request context.
- Handle Different Request Types: The middleware should be able to handle requests with and without query parameters.
- Return Response: The middleware should return whatever response is generated by the subsequent handler in the chain.
Expected Behavior:
When a request is made to an application protected by this middleware, the middleware will first print the request method, path, and query parameters to standard output. Then, it will allow the request to proceed to the actual application logic.
Edge Cases:
- Requests with no query parameters.
- Requests with multiple query parameters.
- Requests to the root path (
/).
Examples
Let's assume a simplified framework where app is the main application handler, and middleware(app) returns a new handler that includes the middleware's logic.
Example 1: Simple GET Request
# --- Conceptual Application Setup ---
def main_app(environ, start_response):
status = '200 OK'
headers = [('Content-type', 'text/plain')]
start_response(status, headers)
return [b"Hello, World!"]
# Assume 'request_logging_middleware' is your implemented middleware function
logged_app = request_logging_middleware(main_app)
# --- Simulating a WSGI-like request ---
# A simplified WSGI environ dictionary
environ_get = {
'REQUEST_METHOD': 'GET',
'PATH_INFO': '/greet',
'QUERY_STRING': '', # No query string
'wsgi.input': b''
}
# Call the middleware-wrapped app
# (This would typically be handled by a WSGI server)
# For demonstration, we'll simulate the call flow:
# logged_app(environ_get, lambda status, headers: print(f"Response Status: {status}, Headers: {headers}"))
# Expected Console Output:
# Request: GET /greet
# Query Parameters: {}
# --- Output from the simulated response (not directly from middleware logging) ---
# Response Status: 200 OK, Headers: [('Content-type', 'text/plain')]
Explanation:
The GET request to /greet has no query parameters. The middleware logs the method and path. It then calls the main_app, which returns a standard "Hello, World!" response.
Example 2: GET Request with Query Parameters
# --- Conceptual Application Setup (same as Example 1) ---
def main_app(environ, start_response):
status = '200 OK'
headers = [('Content-type', 'application/json')]
start_response(status, headers)
return [b'{"message": "Data retrieved"}']
logged_app = request_logging_middleware(main_app)
# --- Simulating a WSGI-like request ---
environ_get_params = {
'REQUEST_METHOD': 'GET',
'PATH_INFO': '/items',
'QUERY_STRING': 'category=electronics&sort=price_asc', # Query string
'wsgi.input': b''
}
# Call the middleware-wrapped app
# logged_app(environ_get_params, lambda status, headers: print(f"Response Status: {status}, Headers: {headers}"))
# Expected Console Output:
# Request: GET /items
# Query Parameters: {'category': 'electronics', 'sort': 'price_asc'}
# --- Output from the simulated response ---
# Response Status: 200 OK, Headers: [('Content-type', 'application/json')]
Explanation:
The GET request to /items includes query parameters category=electronics and sort=price_asc. The middleware correctly parses and logs these parameters.
Example 3: POST Request
# --- Conceptual Application Setup ---
def main_app_post(environ, start_response):
status = '201 Created'
headers = [('Content-type', 'application/json')]
start_response(status, headers)
return [b'{"status": "created"}']
logged_app_post = request_logging_middleware(main_app_post)
# --- Simulating a WSGI-like request ---
# For POST requests, wsgi.input would contain the body, but our middleware doesn't log it.
environ_post = {
'REQUEST_METHOD': 'POST',
'PATH_INFO': '/users',
'QUERY_STRING': '',
'wsgi.input': b'{"name": "Alice", "email": "alice@example.com"}'
}
# Call the middleware-wrapped app
# logged_app_post(environ_post, lambda status, headers: print(f"Response Status: {status}, Headers: {headers}"))
# Expected Console Output:
# Request: POST /users
# Query Parameters: {}
# --- Output from the simulated response ---
# Response Status: 201 Created, Headers: [('Content-type', 'application/json')]
Explanation:
A POST request to /users is handled. The middleware logs the method and path, and correctly identifies that there are no query parameters.
Constraints
- The middleware should be implemented as a Python function.
- The middleware should adhere to the basic WSGI (Web Server Gateway Interface) callable signature:
middleware_function(application) -> new_application_callable. The returned callable should have the signature(environ, start_response). - The logging output should be to
sys.stdout. - Parsing of query strings should be robust.
Notes
- Think about how to parse the
QUERY_STRINGfrom theenvirondictionary. Theurllib.parsemodule might be useful. - The
environdictionary is a standard WSGI way to pass request information. You'll need to access keys likeREQUEST_METHOD,PATH_INFO, andQUERY_STRING. - The
start_responsecallable is provided by the WSGI server and is used by your application (or middleware if it needs to set headers/status before calling the next handler) to set the HTTP status and headers. Your middleware should pass this callable along to the next application in the chain. - For this challenge, you don't need to implement a full WSGI server; you just need to write the middleware function itself and understand how it would be integrated. The examples simulate the interaction.