Conventional analytics reports how long a tab existed. Loghound keeps four separate numbers and never conflates them.
| Number | Accumulates while… |
|---|---|
| Log span | Last request minus first request. What log-only tools call “time on site”. It is really the time between the first and last byte the server saw, and it is structurally blind to the final page of a visit. Present for every session. |
| Wall | The page exists. Nothing else. This is the number everyone else reports. |
| Visible | The tab is shown and the window is focused. |
| Engaged | Visible and the last interaction was less than thirty seconds ago. The honest number. |
A visitor opens your article in a background tab at 09:00, gets distracted, comes back at 09:20, reads for four minutes with regular scrolling, then switches to another window and leaves the tab open until 10:00.
Figure 1 — one visit, four numbers. The industry figure here is fifteen times the true one. That gap is not an edge case; it is what a browser with tabs does all day.
- Only monotonic clock deltas. Never wall-clock time. An NTP step, a daylight-saving change or a user correcting their clock would otherwise be silently added to somebody’s reading time.
- Exact accumulation, not sampling. Every event that can change the state — a visibility change, a blur, a focus, and every interaction — closes the current time segment before the state changes, so a segment is always attributed to the state it was really in. Engaged time is computed as the overlap of the segment with the thirty-second window following the last interaction, so the boundary is exact rather than rounded to the nearest heartbeat.
- A page that starts hidden accrues nothing. Prerendered pages and pages opened in a background tab start at zero visible time and stay there until they are actually shown.
The interactions that count as engagement: mouse movement (throttled to once a second), scrolling, key presses, clicks, pointer down, touch start and wheel. All of them are passive and throttled — the beacon must not cost jank.
Log-based analytics structurally cannot measure the final pageview of a visit: the visitor leaves and there is no further request to time against. The beacon sends:
- A heartbeat every fifteen seconds, but only while engaged time is advancing — an idle tab produces one beat, not 240 — so a tab killed by the operating system still leaves data up to the last beat.
- A final beacon send when the page is hidden and on page hide. The browser queues that request and delivers it after the document is gone.
unload is never used
It disables the back/forward cache in every modern browser, which measurably slows the site down for real visitors, and it does not fire reliably on mobile at all.
A restore from the back/forward cache starts a new pageview. The clocks reset and a fresh pageview id is generated. Carrying the old clocks over would count time spent frozen in the cache as time on site — exactly the class of lie this beacon exists to stop telling.
A zero is a value: it would enter every average, median and percentile as a genuine “0 seconds engaged” observation, and ten thousand beacon-less bot sessions would drag a site’s reported engagement to nearly nothing. Confidently wrong is worse than missing. Absent means an aggregate simply covers a smaller population — which every chart in the panel is required to state.
Each POST carries cumulative counters for its pageview rather than deltas, so the merge is the maximum per pageview and then the sum across pageviews. Summing the rows directly would multiply a ten-minute pageview by its number of heartbeats.
One deliberate limitation: visible time requires focus, so a visitor reading your page in a small window while typing in another application accrues none. That is the right trade — attention without focus is not measurable — but it means the visible figure is a lower bound.