Source code for liberata_metrics.logging
import logging
import os
from pathlib import Path
from logging.handlers import RotatingFileHandler
from typing import Optional
LIB_NAME = 'liberata_metrics'
[docs]
def get_logger(name: Optional[str] = None) -> logging.Logger:
"""
Return a library logger.
Usage (inside library modules):
logger = get_logger(__name__)
logger.debug("message")
This logger installs a NullHandler by default so importing the library does not
configure application logging or emit output. Application code (scripts/CLI)
should call configure_logging() once at startup to enable handlers/formatting.
Parameters
----------
name : Optional[str]
Optional module name to append to the library logger root. If provided the
returned logger name will be "liberata_metrics.<name>".
Returns
-------
logging.Logger
Configured logger object (may carry NullHandler by default).
"""
logger_name = f"{LIB_NAME}.{name}" if name else LIB_NAME
logger = logging.getLogger(logger_name)
# ensure importing the library doesn't add handlers or configure root
if not logger.handlers:
logger.addHandler(logging.NullHandler())
return logger
[docs]
def configure_logging(
level: Optional[str] = None,
log_file: Optional[str] = None,
max_bytes: int = 10_000_000,
backup_count: int = 5,
fmt: Optional[str] = None,
) -> None:
"""
Configure application-level logging for scripts/CLIs.
Call this once from top-level entrypoints (scripts, CLI) before performing work.
Parameters
----------
level : Optional[str]
Logging level name (e.g., "DEBUG", "INFO"). If None, reads from environment
variable LOG_LEVEL or defaults to "INFO".
log_file : Optional[str]
Path to a rotating log file. If provided, a RotatingFileHandler is added.
Parent directories are created if necessary.
max_bytes : int
Maximum bytes per log file before rotation.
backup_count : int
Number of rotated log files to keep.
fmt : Optional[str]
Optional log format string. If None, a sensible default is used.
Notes
-----
- This function configures the root logger. Library modules should not call this;
only application code (scripts or CLI entrypoints) should configure logging.
- Multiple calls are safe: if the root logger already has handlers, the function
sets the level and returns early to avoid duplicate handlers in interactive runs.
Example
-------
from liberata_metrics.logging import configure_logging
configure_logging(level="DEBUG", log_file="logs/run.log")
"""
if level is None:
level = os.getenv("LOG_LEVEL", "INFO")
numeric_level = getattr(logging, level.upper(), logging.INFO)
root = logging.getLogger()
# If handlers already configured (e.g., in REPL/pytest), set level and return
if root.handlers:
root.setLevel(numeric_level)
return
if fmt is None:
fmt = "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
formatter = logging.Formatter(fmt)
console = logging.StreamHandler()
console.setFormatter(formatter)
root.addHandler(console)
if log_file:
Path(log_file).parent.mkdir(parents=True, exist_ok=True)
fh = RotatingFileHandler(log_file, maxBytes=max_bytes, backupCount=backup_count)
fh.setFormatter(formatter)
root.addHandler(fh)
root.setLevel(numeric_level)