| |
| |
| |
| |
|
|
| import functools |
| import logging |
| import os |
| import sys |
| from typing import Optional |
|
|
| from termcolor import colored |
|
|
| from dinov3.distributed import TorchDistributedEnvironment |
|
|
| from dinov3.logging.helpers import MetricLogger, SmoothedValue |
|
|
| _LEVEL_COLORED_KWARGS = { |
| logging.DEBUG: {"color": "green", "attrs": ["bold"]}, |
| logging.INFO: {"color": "green"}, |
| logging.WARNING: {"color": "yellow"}, |
| logging.ERROR: {"color": "red"}, |
| logging.CRITICAL: {"color": "red", "attrs": ["bold"]}, |
| } |
|
|
|
|
| class _LevelColoredFormatter(logging.Formatter): |
| def __init__(self, *args, **kwargs): |
| super().__init__(*args, **kwargs) |
|
|
| def formatMessage(self, record): |
| log = super().formatMessage(record) |
|
|
| colored_kwargs = _LEVEL_COLORED_KWARGS.get(record.levelno) |
| if colored_kwargs is None: |
| return log |
|
|
| msg = record.msg % record.args if record.msg == "%s" else record.msg |
| index = log.rfind(msg, len(log) - len(msg)) |
| |
| |
| if index == -1: |
| return log |
| prefix = log[:index] |
| prefix = colored(prefix, **colored_kwargs) |
| return prefix + msg |
|
|
|
|
| |
| @functools.lru_cache() |
| def _configure_logger( |
| name: Optional[str] = None, |
| *, |
| level: int = logging.DEBUG, |
| output: Optional[str] = None, |
| color: bool = True, |
| log_to_stdout_only_in_main_process: bool = True, |
| ): |
| """ |
| Configure a logger. |
| |
| Adapted from Detectron2. |
| |
| Args: |
| name: The name of the logger to configure. |
| level: The logging level to use. |
| output: A file name or a directory to save log. If None, will not save log file. |
| If ends with ".txt" or ".log", assumed to be a file name. |
| Otherwise, logs will be saved to `output/log.txt`. |
| color: Whether stdout output should be colored (ignored if stdout is not a terminal). |
| log_to_stdout_only_in_main_process: The main process (rank 0) always logs to stdout, |
| regardless of this flag. If False, other ranks will also log to their stdout. |
| |
| Returns: |
| The configured logger. |
| """ |
|
|
| |
| color = color and os.isatty(sys.stdout.fileno()) |
|
|
| logger = logging.getLogger(name) |
| logger.setLevel(level) |
| logger.propagate = False |
|
|
| |
| |
| |
| |
| fmt_prefix = "%(levelname).1s%(asctime)s %(process)s %(name)s %(filename)s:%(lineno)s] " |
| fmt_message = "%(message)s" |
| fmt = fmt_prefix + fmt_message |
| datefmt = "%Y%m%d %H:%M:%S" |
| plain_formatter = logging.Formatter(fmt=fmt, datefmt=datefmt) |
|
|
| torch_env = TorchDistributedEnvironment() |
|
|
| |
| should_log_to_stdout = torch_env.is_main_process or not log_to_stdout_only_in_main_process |
| if should_log_to_stdout: |
| handler = logging.StreamHandler(stream=sys.stdout) |
| handler.setLevel(logging.DEBUG) |
|
|
| formatter: logging.Formatter |
| if color: |
| formatter = _LevelColoredFormatter( |
| fmt=fmt, |
| datefmt=datefmt, |
| ) |
| else: |
| formatter = plain_formatter |
|
|
| handler.setFormatter(formatter) |
| logger.addHandler(handler) |
|
|
| |
| if output: |
| if os.path.splitext(output)[-1] in (".txt", ".log"): |
| filename = output |
| else: |
| filename = os.path.join(output, "logs", "log.txt") |
|
|
| if not torch_env.is_main_process: |
| filename = filename + f".rank{torch_env.rank}" |
|
|
| os.makedirs(os.path.dirname(filename), exist_ok=True) |
|
|
| handler = logging.StreamHandler(open(filename, "a")) |
| handler.setLevel(logging.DEBUG) |
| handler.setFormatter(plain_formatter) |
| logger.addHandler(handler) |
|
|
| logger.debug(f"PyTorch distributed environment: {torch_env}") |
| return logger |
|
|
|
|
| def setup_logging( |
| output: Optional[str] = None, |
| *, |
| name: Optional[str] = None, |
| level: int = logging.DEBUG, |
| color: bool = True, |
| capture_warnings: bool = True, |
| log_to_stdout_only_in_main_process: bool = True, |
| ) -> None: |
| """ |
| Setup logging. |
| |
| Args: |
| output: A file name or a directory to save log files. If None, log |
| files will not be saved. If output ends with ".txt" or ".log", it |
| is assumed to be a file name. |
| Otherwise, logs will be saved to `output/log.txt`. |
| name: The name of the logger to configure, by default the root logger. |
| level: The logging level to use. |
| color: Whether stdout output should be colored (ignored if stdout is not a terminal). |
| capture_warnings: Whether warnings should be captured as logs. |
| log_to_stdout_only_in_main_process: The main process (rank 0) always logs to stdout, |
| regardless of this flag. If False, other ranks will also log to their stdout. |
| """ |
| logging.captureWarnings(capture_warnings) |
| |
| output = output if output is None else os.path.realpath(output) |
| _configure_logger( |
| name, |
| level=level, |
| output=output, |
| color=color, |
| log_to_stdout_only_in_main_process=log_to_stdout_only_in_main_process, |
| ) |
|
|
|
|
| def cleanup_logging(*, name: Optional[str] = None) -> None: |
| logger = logging.getLogger(name) |
| for handler in logger.handlers: |
| handler.flush() |
| handler.close() |
| logger.removeHandler(handler) |
|
|
| |
| _configure_logger.cache_clear() |
|
|