Files
6krrt/tests/test_logs.py
adlee-was-taken 9657334b5e feat: one line per request saying what was decided, and why
The machinery landed last commit; these are the events. What a routed request
now leaves behind, at info:

    route id=r9116d9 cat=coding_refactor tier=2 ctx=500 src=classifier
      tools=0 latency=interactive cand=8 pick=deepseek-v4-flash
      est_usd=0.00016296 prof=1 ms=1868
    dispatch id=r9116d9 model=deepseek-v4-flash rid=chatcmpl-... sess=c090d751
      p_tok=59511 c_tok=415 kwh=4.8e-05 usd=0.000783 verdict=unverifiable
      upstream_ms=3980 total_ms=5692

`rid` and `sess` are columns in energy_observations, so a journal line pivots
to its database row and back with a --grep. That is why no schema change was
needed to get traceability.

At debug it also says why, which is the half that was never recorded anywhere:

    classify id=r9116d9 cat=coding_refactor tier=2 ctx=500 confidence=0.95 ms=1863
    filter id=r9116d9 model=gemma-4-31b reason=tier(1<2)
    filter id=r9116d9 model=glm-5.2-short reason=access_level(preview)
    filter id=r9116d9 model=kimi-k3-flex reason=latency_class(flex)
    rank id=r9116d9 pos=0 model=deepseek-v4-flash prof=1 est_usd=0.00016296

Those reasons come from routing.rejection_reason, which is is_eligible turned
inside out: it returns the failing filter and its numbers instead of False, and
is_eligible is now a one-line wrapper over it. One copy of the rules, so the
log cannot drift from the decision it describes.

log_decision is called by the ENDPOINTS, not by route(). chat_completions
routes twice whenever the measured conversation exceeds the classifier's
estimate -- with opencode sending ~32K of system prompt, essentially always --
and logging inside route() would double every line for no new information.

Two things the tests caught that reasoning had not:

The streaming path could not use the ContextVar. starlette resumes the
generator through a threadpool and every next() gets a fresh COPY of the
caller's context, so a trace id set inside is gone by the finally block that
logs the dispatch line. It logged id=- until the id was carried explicitly by
logs.bind() -- on the path all agent traffic takes.

$JOURNAL_STREAM is inherited. A foreground uvicorn started from a
systemd-managed session, output redirected to a file, put a literal <7> on
every line: the variable was set, but described a stream that was no longer
ours. It is now compared against the actual fd's device:inode, which is what
systemd documents.

A test asserts no conversation text reaches the log at any level, prompt or
answer. Prompts here run 60k-150k tokens and the journal is on disk.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WSkcSD2Jzkxo1Kw27ecfXJ
2026-08-22 21:52:58 -04:00

239 lines
6.6 KiB
Python

"""Tests for the router's structured logging.
Written against the real handler and formatter rather than pytest's caplog,
because the thing worth pinning is what actually lands in the journal --
including the priority prefix, which caplog would never see.
"""
import io
import logging
import os
import pytest
import logs
@pytest.fixture(autouse=True)
def isolated_logger(monkeypatch):
"""Every test gets a fresh logger and a clean environment."""
monkeypatch.delenv(logs.LEVEL_ENV, raising=False)
monkeypatch.delenv("JOURNAL_STREAM", raising=False)
logs.set_trace(logs.NO_TRACE)
yield
for handler in list(logs.log.handlers):
logs.log.removeHandler(handler)
def _capture(level="debug", **kwargs):
buf = io.StringIO()
logs.configure(level, stream=buf, **kwargs)
return buf
# --- rendering ------------------------------------------------------------
def test_a_line_is_logfmt_with_the_trace_id_first():
buf = _capture()
logs.set_trace("r123456")
logs.info("route", cat="coding_general", tier=2)
assert buf.getvalue() == "route id=r123456 cat=coding_general tier=2\n"
def test_values_with_spaces_are_quoted():
buf = _capture()
logs.info("filter", detail="held during peak")
assert 'detail="held during peak"' in buf.getvalue()
def test_a_value_never_breaks_the_line():
"""One event, one line -- a newline in a detail string would forge a record."""
buf = _capture()
logs.info("verify", detail="line one\nline two")
assert buf.getvalue().count("\n") == 1
def test_none_is_a_dash_and_bools_are_binary():
buf = _capture()
logs.info("route", pick=None, tools=True, stream=False)
assert "pick=- tools=1 stream=0" in buf.getvalue()
def test_floats_are_readable_not_repr():
"""Microdollars and kWh, without 0.0007830000000000001."""
buf = _capture()
logs.info("dispatch", usd=0.000783123456, kwh=4.75e-05)
assert "usd=0.000783123" in buf.getvalue()
assert "kwh=4.75e-05" in buf.getvalue()
def test_an_empty_string_is_visible():
buf = _capture()
logs.info("classify", cat="")
assert 'cat=""' in buf.getvalue()
# --- levels ---------------------------------------------------------------
def test_debug_is_silent_at_info_level():
buf = _capture("info")
logs.debug("filter", model="m")
logs.info("route", pick="m")
assert "filter" not in buf.getvalue()
assert "route" in buf.getvalue()
def test_enabled_for_debug_reports_the_level():
_capture("info")
assert logs.enabled_for_debug() is False
_capture("debug")
assert logs.enabled_for_debug() is True
def test_the_env_var_overrides_the_configured_level(monkeypatch):
"""So a live service can be turned up without editing a tracked file."""
monkeypatch.setenv(logs.LEVEL_ENV, "debug")
buf = _capture("warning")
logs.debug("filter", model="m")
assert "filter" in buf.getvalue()
def test_a_nonsense_env_level_falls_back_rather_than_crashing(monkeypatch):
monkeypatch.setenv(logs.LEVEL_ENV, "loud")
assert logs.resolve_level("warning") == logging.WARNING
# --- journald -------------------------------------------------------------
def test_no_priority_prefix_in_a_terminal():
"""A foreground uvicorn should not print a literal <6> on every line."""
buf = _capture(journald=False)
logs.info("route", pick="m")
assert buf.getvalue().startswith("route ")
def test_priority_prefix_under_journald():
"""This is what makes `journalctl -p warning` mean anything."""
buf = _capture(journald=True)
logs.warning("retry", verdict="malformed")
logs.error("upstream", status=500)
logs.info("route", pick="m")
lines = buf.getvalue().splitlines()
assert lines[0].startswith("<4>") # warning
assert lines[1].startswith("<3>") # error
assert lines[2].startswith("<6>") # info
def test_journald_is_detected_by_matching_the_actual_stream(tmp_path, monkeypatch):
"""The env var alone is not enough -- it is inherited by every child.
A foreground uvicorn started from a systemd-managed session, with output
redirected to a file, inherited JOURNAL_STREAM and put a literal <7> on
every line. systemd documents comparing the value against the fd.
"""
path = tmp_path / "out.log"
with open(path, "w") as handle:
stat = os.fstat(handle.fileno())
monkeypatch.setenv("JOURNAL_STREAM", f"{stat.st_dev}:{stat.st_ino}")
assert logs.under_journald(handle) is True
# Same variable, a different stream: this is the inherited case.
monkeypatch.setenv("JOURNAL_STREAM", f"{stat.st_dev}:{stat.st_ino + 1}")
assert logs.under_journald(handle) is False
def test_no_journald_variable_means_no_prefix():
assert logs.under_journald() is False
def test_a_stream_with_no_fileno_is_not_journald():
"""StringIO under test, and anything else without an fd."""
assert logs.under_journald(io.StringIO()) is False
def test_a_malformed_journald_variable_is_ignored(monkeypatch):
monkeypatch.setenv("JOURNAL_STREAM", "not-a-device")
assert logs.under_journald() is False
# --- trace ids ------------------------------------------------------------
def test_a_trace_id_is_stable_until_a_new_one_is_started():
first = logs.new_trace()
assert logs.current_trace() == first
assert logs.new_trace() != first
def test_trace_ids_do_not_collide():
assert len({logs.new_trace() for _ in range(200)}) == 200
def test_configure_is_idempotent():
"""Re-importing or re-configuring must not double every line."""
buf = io.StringIO()
logs.configure("info", stream=buf, journald=False)
logs.configure("info", stream=buf, journald=False)
logs.info("route", pick="m")
assert buf.getvalue().count("route") == 1
def test_the_logger_does_not_propagate():
"""uvicorn configures its own logging; a root handler would double-print."""
_capture()
assert logs.log.propagate is False
# --- bound emitters -------------------------------------------------------
def test_a_bound_emitter_ignores_the_ambient_trace():
"""What the streaming path needs: an id that survives a context copy."""
buf = _capture()
bound = logs.bind("rfixed1")
logs.set_trace("rother2")
bound.info("dispatch", model="m")
assert "id=rfixed1" in buf.getvalue()
def test_bind_defaults_to_the_current_trace():
buf = _capture()
logs.set_trace("rcurrent")
logs.bind().warning("retry", verdict="malformed")
assert "id=rcurrent" in buf.getvalue()
def test_a_bound_emitter_respects_the_level():
buf = _capture("info")
logs.bind("r1").debug("filter", model="m")
assert buf.getvalue() == ""