"Something went wrong" is not enough for the team
That sentence may be appropriate for a public error message. It is a terrible incident report. When a user says a submission failed, the team needs to reconstruct what happened without asking them to repeat their whole story.
The minimum useful trail connects a user-visible result to a server operation. That usually means a request or operation ID, a timestamp, a named action, and a terminal outcome.
Log the decision, not the secret
A structured event for an application submission might look like this:
{
"operation": "application.create",
"requestId": "req_7c2f",
"actorId": "user_123",
"jobId": "job_456",
"outcome": "rejected",
"reason": "job_closed",
"durationMs": 43
}This answers the first useful questions: which operation, for whom, against what, and why did it stop? It does not dump a resume, message body, authentication token, SMTP URL, or database connection string into logs.
For an unexpected exception, log the error internally with the same request ID. Return a calm, safe message to the user that includes a support reference. Internal detail stays internal.
Track transitions as well as failures
Logs that only record errors miss many important incidents. A request can return 200 while the intended business action never happened. For critical flows, record a small sequence:
application.received
application.validated
application.persisted
notification.queuedDo not log every function call. Record the transitions that explain a user-visible outcome. If a notification is asynchronous, "queued" is a different fact from "delivered." Name it honestly.
Separate correlation from audit history
Request logs help diagnose a single attempt. An audit trail answers who changed a durable record and when. For an admin status change, store the actor, old status, new status, time, and reason if the workflow needs one. That history should survive log rotation.
The two tools solve different questions:
- "Why did this request fail?" -> correlated operational logs.
- "Who approved this application yesterday?" -> durable audit record.
Test observability by simulating a support question
Pick a failed flow and ask a teammate to find the answer using only the reference ID. If they need to search by someone's email, inspect raw payloads, or guess which of several requests is relevant, the trail is incomplete.
Good observability shortens the distance between a user saying "it didn't work" and an engineer saying exactly what happened. That is part of product quality, not an optional operations add-on.