Source code for isaaclab.utils.logger
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Sub-module with logging utilities.
To use the logger, you can use the :func:`logging.getLogger` function.
Example:
>>> import logging
>>>
>>> # define logger for the current module (enables fine-control)
>>> logger = logging.getLogger(__name__)
>>>
>>> # log messages
>>> logger.info("This is an info message")
>>> logger.warning("This is a warning message")
>>> logger.error("This is an error message")
>>> logger.critical("This is a critical message")
>>> logger.debug("This is a debug message")
"""
from __future__ import annotations
import logging
import os
import sys
import tempfile
import time
from datetime import datetime
from typing import Literal
[docs]def configure_logging(
logging_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "WARNING",
save_logs_to_file: bool = True,
log_dir: str | None = None,
) -> logging.Logger:
"""Setup the logger with a colored formatter and a rate limit filter.
This function defines the default logger for IsaacLab. It adds a stream handler with a colored formatter
and a rate limit filter. If :attr:`save_logs_to_file` is True, it also adds a file handler to save the logs
to a file. The log directory can be specified using :attr:`log_dir`. If not provided, the logs will be saved
to the temp directory with the sub-directory "isaaclab/logs".
The log file name is formatted as "isaaclab_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.log".
The log record format is "%(asctime)s [%(filename)s:%(lineno)d] %(levelname)s: %(message)s".
The date format is "%Y-%m-%d %H:%M:%S".
Args:
logging_level: The logging level.
save_logs_to_file: Whether to save the logs to a file.
log_dir: The directory to save the logs to. Default is None, in which case the logs
will be saved to the temp directory with the sub-directory "isaaclab/logs".
Returns:
The root logger.
"""
root_logger = logging.getLogger()
# the root logger must be the lowest level to ensure that all messages are logged
root_logger.setLevel(logging.DEBUG)
# remove existing handlers
# Note: iterate over a copy [:] to avoid modifying list during iteration
for handler in root_logger.handlers[:]:
root_logger.removeHandler(handler)
# add a stream handler with default level
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging_level)
# add a colored formatter
formatter = ColoredFormatter(fmt="%(asctime)s [%(filename)s] %(levelname)s: %(message)s", datefmt="%H:%M:%S")
handler.setFormatter(formatter)
handler.addFilter(RateLimitFilter(interval_seconds=5))
root_logger.addHandler(handler)
# add a file handler
if save_logs_to_file:
# if log_dir is not provided, use the temp directory
if log_dir is None:
log_dir = os.path.join(tempfile.gettempdir(), "isaaclab", "logs")
# create the log directory if it does not exist
os.makedirs(log_dir, exist_ok=True)
# create the log file path
log_file_path = os.path.join(log_dir, f"isaaclab_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.log")
# create the file handler
file_handler = logging.FileHandler(log_file_path, mode="w", encoding="utf-8")
file_handler.setLevel(logging.DEBUG)
file_formatter = logging.Formatter(
fmt="%(asctime)s [%(filename)s:%(lineno)d] %(levelname)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
)
file_handler.setFormatter(file_formatter)
root_logger.addHandler(file_handler)
# print the log file path once at startup with nice formatting
cyan = "\033[36m" # cyan color
bold = "\033[1m" # bold text
reset = "\033[0m" # reset formatting
message = f"[INFO][IsaacLab]: Logging to file: {log_file_path}"
border = "=" * len(message)
print(f"\n{cyan}{border}{reset}")
print(f"{cyan}{bold}{message}{reset}")
print(f"{cyan}{border}{reset}\n")
# return the root logger
return root_logger
[docs]class ColoredFormatter(logging.Formatter):
"""Colored formatter for logging.
This formatter colors the log messages based on the log level.
"""
COLORS = {
"WARNING": "\033[33m", # orange/yellow
"ERROR": "\033[31m", # red
"CRITICAL": "\033[1;31m", # bold red
"INFO": "\033[0m", # reset
"DEBUG": "\033[0m",
}
"""Colors for different log levels."""
RESET = "\033[0m"
"""Reset color."""
[docs] def format(self, record: logging.LogRecord) -> str:
"""Format the log record.
Args:
record: The log record to format.
Returns:
The formatted log record.
"""
color = self.COLORS.get(record.levelname, self.RESET)
message = super().format(record)
return f"{color}{message}{self.RESET}"
[docs]class RateLimitFilter(logging.Filter):
"""Custom rate-limited warning filter.
This filter allows warning-level messages only once every few seconds per message.
This is useful to avoid flooding the log with the same message multiple times.
"""
[docs] def __init__(self, interval_seconds: int = 5):
"""Initialize the rate limit filter.
Args:
interval_seconds: The interval in seconds to limit the warnings.
Defaults to 5 seconds.
"""
super().__init__()
self.interval = interval_seconds
self.last_emitted = {}
[docs] def filter(self, record: logging.LogRecord) -> bool:
"""Allow warning-level messages only once every few seconds per message.
Args:
record: The log record to filter.
Returns:
True if the message should be logged, False otherwise.
"""
# only filter warning-level messages
if record.levelno != logging.WARNING:
return True
# check if the message has been logged in the last interval
now = time.time()
msg_key = record.getMessage()
if msg_key not in self.last_emitted or (now - self.last_emitted[msg_key]) > self.interval:
# if the message has not been logged in the last interval, log it
self.last_emitted[msg_key] = now
return True
# if the message has been logged in the last interval, do not log it
return False