Application Logs That Help You Debug Production Issues
Write useful production logs with structured events, consistent levels, request correlation, and deliberate sensitive-data handling.
Application Logs That Help You Debug Production Issues
A log line saying something went wrong confirms the problem you already know about. A useful log helps answer the next questions: which operation failed, what stage it reached, whether the failure affected one request or many, and what evidence is safe to inspect.
Good production logging starts with those questions. Adding more output without deciding what each event explains usually creates a larger search problem.
Describe events rather than narrating code
Messages such as started function or reached here are temporary debugging notes. Production events should describe something meaningful to the application: an export completed, a dependency timed out, or a request was rejected.
Choose stable event names and keep variable details in separate fields. That lets someone search for the event without guessing how a sentence was assembled.
{
"timestamp": "2026-10-29T10:15:00.000Z",
"level": "error",
"event": "export.failed",
"service": "report-worker",
"environment": "production",
"requestId": "req-demo-123",
"jobId": "job-demo-456",
"stage": "upload-result",
"durationMs": 1842,
"errorCode": "STORAGE_TIMEOUT",
"retryable": true
}
This example uses synthetic identifiers. It explains the failed operation without dumping a user record or the uploaded file.
Make log levels a shared convention
Use debug for detail needed during investigation, info for meaningful normal outcomes, warn for unexpected conditions the system can currently handle, and error for operations that failed.
The exact policy can vary, but the team should agree on it. An ordinary invalid form submission should not automatically page someone as a production incident. A dependency failure should not disappear into a stream of informational messages either.
Record duration and outcome consistently so that a slow success can be distinguished from an immediate rejection. Keep verbose diagnostics adjustable without making every deployment produce full request dumps.
Carry an identifier across the operation
Create a request ID at the service boundary and attach it to relevant events. For background work, include the job ID and propagate correlation information through the message.
An identifier supplied by an external caller is untrusted input. Validate its format and length, or generate an internal identifier while keeping a separately validated external reference. Do not let arbitrary headers become unrestricted log fields.
Within a Node.js service, AsyncLocalStorage can help carry request context through asynchronous work. Across service boundaries, use the tracing system's supported propagation rather than expecting local context to travel over the network.
OpenTelemetry's logging model describes how logs can be correlated with trace and span context. A request ID helps local investigation; a trace gives additional structure to work distributed across services.
Capture error context intentionally
Include the operation, stage, stable error code, dependency name, and whether recovery will be attempted. Add a stack trace to an appropriately restricted diagnostic destination when it is useful and safe.
Avoid logging the same exception at every layer. A low-level helper can add context and rethrow; the layer that knows the final outcome can emit the authoritative failure event. Otherwise one failed request can appear to be several unrelated incidents.
Raw error messages and stacks can contain URLs, query values, or credentials. Review them before forwarding them to a broadly accessible log store. Do not assume a field is safe merely because it came from an exception object.
Prefer a safe field list over object dumps
Passwords, access tokens, cookies, authorization headers, database connection strings, and presigned URLs do not belong in routine logs. Request bodies can contain sensitive information even when the endpoint sounds harmless.
Use a central logger with an explicit field policy and redaction as a second layer of defense. Test nested objects and unusual error paths, not only the normal request. OWASP's logging guidance covers sensitive data, event handling, and log protection.
Structured encoding helps preserve field boundaries, but untrusted values still need length limits and safe handling in downstream viewers. Apply access controls and retention rules to the logs themselves.
Test whether the logs answer a real question
Simulate a dependency timeout and follow one operation from acceptance through failure and retry. Can you find it using the request ID? Can you identify the failing stage without opening the database? Can you tell whether a retry eventually completed?
Also inspect a successful operation and an expected rejection. Those paths reveal whether the level policy is consistent. Confirm that log delivery failures do not expose data or unexpectedly break the application.
Use metrics to notice changes in error rate or latency, traces to follow distributed execution, and logs to explain individual events. The goal is a short investigation with enough context to act, rather than a large collection of output nobody wants to read.