20.4. Request Logging
Table of Contents
Why Request Logging Matters
When a real user calls your API, many things can go wrong. A request might be slow, might fail with a 500 error, or might be abused by a bot. Without request logs, you only know that “something is wrong.” With good request logging, you know who called what, when, how, and what happened.
Request logging focuses on recording information about each incoming HTTP request and its corresponding response. It is one of the main tools for:
- Debugging problems that happen in production.
- Understanding how users interact with your API.
- Detecting abuse or suspicious traffic.
- Measuring performance such as latency and error rates.
Key rule: Every production backend should log every HTTP request with at least:
- Time
- Method
- Path
- Status code
- Duration
- Some kind of correlation or request ID
What To Log for Each Request
You do not need to log everything, but you should log enough to reconstruct what happened.
Minimal Request Log Fields
A very typical minimal log entry for a single HTTP request:
2026-08-28T10:15:30Z INFO request_id=5f3c9d
method=GET path=/api/users/42
status=200 duration_ms=23 ip=203.0.113.5 user_id=42
user_agent="Mozilla/5.0 ..."Useful fields:
| Field | Why it matters |
|---|---|
| Timestamp | When did the request happen. |
| HTTP method | Shows intent (GET, POST, etc). |
| Path / route | Which endpoint was hit. |
| Status code | Success, client error, server error. |
| Duration | Performance measurement and bottleneck detection. |
| Request ID | Correlate logs from the same request across services. |
| Client IP | Abuse detection, rate limiting analysis, geo info. |
| User ID | See which user triggered the action. |
| User-Agent | Browser, app, or client type. |
In a JSON log, the same record might look like:
{
"ts": "2026-08-28T10:15:30Z",
"level": "info",
"event": "http_request",
"request_id": "5f3c9d",
"method": "GET",
"path": "/api/users/42",
"status": 200,
"duration_ms": 23,
"ip": "203.0.113.5",
"user_id": 42,
"user_agent": "Mozilla/5.0"
}JSON makes it much easier for log tools to filter and aggregate.
Additional Optional Fields
Depending on your needs, you might also log:
- Query string, for example
?page=2&limit=10. - Route name, for example
"get_user". - Request size and response size in bytes.
- Referrer header, to see which page sent the request.
- Backend service or instance name.
You should be careful with anything that could contain sensitive data. This is covered further below.
Correlation IDs and Traceability
When an HTTP request enters your system, it might flow through:
- Load balancer.
- API gateway or reverse proxy.
- Application server.
- Multiple internal services.
- Database queries and background jobs.
If each component logs separately, you need a way to connect the logs that belong to the same original request. That is where correlation IDs come in.
Request IDs
A request ID is a unique identifier associated with one HTTP request.
Workflow:
- Client sends a request.
- Your system checks if the request has a header like
X-Request-ID. - If yes, reuse it.
- If no, generate a new random ID, for example
b5e4f1f2-38fb-4f7d-9c5f-9b1e1e3a9d7a. - Attach this ID to:
- Your log context.
- The response header, for example
X-Request-ID: b5e4f1....
Example log line:
ts=... level=info request_id=b5e4f1... method=POST path=/orders status=201 ...
When a user reports “My POST /orders at 12:05 failed,” you can ask for the response header X-Request-ID and search for that ID in your logs.
Important rule: Always include a request ID or correlation ID in every request and add it to every log line produced while that request is handled.
Distributed Tracing Headers
In more complex systems you might use standardized tracing headers, such as:
traceparent/tracestate(W3C trace context).X-B3-TraceId,X-B3-SpanId(Zipkin / B3).
These allow external tracing systems to draw a timeline of a request across services. For this chapter, you only need to understand that they are more advanced versions of correlation IDs that can represent both entire traces and single spans.
Structured Request Logs vs Plain Text
You can log requests as plain human-readable text or as structured data like JSON.
Plain Text Example
2026-08-28 10:15:30 INFO GET /api/users/42 200 23ms ip=203.0.113.5 user_id=42Pros:
- Easy to read manually.
- Simple to implement.
Cons:
- Harder for tools to parse reliably.
- Filtering and aggregation rely on regex and string parsing.
Structured JSON Example
{
"ts": "2026-08-28T10:15:30Z",
"level": "info",
"event": "http_request",
"method": "GET",
"path": "/api/users/42",
"status": 200,
"duration_ms": 23,
"ip": "203.0.113.5",
"user_id": 42
}Pros:
- Very easy for log systems to parse and index.
- Allows complex queries, such as “count requests where status >= 500 grouped by path.”
- Works better with dashboards and alerting.
Cons:
- Slightly harder to read as raw text, though tools usually format it nicely.
- Requires a logging library that supports structured logging.
For production backends, structure is extremely helpful. Many teams use more human-friendly logs in local development, but JSON in production.
Avoiding Sensitive Data in Request Logs
Request logs must be useful, but they must also be safe. It is easy to accidentally log:
- Passwords.
- Authentication tokens.
- Session IDs.
- Personal user data such as email, phone, address.
- Credit card information.
If you log full request bodies or full headers blindly, you will almost certainly leak something sensitive.
Common Dangerous Patterns
Some examples of what not to log:
POST /login body={"email":"user@example.com","password":"mysecret"}
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Better:
POST /login body={"email":"user@example.com","password":"***"}
Authorization: Bearer ***redacted***Or even safer for high-risk operations:
POST /login body=***redacted***
headers=***sensitive***Never log:
- Plaintext passwords.
- Raw authentication tokens.
- Complete credit card numbers or CVV.
Instead, you can log:
- The type of token, for example
"auth_type": "bearer". - Last 4 digits of a card number, for example
"card_last4": "1234". - That a secret exists, for example
"has_password": true.
Whitelisting vs Blacklisting
To control what is logged, two common strategies are:
- Blacklist: log most of the data but mask certain fields by name, for example
"password","token","secret". - Whitelist: only log pre-approved fields, for example
"method","path","status","duration_ms".
Whitelisting is safer but less flexible. Many production systems use a combination: structured whitelisted request logs and separate, more detailed logs for debugging, with extra safeguards.
Performance and Log Volume
Logging every request adds some cost. Each log line:
- Consumes CPU to format.
- Writes data to disk or over the network.
- Uses storage in your logging system.
For a high traffic API, this can be millions of requests per day. You need strategies to control volume without losing important information.
Summarizing vs Sampling
Two common approaches:
Summarized Metrics
You might use metrics (covered in another chapter) to count:
- Total requests by status code.
- Average / p95 / p99 latency by endpoint.
This is not a replacement for request logs, but it reduces the need to inspect every single log.
Sampling
Instead of logging every request, you could log:
- All errors and slow requests.
- Only a fraction of successful requests, for example 1% of HTTP 200 responses.
Example logic:
if status >= 500:
log_request()
elif duration_ms > 1000:
log_request()
elif random.random() < 0.01:
log_request()This keeps enough data to understand errors and performance issues while keeping volume manageable.
Rule of thumb: Always log all 5xx errors and very slow requests, even if you sample normal traffic.
Log Levels for Requests
You can use different log levels for different outcomes:
| Outcome | Typical log level |
|---|---|
| Normal successful requests | INFO or DEBUG |
| Client errors (4xx) | INFO or WARN |
| Server errors (5xx) | ERROR |
| Security-related anomalies | WARN or ERROR |
For example:
INFO request_id=... method=GET path=/items status=200 duration_ms=18
WARN request_id=... method=POST path=/login status=401 reason=invalid_credentials
ERROR request_id=... method=GET path=/orders status=500 error="DBTimeout"This allows you to highlight serious problems more clearly and filter by level.
Implementing Request Logging in a Typical Web Stack
Exact implementation details depend on the framework and language, but the pattern is similar.
Request Logging with Middleware
Common strategy:
- Create middleware that runs before and after each request.
- Before the request:
- Note the start time.
- Generate or extract the request ID.
- Add it to the logging context.
- After the response:
- Compute the duration.
- Gather path, method, status code, user ID, IP.
- Log one structured entry.
- Add the request ID to the response headers.
Simplified pseudocode:
async def request_logging_middleware(request, call_next):
start = now()
request_id = get_or_create_request_id(request)
set_log_context(request_id=request_id)
try:
response = await call_next(request)
except Exception as exc:
response = make_500_response()
log_error("unhandled_exception", exc=exc)
duration_ms = elapsed_ms(start)
log_info(
"http_request",
request_id=request_id,
method=request.method,
path=request.url.path,
status=response.status_code,
duration_ms=duration_ms,
ip=get_client_ip(request),
user_id=get_user_id_or_none(request),
)
response.headers["X-Request-ID"] = request_id
return responseYou do not need to know this middleware syntax in detail yet. The idea is that there is central code that logs every request in a consistent way.
Multi Layer Logging
Request logging can happen at different layers:
- Reverse proxy or load balancer, for example Nginx access logs.
- Application server, for example Gunicorn or Uvicorn access logs.
- Your application code, which knows user IDs and business context.
You can use all three:
- Nginx logs: raw HTTP traffic and client IPs.
- Application server logs: basic performance metrics.
- App-level logs: user and domain-specific information.
It is common to send all of them to a central logging system where you can search and correlate by timestamp and request ID.
Using Request Logs in Practice
Once you have good request logs, you can use them for many tasks.
Debugging a Single Problem
Example: A user says “My request to /orders/123 just failed.”
Steps:
- Ask for the approximate time and their request ID from the
X-Request-IDresponse header. - Search your logs for
request_id=<that id>. - Inspect the request log entry:
- Path and method: confirm they hit the right endpoint.
- Status code: see if it was 4xx or 5xx.
- Duration: see if it was a timeout or very slow.
- Filter surrounding logs with the same request ID to see internal error logs from that request.
This is far more effective than guessing based on timestamps alone.
Finding Performance Problems
Example: You run a query against your logs:
- Count number of requests by path.
- Compute average and p95
duration_msfor each path. - Filter where
duration_ms > 1000.
You might discover:
/api/orders avg=250ms p95=700ms
/api/reports avg=1500ms p95=4000ms
Clearly /api/reports is slow and you can focus your optimization efforts there.
Monitoring Error Rates
You can compute:
- Error rate per minute: $\text{error\_rate} = \frac{\text{5xx requests}}{\text{total requests}}$.
- Client error rate: $\frac{\text{4xx requests}}{\text{total requests}}$.
If error rates spike, your alerting system can notify you. Request logs are the raw data that feeds such alerts.
Simple formula:
$$\text{error\_rate\_percent} = \frac{\text{5xx\_count}}{\text{total\_count}} \times 100$$
Keep this value low in production. A sudden increase is a red flag.
Designing a Request Logging Strategy
To integrate request logging into your backend, answer these questions:
- What fields will I log for each request?
Define a minimal, consistent set, for example: timestamp, method, path, status, duration, request ID, IP, user ID. - How will I avoid sensitive data?
Decide whether to log query strings, request headers, or bodies, and how to redact or omit sensitive fields. - Will I use structured logs?
For production, prefer JSON or another structured format. - Where will the logs go?
File, stdout (for Docker), or a log collector. This is usually combined with a centralized logging solution. - How will I control volume?
Possibly sample successful requests, always log errors and slow requests, and set appropriate log levels. - How will I correlate logs across services?
Introduce a request ID header and make sure every service passes it along and logs it.
With clear answers to these, your request logging will be a powerful tool instead of random text prints in your code.
Views: 15
KAHIBARO