Short answer: The page sends one request, a time window or a line count, and lets the backend that holds the logs apply it. Docker‘s logs API and Loki‘s query_range both answer with a list of lines. Any further filter selects from that list. It does not start another query.
What the page actually sends
The browser asks for one source and one bound. The bound is a time window or a line count, never both.
A viewer on the Docker host translates the bound into the engine logs API: stdout, stderr, timestamps=1, and either since (Unix seconds) or tail. The clock is the timestamp Docker writes on the frame, so a clock written inside the message does not move the window. For a time window the call asks for one extra line. If that line arrives, the newest lines up to a cap are kept and the response says the range was cut.
A viewer reading Loki translates the same bound into query_range: a stream selector, a start, an end, and a limit, direction=backward. The container name is checked against a plain pattern (letters, digits, ., _, -) before it is placed in the selector. A time window that fills the cap is probed for one older line, and a hit marks the range as cut. A line count uses the limit alone.
Both answers are the same JSON: lines, and truncated when the cap dropped older lines.
What happens to the lines that come back
A text filter runs on those lines, including the timestamp prefix the API already attached. Docker’s logs API has no substring parameter, so the same filter cannot be a second call there. Putting it only in LogQL would make the two sources disagree.
A line the backend already dropped is gone. The text filter sees the returned window, not the store behind it.
The working path
- The request names one source and one bound: a time window or a line count.
- Docker receives
sinceortail. Loki receivesquery_rangewith the same bound. - The response is
linesand, when a time window was cut,truncated. - Anything beyond that bound selects from
lines. It does not call the backend again.
Approaches that look simpler and fail
| Approach | Why it fails if both backends have to behave the same way |
|---|---|
| Parse the message text to apply the time window | The window follows Docker’s frame time or Loki’s entry time. The text is not that clock. |
| Put the search in LogQL and leave Docker unsearched | The Docker logs API has no substring parameter. The two sources would filter differently. |
| Send the text filter back to the backend | The window is already in the response. A new request is for a new source or a new bound. |
LogQL is the right place for a label Loki indexed. It is the wrong place for a text filter that also has to work on a local logs API.
When you can drop the split
Drop the second pass when you are willing to offer only filters that one backend can apply. Drop the Docker call when the viewer no longer runs on that host. Keep the split while one source is a local logs API, another is Loki, and a text filter has to mean the same thing on both.
Why this stays published
We hit this on a homelab log page that reads a local Docker socket and a remote Loki. Send the time window or the line count to the backend that stores the lines, and run any further filter on the list that comes back.
本文由 HoHo 與 AI 協作整理,最後更新於 2026 年 10 月 4 日。
