log.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import logging
  2. import threading
  3. import time
  4. from typing import Optional, Union
  5. INTERNAL_TIMESTAMP_LOG_KEY = "_ray_timestamp_ns"
  6. def _print_loggers():
  7. """Print a formatted list of loggers and their handlers for debugging."""
  8. loggers = {logging.root.name: logging.root}
  9. loggers.update(dict(sorted(logging.root.manager.loggerDict.items())))
  10. for name, logger in loggers.items():
  11. if isinstance(logger, logging.Logger):
  12. print(f" {name}: disabled={logger.disabled}, propagate={logger.propagate}")
  13. for handler in logger.handlers:
  14. print(f" {handler}")
  15. def clear_logger(logger: Union[str, logging.Logger]):
  16. """Reset a logger, clearing its handlers and enabling propagation.
  17. Args:
  18. logger: Logger to be cleared
  19. """
  20. if isinstance(logger, str):
  21. logger = logging.getLogger(logger)
  22. logger.propagate = True
  23. logger.handlers.clear()
  24. class PlainRayHandler(logging.StreamHandler):
  25. """A plain log handler.
  26. This handler writes to whatever sys.stderr points to at emit-time,
  27. not at instantiation time. See docs for logging._StderrHandler.
  28. """
  29. def __init__(self):
  30. super().__init__()
  31. self.plain_handler = logging._StderrHandler()
  32. self.plain_handler.level = self.level
  33. self.plain_handler.formatter = logging.Formatter(fmt="%(message)s")
  34. def emit(self, record: logging.LogRecord):
  35. """Emit the log message.
  36. If this is a worker, bypass fancy logging and just emit the log record.
  37. If this is the driver, emit the message using the appropriate console handler.
  38. Args:
  39. record: Log record to be emitted
  40. """
  41. import ray
  42. if (
  43. hasattr(ray, "_private")
  44. and hasattr(ray._private, "worker")
  45. and ray._private.worker.global_worker.mode
  46. == ray._private.worker.WORKER_MODE
  47. ):
  48. self.plain_handler.emit(record)
  49. else:
  50. logging._StderrHandler.emit(self, record)
  51. logger_initialized = False
  52. logging_config_lock = threading.Lock()
  53. def _setup_log_record_factory():
  54. """Setup log record factory to add _ray_timestamp_ns to LogRecord."""
  55. old_factory = logging.getLogRecordFactory()
  56. def record_factory(*args, **kwargs):
  57. record = old_factory(*args, **kwargs)
  58. # Python logging module starts to use `time.time_ns()` to generate `created`
  59. # from Python 3.13 to avoid the precision loss caused by the float type.
  60. # Here, we generate the `created` for the LogRecord to support older Python
  61. # versions.
  62. ct = time.time_ns()
  63. record.created = ct / 1e9
  64. record.__dict__[INTERNAL_TIMESTAMP_LOG_KEY] = ct
  65. return record
  66. logging.setLogRecordFactory(record_factory)
  67. def generate_logging_config():
  68. """Generate the default Ray logging configuration."""
  69. with logging_config_lock:
  70. global logger_initialized
  71. if logger_initialized:
  72. return
  73. logger_initialized = True
  74. plain_formatter = logging.Formatter(
  75. "%(asctime)s\t%(levelname)s %(filename)s:%(lineno)s -- %(message)s"
  76. )
  77. default_handler = PlainRayHandler()
  78. default_handler.setFormatter(plain_formatter)
  79. ray_logger = logging.getLogger("ray")
  80. ray_logger.setLevel(logging.INFO)
  81. ray_logger.addHandler(default_handler)
  82. ray_logger.propagate = False
  83. # Special handling for ray.rllib: only warning-level messages passed through
  84. # See https://github.com/ray-project/ray/pull/31858 for related PR
  85. rllib_logger = logging.getLogger("ray.rllib")
  86. rllib_logger.setLevel(logging.WARN)
  87. # Set up the LogRecord factory.
  88. _setup_log_record_factory()
  89. def setup_process_exit_logger(
  90. process_exit_log_path: str,
  91. level: int = logging.INFO,
  92. formatter: Optional[logging.Formatter] = None,
  93. ) -> logging.Logger:
  94. """Configure and return the 'ray.process_exit' logger with a FileHandler."""
  95. logger = logging.getLogger("ray.process_exit")
  96. logger.setLevel(level)
  97. logger.propagate = False
  98. fh = logging.FileHandler(process_exit_log_path, encoding="utf-8")
  99. if formatter is None:
  100. formatter = logging.Formatter(
  101. "%(asctime)s\t%(levelname)s %(filename)s:%(lineno)s -- %(message)s"
  102. )
  103. fh.setFormatter(formatter)
  104. logger.addHandler(fh)
  105. return logger
  106. def format_returncode(rc: Optional[int]) -> str:
  107. """Return a consistent string for process return code."""
  108. if rc is None:
  109. return "None"
  110. try:
  111. rc_int = int(rc)
  112. except Exception:
  113. return str(rc)
  114. if rc_int < 0:
  115. return f"{rc_int} (signal {-rc_int})"
  116. return f"{rc_int}"