Files
devx/src/devx/utils/ui.py
T

80 lines
2.6 KiB
Python

"""User-facing output utilities combining console and log output.
Console messages are colorised via ``click.style`` for visual feedback.
The persistent log file always receives plain text (no ANSI codes).
This is a generalisation of grm's ``ui.say()`` function, extracted so
that any CLI tool can use the same pattern. The logger name and
console-level env var are configurable.
Usage::
from devx.utils.ui import say
say("Starting deployment...")
say("Error occurred", level=logging.ERROR, err=True, color="red")
"""
from __future__ import annotations
import logging
import os
import click
# Configurable env var for console verbosity — projects can override
# via :func:`configure_ui`.
_LOG_LEVEL_ENV_VAR = "DEVX_LOG_LEVEL"
_LOGGER_NAME = "devx"
def configure_ui(*, log_level_env_var: str = "DEVX_LOG_LEVEL", logger_name: str = "devx") -> None:
"""Override the env var name and logger name used by :func:`say`.
This allows downstream projects (e.g. grm) to use their own env var
names (e.g. ``GRM_LOG_LEVEL``) and logger names while still using
devx's ui module.
Args:
log_level_env_var: Environment variable name for console log level.
logger_name: Logger name for persistent log file output.
"""
global _LOG_LEVEL_ENV_VAR, _LOGGER_NAME
_LOG_LEVEL_ENV_VAR = log_level_env_var
_LOGGER_NAME = logger_name
def _console_level() -> int:
"""Return the minimum level for console output from the configured env var."""
value = os.getenv(_LOG_LEVEL_ENV_VAR, "INFO")
try:
return getattr(logging, value.upper())
except AttributeError:
return logging.INFO
def say(
msg: str,
level: int = logging.INFO,
err: bool = False,
color: str | None = None,
) -> None:
"""Output a message to the user and also log it for auditing.
Console output goes via ``click.echo`` (handles encoding, CliRunner,
Windows colorama) only when *level* is at least the configured
console log level (default ``DEVX_LOG_LEVEL``, falls back to INFO).
The same message is always sent to the configured logger so it
appears in the persistent log file regardless of console verbosity.
Args:
msg: Message to display.
level: Logging level (e.g. ``logging.INFO``, ``logging.ERROR``).
err: If True, output to stderr instead of stdout.
color: Optional ``click.style`` fg color (e.g. ``"green"``, ``"red"``).
"""
if level >= _console_level():
styled = click.style(msg, fg=color) if color else msg
click.echo(styled, err=err)
logging.getLogger(_LOGGER_NAME).log(level, msg)