/logs: with since, only the newest limit records are returned, and the older ones are skipped for good #475
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#475
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo at
95348908d, reading the log endpoint and running it against a log file of 500 records.Related to #51.
Checked again on 2026-10-01 against
mainat0a5cad587: still present./logs?since=…is documented as the way a collector asks only for what it has not seen, but the view takes the newestlimitrecords first and filters bysinceafterwards. When more thanlimitrecords were written since the last poll, the collector receives the newest ones, takes the newest time as its nextsince, and the older records are never served. Nothing in the response says that anything was left out. The comparison is also a plain string comparison against whatever the caller sent, so asincewith an offset other than+00:00, or one that is not a time at all, silently selects the wrong records.Where:
core/views_logs.py:97-108(collect:records = logs.read(limit=limit, level=...), thenrecords = [record for record in records if record.time > since]);core/logs.py:267-286(readstops oncelen(found) >= limit, counting from the newest record).What happens:
sinceset to the lasttimeit holds, as the wiki's Health, metrics and logs page describes, and nolimit(so 200).Checked against the snapshot: 500 records 100 ms apart and
sincea minute before the first answered 200 lines, starting at the 301st record.since=2026-09-29T14:00:10+02:00(the 11th second of the same run, written with an offset) answered no lines at all, and so didsince=yesterday, both with200.Why it matters: the endpoint exists so that a collector off the host holds the instance's log. A burst is exactly when the log matters, and that is when the collector loses the start of it without any sign.
Fix: when
sinceis given, read backwards until reaching a record at or beforesince, then return the oldestlimitrecords after it, so a collector that polls again carries on from where it stopped rather than from the newest record. Parsesincewithdatetime.fromisoformatand compareRecord.whenagainst it (a naive value read as UTC), answering400for a value that is not a time. Say in the wiki that a full page means there is more to fetch.Done when
tests/security/test_logs_endpoint.pywrites 500 records, asks withsincebefore the first and the defaultlimit, and gets the oldest 200; asking again with the lasttimereceived gets the next 200.sincewritten with a+02:00offset and gets the records after that instant, and one withsince=yesterdaygets400.