Source code for molecular_simulations.logging_config

"""Logging configuration module for molecular_simulations.

This module provides opt-in logging configuration for applications, CLIs,
and examples. Library code should not call this implicitly.
"""

from __future__ import annotations

import logging
import logging.config
import os
import socket


[docs] def configure_logging( level: str | int | None = None, to_file: str | None = None, fmt: str | None = None, ) -> None: """Configure logging for the application. Opt-in configuration for apps/CLIs/examples. Library code should *not* call this implicitly. Args: level: The logging level. Can be a string (e.g., 'INFO', 'DEBUG') or an integer. If None, uses the MS_LOG_LEVEL environment variable or defaults to 'INFO'. to_file: Path to a log file. If None, uses the MS_LOG_FILE environment variable. If neither is set, logs only to console. fmt: Log message format string. If None, uses the MS_LOG_FMT environment variable or a default format including hostname, PID, and MPI rank. Example: >>> configure_logging(level="DEBUG", to_file="simulation.log") """ level = level or os.getenv('MS_LOG_LEVEL') or 'INFO' fmt = ( fmt or os.getenv('MS_LOG_FMT') or ( '%(asctime)s | %(levelname)s | %(name)s | %(message)s ' '[host=%(hostname)s pid=%(process)d rank=%(mpirank)s]' ) ) class _ContextFilter(logging.Filter): """Filter that adds hostname and MPI rank to log records.""" def filter(self, record: logging.LogRecord) -> bool: """Add context information to the log record. Args: record: The log record to modify. Returns: Always returns True to allow the record to be logged. """ record.hostname = socket.gethostname() # Fill MPI rank if available; else 0 try: from mpi4py import MPI # ty: ignore[unresolved-import] record.mpirank = MPI.COMM_WORLD.Get_rank() except Exception: record.mpirank = 0 return True handlers = { 'console': { 'class': 'logging.StreamHandler', 'level': level, 'filters': ['ctx'], 'formatter': 'standard', } } file_path = to_file or os.getenv('MS_LOG_FILE') if file_path: handlers['file'] = { 'class': 'logging.handlers.RotatingFileHandler', 'level': level, 'filters': ['ctx'], 'formatter': 'standard', 'filename': file_path, 'maxBytes': 10 * 1024 * 1024, 'backupCount': 3, 'encoding': 'utf-8', } config = { 'version': 1, 'disable_existing_loggers': False, 'filters': {'ctx': {'()': _ContextFilter}}, 'formatters': {'standard': {'format': fmt}}, 'handlers': handlers, 'root': {'level': level, 'handlers': list(handlers)}, } logging.config.dictConfig(config)