Compare commits

...

2 Commits

Author SHA1 Message Date
Teknium
258afa6cd1 fix: use RedactingFormatter on stderr handler, update types and test mock
- stderr handler now uses RedactingFormatter to match file handlers
- restart path uses verbose=0 (int) instead of verbose=False (bool)
- test mock updated with new run_gateway(verbose, quiet, replace) signature
2026-04-01 10:56:12 -07:00
Alan Justino
765f717f91 fix(gateway): wire -v/-q flags to stderr logging
By default 'hermes gateway run' now prints WARNING+ to stderr so
connection errors and startup failures are visible in the terminal
without having to tail ~/.hermes/logs/gateway.log.

- gateway/run.py: start_gateway() accepts verbosity: Optional[int]=0.
  When not None, attaches a StreamHandler to stderr with level mapped
  from the count (0=WARNING, 1=INFO, 2+=DEBUG). Root logger level is
  also lowered when DEBUG is requested so records are not swallowed.

- hermes_cli/gateway.py: run_gateway() gains verbose: int and
  quiet: bool params. -q translates to verbosity=None (no stderr
  handler). Wired through gateway_command().

- hermes_cli/main.py: -v changed from store_true to action=count so
  -v/-vv/-vvv each increment the level. -q/--quiet added as a new flag.

Behaviour summary:
  hermes gateway run        -> WARNING+ on stderr (default)
  hermes gateway run -q     -> silent
  hermes gateway run -v     -> INFO+
  hermes gateway run -vv    -> DEBUG
2026-04-01 10:51:58 -07:00
4 changed files with 30 additions and 9 deletions

View File

@@ -6186,7 +6186,7 @@ def _start_cron_ticker(stop_event: threading.Event, adapters=None, interval: int
logger.info("Cron ticker stopped") logger.info("Cron ticker stopped")
async def start_gateway(config: Optional[GatewayConfig] = None, replace: bool = False) -> bool: async def start_gateway(config: Optional[GatewayConfig] = None, replace: bool = False, verbosity: Optional[int] = 0) -> bool:
""" """
Start the gateway and run until interrupted. Start the gateway and run until interrupted.
@@ -6288,6 +6288,21 @@ async def start_gateway(config: Optional[GatewayConfig] = None, replace: bool =
logging.getLogger().addHandler(file_handler) logging.getLogger().addHandler(file_handler)
logging.getLogger().setLevel(logging.INFO) logging.getLogger().setLevel(logging.INFO)
# Optional stderr handler — level driven by -v/-q flags on the CLI.
# verbosity=None (-q/--quiet): no stderr output
# verbosity=0 (default): WARNING and above
# verbosity=1 (-v): INFO and above
# verbosity=2+ (-vv/-vvv): DEBUG
if verbosity is not None:
_stderr_level = {0: logging.WARNING, 1: logging.INFO}.get(verbosity, logging.DEBUG)
_stderr_handler = logging.StreamHandler()
_stderr_handler.setLevel(_stderr_level)
_stderr_handler.setFormatter(RedactingFormatter('%(levelname)s %(name)s: %(message)s'))
logging.getLogger().addHandler(_stderr_handler)
# Lower root logger level if needed so DEBUG records can reach the handler
if _stderr_level < logging.getLogger().level:
logging.getLogger().setLevel(_stderr_level)
# Separate errors-only log for easy debugging # Separate errors-only log for easy debugging
error_handler = RotatingFileHandler( error_handler = RotatingFileHandler(
log_dir / 'errors.log', log_dir / 'errors.log',

View File

@@ -1092,11 +1092,12 @@ def launchd_status(deep: bool = False):
# Gateway Runner # Gateway Runner
# ============================================================================= # =============================================================================
def run_gateway(verbose: bool = False, replace: bool = False): def run_gateway(verbose: int = 0, quiet: bool = False, replace: bool = False):
"""Run the gateway in foreground. """Run the gateway in foreground.
Args: Args:
verbose: Enable verbose logging output. verbose: Stderr log verbosity count added on top of default WARNING (0=WARNING, 1=INFO, 2+=DEBUG).
quiet: Suppress all stderr log output.
replace: If True, kill any existing gateway instance before starting. replace: If True, kill any existing gateway instance before starting.
This prevents systemd restart loops when the old process This prevents systemd restart loops when the old process
hasn't fully exited yet. hasn't fully exited yet.
@@ -1115,7 +1116,8 @@ def run_gateway(verbose: bool = False, replace: bool = False):
# Exit with code 1 if gateway fails to connect any platform, # Exit with code 1 if gateway fails to connect any platform,
# so systemd Restart=on-failure will retry on transient errors # so systemd Restart=on-failure will retry on transient errors
success = asyncio.run(start_gateway(replace=replace)) verbosity = None if quiet else verbose
success = asyncio.run(start_gateway(replace=replace, verbosity=verbosity))
if not success: if not success:
sys.exit(1) sys.exit(1)
@@ -1889,9 +1891,10 @@ def gateway_command(args):
# Default to run if no subcommand # Default to run if no subcommand
if subcmd is None or subcmd == "run": if subcmd is None or subcmd == "run":
verbose = getattr(args, 'verbose', False) verbose = getattr(args, 'verbose', 0)
quiet = getattr(args, 'quiet', False)
replace = getattr(args, 'replace', False) replace = getattr(args, 'replace', False)
run_gateway(verbose, replace=replace) run_gateway(verbose, quiet=quiet, replace=replace)
return return
if subcmd == "setup": if subcmd == "setup":
@@ -2019,7 +2022,7 @@ def gateway_command(args):
# Start fresh # Start fresh
print("Starting gateway...") print("Starting gateway...")
run_gateway(verbose=False) run_gateway(verbose=0)
elif subcmd == "status": elif subcmd == "status":
deep = getattr(args, 'deep', False) deep = getattr(args, 'deep', False)

View File

@@ -3857,7 +3857,10 @@ For more help on a command:
# gateway run (default) # gateway run (default)
gateway_run = gateway_subparsers.add_parser("run", help="Run gateway in foreground") gateway_run = gateway_subparsers.add_parser("run", help="Run gateway in foreground")
gateway_run.add_argument("-v", "--verbose", action="store_true") gateway_run.add_argument("-v", "--verbose", action="count", default=0,
help="Increase stderr log verbosity (-v=INFO, -vv=DEBUG)")
gateway_run.add_argument("-q", "--quiet", action="store_true",
help="Suppress all stderr log output")
gateway_run.add_argument("--replace", action="store_true", gateway_run.add_argument("--replace", action="store_true",
help="Replace any existing gateway instance (useful for systemd)") help="Replace any existing gateway instance (useful for systemd)")

View File

@@ -271,7 +271,7 @@ class TestGatewaySystemServiceRouting:
) )
run_calls = [] run_calls = []
monkeypatch.setattr(gateway_cli, "run_gateway", lambda verbose=False, replace=False: run_calls.append((verbose, replace))) monkeypatch.setattr(gateway_cli, "run_gateway", lambda verbose=0, quiet=False, replace=False: run_calls.append((verbose, quiet, replace)))
monkeypatch.setattr(gateway_cli, "kill_gateway_processes", lambda force=False: 0) monkeypatch.setattr(gateway_cli, "kill_gateway_processes", lambda force=False: 0)
try: try: