Source code for irsim.env.env_logger

import contextlib
import itertools
import sys
import weakref
from collections.abc import Callable
from typing import Any

from loguru import logger


[docs] class EnvLogger: """Thin wrapper around Loguru used by IR-SIM environments. Each environment owns a console sink at its ``log_level`` and, when ``log_file`` is given, a file sink. Its messages are tagged and its sinks accept only that tag, so a second environment in the same process neither replaces the first one's sinks nor receives its messages. Sinks added by user code are left alone. ``close`` removes the sinks; garbage collection does the same. """ CONSOLE_FORMAT = ( "<green>{time:YYYY-MM-DD HH:mm}</green> | " "<level>{level: <8}</level> | " "<level>{message}</level>" ) _tags = itertools.count() _default_handler_dropped = False def __init__( self, log_file: str | None = "irsim_error.log", log_level: str = "WARNING" ) -> None: """ Initialize the EnvLogger. Args: log_file (str, optional): Path to the log file. Default is 'irsim_error.log'. log_level (str, optional): Logging level. Default is 'WARNING'. """ self._drop_loguru_default_handler() self._tag = next(self._tags) self._logger = logger.bind(irsim_env=self._tag) self._sink_ids = self._add_sinks(log_file, log_level) self._finalizer = weakref.finalize( self, self._remove_sinks, list(self._sink_ids) ) self._once_keys: set[str] = set() @classmethod def _drop_loguru_default_handler(cls) -> None: """Remove loguru's own stderr handler once; every environment adds its own sinks.""" if cls._default_handler_dropped: return with contextlib.suppress(ValueError): logger.remove(0) cls._default_handler_dropped = True @staticmethod def _messages_of(tag: int) -> Callable[[Any], bool]: """Sink filter accepting the messages of one environment. Messages logged through a plain loguru logger carry no tag and are accepted by every environment's sinks. """ def accept(record: Any) -> bool: return record["extra"].get("irsim_env", tag) == tag return accept def _add_sinks(self, log_file: str | None, log_level: str) -> list[int]: """Add this environment's console sink and optional file sink.""" accept = self._messages_of(self._tag) sink_ids = [ logger.add( sys.stdout, level=log_level, format=self.CONSOLE_FORMAT, filter=accept ) ] if log_file is not None: sink_ids.append(logger.add(log_file, level=log_level, filter=accept)) return sink_ids @staticmethod def _remove_sinks(sink_ids: list[int]) -> None: for sink_id in sink_ids: with contextlib.suppress(ValueError): logger.remove(sink_id)
[docs] def close(self) -> None: """Remove this environment's console and file sinks.""" self._finalizer() self._sink_ids = []
[docs] def trace(self, msg: str) -> None: """ Log a trace message. Args: msg (str): The message to log. """ self._logger.trace(msg)
[docs] def info(self, msg: str) -> None: """ Log an info message. Args: msg (str): The message to log. """ self._logger.info(msg)
[docs] def error(self, msg: str) -> None: """ Log an error message. Args: msg (str): The message to log. """ self._logger.error(msg)
[docs] def debug(self, msg: str) -> None: """ Log a debug message. Args: msg (str): The message to log. """ self._logger.debug(msg)
[docs] def warning(self, msg: str) -> None: """ Log a warning message. Args: msg (str): The message to log. """ self._logger.warning(msg)
[docs] def warning_once(self, msg: str, key: str | None = None) -> None: """ Log a warning the first time ``key`` (default: ``msg``) is seen, then at DEBUG. For per-step conditions that would otherwise flood the log every step. Args: msg (str): The message to log. key (str, optional): Identity of the condition when ``msg`` varies. """ key = key or msg if key in self._once_keys: self._logger.debug(msg) return self._once_keys.add(key) self._logger.warning(f"{msg} (further occurrences are logged at DEBUG level)")
[docs] def success(self, msg: str) -> None: """ Log a success message. Args: msg (str): The message to log. """ self._logger.success(msg)
[docs] def critical(self, msg: str) -> None: """ Log a critical message. Args: msg (str): The message to log. """ self._logger.critical(msg)