L3 — Severity matches reality
Log level assigned matches severity: unexpected/unhandled failures at ERROR, expected/handled fallback conditions at WARN or INFO. Fail if all error paths log at the same level regardless of whether they were handled.
Python Implementation
L3: log level matches severity — unexpected/unhandled failures at ERROR, expected/handled fallback conditions at WARN or INFO. rubric_store/ definitions/logs.yaml tags this `both`; the static half is scoped to the one crisp, unambiguous signal available from syntax: whether a catch block sits inside a retry loop. A catch block inside a loop is, by construction, a *handled* failure — the code is about to try again, not give up — so logging it at ERROR is the exact "all error paths log at the same level regardless of whether they were handled" pattern the rubric calls out as a fail (this is precisely the PAY-482 example in rubric_store/validation_notes.md: "Retryable and terminal failures both logged at ERROR — no WARN for the retryable case."). A catch block outside any loop is a plausible terminal/unhandled failure, so logging it at ERROR matches severity; logging it below ERROR there is a genuinely softer signal (it might be an intentionally low-severity fallback) and gets "unclear", not a guessed fail. Only fires when a log call exists at all — whether one exists is L1's question, not this rule's.
Java Implementation
L3 for Java: log level matches severity, using the same retry-loop signal as the Python version (log_severity_rule.py) — a catch block inside a loop is a handled/retryable failure and should log at WARN/INFO, not ERROR/FATAL; a catch block outside any loop logging at ERROR/FATAL matches severity for a plausible terminal failure. Uses the fixed enclosing_loop (now covering all four of Java's loop shapes: for, while, do-while, enhanced-for) — a retry loop written as a for-each or do-while is just as much a retry loop as a plain for/while.
Go Implementation
L3 for Go: log level matches severity, same retry-loop signal as the Python/Java versions. Go's standard-library Print/Println/Printf carry no level at all (go_log_level returns None for them) and are skipped — this rule only has an opinion when a structured logger's level is explicit in the method name (Error/Warn/Info/Debug, slog- or zap-style).