hoodini.utils.logging_utils
Centralized logging utilities with Rich styling and log levels.
1"""Centralized logging utilities with Rich styling and log levels.""" 2 3from __future__ import annotations 4 5import logging 6from collections.abc import Callable 7from datetime import datetime 8from typing import Any 9 10from rich.console import Console 11from rich.panel import Panel 12 13_QUIET = False 14_DEBUG = False 15 16 17def _make_console(quiet: bool, debug: bool) -> Console: 18 # We add timestamps manually to messages to keep them visible during progress rendering. 19 c = Console( 20 quiet=quiet, 21 log_time=False, 22 log_path=False, 23 ) 24 return c 25 26 27console = _make_console(False, False) 28logger = logging.getLogger("hoodini") 29 30 31def configure_logging(*, quiet: bool = False, debug: bool = False) -> None: 32 """Configure console/logging flags.""" 33 global _QUIET, _DEBUG, console 34 _QUIET = quiet 35 _DEBUG = debug 36 console = _make_console(quiet, debug) 37 level = logging.DEBUG if debug else logging.INFO 38 logger.setLevel(level) 39 40 41def info(message: str) -> None: 42 if _QUIET: 43 return 44 console.print(f"{_ts()} [light_slate_grey]{message}[/light_slate_grey]") 45 46 47def success(message: str) -> None: 48 if _QUIET: 49 return 50 console.print(f"{_ts()} [green]✔ {message}[/green]") 51 52 53def warn(message: str) -> None: 54 if _QUIET: 55 return 56 console.print(f"{_ts()} [orange3]Warning:[/orange3] {message}") 57 58 59def error(message: str) -> None: 60 if _QUIET: 61 return 62 console.print(f"{_ts()} [bright_red]Error:[/bright_red] {message}") 63 64 65def debug(message: str) -> None: 66 if _QUIET or not _DEBUG: 67 return 68 console.print(f"{_ts()} [dim]{message}[/dim]") 69 70 71def is_debug_enabled() -> bool: 72 return _DEBUG and not _QUIET 73 74 75def header(title: str, subtitle: str | None = None, border_style: str = "light_slate_grey") -> None: 76 """Render a boxed header with optional subtitle.""" 77 if _QUIET: 78 return 79 text = f"[bold light_slate_grey]{title}[/bold light_slate_grey]" 80 if subtitle: 81 text += f"\n[dim]{subtitle}[/dim]" 82 console.print(Panel.fit(text, border_style=border_style)) 83 84 85def stage_header(title: str, emoji: str = "") -> None: 86 """Log a stage banner with timestamp.""" 87 header(f"{emoji} {title}" if emoji else title) 88 89 90def stage_done(message: str) -> None: 91 """Log a completion message with timestamp.""" 92 if _QUIET: 93 return 94 console.print(Panel.fit(f"[bold green]✔️ {message}[/bold green]", border_style="green")) 95 console.print("[light_slate_grey]" + "─" * 80 + "[/light_slate_grey]") 96 97 98def prompt(message: str, default: str | None = None) -> str: 99 """Prompt user input aligned with log indentation.""" 100 if _QUIET: 101 return default or "" 102 suffix = f" ({default})" if default else "" 103 prompt_text = f"{_ts()} {message}{suffix}: " 104 return console.input(prompt_text).strip() 105 106 107def run_with_spinner( 108 title: str, func: Callable[..., Any], *args, spinner_name: str = "dots", **kwargs 109): 110 """ 111 Display a Rich spinner with `title` while running `func(*args, **kwargs)`. 112 Returns whatever `func` returns. 113 """ 114 if _QUIET: 115 return func(*args, **kwargs) 116 with console.status(f"[bold cyan]{title}[/bold cyan]", spinner=spinner_name): 117 return func(*args, **kwargs) 118 119 120def _ts() -> str: 121 t = datetime.now().strftime("%H:%M:%S") 122 return f"[grey53][[/grey53][light_slate_grey]{t}[/light_slate_grey][grey53]][/grey53]"
console =
<console width=534 None>
logger =
<Logger hoodini (WARNING)>
def
configure_logging(*, quiet: bool = False, debug: bool = False) -> None:
32def configure_logging(*, quiet: bool = False, debug: bool = False) -> None: 33 """Configure console/logging flags.""" 34 global _QUIET, _DEBUG, console 35 _QUIET = quiet 36 _DEBUG = debug 37 console = _make_console(quiet, debug) 38 level = logging.DEBUG if debug else logging.INFO 39 logger.setLevel(level)
Configure console/logging flags.
def
info(message: str) -> None:
def
success(message: str) -> None:
def
warn(message: str) -> None:
def
error(message: str) -> None:
def
debug(message: str) -> None:
def
is_debug_enabled() -> bool:
def
header( title: str, subtitle: str | None = None, border_style: str = 'light_slate_grey') -> None:
76def header(title: str, subtitle: str | None = None, border_style: str = "light_slate_grey") -> None: 77 """Render a boxed header with optional subtitle.""" 78 if _QUIET: 79 return 80 text = f"[bold light_slate_grey]{title}[/bold light_slate_grey]" 81 if subtitle: 82 text += f"\n[dim]{subtitle}[/dim]" 83 console.print(Panel.fit(text, border_style=border_style))
Render a boxed header with optional subtitle.
def
stage_header(title: str, emoji: str = '') -> None:
86def stage_header(title: str, emoji: str = "") -> None: 87 """Log a stage banner with timestamp.""" 88 header(f"{emoji} {title}" if emoji else title)
Log a stage banner with timestamp.
def
stage_done(message: str) -> None:
91def stage_done(message: str) -> None: 92 """Log a completion message with timestamp.""" 93 if _QUIET: 94 return 95 console.print(Panel.fit(f"[bold green]✔️ {message}[/bold green]", border_style="green")) 96 console.print("[light_slate_grey]" + "─" * 80 + "[/light_slate_grey]")
Log a completion message with timestamp.
def
prompt(message: str, default: str | None = None) -> str:
99def prompt(message: str, default: str | None = None) -> str: 100 """Prompt user input aligned with log indentation.""" 101 if _QUIET: 102 return default or "" 103 suffix = f" ({default})" if default else "" 104 prompt_text = f"{_ts()} {message}{suffix}: " 105 return console.input(prompt_text).strip()
Prompt user input aligned with log indentation.
def
run_with_spinner( title: str, func: Callable[..., typing.Any], *args, spinner_name: str = 'dots', **kwargs):
108def run_with_spinner( 109 title: str, func: Callable[..., Any], *args, spinner_name: str = "dots", **kwargs 110): 111 """ 112 Display a Rich spinner with `title` while running `func(*args, **kwargs)`. 113 Returns whatever `func` returns. 114 """ 115 if _QUIET: 116 return func(*args, **kwargs) 117 with console.status(f"[bold cyan]{title}[/bold cyan]", spinner=spinner_name): 118 return func(*args, **kwargs)
Display a Rich spinner with title while running func(*args, **kwargs).
Returns whatever func returns.