fix(logs): keep polling reads and a channel-less account out of the warnings

The reads the frontend polls (unread counts, /api/me, the download index,
sync status, subtitle progress) are logged at DEBUG: at INFO they were ~95%
of the stored log on prod. A scheduler job failure and a playlist sync
failure carry event codes, and a Google account without a YouTube channel
has an empty playlist list instead of a warning every six hours.
This commit is contained in:
2026-09-19 04:51:31 +02:00
parent 29094f5f51
commit 3ee00fb051
6 changed files with 136 additions and 10 deletions
+32
View File
@@ -219,6 +219,14 @@ def _client() -> TestClient:
raise HTTPException(status_code=400, detail={"code": "bad_cursor"})
return {"ok": True}
@app.get("/api/messages/unread_count")
def _unread():
return {"count": 0}
@app.post("/api/messages/unread_count")
def _unread_write():
return {"ok": True}
@app.get("/boom")
def _boom():
raise RuntimeError("kaboom")
@@ -323,6 +331,30 @@ def test_the_log_consoles_own_reads_are_debug_but_its_failures_are_not(sink):
assert sink.objects()[-1]["level"] == "WARNING"
def test_a_polled_read_is_debug_a_write_to_the_same_route_is_not(sink):
# One open tab polled the unread counts, /api/me and the download index every 30 s: at INFO
# that was ~95% of the stored log on prod.
http = logging.getLogger("siftlode.http")
http.setLevel(logs.TRACE)
client = _client()
client.get("/api/messages/unread_count")
assert sink.objects()[-1]["level"] == "DEBUG"
client.post("/api/messages/unread_count")
assert sink.objects()[-1]["level"] == "INFO"
client.get("/api/videos/abc")
assert sink.objects()[-1]["level"] == "INFO"
def test_every_polled_route_is_a_route_the_app_has():
# The list names route TEMPLATES; one renamed on the router side would silently fall back to
# INFO, so the list is held to the app's own routes.
from app.main import app
# The schema, not `app.routes`: FastAPI 0.141 keeps an included router behind a wrapper there.
gets = {path for path, ops in app.openapi()["paths"].items() if "get" in ops}
assert logs._POLLED_ROUTES - gets == set()
def test_a_failing_request_is_logged_by_status_and_an_unhandled_error_is_logged_at_all(sink):
client = _client()
client.get("/api/videos/missing/deeper") # 404 from the router
+38
View File
@@ -0,0 +1,38 @@
"""A Google account without a YouTube channel: its `mine` playlists read answers 404
`channelNotFound`. That is an empty list, not a failed sync — it came back as a warning every six
hours on prod for an account that will never have a channel."""
import logging
import httpx
import pytest
from app.youtube import client as yt
def _client(monkeypatch, status: int, reason: str) -> yt.YouTubeClient:
resp = httpx.Response(
status,
json={"error": {"code": status, "errors": [{"reason": reason, "message": "prose"}]}},
request=httpx.Request("GET", f"{yt.API_BASE}/playlists"),
)
c = yt.YouTubeClient.__new__(yt.YouTubeClient) # no HTTP client: `_send` is the stub
c.db = None
monkeypatch.setattr(yt.sysconfig, "get_str", lambda db, key: "")
monkeypatch.setattr(yt.quota, "record_usage", lambda cost: None)
monkeypatch.setattr(c, "_access_token", lambda: "t")
monkeypatch.setattr(c, "_send", lambda *a, **k: resp)
return c
def test_no_channel_is_an_empty_playlist_list_and_no_warning(monkeypatch, caplog):
caplog.set_level(logging.DEBUG, logger="siftlode")
assert list(_client(monkeypatch, 404, "channelNotFound").iter_my_playlists()) == []
assert not [r for r in caplog.records if r.levelno >= logging.WARNING]
def test_any_other_failure_still_raises_and_warns(monkeypatch, caplog):
caplog.set_level(logging.DEBUG, logger="siftlode")
with pytest.raises(yt.YouTubeError):
list(_client(monkeypatch, 403, "quotaExceeded").iter_my_playlists())
assert [r for r in caplog.records if r.levelno == logging.WARNING]