29
14 Comments

How we learned to log bugs properly

In our previous post, we talked about a simple realization:

We weren’t slow at fixing bugs.

We were slow at understanding them.

After that, we started looking deeper into one specific question:

What does a “good” bug log actually look like?


Most bug reports fail before debugging even starts

A typical report looks like this:

  • “It’s broken”

  • “Webhook not working”

  • “App doesn’t respond”

From a user’s perspective, that’s completely reasonable.

But from a developer’s perspective, it’s missing everything we need:

  • Where did it happen?

  • What did the user do before that?

  • What failed exactly?

  • Can we reproduce it?

So the real process becomes:

1. Support asks follow-up questions

2. Devs try to guess

3. Time gets lost before any fix even begins

The problem isn’t debugging.

It’s the input we start with.


A useful bug is not a message — it’s a reconstructable event

After running into this repeatedly, we started thinking differently.

A bug report shouldn’t be something you read.

It should be something you can reconstruct.

At minimum, a useful log should answer:

  • Where did this happen? (URL / screen)

  • What failed? (request, error, response)

  • What led to it? (user actions, event sequence)

  • Under what conditions? (device, browser, network)

  • When did it happen? (timestamp)

Once you have that, the nature of debugging changes.

You’re no longer asking: “What might have happened?”

You’re asking: “Why did this specific sequence lead to failure?”


Why user-reported steps are not enough

One of the most fragile parts of debugging is reproduction.

We usually rely on:

  • users

  • or support teams to describe steps.

But by the time that happens:

  • details are forgotten

  • steps are incomplete

  • or slightly inaccurate

Even small differences can make a bug impossible to reproduce.


Reproduction should come from the system, not memory

So we stopped asking users for steps.

Instead, we derive them from the actual session.

We look at what really happened:

  • page navigation

  • clicks and interactions

  • network requests

  • state changes

From that, we reconstruct a simplified sequence of events leading up to the issue.

Not a perfect script, but usually enough to trigger the same failure again.

That turns reproduction from guesswork into something much closer to replay.


Bugs are rarely universal

Another thing we kept seeing:

The same issue doesn’t affect everyone.

Sometimes it only happens:

  • on a specific browser

  • on a specific device

  • for a specific user

  • under certain network conditions

Without that context, bugs feel random.

With it, patterns start to emerge.

That’s why environment data matters just as much as the error itself.


What we actually log now

Over time, our logs evolved into something closer to a structured issue.

For each bug, we capture:

1. The failure itself

  • request URL

  • method

  • status code

  • response body

2. Where it happened

  • page URL

  • screen / feature

3. What led to it

  • sequence of user actions

  • navigation flow

4. The environment

  • browser

  • OS

  • device type

  • network conditions

5. When it happened

  • precise timestamp

6. Reproduction context

  • a reconstructed path to trigger the issue again

At that point, a bug stops being a vague report

and becomes something you can actually work with immediately.


What changed for us

The biggest shift wasn’t logging more.

It was logging the right things.

Before:

  • we had errors

  • but no context

Now:

  • we have context

  • and the error becomes obvious

We spend less time asking: “What happened?”

And more time on: “How do we fix it?”


What we’re still figuring out

Even with all of this, it still feels incomplete.

There are still cases where:

  • everything looks technically correct

  • but the outcome is wrong for the user

No error. No exception.

Just a mismatch between what the system did and what the user expected.

Those are harder to capture.

And it raises a bigger question:

What does a truly complete bug log look like?

If you’ve run into similar cases, I’d really appreciate your perspective.

We’re still building this and learning from real-world usage — you can try it here.

posted toAvatar for product Flashlog
Flashlog
  1. 1

    This really resonates — most of the time the issue isn’t fixing bugs, it’s understanding them clearly.

    I like the idea of treating bug logs as reconstructable events instead of just messages. Curious, did implementing this system significantly reduce your debugging time?

    1. 1

      That’s a great question.

      We’ve definitely seen a meaningful reduction in debugging time — mostly because we spend far less time trying to understand what actually happened.

      Before, we often had to go back and forth with users just to gather enough context, and when multiple issues came in at once, it quickly became chaotic.

      Flashlog originally came out of that exact pain. We built it to solve our own debugging workflow first — making issues immediately understandable instead of something we had to reconstruct manually.

      It’s still evolving, but even in its current form, it’s already removed a lot of that initial “guessing” phase.

  2. 1

    does it have any impact on the response time and performance of the websites or applications for which it is running?

    1. 1

      That’s a really interesting approach to logging.

      I’m curious — does implementing this kind of detailed logging have any noticeable impact on application performance or response time, especially under high traffic?

      Also, how do you balance between capturing enough detail for debugging and avoiding performance overhead?

      1. 1

        Great questions — this is something we were very careful about from the beginning.

        Flashlog is designed to be lightweight and mostly asynchronous, so it doesn’t block the main request flow. The goal is to capture context without adding noticeable latency to user-facing operations.

        In practice, we only collect a minimal set of data during runtime and defer heavier processing (like aggregation or analysis) to the background.

        On the trade-off side, we don’t try to capture everything. We focus on signals that are most useful for debugging (errors, key events, request/response context), and allow filtering so teams can avoid unnecessary overhead or sensitive data.

        So far, the impact has been negligible in typical setups, but it’s definitely something we keep monitoring as usage scales.

  3. 1

    I feel like debugging is a new art away from the logs these days. Logs normally tell you what happened, but exactly what the state was in when this happened is still missing i feel

  4. 1

    ran into this with AI agents - user says 'it's not working', log shows a 5-step cascade. the description never tells you where it actually started.

    1. 1

      Yeah, this feels like a completely different class of problem.

      Nothing actually fails — the system technically does everything “correctly”, but the outcome doesn’t match what the user expected.

      So the issue isn’t really in the logs anymore, it’s in that gap between system behavior and user expectation.

      We’ve started noticing more of this too, especially with AI-driven flows, but honestly we’re still trying to figure out how to handle it properly.

      Feels like this needs something beyond traditional logging.

      1. 1

        yeah, "correct but wrong" is the hard one. we've had this with AI agents too - the action succeeded, the intent didn't. and there's usually no field in the log for "what the user actually wanted."

  5. 1

    The silent failure case at the end is the hardest one because there’s no signal to catch — the system did exactly what it was told, the user just expected something different. That gap lives between the spec and the mental model, not in the code. The expectation log framing from the comment above is interesting but I’d push it further the real primitive might be outcome verification rather than expectation capture. Instead of asking what the user thought would happen, verify whether the intended outcome actually occurred. Did the form submission result in a record? Did the payment result in access? Silent failures often have a detectable downstream consequence even when the upstream looks clean. That’s a harder instrumentation problem but probably more reliable than trying to capture user expectations in real time.

    1. 2

      This is a really solid direction.

      Outcome verification feels much more reliable than trying to capture user expectations.

      We’re seeing the same pattern — everything looks correct upstream, but the intended result doesn’t happen.

      Moving toward verifying outcomes (like whether a record was actually created or access was granted) is something we’re starting to explore as the next step.

      1. 1

        The “everything looks correct upstream” problem is the hardest one to debug because your instrumentation is telling you the truth it’s just telling you the wrong truth. Capturing what was supposed to happen and whether it actually happened are two completely different questions and most logging only answers the first one.

        Outcome verification as a primitive feels like it changes the mental model entirely. You’re not asking “did the code run” you’re asking “did the world change the way it was supposed to.”

        I’ve been thinking about something adjacent with ReleaseLog whether a user saw an update isn’t the same question as whether it changed their behaviour. Same gap, different layer.

        Curious how you’re handling the cases where outcome verification itself is ambiguous like access was granted but the user still can’t do the thing?

  6. 1

    Felt this one. At CX Genie we had the exact same loop: user reports "it's broken" -> CS asks 3 follow-ups -> user goes silent -> product team guesses. Days lost before a single line of code gets touched.

    The reframe from "bug report" to "reconstructable event" is the right one. Reproduction shouldn't depend on user memory.

    For your open question - the silent-failure cases where nothing errors but the outcome is wrong - I suspect those need a different primitive entirely. Not error logs, but expectation logs: what did the user think would happen vs. what did. Hard to capture passively, but probably where the next frontier is.

    1. 1

      Yeah, exactly a few other people have pointed out something very similar, especially around the idea of “expectation vs outcome”.

      This is a great way to frame it.

      Right now, Flashlog is still very much focused on what you described as Layer 1 capturing concrete failures like JS errors, failed API calls, or broken flows, and tying them back to real user sessions so they’re actually debuggable.

      But we’re starting to run into exactly the Layer 2 problem you mentioned, where everything “works” technically, but the outcome is still wrong from the user’s perspective.

      What we’re exploring next is moving beyond just error capture into session-level understanding looking at sequences of events and user actions to detect when something goes off track, even if no exception is thrown.

      That likely means capturing more of the decision layer (especially for AI-driven flows), not just application state.

      Still early for us, but I agree this is where things get much more interesting and also much harder.