| """Environment-driven settings for the observability layer. |
| |
| One small frozen dataclass reads the ``MAL_*`` env vars once at |
| :func:`src.observability.configure` time. Keeping the parsing here (not scattered |
| across modules) means every layer sees the same, already-validated knobs. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import os |
| from dataclasses import dataclass |
|
|
| _DEFAULT_CAPACITY = 4000 |
| _DEFAULT_TEXT_LIMIT = 4000 |
|
|
|
|
| def _int_env(name: str, default: int) -> int: |
| raw = os.getenv(name) |
| if not raw: |
| return default |
| try: |
| return int(raw) |
| except ValueError: |
| return default |
|
|
|
|
| @dataclass(frozen=True) |
| class ObservabilitySettings: |
| """Resolved observability configuration. |
| |
| * ``level`` β root log level (``DEBUG`` surfaces full prompts + memory). |
| * ``fmt`` β terminal log format: ``text`` (human) or ``json`` (structured). |
| * ``tracing`` β span sink: ``off`` | ``console`` | ``memory`` | ``both``. |
| ``memory`` (default) feeds the in-app Gradio Telemetry panel with zero |
| terminal noise; ``console`` also prints spans; ``both`` does each. |
| * ``store_capacity`` β ring-buffer size for logs/spans kept for the UI. |
| * ``store_text_limit`` β prompt/memory truncation length in stored snapshots |
| (the full text still reaches the terminal at ``DEBUG``). |
| """ |
|
|
| level: str = "INFO" |
| fmt: str = "text" |
| tracing: str = "memory" |
| store_capacity: int = _DEFAULT_CAPACITY |
| store_text_limit: int = _DEFAULT_TEXT_LIMIT |
|
|
| @classmethod |
| def from_env(cls) -> "ObservabilitySettings": |
| return cls( |
| level=(os.getenv("MAL_LOG_LEVEL") or "INFO").upper(), |
| fmt=(os.getenv("MAL_LOG_FORMAT") or "text").lower(), |
| tracing=(os.getenv("MAL_TRACING") or "memory").lower(), |
| store_capacity=_int_env("MAL_TELEMETRY_BUFFER", _DEFAULT_CAPACITY), |
| store_text_limit=_int_env("MAL_TELEMETRY_TEXT_LIMIT", _DEFAULT_TEXT_LIMIT), |
| ) |
|
|