Log the Right Things Before You Need Them
Logs Exist, But They Don't Explain Anything
The biggest time sink in bug investigations usually isn't "we can't reproduce it" — it's "we have logs, but they don't tell us anything useful." Access logs exist, but you can't tell which user or which action triggered the issue. Error logs exist, but there's no trace of what happened right before the failure. This isn't a debugging-skill problem. In most cases, it's the result of deferring log design — deciding what to log, at what granularity, in what format — until after the feature was already built.
Logging isn't something you add once implementation is "done." It has to be designed alongside the implementation itself.
A Minimal Field Set for Structured Logs
The starting point is treating logs as structured key-value data, not free-text output. At minimum, emit these five fields consistently, under the same key names, every time:
- request_id — a unique ID per request, carried through from the edge to the database
- user_id (or session_id) — identifies who triggered the action
- event — a fixed string identifying the processing stage (e.g.
payment.charge.started) - status — outcome, such as success / failure
- duration_ms — how long the operation took
In Rails, a simple setup is to issue a request_id in middleware at the entry point, then pass a hash into Rails.logger.info:
Rails.logger.info({
event: "payment.charge.started",
request_id: request.request_id,
user_id: current_user&.id,
order_id: order.id,
status: "started"
}.to_json)
# on completion
Rails.logger.info({
event: "payment.charge.completed",
request_id: request.request_id,
order_id: order.id,
status: charge.success? ? "success" : "failure",
duration_ms: (Time.current - started_at) * 1000
}.to_json)On the frontend, pull the same request_id from the response header around each API call and include it in console logs or your error collector, so client-side and server-side logs can be cross-referenced later.
Three Decisions to Make at Implementation Time
- Which events to log — before/after external API calls, state transitions (pending → completed), and branch points in authorization checks. Anything that crosses a boundary belongs in the log
- Granularity — assign clear roles: INFO for normal-path traces, WARN for retryable anomalies, ERROR for anomalies that need a human. Don't mix them
- Format — agree on a shared key dictionary (
event,request_id, etc.) across the team. If one engineer usesreqIdand another usesrequest_id, searching logs breaks down
What Happens When You Log Too Little — Or Too Much
Too little
In one case, requests to an external payment API occasionally timed out, but the timeout itself was never logged. All the team had was a user report saying "checkout just hangs," with no lead to narrow down the reproduction conditions — it took days to track down. Applying the boundary rule (log around external API calls) from the start would have caught this immediately.
Too much
In the opposite direction, a team optimizing for debugging convenience logged full request bodies as-is, which meant email addresses and names entered into forms ended up sitting in the logging backend. Because logs are highly searchable and retained long-term, including personal data creates access-control and retention obligations you now have to manage. What to log should be judged not only by "is this useful for debugging" but also "what happens if this leaks."
An Implementation Checklist
- Is request_id issued at the entry point and carried through into async jobs and retries?
- Are external API calls, state transitions, and authorization checks logged before and after?
- Are personal data and credentials (passwords, tokens, full card numbers) excluded from log output?
- Do ERROR logs include enough context (what happened, which ID, what to do next) to act on?
- Do log key names match conventions used elsewhere in the codebase?
These items translate directly into a code review checklist. Asking "will this log let future-me trace the cause three months from now" alongside the usual feature review has an outsized effect on investigation time.
Putting This Into Practice With Bugoon
When someone files a bug through Bugoon's widget, it captures a screenshot with annotations plus the interaction steps leading up to the report. In cases where an issue looks like a UI bug but was actually caused by an API error moments earlier, having the interaction history and error context bundled into a single report cuts down the time spent hunting for logs separately.
Being able to pass an identifier like the request_id discussed here along with a bug report could make it easier to cross-reference server-side structured logs with the report during the same investigation. That capability isn't built today, but structured logging itself — done at implementation time, regardless of how issues eventually get reported — is what makes any of these investigations faster to begin with.
Streamline bug reporting for your team.
Bugoon is free to get started. Add one line of code to your site and transform how your team handles bugs.
Get Started