River
Y CombinatorBacked by Y Combinator

Software & TechnicalFree

Incident Timeline Reconstruction From Logs

Every event on one clock, each row saying what its timestamp measured, and the windows the record can only bound left bounded.

Start here

River's timeline tool takes the material one source at a time and returns a single ordered record. Log excerpts, alert notifications, deploy entries and the incident channel all go in. Each row lands on one clock and carries three things: the source it was read from, the raw timestamp as that source wrote it, and what that timestamp actually measured. Where the record can only place an event inside a window, the row holds the window rather than a false minute.

The results for this search are about aggregation. Ship everything to one place, filter by time range, read down the page. That solves collection and leaves the actual problem untouched, because the timestamps you just collected do not mean the same thing. One is when a request failed, one is when a collector shipped the line, one is when a rule finished evaluating, and sorting them together produces an order that looks authoritative and is wrong.

Built for whoever has to stand behind the sequence: the incident commander writing it up, the engineer defending a root cause, the manager answering what we knew and when. Reach for it before the writeup, because the timeline is the input to everything else that gets written. The postmortem built from the incident record takes it from there, and the runbook built from resolution history turns the same material into what the next on-call does.

The clocks agree. The timestamps do not.

Machine clocks are not the problem. RFC 5905, the NTPv4 specification, puts the protocol's potential accuracy at tens of microseconds on fast networks. The minutes of disagreement in a merged timeline come from somewhere else. OpenTelemetry's log data model makes that explicit by carrying two fields per record: Timestamp, the time the event occurred measured by the origin clock, and ObservedTimestamp, the time the collection system saw it. Timestamp is optional, and the spec's advice for a system with one field is to fall back to the observation time.

The alert row is the one people trust most, and it is an evaluation time. Prometheus's alerting rules documentation describes the for clause as a wait between first encountering the condition and counting the alert as firing, re-checking at every evaluation in between. An alert carrying a ten minute for-duration therefore reached the pager at least ten minutes after the condition first held, and the pending period never left the server. Subtract it, or the first row of your timeline is late by design.

Sorting is where a careful merge still fails. RFC 3339 section 5.1 says string-sorted timestamps come back in time order only when every one shares a zone, is written the same way, and carries the same number of fractional-second digits. A four-tool export satisfies none of that. Section 4.3 supplies the trap underneath: an offset of -00:00 means the local offset is unknown and differs semantically from Z, so the one honest row is the row a parser most confidently misplaces.

How it works

  1. Paste each source

    Drop in the log excerpts, alert notifications, deploy entries and the channel transcript.

  2. River reads the clocks

    Each source examined for what it measured, what zone it wrote, and how late it is.

  3. Read the ordered record

    The timeline sheet with a source on every row, and the sequence with its gaps named.

  4. Close a gap

    It names the query or the person that would settle each window, then rebuilds around the answer.

What you get

  • One clock for every row, with the raw timestamp kept beside the converted one
  • Each row labelled by what its timestamp measured: occurred, observed, evaluated or written
  • Alert times corrected backwards through the for-duration and the evaluation interval that hid them
  • Events the record can only bound written as a window rather than a minute
  • The gaps named with their length, and what would close each one
  • The causal chain marked where it rests on inference rather than a recorded event

Common questions

What does it need from me?

One source at a time, in whatever shape it exports. Log excerpts, an alert or incident export, deploy records and the channel transcript are the four that matter. Name the zone each tool reports in where you know it. Where you do not, it reads the offsets off the data and tells you which source it could not place.

How does it handle sources whose clocks disagree?

It does not average them. Each source is placed using its own timestamp semantics, so an event known only from a collector-shipped log line is bounded by the batch interval rather than pinned to the second. Where two sources describe the same event at different times, both stay on the row and the skew is stated.

What counts as a gap?

A stretch with no alert, no log line, no deploy entry and no message. It appears as a gap with its length rather than as a smoothed sequence, because a causal claim spanning an unrecorded window is a guess. Each one arrives with the specific query, log or person that would close it.

Can it settle when impact actually began?

That is the question it is built for, and the answer is usually earlier than the page. An alert with a ten minute for-duration reached someone at least ten minutes late, the first customer message frequently precedes it, and both of those move the start of impact. You get the bound and what produced it.

How is this different from writing the postmortem?

Scope. This produces the ordered record and the causal chain, then stops. The postmortem built from the alert record writes the document around a timeline with impact, action items and the failure path. Run this first when three tools disagree, and the postmortem report writer when the sequence is already settled.

Does it hold up for an audit or a security review?

Provenance is why it does. Every row names the source it was read from and the raw timestamp as that source wrote it, so a disputed minute is settled by opening one record rather than by argument. Rows resting on inference are marked inferred, which is the distinction a reviewer asks about first.

What if a source only gives me one timestamp?

Then the row is labelled for what that one timestamp is. OpenTelemetry's own recommendation for a system with a single field is to fall back to the observation time, so a single-timestamp export often holds when a collector saw the line. That row gets bounded by the collection interval rather than trusted to the second.

Incident Timeline Reconstruction From Logs

Fill in the form and your workspace opens with the work already underway.