Module neuralops.experiment_logging.logger
Structured and thread-safe logger setup with JSON and human-readable formats. Implements QueueHandler / QueueListener for non-blocking logging across threads and ensures each DDP worker rank writes to an isolated log file (rank_{LOCAL_RANK}.log).
Functions
def get_rank(configured_rank: int | None = None) ‑> int-
Expand source code
def get_rank(configured_rank: int | None = None) -> int: """ Determine the current process rank in single-node or DDP multi-GPU environments. Args: configured_rank: Explicit rank if specified in config. Returns: int: Process local/global rank (0 for single-node / master). """ if configured_rank is not None: return int(configured_rank) for env_key in ( "LOCAL_RANK", "RANK", "SLURM_LOCALID", "OMPI_COMM_WORLD_LOCAL_RANK", ): val = os.environ.get(env_key) if val is not None: try: return int(val) except ValueError: pass try: import torch.distributed as dist if dist.is_available() and dist.is_initialized(): return dist.get_rank() except ( AttributeError, KeyError, RuntimeError, TypeError, ValueError, OSError, ImportError, ): pass return 0Determine the current process rank in single-node or DDP multi-GPU environments.
- Args
- -----=
configured_rank- Explicit rank if specified in config.
- Returns
- -----=
int- Process local/global rank (0 for single-node / master).
def setup_logging(config: LoggingConfig | None = None) ‑> logging.Logger-
Expand source code
def setup_logging(config: LoggingConfig | None = None) -> logging.Logger: """ Create and configure a thread-safe logger with isolated rank logging. Args: config: LoggingConfig dataclass specifying log parameters. Returns: logging.Logger: Configured logger instance. """ cfg = config or LoggingConfig() rank = get_rank(cfg.rank) root = logging.getLogger() root.handlers.clear() root.setLevel(getattr(logging, cfg.log_level.upper(), logging.INFO)) os.makedirs(cfg.log_dir, exist_ok=True) if cfg.log_format == "structured": fmt = StructuredFormatter(rank=rank) else: fmt = logging.Formatter( f"%(asctime)s [Rank {rank}] [%(levelname)s] %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) destination_handlers: list[logging.Handler] = [] if cfg.log_to_console: # Stream logs to stdout (typically rank 0 only or all ranks if formatted) ch = logging.StreamHandler(sys.stdout) ch.setFormatter(fmt) destination_handlers.append(ch) if cfg.log_to_file: # FIX: Ensure each DDP rank writes to an isolated log file (rank_{LOCAL_RANK}.log) if cfg.rank_specific_logs: fname = f"rank_{rank}.log" else: fname = f"{cfg.experiment_name}_{cfg.run_id or ''}_rank_{rank}.log".strip("_") log_file_path = os.path.join(cfg.log_dir, fname) fh = RotatingFileHandler( log_file_path, maxBytes=10 * 1024 * 1024, backupCount=5, encoding="utf-8", ) fh.setFormatter(fmt) destination_handlers.append(fh) if cfg.use_queue_handler: # FIX: Implement Python logging with QueueHandler and QueueListener for thread safety and non-blocking I/O log_queue: queue.Queue = queue.Queue(-1) queue_handler = QueueHandler(log_queue) root.addHandler(queue_handler) listener = QueueListener(log_queue, *destination_handlers, respect_handler_level=True) listener.start() _ACTIVE_LISTENERS.append(listener) else: for handler in destination_handlers: root.addHandler(handler) logger = logging.getLogger("neuralops") logger.info( "Logging initialised. Experiment: %s, Run: %s, Rank: %d, ThreadSafeQueue: %s", cfg.experiment_name, cfg.run_id, rank, cfg.use_queue_handler, extra={"rank": rank}, ) return loggerCreate and configure a thread-safe logger with isolated rank logging.
- Args
- -----=
config- LoggingConfig dataclass specifying log parameters.
- Returns
- -----=
logging.Logger- Configured logger instance.
def shutdown_logging() ‑> None-
Expand source code
def shutdown_logging() -> None: """ Stop all background QueueListener workers and flush pending log events. """ global _ACTIVE_LISTENERS for listener in _ACTIVE_LISTENERS: try: listener.stop() except ( AttributeError, KeyError, RuntimeError, TypeError, ValueError, OSError, ImportError, ): pass _ACTIVE_LISTENERS.clear()Stop all background QueueListener workers and flush pending log events.
Classes
class StructuredFormatter (rank: int = 0)-
Expand source code
class StructuredFormatter(logging.Formatter): """JSON-line formatter for structured logging with rank metadata.""" def __init__(self, rank: int = 0) -> None: super().__init__() self.rank = rank def format(self, record: logging.LogRecord) -> str: """Format log record into JSON string.""" entry: dict[str, Any] = { "timestamp": datetime.now(timezone.utc).isoformat(), "level": record.levelname, "logger": record.name, "rank": getattr(record, "rank", self.rank), "message": record.getMessage(), } if hasattr(record, "extra_data") and isinstance(record.extra_data, dict): entry.update(record.extra_data) if record.exc_info: entry["exception"] = self.formatException(record.exc_info) return json.dumps(entry, default=str)JSON-line formatter for structured logging with rank metadata.
Initialize the formatter with specified format strings.
Initialize the formatter either with the specified format string, or a default as described above. Allow for specialized date formatting with the optional datefmt argument. If datefmt is omitted, you get an ISO8601-like (or RFC 3339-like) format.
Use a style parameter of '%', '{' or '$' to specify that you want to use one of %-formatting, :meth:
str.format({}) formatting or :class:string.Templateformatting in your format string.Changed in version: 3.2
Added the
styleparameter.Ancestors
- logging.Formatter
Methods
def format(self, record: logging.LogRecord) ‑> str-
Expand source code
def format(self, record: logging.LogRecord) -> str: """Format log record into JSON string.""" entry: dict[str, Any] = { "timestamp": datetime.now(timezone.utc).isoformat(), "level": record.levelname, "logger": record.name, "rank": getattr(record, "rank", self.rank), "message": record.getMessage(), } if hasattr(record, "extra_data") and isinstance(record.extra_data, dict): entry.update(record.extra_data) if record.exc_info: entry["exception"] = self.formatException(record.exc_info) return json.dumps(entry, default=str)Format log record into JSON string.