◆ NFRGate / Rule Reference

L3 — Severity matches reality

medium 📝 logs · both

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).
← All rules