配置日志记录#
本指南帮助你理解和修改 Ray 的日志系统配置。
日志目录#
默认情况下,Ray 日志文件存储在 /tmp/ray/session_*/logs
目录中。查看下面的 日志文件在日志目录中的结构 以了解它们在日志文件夹中的组织方式。
备注
Ray 使用 /tmp/ray
(适用于 Linux 和 macOS)作为默认的临时目录。要更改临时目录和日志目录,请在调用 ray start
或 ray.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 对颜色数组进行取模索引。
禁用日志记录到驱动程序#
在大规模运行中,将所有工作节点日志路由到驱动程序可能是不需要的。通过在 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)
此功能在导入诸如 tensorflow
或 numpy
等库时特别有用,这些库在导入时可能会发出许多冗长的警告消息。按如下方式配置此功能:
设置
RAY_DEDUP_LOGS=0
以完全禁用此功能。设置
RAY_DEDUP_LOGS_AGG_WINDOW_S=<int>
以更改聚合窗口。设置
RAY_DEDUP_LOGS_ALLOW_REGEX=<字符串>
以指定永不重复的日志消息。设置
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 实现的工作原理如下:
tqdm_ray
模块将 TQDM 调用转换为写入工作进程标准输出的特殊 JSON 日志消息。Ray 日志监控器将这些日志消息路由到一个 tqdm 单例,而不是直接复制到驱动程序的标准输出。
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日志记录:
在导入
ray
后,为驱动进程设置日志记录器。使用
worker_process_setup_hook
来配置所有工作进程的 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 上的日志持久化 和 虚拟机上的日志持久化 以获取更多详细信息。