配置日志记录#

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

日志目录#

默认情况下,Ray 日志文件存储在 /tmp/ray/session_*/logs 目录中。查看下面的 日志文件在日志目录中的结构 以了解它们在日志文件夹中的组织方式。

备注

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

一个新的 Ray 会话会在临时目录中创建一个新文件夹。最新的会话文件夹会被符号链接到 /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
│   ├── ...

通常,临时目录会在机器重启时被清理。因此,每当你的集群或某些节点停止或终止时,日志文件可能会丢失。

如果您需要在集群停止或终止后检查日志,您需要存储并持久化这些日志。查看有关如何处理和导出日志的说明,适用于 虚拟机上的集群KubeRay 集群

日志文件在日志目录中#

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

备注

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

应用程序日志#

  • job-driver-[submission_id].log: 通过 Ray Jobs API 提交作业的标准输出。

  • worker-[worker_id]-[job_id]-[pid].[out|err]: Ray 驱动程序和工作者中的 Python 或 Java 部分。所有来自任务或角色的标准输出和标准错误都会流到这些文件中。请注意,job_id 是驱动程序的 ID。

系统(组件)日志#

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

  • dashboard_agent.log: 每个 Ray 节点都有一个仪表盘代理。这是该代理的日志文件。

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

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

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

  • monitor.[out|err]: 集群启动器的标准输出和标准错误。

  • monitor.log: Ray 的集群启动器从一个监控进程运行。它还管理自动扩展器。

  • plasma_store.[out|err]: 已弃用。

  • python-core-driver-[worker_id]_[pid].log: Ray 驱动程序由 CPP 核心和 Python 或 Java 前端组成。CPP 代码生成此日志文件。

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

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

  • redis-shard_[shard_index].[out|err]: Redis 分片日志文件。

  • redis.[out|err]: Redis 日志文件。

  • runtime_env_agent.log: 每个 Ray 节点都有一个代理管理 运行时环境 的创建、删除和缓存。这是代理的日志文件,包含创建或删除请求以及缓存命中和未命中的日志。对于实际安装的日志(例如,pip install 日志),请参阅 runtime_env_setup-[job_id].log 文件(见下文)。

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

  • runtime_env_setup-[job_id].log: 安装任务、角色或作业的 运行时环境 的日志。仅当安装了运行时环境时,此文件才会存在。

将工作节点日志重定向到驱动节点#

默认情况下,任务和角色的 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())

所有从 print 方法发出的 stdout 都会以 (任务或角色表示, 进程ID, IP地址) 前缀打印到驱动程序。

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

自定义Actor日志的前缀#

区分来自不同参与者的日志消息通常很有用。例如,如果你有大量工作参与者,你可能希望轻松看到记录特定消息的参与者的索引。为参与者类定义 __repr__ <https://docs.python.org/3/library/functions.html#repr>__ 方法,以用参与者的表示替换参与者名称。例如:

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

为演员日志前缀着色#

默认情况下,Ray 以浅蓝色打印 Actor 日志前缀。通过设置环境变量 RAY_COLOR_PREFIX=0 关闭颜色日志(例如,当将日志输出到不支持 ANSI 代码的文件或其他位置时)。或者通过设置环境变量 RAY_COLOR_PREFIX=1 激活多色前缀;这会根据每个进程的 PID 对颜色数组进行取模索引。

coloring-actor-log-prefixes

禁用日志记录到驱动程序#

在大规模运行中,将所有工作节点日志路由到驱动程序可能是不需要的。通过在 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 会去重在多个进程中重复出现的日志。每条日志消息的第一个实例总是立即打印。然而,后续相同模式的日志消息(忽略包含数字成分的单词)会被缓冲最多五秒钟,然后批量打印。例如,对于以下代码片段:

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 等库时特别有用,这些库在导入时可能会发出许多冗长的警告消息。按如下方式配置此功能:

  1. 设置 RAY_DEDUP_LOGS=0 以完全禁用此功能。

  2. 设置 RAY_DEDUP_LOGS_AGG_WINDOW_S=<int> 以更改聚合窗口。

  3. 设置 RAY_DEDUP_LOGS_ALLOW_REGEX=<字符串> 以指定永不重复的日志消息。

  4. 设置 RAY_DEDUP_LOGS_SKIP_REGEX=<字符串> 以指定跳过打印的日志消息。

分布式进度条 (tqdm)#

在使用 tqdm 在 Ray 远程任务或角色中时,你可能会注意到进度条输出是混乱的。为了避免这个问题,请使用 Ray 分布式的 tqdm 实现 ray.experimental.tqdm_ray:

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. tqdm_ray 模块将 TQDM 调用转换为写入工作进程标准输出的特殊 JSON 日志消息。

  2. Ray 日志监控器将这些日志消息路由到一个 tqdm 单例,而不是直接复制到驱动程序的标准输出。

  3. tqdm 单例确定来自各种 Ray 任务或角色的进度条的位置,确保它们不会相互碰撞或冲突。

限制:

  • 仅支持 tqdm 功能的一部分。更多详情请参阅 ray_tqdm 实现

  • 如果每秒更新次数超过几千次(更新未批处理),性能可能会较差。

默认情况下,当使用 tqdm_ray 时,内置的 print 也会被修补以使用 ray.experimental.tqdm_ray.safe_print。这避免了在驱动程序打印语句时进度条的损坏。要禁用此功能,请设置 RAY_TQDM_PATCH_PRINT=0

使用 Ray 的日志记录器#

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

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

import logging

logger = logging.getLogger("ray")
logger # 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 aditional file handler for Ray Tune:
ray_tune_logger.addHandler(logging.FileHandler("extra_ray_tune_log.log"))

结构化日志#

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

应用程序日志#

Ray 应用程序包括驱动程序和工作进程。对于 Python 应用程序,使用 Python 记录器来格式化和结构化您的日志。因此,需要为驱动程序和工作进程设置 Python 记录器。

注意

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

分别为驱动程序和工作进程设置Python日志记录:

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

  2. 使用 worker_process_setup_hook 来配置所有工作进程的 Python 日志记录器。

设置Python日志记录器

如果你想控制特定角色或任务的记录器,请查看 为单个工作进程自定义记录器

如果你正在使用任何 Ray 库,请按照该库文档中提供的说明进行操作。

系统日志#

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

CPP 日志的日志格式

[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 的 ray.runtime_context.get_runtime_context API 获取 Jobs、Tasks 或 Actors 的元数据。

获取作业ID。

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

job_id = ray.get_runtime_context().get_job_id

注意

作业提交ID 尚未支持。此 GitHub 问题 跟踪了支持该功能的工作。

获取演员ID。

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

获取任务ID。

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

获取节点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 时,任务和角色在 Ray 的工作进程中远程执行。要为工作进程提供自己的日志配置,请按照以下说明自定义工作进程的日志记录器:

在定义任务或角色时,自定义日志记录配置。

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 将新的日志配置应用于作业中的所有工作进程。

# 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 支持日志文件的日志轮转。请注意,并非所有组件都支持日志轮转。(Raylet、Python 和 Java 工作线程日志不进行轮转)。

默认情况下,日志在达到512MB(maxBytes)时会进行轮转,并且最多保留五个备份文件(backupCount)。索引会附加到所有备份文件中(例如,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 上的日志持久化虚拟机上的日志持久化 以获取更多详细信息。