# Python Logging Usage

Practical examples for using `pytools.logging`.

## Example 1: Typer CLI (Single Process)

```python
import logging
from pathlib import Path

import typer

from pytools.logging import bootstrap, merge_logger_levels, set_root_level
from pytools.logging.apply import apply_state
from pytools.logging.resolve import resolve_logging_env_path
from pytools.logging.utils import load_logging_env, parse_logger_level_specs

app = typer.Typer()
log = logging.getLogger(__name__)


@app.command()
def run(
    log_level: str = "info",
    logger: list[str] | None = None,
    logger_file: str | None = None,
):
    # Define baseline state
    state = bootstrap(root_level=logging.WARNING)

    # Resolve optional env file path
    config_path = resolve_logging_env_path(logger_file)
    file_levels = (
        load_logging_env(config_path)
        if config_path and Path(config_path).expanduser().is_file()
        else {}
    )

    # App-level additive precedence: file first, then CLI per-key override
    cli_levels = parse_logger_level_specs(logger or [])
    state = merge_logger_levels(state, {**file_levels, **cli_levels})
    state = set_root_level(state, getattr(logging, log_level.upper()))

    # Activate runtime logging
    apply_state(state)
    log.info("CLI started")
```

## Example 2: Uvicorn / Multiprocess Parity

Apply locally and pass the same config artifact to Uvicorn workers.

```python
import logging
import uvicorn

from pytools.logging import bootstrap, export_config, merge_logger_levels, set_root_level
from pytools.logging.apply import apply_state

state = bootstrap(root_level=logging.WARNING)
state = merge_logger_levels(state, {"ai_server": logging.INFO})
state = set_root_level(state, logging.INFO)

apply_state(state)  # local process

uvicorn.run(
    "ai_server.app:app",
    host="0.0.0.0",
    port=8000,
    log_config=export_config(state),  # worker process parity
)
```

## Example 3: Minimal Script

```python
import logging
from pytools.logging import bootstrap
from pytools.logging.apply import apply_state

state = bootstrap(
    root_level=logging.INFO,
    logger_levels={"my.task": logging.DEBUG},
)
apply_state(state)
logging.getLogger("my.task").debug("ready")
```

## Notes

- `bootstrap`, `resolve`, `mutate`, and `export` are data-oriented stages.
- `apply_state` / `apply_config` are the explicit side-effect boundary.
- Keep app-specific precedence rules in the application layer (for example `ai_server/cli.py`).
- Default file discovery uses `{project_root}/.env.pytools_logging` when no explicit path or env var is provided.


## Example 4: Inherited or Base Class this.log

```python
import logging
from abc import ABC

class BaseABC(ABC):  # noqa: B024
    """
    Base abstract class with automatic logger initialization.

    All ABC classes (SingletonABC, ServiceABC, etc.) should inherit from this
    to get consistent logger functionality with proper environment detection.
    """

    _id: str = ""

    def __init__(
        self,
        **kwargs,
    ):
        """Initialize base class with automatic logger setup"""
        super().__init__(**kwargs)

    @property
    def log(self):
        return logging.getLogger(f"{self.__module__}.{self.__class__.__name__}")
```