Source code for coseda.logging_utils

"""Logging helpers for consistent stdout formatting."""

from __future__ import annotations

import logging
import os
import sys
import datetime
from typing import Optional

_CONFIGURED = False


def _resolve_level(level: Optional[str]) -> int:
    if level is None:
        level = os.getenv("COSEDA_LOG_LEVEL", "INFO")
    return getattr(logging, str(level).upper(), logging.INFO)


[docs] def configure_logging(level: Optional[str] = None) -> None: """Configure the root COSEDA logger once.""" global _CONFIGURED if _CONFIGURED: return logger = logging.getLogger("coseda") logger.setLevel(_resolve_level(level)) if not logger.handlers: handler = logging.StreamHandler(sys.stdout) fmt = os.getenv("COSEDA_LOG_FORMAT", "%(message)s") handler.setFormatter(logging.Formatter(fmt)) logger.addHandler(handler) logger.propagate = False _CONFIGURED = True
[docs] def get_logger(name: Optional[str] = None) -> logging.Logger: """Return a configured logger for the given module or the root COSEDA logger.""" configure_logging() if not name or name == "coseda": return logging.getLogger("coseda") if name.startswith("coseda."): return logging.getLogger(name) return logging.getLogger(f"coseda.{name}")
[docs] def log_print(*args, sep: str = " ", end: str = "", file=None, flush: bool = False, level: Optional[str] = None): """ Drop-in replacement for print() that routes through the COSEDA logger. `level` can be one of: "debug", "info", "warning", "error". """ msg = sep.join(str(arg) for arg in args) if end: msg = f"{msg}{end}" msg = msg.rstrip("\n") logger = get_logger() if level: log_fn = getattr(logger, level, logger.info) log_fn(msg) return if file is sys.stderr: logger.error(msg) else: logger.info(msg)
[docs] def log_start(logfile_path, message): """ Print a message and append it (timestamped) to the logfile if a path is provided. """ log_print(message) if logfile_path is not None: with open(f"{logfile_path}", "a") as file: file.write(f"{datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3]}; {message}\n")
[docs] def log_result(logfile_path, message, error): """ Log a success or failure message to stdout and, when available, to the logfile. """ if error is None: log_print(message) if logfile_path is not None: with open(f"{logfile_path}", "a") as file: file.write(f"{datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3]}; {message}\n") else: log_print(error, file=sys.stderr) log_print("+++ This error might already be fixed. Did you pull the latest version? +++", level="warning") if logfile_path is not None: with open(f"{logfile_path}", "a") as file: file.write(f"{datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3]}; Error: {error}\n")
[docs] def shoutout(configfile_path): """Print which config file is being processed (tiny UX helper for loops).""" log_print(f"working with {configfile_path}")