From dfc29238b548447ece38ef9258556753116bc7f6 Mon Sep 17 00:00:00 2001 From: Marius Ciepluch <11855163+norandom@users.noreply.github.com> Date: Fri, 18 Sep 2026 15:50:08 +0200 Subject: [PATCH] fix(cli): make the stage log reachable, nothing configured logging (M3) The stage costs added for PHP diagnosis never appeared: nothing in this project configures logging, so the default root level of WARNING dropped every informational line and a PMPro case ran for an hour producing none. That is the same failure shape the project keeps relearning. A run that reports nothing is indistinguishable from a run with nothing to report, and it is how four PHP investigations were spent reading a later stage's timeout as the cause. Logging is now configured from OUSAST_LOG_LEVEL, quiet by default and sent to stderr when asked, so a hook stays silent while a diagnostic run says where its budget went. Verified end to end: six stage lines with durations on a real replay. 1,410 tests passed, nine skipped. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo --- src/openultrasast/cli.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/openultrasast/cli.py b/src/openultrasast/cli.py index a7e76ce..f615822 100644 --- a/src/openultrasast/cli.py +++ b/src/openultrasast/cli.py @@ -2,6 +2,7 @@ from __future__ import annotations import argparse import json +import logging import os import subprocess import sys @@ -109,6 +110,19 @@ if TYPE_CHECKING: pass +def _configure_logging() -> None: + """Send logs to stderr at the level the environment asks for; quiet by default.""" + level = os.environ.get("OUSAST_LOG_LEVEL", "").strip().upper() + if level not in {"DEBUG", "INFO", "WARNING", "ERROR"}: + return + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter("%(levelname)s %(name)s %(message)s")) + root = logging.getLogger("openultrasast") + root.handlers = [handler] + root.setLevel(getattr(logging, level)) + root.propagate = False + + def main(argv: list[str] | None = None) -> int: if (argv if argv is not None else sys.argv[1:])[:1] != ["pre-push"]: load_dotenv() @@ -224,6 +238,11 @@ def main(argv: list[str] | None = None) -> int: candidates.add_argument("--json", action="store_true") args = parser.parse_args(argv) + # Diagnosis needs the stage costs, and nothing configures logging, so the default root level of + # WARNING silently dropped every informational line. A run that reports nothing is + # indistinguishable from a run with nothing to report, which is how four PHP investigations were + # spent reading a later stage's timeout as the cause. + _configure_logging() if args.command == "pre-push": from .config import PushConfig from .push.runner import push, replay