配置日志记录#

本指南帮助您理解和修改 Ray 日志系统的配置。

日志目录#

默认情况下,Ray 将日志文件存储在 /tmp/ray/session_*/logs 目录中。查看下方的日志目录中的日志文件,了解 Ray 如何在 logs 文件夹中组织日志文件。

注意

对于 Linux 和 macOS,Ray 使用 /tmp/ray 作为默认临时目录。要更改临时目录和日志目录,请在调用 ray startray.init() 时指定。

新的 Ray session 会在临时目录中创建一个新文件夹。Ray 将最新的 session 文件夹符号链接到 /tmp/ray/session_latest。以下是临时目录示例

├── tmp/ray
│   ├── session_latest
│   │   ├── logs
│   │   ├── ...
│   ├── session_2023-05-14_21-19-58_128000_45083
│   │   ├── logs
│   │   ├── ...
│   ├── session_2023-05-15_21-54-19_361265_24281
│   ├── ...

通常,Ray 会在机器重启时清理临时目录。因此,当您的集群或部分节点停止时,日志文件可能会丢失。

如果您需要在集群停止后检查日志,则需要存储和持久化日志。有关如何处理和导出日志的说明,请参阅 日志持久化KubeRay 集群

日志目录中的日志文件#

以下是日志目录中的日志文件。广义上讲,存在两种类型的日志文件:系统日志文件和应用程序日志文件。请注意,.out 日志来自 stdout/stderr,.err 日志来自 stderr。Ray 不保证日志目录的向后兼容性。

注意

系统日志可能包含有关您的应用程序的信息。例如,runtime_env_setup-[job_id].log 可能包含有关您的应用程序环境和依赖项的信息。

应用程序日志#

  • job-driver-[submission_id].log:使用 Ray Jobs API 提交的作业的 stdout。

  • worker-[worker_id]-[job_id]-[pid].[out|err]:Ray driver 和 worker 的 Python 或 Java 部分。Ray 将 Tasks 或 Actors 的所有 stdout 和 stderr 流式传输到这些文件。请注意,job_id 是 driver 的 ID。

系统/组件日志#

  • dashboard.[log|out|err]:Ray Dashboard 的日志文件。.log 文件包含由 dashboard 日志记录器生成的日志。.out.err 文件分别包含从 dashboard 打印的 stdout 和 stderr。除非 dashboard 意外崩溃,否则它们通常是空的。

  • dashboard_agent.[log|out|err]:每个 Ray 节点都有一个 dashboard agent。.log 文件包含由 dashboard agent 日志记录器生成的日志。.out.err 文件分别包含从 dashboard agent 打印的 stdout 和 stderr。除非 dashboard agent 意外崩溃,否则它们通常是空的。

  • dashboard_[module_name].[log|out|err]:Ray Dashboard 子进程的日志文件,每个模块一个。.log 文件包含由模块日志记录器生成的日志。.out.err 文件分别包含从模块打印的 stdout 和 stderr。除非模块意外崩溃,否则它们通常是空的。

  • gcs_server.[out|err]:GCS 服务器是管理 Ray 集群元数据的无状态服务器。它仅存在于 head 节点中。

  • io-worker-[worker_id]-[pid].[out|err]:Ray 默认从 Ray 1.3+ 开始创建 IO worker 用于溢出/恢复对象到外部存储。这是 IO worker 的日志文件。

  • log_monitor.[log|out|err]:日志监控器负责将日志流式传输到 driver。.log 文件包含由日志监控器日志记录器生成的日志。.out.err 文件分别包含从日志监控器打印的 stdout 和 stderr。除非日志监控器意外崩溃,否则它们通常是空的。

  • monitor.[log|out|err]:Autoscaler 的日志文件。.log 文件包含由 autoscaler 日志记录器生成的日志。.out.err 文件分别包含从 autoscaler 打印的 stdout 和 stderr。除非 autoscaler 意外崩溃,否则它们通常是空的。

  • python-core-driver-[worker_id]_[pid].log:Ray driver 由 C++ core 和 Python 或 Java 前端组成。C++ 代码生成此日志文件。

  • python-core-worker-[worker_id]_[pid].log:Ray worker 由 C++ core 和 Python 或 Java 前端组成。C++ 代码生成此日志文件。

  • raylet.[out|err]:raylet 的日志文件。

  • runtime_env_agent.[log|out|err]:每个 Ray 节点都有一个代理,管理 运行时环境 的创建、删除和缓存。.log 文件包含由 runtime env agent 日志记录器生成的日志。.out.err 文件分别包含从 runtime env agent 打印的 stdout 和 stderr。除非 runtime env agent 意外崩溃,否则它们通常是空的。pip install 的实际安装日志位于以下 runtime_env_setup-[job_id].log 文件中。

  • runtime_env_setup-ray_client_server_[port].log:使用 Ray Client 连接时安装 运行时环境 的日志。

  • runtime_env_setup-[job_id].log:为 Task、Actor 或 Job 安装 运行时环境 的日志。仅当您安装运行时环境时才会出现此文件。

将 Worker 日志重定向到 Driver#

默认情况下,Tasks 和 Actors 的 Worker stdout 和 stderr 会流式传输到 Ray Driver(调用 ray.init 的入口脚本)。这有助于用户将分布式 Ray 应用程序的日志聚合到一处。

import ray

# Initiate a driver.
ray.init()


@ray.remote
def task():
    print("task")


ray.get(task.remote())


@ray.remote
class Actor:
    def ready(self):
        print("actor")


actor = Actor.remote()
ray.get(actor.ready.remote())

Ray 会将从 print 方法输出的所有 stdout 打印到 driver,并带有 (Task or Actor repr, process ID, IP address) 前缀。

(pid=45601) task
(Actor pid=480956) actor

自定义 Actor 日志前缀#

区分不同 Actor 的日志消息通常很有用。例如,如果您有大量 worker Actor,您可能希望轻松查看记录特定消息的 Actor 的索引。为 Actor 类定义 __repr__ <https://docs.pythonlang.cn/3/library/functions.html#repr>__ 方法,以 Actor repr 替换 Actor 名称。例如

import ray


@ray.remote
class MyActor:
    def __init__(self, index):
        self.index = index

    def foo(self):
        print("hello there")

    def __repr__(self):
        return f"MyActor(index={self.index})"


a = MyActor.remote(1)
b = MyActor.remote(2)
ray.get(a.foo.remote())
ray.get(b.foo.remote())

结果输出如下

(MyActor(index=2) pid=482120) hello there
(MyActor(index=1) pid=482119) hello there

为 Actor 日志前缀着色#

默认情况下,Ray 使用浅蓝色打印 Actor 日志前缀。通过设置环境变量 RAY_COLOR_PREFIX=0 可以关闭彩色日志记录

  • 例如,当将日志输出到不支持 ANSI 代码的文件或其他位置时。或者通过设置环境变量 RAY_COLOR_PREFIX=1 激活多色前缀;这将根据每个进程的 PID 索引到颜色数组中。

coloring-actor-log-prefixes

禁用日志记录到 driver#

在大规模运行时,您可能不想将所有 worker 日志路由到 driver。通过在 ray.init 中设置 log_to_driver=False 可以禁用此功能

import ray

# Task and Actor logs are not copied to the driver stdout.
ray.init(log_to_driver=False)

日志去重#

默认情况下,Ray 会对跨多个进程冗余出现的日志进行去重。每个日志消息的第一个实例总是立即打印。但是,Ray 会缓冲相同模式的后续日志消息长达五秒钟,并批量打印它们。请注意,Ray 还会忽略带有数字成分的单词。例如,对于以下代码片段

import ray
import random

@ray.remote
def task():
    print("Hello there, I am a task", random.random())

ray.get([task.remote() for _ in range(100)])

输出如下

2023-03-27 15:08:34,195	INFO worker.py:1603 -- Started a local Ray instance. View the dashboard at http://127.0.0.1:8265
(task pid=534172) Hello there, I am a task 0.20583517821231412
(task pid=534174) Hello there, I am a task 0.17536720316370757 [repeated 99x across cluster] (Ray deduplicates logs by default. Set RAY_DEDUP_LOGS=0 to disable log deduplication)

此功能在导入 tensorflownumpy 等库时非常有用,这些库在导入时可能会发出许多详细的警告消息。

在 driver 进程中导入 Ray 之前配置以下环境变量以自定义日志去重

  • 设置 RAY_DEDUP_LOGS=0 可完全关闭此功能。

  • 设置 RAY_DEDUP_LOGS_AGG_WINDOW_S=<int> 可更改聚合窗口。

  • 设置 RAY_DEDUP_LOGS_ALLOW_REGEX=<string> 可指定永不去重的日志消息。

    • 示例#

      import os
      os.environ["RAY_DEDUP_LOGS_ALLOW_REGEX"] = "ABC"
      
      import ray
      
      @ray.remote
      def f():
          print("ABC")
          print("DEF")
      
      ray.init()
      ray.get([f.remote() for _ in range(5)])
      
      # 2024-10-10 17:54:19,095 INFO worker.py:1614 -- Connecting to existing Ray cluster at address: 172.31.13.10:6379...
      # 2024-10-10 17:54:19,102 INFO worker.py:1790 -- Connected to Ray cluster. View the dashboard at 127.0.0.1:8265
      # (f pid=1574323) ABC
      # (f pid=1574323) DEF
      # (f pid=1574321) ABC
      # (f pid=1574318) ABC
      # (f pid=1574320) ABC
      # (f pid=1574322) ABC
      # (f pid=1574322) DEF [repeated 4x across cluster] (Ray deduplicates logs by default. Set RAY_DEDUP_LOGS=0 to disable log deduplication, or see https://docs.rayai.org.cn/en/master/ray-observability/user-guides/configure-logging.html#log-deduplication for more options.)
      
  • 设置 RAY_DEDUP_LOGS_SKIP_REGEX=<string> 可指定跳过打印的日志消息。

    • 示例#

      import os
      os.environ["RAY_DEDUP_LOGS_SKIP_REGEX"] = "ABC"
      
      import ray
      
      @ray.remote
      def f():
          print("ABC")
          print("DEF")
      
      ray.init()
      ray.get([f.remote() for _ in range(5)])
      # 2024-10-10 17:55:05,308 INFO worker.py:1614 -- Connecting to existing Ray cluster at address: 172.31.13.10:6379...
      # 2024-10-10 17:55:05,314 INFO worker.py:1790 -- Connected to Ray cluster. View the dashboard at 127.0.0.1:8265
      # (f pid=1574317) DEF
      # (f pid=1575229) DEF [repeated 4x across cluster] (Ray deduplicates logs by default. Set RAY_DEDUP_LOGS=0 to disable log deduplication, or see https://docs.rayai.org.cn/en/master/ray-observability/user-guides/configure-logging.html#log-deduplication for more options.)
      

使用 tqdm 的分布式进度条#

当在 Ray 远程 Tasks 或 Actors 中使用 tqdm 时,您可能会注意到进度条输出已损坏。为避免此问题,请使用位于 ray.experimental.tqdm_ray 的 Ray 分布式 tqdm 实现

import time
import ray

# Instead of "from tqdm import tqdm", use:
from ray.experimental.tqdm_ray import tqdm


@ray.remote
def f(name):
    for x in tqdm(range(100), desc=name):
        time.sleep(0.1)


ray.get([f.remote("task 1"), f.remote("task 2")])

此 tqdm 实现工作原理如下

  1. The tqdm_ray 模块将 tqdm 调用转换为写入 worker stdout 的特殊 JSON 日志消息。

  2. Ray 日志监控器将这些日志消息路由到 tqdm 单例,而不是直接复制到 driver stdout。

  3. The tqdm singleton determines the positions of progress bars from various Ray Tasks or Actors, ensuring they don’t collide or conflict with each other.

限制

  • Ray 仅支持 tqdm 功能的子集。有关更多详细信息,请参阅 ray_tqdm 的实现

  • 如果每秒更新次数超过几千次,性能可能会很差,因为 Ray 不会批量更新。

默认情况下,当您使用 tqdm_ray 时,内置的 print 函数也会被修补以使用 ray.experimental.tqdm_ray.safe_print。这可以避免 driver print 语句上的进度条损坏。要关闭此功能,请设置 RAY_TQDM_PATCH_PRINT=0

使用 Ray 的日志记录器#

当 Ray 执行 import ray 时,Ray 会初始化 Ray 的日志记录器,生成 python/ray/_private/log.py 中给出的默认配置。默认日志级别为 logging.INFO

所有 Ray 日志记录器都在 ray._private.ray_logging 中自动配置。要修改 Ray 日志记录器

import logging

logger = logging.getLogger("ray")
logger.setLevel(logging.WARNING) # Modify the Ray logging config

类似地,要修改 Ray 库的日志记录配置,请指定适当的日志记录器名称

import logging

# First, get the handle for the logger you want to modify
ray_data_logger = logging.getLogger("ray.data")
ray_tune_logger = logging.getLogger("ray.tune")
ray_rllib_logger = logging.getLogger("ray.rllib")
ray_train_logger = logging.getLogger("ray.train")
ray_serve_logger = logging.getLogger("ray.serve")

# Modify the ray.data logging level
ray_data_logger.setLevel(logging.WARNING)

# Other loggers can be modified similarly.
# Here's how to add an additional file handler for Ray Tune:
ray_tune_logger.addHandler(logging.FileHandler("extra_ray_tune_log.log"))

将 Ray 日志记录器用于应用程序日志#

Ray 应用程序包括 driver 和 worker 进程。对于 Python 应用程序,请使用 Python 日志记录器格式化您的日志。因此,您需要为 driver 和 worker 进程设置 Python 日志记录器。

注意

这是一项实验性功能。它尚不支持 Ray Client

分别设置 driver 和 worker 进程的 Python 日志记录器

  1. 导入 ray 后,为 driver 进程设置日志记录器。

  2. 使用 worker_process_setup_hook 为所有 worker 进程配置 Python 日志记录器。

Set up python loggers

如果您想控制特定 Actor 或任务的日志记录器,请参阅以下 为单个 worker 进程自定义日志记录器

如果您正在使用任何 Ray 库,请遵循库文档中提供的说明。

自定义 worker 进程日志记录器#

Ray 在 Ray 的 worker 进程中远程执行 Tasks 和 Actors。要为 worker 进程提供您自己的日志记录配置,请按照以下说明自定义 worker 日志记录器

在定义 Tasks 或 Actors 时自定义日志记录配置。

import ray
import logging
# Initiate a driver.
ray.init()

@ray.remote
class Actor:
    def __init__(self):
        # Basic config automatically configures logs to
        # stream to stdout and stderr.
        # Set the severity to INFO so that info logs are printed to stdout.
        logging.basicConfig(level=logging.INFO)

    def log(self, msg):
        logger = logging.getLogger(__name__)
        logger.info(msg)

actor = Actor.remote()
ray.get(actor.log.remote("A log message for an actor."))

@ray.remote
def f(msg):
    logging.basicConfig(level=logging.INFO)
    logger = logging.getLogger(__name__)
    logger.info(msg)

ray.get(f.remote("A log message for a task."))
(Actor pid=179641) INFO:__main__:A log message for an actor.
(f pid=177572) INFO:__main__:A log message for a task.

注意

这是一项实验性功能。此 API 的语义可能会发生变化。它尚不支持 Ray Client

使用 worker_process_setup_hook 将新的日志记录配置应用于作业内的所有 worker 进程。

# driver.py
def logging_setup_func():
    logger = logging.getLogger("ray")
    logger.setLevel(logging.DEBUG)
    warnings.simplefilter("always")

ray.init(runtime_env={"worker_process_setup_hook": logging_setup_func})

logging_setup_func()

如果您使用任何 Ray 库,请遵循库文档中提供的说明。

结构化日志记录#

实现结构化日志记录,使下游用户和应用程序能够高效地使用日志。

应用程序日志#

Ray 允许用户配置 Python logging 库以结构化格式输出日志。这种设置标准化了日志条目,使其更易于处理。

配置 Ray Core 的结构化日志记录#

Ray 库

如果您正在使用任何 Ray 库,请遵循库文档中提供的说明。

以下方法是配置 Ray Core 结构化日志格式的方式

方法 1:使用 ray.init 配置结构化日志记录#
ray.init(
    log_to_driver=False,
    logging_config=ray.LoggingConfig(encoding="JSON", log_level="INFO")
)

您可以配置以下参数

  • encoding:日志的编码格式。默认值为 TEXT,用于纯文本日志。另一个选项是 JSON,用于结构化日志。在 TEXTJSON 编码格式中,如果可用,日志会包含 Ray 特有的字段,例如 job_idworker_idnode_idactor_idactor_nametask_idtask_nametask_function_name

  • log_level:driver 进程的日志级别。默认值为 INFO。可用日志级别在 Python logging 库 中定义。

  • additional_log_standard_attrs:Ray 2.43 版本起可用。要在日志记录中包含的其他标准 Python logger 属性列表。默认值为空列表。已包含的标准属性列表为:asctimelevelnamemessagefilenamelinenoexc_text。所有有效属性的列表在 Python logging 库 中指定。

当您在 ray.init 中设置 logging_config 时,它会配置 driver 进程、Ray Actor 和 Ray Tasks 的根日志记录器。

注意

log_to_driver 参数设置为 False 可禁用日志记录到 driver 进程,因为重定向到 driver 的日志将包含使日志无法被 JSON 解析的前缀。

方法 2:使用环境变量配置结构化日志记录#

您可以将 RAY_LOGGING_CONFIG_ENCODING 环境变量设置为 TEXTJSON,以设置日志的编码格式。请注意,您需要在 import ray 之前设置环境变量。

import os
os.environ["RAY_LOGGING_CONFIG_ENCODING"] = "JSON"

import ray
import logging

ray.init(log_to_driver=False)
# Use the root logger to print log messages.

示例#

以下示例将 LoggingConfig 配置为以结构化 JSON 格式输出日志,并将日志级别设置为 INFO。然后,它使用 driver 进程、Ray Tasks 和 Ray Actors 中的根日志记录器记录消息。日志会包含适用的 Ray 特有字段,例如 job_idworker_idnode_idactor_idactor_nametask_idtask_nametask_function_name

import ray
import logging

ray.init(
    logging_config=ray.LoggingConfig(encoding="JSON", log_level="INFO", additional_log_standard_attrs=['name'])
)

def init_logger():
    """Get the root logger"""
    return logging.getLogger()

logger = logging.getLogger()
logger.info("Driver process")

@ray.remote
def f():
    logger = init_logger()
    logger.info("A Ray task")

@ray.remote
class actor:
    def print_message(self):
        logger = init_logger()
        logger.info("A Ray actor")

task_obj_ref = f.remote()
ray.get(task_obj_ref)

actor_instance = actor.remote()
ray.get(actor_instance.print_message.remote())

"""
{"asctime": "2025-02-25 22:06:00,967", "levelname": "INFO", "message": "Driver process", "filename": "test-log-config-doc.py", "lineno": 13, "name": "root", "job_id": "01000000", "worker_id": "01000000ffffffffffffffffffffffffffffffffffffffffffffffff", "node_id": "543c939946ec1321c9c1a10899bfb72f59aa6eab7655719f2611da04", "timestamp_ns": 1740549960968002000}
{"asctime": "2025-02-25 22:06:00,974", "levelname": "INFO", "message": "A Ray task", "filename": "test-log-config-doc.py", "lineno": 18, "name": "root", "job_id": "01000000", "worker_id": "162f2bd846e84685b4c07eb75f2c1881b9df1cdbf58ffbbcccbf2c82", "node_id": "543c939946ec1321c9c1a10899bfb72f59aa6eab7655719f2611da04", "task_id": "c8ef45ccd0112571ffffffffffffffffffffffff01000000", "task_name": "f", "task_func_name": "test-log-config-doc.f", "timestamp_ns": 1740549960974027000}
{"asctime": "2025-02-25 22:06:01,314", "levelname": "INFO", "message": "A Ray actor", "filename": "test-log-config-doc.py", "lineno": 24, "name": "root", "job_id": "01000000", "worker_id": "b7fd965bb12b1046ddfa3d73ead5ed54eb7678d97e743d98dfab852b", "node_id": "543c939946ec1321c9c1a10899bfb72f59aa6eab7655719f2611da04", "actor_id": "43b5d1828ad0a003ca6ebcfc01000000", "task_id": "c2668a65bda616c143b5d1828ad0a003ca6ebcfc01000000", "task_name": "actor.print_message", "task_func_name": "test-log-config-doc.actor.print_message", "actor_name": "", "timestamp_ns": 1740549961314391000}
"""

向结构化日志添加元数据#

通过在 logger.info 方法中使用 extra 参数,可以向日志条目添加额外字段。

import ray
import logging

ray.init(
    log_to_driver=False,
    logging_config=ray.LoggingConfig(encoding="JSON", log_level="INFO")
)

logger = logging.getLogger()
logger.info("Driver process with extra fields", extra={"username": "anyscale"})

# The log entry includes the extra field "username" with the value "anyscale".

# {"asctime": "2024-07-17 21:57:50,891", "levelname": "INFO", "message": "Driver process with extra fields", "filename": "test.py", "lineno": 9, "username": "anyscale", "job_id": "04000000", "worker_id": "04000000ffffffffffffffffffffffffffffffffffffffffffffffff", "node_id": "76cdbaa32b3938587dcfa278201b8cef2d20377c80ec2e92430737ae"}

如果需要,您可以使用 Ray 的 ray.runtime_context.get_runtime_context API 获取 Jobs、Tasks 或 Actors 的元数据。

获取 job ID。

import ray
# Initiate a driver.
ray.init()

job_id = ray.get_runtime_context().get_job_id

注意

尚不支持 job submission ID。此 GitHub issue 正在跟踪支持此功能的工作。

获取 actor ID。

import ray
# Initiate a driver.
ray.init()
@ray.remote
class actor():
    actor_id = ray.get_runtime_context().get_actor_id

获取 task ID。

import ray
# Initiate a driver.
ray.init()
@ray.remote
def task():
    task_id = ray.get_runtime_context().get_task_id

获取 node ID。

import ray
# Initiate a driver.
ray.init()

# Get the ID of the node where the driver process is running
driver_process_node_id = ray.get_runtime_context().get_node_id

@ray.remote
def task():
    # Get the ID of the node where the worker process is running
    worker_process_node_id = ray.get_runtime_context().get_node_id

提示

如果您需要节点 IP,请使用 ray.nodes API 获取所有节点,并将节点 ID 映射到相应的 IP。

系统日志#

Ray 默认对大多数系统或组件日志进行结构化。

Python 日志记录格式

%(asctime)s\t%(levelname)s %(filename)s:%(lineno)s -- %(message)s

示例#

2023-06-01 09:15:34,601	INFO job_manager.py:408 -- Submitting job with RAY_ADDRESS = 10.0.24.73:6379

C++ 日志记录格式

[year-month-day, time, pid, thread_id] (component) [file]:[line] [message]

示例#

[2023-06-01 08:47:47,457 I 31009 225171] (gcs_server) gcs_node_manager.cc:42: Registering node info, node id = 8cc65840f0a332f4f2d59c9814416db9c36f04ac1a29ac816ad8ca1e, address = 127.0.0.1, node name = 127.0.0.1

注意

截至 2.5 版本,部分系统组件日志并未如前文所述进行结构化。将系统日志迁移到结构化日志的工作正在进行中。

日志轮换#

Ray 支持日志文件的轮换。请注意,并非所有组件都支持日志轮换。(Raylet、Python 和 Java worker 日志不进行轮换)。

默认情况下,日志达到 512 MB (maxBytes) 时会进行轮换,并最多保留五个备份文件 (backupCount)。Ray 会向所有备份文件追加索引,例如 raylet.out.1。要更改日志轮换配置,请指定环境变量。例如,

RAY_ROTATION_MAX_BYTES=1024; ray start --head # Start a ray instance with maxBytes 1KB.
RAY_ROTATION_BACKUP_COUNT=1; ray start --head # Start a ray instance with backupCount 1.

日志文件及其备份的最大大小为 RAY_ROTATION_MAX_BYTES * RAY_ROTATION_BACKUP_COUNT + RAY_ROTATION_MAX_BYTES

日志持久化#

要处理日志并将其导出到外部存储或管理系统,请查看 Kubernetes 上的日志持久化,并参阅 虚拟机上的日志持久化 以了解更多详细信息。