/c/Users/alexb/git/core-mfg/ai2/src/shared/logging/__init__.py:
# ./shared/logging/__init__.py

from .logging import _LOGGER_STYLE, LevelColorFormatter, setup_logging

__all__ = [
    "_LOGGER_STYLE",
    "LevelColorFormatter",
    "setup_logging",
]


/c/Users/alexb/git/core-mfg/ai2/src/shared/logging/formatters/level_color_formatter.py:
import logging

from .styles import _LEVEL_COLORS, _RESET


class LevelColorFormatter(logging.Formatter):
    """ANSI-colored level names; pads level before coloring so widths stay aligned."""

    def format(self, record: logging.LogRecord) -> str:
        saved = record.levelname
        color = _LEVEL_COLORS.get(record.levelno, "")
        padded = f"{saved:8}"
        record.levelname = f"{color}{padded}{_RESET}" if color else padded
        try:
            return super().format(record)
        finally:
            record.levelname = saved


/c/Users/alexb/git/core-mfg/ai2/src/shared/logging/formatters/styles.py:
import logging

_COLORS = {
    "CYAN": "\033[36m",  # cyan
    "GREEN": "\033[32m",  # green
    "GRAY": "\033[90m",  # gray
    "YELLOW": "\033[33m",  # yellow
    "RED": "\033[31m",  # red
    "BOLD_RED": "\033[1;31m",  # bold red
    "RESET": "\033[0m",  # reset
}

_STYLES = {
    "ITALIC": "\033[3;90m",  # italic + dim grey
}

_LEVEL_COLORS: dict[int, str] = {
    logging.DEBUG: _COLORS["CYAN"],  # cyan
    logging.INFO: _COLORS["GREEN"],  # green
    logging.WARNING: _COLORS["YELLOW"],  # yellow
    logging.ERROR: _COLORS["RED"],  # red
    logging.CRITICAL: _COLORS["BOLD_RED"],  # bold red
}
_RESET = _COLORS["RESET"]
_LOGGER_STYLE = _COLORS["GRAY"] + _STYLES["ITALIC"]  # italic + dim grey

# Rich markup for the same logger-name look (use with RichHandler markup=True, not ANSI).
_LOGGER_RICH_OPEN = "[dim italic]"
_LOGGER_RICH_CLOSE = "[/dim italic]"


/c/Users/alexb/git/core-mfg/ai2/src/shared/logging/logging.py:
# src/shared/logging.py
import logging
import sys

from rich.console import Console
from rich.logging import RichHandler
from rich.theme import Theme

from .formatters.level_color_formatter import LevelColorFormatter
from .formatters.styles import _LOGGER_RICH_CLOSE, _LOGGER_RICH_OPEN, _LOGGER_STYLE, _RESET

_handler = logging.StreamHandler(sys.stderr)
_handler.setFormatter(
    LevelColorFormatter(
        fmt=f"[%(asctime)s] %(levelname)s {_LOGGER_STYLE}%(name)s{_RESET} %(message)s",
        datefmt="%y/%m/%d %H:%M:%S",
    )
)

console_format = "(%(name)s) %(message)s"
formatter2 = LevelColorFormatter(
    fmt=f"{_LOGGER_RICH_OPEN}%(name)s{_LOGGER_RICH_CLOSE} %(message)s",
    datefmt="%y/%m/%d %H:%M:%S",
)

_LOG_THEME = Theme(
    {
        "log.path": "dim italic bright_black",
    }
)
console = Console(
    force_terminal=True,
    theme=_LOG_THEME,
    # width=220,
)

console_handler = RichHandler(
    rich_tracebacks=True,
    show_time=True,
    omit_repeated_times=False,
    # show_level=True,
    show_path=True,
    markup=True,
    console=console,
)
# Set custom time format for RichHandler to show only time
console_handler._log_render.show_time = True
console_handler._log_render.time_format = "[%X]"

console_handler.setFormatter(formatter2)


# ---------------- Setup function ----------------
def setup_logging(per_logger_levels: dict[str, int] | None = None, root_level: int = logging.INFO):
    """
    Configure centralized logging with colors and optional per-logger levels.

    Args:
        per_logger_levels: Dict of {logger_name: level}, e.g. {"ocr.some_func": logging.DEBUG}
        root_level: Level for the root logger
    """
    per_logger_levels = per_logger_levels or {}

    # Clear existing handlers
    logging.root.handlers.clear()

    # logging.basicConfig(level=root_level, handlers=[_handler], force=True)
    logging.basicConfig(level=root_level, handlers=[console_handler], force=True)

    # Apply per-logger levels
    for logger_name, level in per_logger_levels.items():
        logging.getLogger(logger_name).setLevel(level)


# ---------------- Convenience default logger ----------------
log = logging.getLogger(__name__)


/c/Users/alexb/git/core-mfg/ai2/src/shared/logging/USAGE.md:


/c/Users/alexb/git/core-mfg/ai2/src/shared/logging/utils/__init__.py:
# ./shared/logging/utils/__init__.py

from .config_path import (
    DEFAULT_LOGGING_ENV_FILENAME,
    find_logging_config_file,
    iter_default_config_search_roots,
    resolve_logging_config_path,
)
from .env_loader import load_logging_env, setup_logging_from_env
from .get_logger import get_logger
from .logger_specs import parse_logger_level_specs
from .utils import get_logger_levels, is_valid_level

__all__ = [
    "DEFAULT_LOGGING_ENV_FILENAME",
    "find_logging_config_file",
    "get_logger",
    "get_logger_levels",
    "is_valid_level",
    "iter_default_config_search_roots",
    "load_logging_env",
    "parse_logger_level_specs",
    "resolve_logging_config_path",
    "setup_logging_from_env",
]


/c/Users/alexb/git/core-mfg/ai2/src/shared/logging/utils/build_logging_config.py:


/c/Users/alexb/git/core-mfg/ai2/src/shared/logging/utils/config_path.py:
# src/shared/logging/utils/config_path.py
"""Locate and resolve paths to per-logger config files (e.g. logging.env)."""

from __future__ import annotations

import os
import sys
from collections.abc import Iterable
from pathlib import Path

# Default filename when searching under cwd and sys.path (documented public contract).
DEFAULT_LOGGING_ENV_FILENAME = "logging.env"


def iter_default_config_search_roots() -> list[Path]:
    """
    Distinct directories to search: current working directory, then each ``sys.path``
    entry that is an existing directory. No relative ``..`` crawling.
    """
    seen: set[Path] = set()
    roots: list[Path] = []
    for candidate in (Path.cwd().resolve(), *(_path_entry_as_dir(p) for p in sys.path)):
        if candidate is None or candidate in seen:
            continue
        seen.add(candidate)
        roots.append(candidate)
    return roots


def _path_entry_as_dir(entry: str) -> Path | None:
    if not entry:
        return None
    p = Path(entry)
    return p.resolve() if p.is_dir() else None


def find_logging_config_file(
    filename: str = DEFAULT_LOGGING_ENV_FILENAME,
    *,
    search_roots: Iterable[Path] | None = None,
) -> str | None:
    """Return the first existing ``root / filename`` among ``search_roots``."""
    roots = list(search_roots) if search_roots is not None else iter_default_config_search_roots()
    for root in roots:
        candidate = root / filename
        if candidate.is_file():
            return str(candidate)
    return None


def resolve_logging_config_path(
    cli_path: str | None = None,
    *,
    env_var: str = "LOGGER_CONFIG_FILE",
    default_filename: str = DEFAULT_LOGGING_ENV_FILENAME,
    search_roots: Iterable[Path] | None = None,
) -> str | None:
    """
    Resolve the path to a logging config file.

    Precedence (highest first):

    1. ``cli_path`` when non-empty
    2. ``os.environ[env_var]`` when set and non-empty
    3. :func:`find_logging_config_file` with ``default_filename`` and ``search_roots``
    """
    if cli_path:
        return cli_path
    env_path = os.environ.get(env_var)
    if env_path:
        return env_path
    return find_logging_config_file(default_filename, search_roots=search_roots)


/c/Users/alexb/git/core-mfg/ai2/src/shared/logging/utils/env_loader.py:
# src/shared/logging/env_loader.py
import logging
from pathlib import Path


def load_logging_env(file_path: str) -> dict[str, int]:
    path = Path(file_path)
    if not path.exists():
        return {}

    logger_levels = {}
    for line in path.read_text().splitlines():
        line = line.strip()
        if not line or line.startswith("#"):
            continue
        if "=" not in line:
            continue
        name, level = line.split("=", 1)
        logger_levels[name.strip()] = getattr(logging, level.strip().upper())
    return logger_levels


def setup_logging_from_env(file_path: str, root_level: int = logging.INFO):
    from pytools.logging import setup_logging

    per_logger_levels = load_logging_env(file_path)
    setup_logging(per_logger_levels=per_logger_levels, root_level=root_level)


/c/Users/alexb/git/core-mfg/ai2/src/shared/logging/utils/get_logger.py:
import inspect
import logging


def get_logger():
    frame = inspect.currentframe().f_back
    func_name = frame.f_code.co_name
    mod_name = frame.f_globals["__name__"]
    return logging.getLogger(f"{mod_name}.{func_name}")


/c/Users/alexb/git/core-mfg/ai2/src/shared/logging/utils/logger_specs.py:
# src/shared/logging/utils/logger_specs.py
"""Parse CLI or config strings into per-logger level mappings."""

from __future__ import annotations

import logging
from collections.abc import Iterable


def parse_logger_level_specs(specs: Iterable[str]) -> dict[str, int]:
    """
    Parse entries of the form ``name|level`` into ``{logger_name: logging level}``.

    Level names match ``logging`` module attributes (e.g. ``DEBUG``, ``info``).

    Raises:
        ValueError: If a spec is malformed or the level is unknown.
    """
    out: dict[str, int] = {}
    for spec in specs:
        if "|" not in spec:
            raise ValueError(f"expected name|level, got: {spec!r}")
        name, level = spec.split("|", 1)
        name, level = name.strip(), level.strip()
        if not name or not level:
            raise ValueError(f"invalid logger spec: {spec!r}")
        try:
            out[name] = getattr(logging, level.upper())
        except AttributeError as e:
            raise ValueError(f"unknown log level in {spec!r}: {level!r}") from e
    return out


/c/Users/alexb/git/core-mfg/ai2/src/shared/logging/utils/utils.py:
# src/shared/logging/utils/utils.py

import logging


def get_logger_levels() -> list[str]:
    return sorted(logging.getLevelNamesMapping().keys())


def is_valid_level(level: str) -> bool:
    return level.upper() in logging.getLevelNamesMapping()


