关于日志
ROS 2 的日志子系统旨在将日志消息分发到多种输出目标,包括:
- 控制台(如果已连接)
- 磁盘上的日志文件(如果有可用的本地存储)
- ROS 2 网络中的
/rosouttopic
默认情况下,ROS 2 节点的日志消息会输出到控制台(stderr)、磁盘上的日志文件以及 ROS 2 网络中的 /rosout 话题。所有这些输出目标都可以按节点单独启用或禁用。
本文档的其余部分将介绍日志子系统背后的一些设计理念。
日志消息有一个关联的严重级别(severity level):按照升序排列为 DEBUG、INFO、WARN、ERROR 和 FATAL。
日志记录器(logger)只会处理严重级别达到或超过其设定级别的日志消息。
每个节点都有一个关联的日志记录器,该记录器会自动包含节点的名称和命名空间(namespace)。如果节点名称被外部重新映射(与源代码中定义的名称不同),这一变化会反映在日志记录器名称中。也可以创建带特定名称的独立日志记录器(不与节点关联)。
日志记录器名称具有层次结构。如果名为 “abc.def” 的日志记录器的级别未设置,它将遵循其父级 “abc” 的级别;如果该级别也未设置,则使用默认的日志记录器级别。当日志记录器 “abc” 的级别被更改时,其所有后代(例如 “abc.def”、“abc.ghi.jkl”)的级别都会受到影响,除非它们的级别已被显式设置。
以下是 ROS 2 日志基础设施面向最终用户的 API,按客户端库分类。
RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}- 每次执行到此行时输出给定的 printf 风格消息RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_ONCE- 仅在第一次执行到此行时输出给定的 printf 风格消息RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_EXPRESSION- 仅在给定表达式为 true 时输出给定的 printf 风格消息RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_FUNCTION- 仅在给定函数返回 true 时输出给定的 printf 风格消息RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_SKIPFIRST- 除第一次外,每次执行到此行时输出给定的 printf 风格消息RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_THROTTLE- 以给定的整数毫秒为最短间隔输出给定的 printf 风格消息RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_SKIPFIRST_THROTTLE- 以给定的整数毫秒为最短间隔输出给定的 printf 风格消息,但跳过第一次RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_STREAM- 每次执行到此行时输出给定的 C++ stream 风格消息RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_STREAM_ONCE- 仅在第一次执行到此行时输出给定的 C++ stream 风格消息RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_STREAM_EXPRESSION- 仅在给定表达式为 true 时输出给定的 C++ stream 风格消息RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_STREAM_FUNCTION- 仅在给定函数返回 true 时输出给定的 C++ stream 风格消息RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_STREAM_SKIPFIRST- 除第一次外,每次执行到此行时输出给定的 C++ stream 风格消息RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_STREAM_THROTTLE- 以给定的整数毫秒为最短间隔输出给定的 C++ stream 风格消息RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_STREAM_SKIPFIRST_THROTTLE- 以给定的整数毫秒为最短间隔输出给定的 C++ stream 风格消息,但跳过第一次
上述每个 API 的第一个参数都是 rclcpp::Logger 对象。可以通过调用 node->get_logger()(推荐方式)从节点 API 获取,也可以构造一个独立的 rclcpp::Logger 对象。
rcutils_logging_set_logger_level- 将特定日志记录器名称的日志级别设置为给定的严重级别rcutils_logging_get_logger_effective_level- 给定日志记录器名称,返回该日志记录器的有效级别(可能未设置)
Python
Section titled “Python”-
logger.{debug,info,warning,error,fatal}- 将给定的 Python 字符串输出到日志基础设施。这些调用接受以下关键字参数来控制行为:throttle_duration_sec- 如果不为 None,则表示节流间隔的时长,以浮点秒为单位skip_first- 如果为 True,则除第一次外每次执行到此行时都输出该消息once- 如果为 True,则仅在第一次执行到此行时输出该消息
-
rclpy.logging.set_logger_level- 将特定日志记录器名称的日志级别设置为给定的严重级别 -
rclpy.logging.get_logger_effective_level- 给定日志记录器名称,返回该日志记录器的有效级别(可能未设置)
由于 rclcpp 和 rclpy 使用相同的底层日志基础设施,因此配置选项是相同的。
以下环境变量控制 ROS 2 日志记录器的某些方面。对于每个环境变量设置,请注意这是进程范围的设置,因此适用于该进程中的所有节点。
-
RCL_LOGGING_IMPLEMENTATION- 控制在运行时使用哪个日志后端实现(当使用动态加载时,这是默认方式)。如果非空,使用指定的日志实现库(例如rcl_logging_spdlog、rcl_logging_noop)。如果为空或未设置,默认使用rcl_logging_spdlog。当 RCL 以静态链接方式构建到特定日志实现时,此变量无效。更多详情请参阅下文的rcl_logging_implementation部分。 -
RCL_LOGGING_SPDLOG_FLUSH_PERIOD_SECONDS- 控制使用rcl_logging_spdlog后端时日志文件的定期刷新间隔。默认情况下,日志每 5 秒刷新一次,并在错误级别消息时立即刷新。如果设置为0,则每条日志消息都会立即刷新(无缓冲模式,可能影响性能)。如果设置为正整数N,则每N秒刷新一次日志,并在错误级别消息时立即刷新。如果设置为无效值(非整数、负数或带尾部字符),初始化将失败并报错。 -
ROS_LOG_DIR- 控制日志消息写入磁盘的日志目录(如果已启用)。如果非空,使用此变量中指定的确切目录。如果为空,则使用ROS_HOME环境变量的内容构造格式为$ROS_HOME/.log的路径。在所有情况下,~字符会展开为用户的 HOME 目录。 -
ROS_HOME- 控制用于各种 ROS 文件(包括日志和配置文件)的主目录。在日志的上下文中,此变量用于构造日志文件目录的路径。如果非空,使用此变量的内容作为 ROS_HOME 路径。在所有情况下,~字符会展开为用户的 HOME 目录。 -
RCUTILS_LOGGING_USE_STDOUT- 控制日志消息发送到哪个流。如果未设置或为 0,使用 stderr。如果为 1,使用 stdout。 -
RCUTILS_LOGGING_BUFFERED_STREAM- 控制日志流(通过RCUTILS_LOGGING_USE_STDOUT配置)是行缓冲还是无缓冲。如果未设置,使用流的默认值(通常 stdout 为行缓冲,stderr 为无缓冲)。如果为 0,强制流为无缓冲。如果为 1,强制流为行缓冲。 -
RCUTILS_COLORIZED_OUTPUT- 控制输出消息时是否使用颜色。如果未设置,根据平台和控制台是否为 TTY 自动确定。如果为 0,强制禁用颜色输出。如果为 1,强制启用颜色输出。 -
RCUTILS_CONSOLE_OUTPUT_FORMAT- 控制每条日志消息输出的字段。可用字段包括:{severity}- 严重级别。{name}- 日志记录器的名称(可能为空)。{message}- 日志消息(可能为空)。{function_name}- 调用来源的函数名(可能为空)。{file_name}- 调用来源的文件名(可能为空)。{short_file_name}- 调用来源的不含目录路径的文件名(仅 basename)(可能为空)。{time}- 自 epoch 以来的时间(秒)。{time_as_nanoseconds}- 自 epoch 以来的时间(纳秒)。{date_time_with_ms}- ISO 格式的时间,例如2024-06-11 09:29:19.304。{line_number}- 调用来源的行号(可能为空)。
如果未给定格式,则使用默认值
[{severity}] [{time}] [{name}]: {message}。
RCUTILS_CONSOLE_OUTPUT_FORMAT 还支持以下转义字符语法。
| 转义字符语法 | 表示的字符 |
|---|---|
\a | Alert(警报) |
\b | Backspace(退格) |
\n | New line(换行) |
\r | Carriage return(回车) |
\t | Horizontal tab(水平制表符) |
在初始化 ROS 2 节点时,可以通过节点选项控制行为的某些方面。由于这些是按节点的选项,即使多个节点被组合到单个进程中,也可以为不同节点设置不同的选项。
log_levels- 用于该特定节点内组件的日志级别。可以通过以下方式设置:ros2 run demo_nodes_cpp talker --ros-args --log-level talker:=DEBUGexternal_log_config_file- 用于配置后端日志记录器的外部文件。如果为 NULL,将使用默认配置。请注意,此文件的格式是后端特定的(目前默认的 spdlog 后端日志记录器尚未实现)。可以通过以下方式设置:ros2 run demo_nodes_cpp talker --ros-args --log-config-file log-config.txtlog_stdout_disabled- 是否禁用将日志消息写入控制台。可以通过以下方式设置:ros2 run demo_nodes_cpp talker --ros-args --disable-stdout-logslog_rosout_disabled- 是否禁用将日志消息写入/rosout。这可以显著节省网络带宽,但外部观察者将无法监控日志。可以通过以下方式设置:ros2 run demo_nodes_cpp talker --ros-args --disable-rosout-logslog_ext_lib_disabled- 是否完全禁用外部日志记录器的使用。在某些情况下可能更快,但意味着日志不会写入磁盘。可以通过以下方式设置:ros2 run demo_nodes_cpp talker --ros-args --disable-external-lib-logs
日志子系统设计
Section titled “日志子系统设计”下图展示了日志子系统的主要组成部分及其交互方式。注意,rcl 可以通过 rcl_logging_implementation 抽象层(用于运行时动态加载,即默认方式)链接到日志实现,也可以直接链接到特定实现如 rcl_logging_spdlog(用于静态链接)。
说明:此处原文包含一张 ROS 2 日志架构图,展示了
rcutils、rcl_logging_implementation、rcl、rclcpp/rclpy等组件之间的关系。详见 ROS 2 官方文档。
rcutils
Section titled “rcutils”rcutils 有一个日志实现,可以根据特定格式(参见上文 配置 部分)格式化日志消息,并将这些日志消息输出到控制台。rcutils 实现了完整的日志解决方案,但允许高层组件以依赖注入的方式介入日志处理流程。
请注意,这是一个进程级别的日志实现,因此在此级别配置的任何内容都会影响整个进程,而不仅仅是单个节点。
rcl_logging_implementation
Section titled “rcl_logging_implementation”rcl_logging_implementation 是一个支持 ROS 2 中运行时动态加载日志后端的包,类似于 rmw_implementation 在中间件选择方面的作用。该抽象层允许用户在不同的日志实现(如 rcl_logging_spdlog 和 rcl_logging_noop)之间切换,而无需重新编译 RCL 或应用程序代码。
说明:此处原文包含一张
rcl_logging_implementation架构图,展示了动态加载和静态链接两种构建配置下的组件关系。详见 ROS 2 官方文档。
运行时动态加载 vs 静态链接
Section titled “运行时动态加载 vs 静态链接”日志系统支持两种构建配置:
动态加载(默认)
默认情况下,rcl 链接到 rcl_logging_implementation,后者在运行时动态加载日志后端。这种方式提供了最大的灵活性,允许通过环境变量更改日志实现而无需重新编译。
使用动态加载时:
- 日志实现在运行时作为共享库加载
- 实际实现可以通过
RCL_LOGGING_IMPLEMENTATION环境变量选择 - 在日志实现之间切换无需重新编译
- 所有日志接口函数符号在首次访问时延迟解析
静态链接
对于需要静态链接的嵌入式系统或部署场景,构建系统可以配置为直接链接到特定的日志实现:
- 在构建时设置 CMake 变量
RCL_LOGGING_IMPLEMENTATION来指定实现(例如rcl_logging_spdlog、rcl_logging_noop) - 或者,在运行 CMake 之前设置
RCL_LOGGING_IMPLEMENTATION环境变量 - 指定的实现将被静态链接到最终的可执行文件中
- 静态链接不支持运行时切换
环境变量配置
Section titled “环境变量配置”使用动态加载(默认)时,RCL_LOGGING_IMPLEMENTATION 环境变量控制运行时加载哪个日志后端。
语法
export RCL_LOGGING_IMPLEMENTATION=<implementation_name>export RCL_LOGGING_IMPLEMENTATION=<implementation_name>Windows
Section titled “Windows”set RCL_LOGGING_IMPLEMENTATION=<implementation_name>可用实现
rcl_logging_spdlog- 使用 spdlog 库的全功能日志记录(默认)rcl_logging_noop- 丢弃所有日志消息的空操作实现(适用于性能关键型应用)
使用示例
# 使用 spdlog 进行日志记录(默认行为)export RCL_LOGGING_IMPLEMENTATION=rcl_logging_spdlogros2 run demo_nodes_cpp talker
# 使用丢弃所有日志消息的空操作日志实现export RCL_LOGGING_IMPLEMENTATION=rcl_logging_noopros2 run demo_nodes_cpp talker# 使用 spdlog 进行日志记录(默认行为)export RCL_LOGGING_IMPLEMENTATION=rcl_logging_spdlogros2 run demo_nodes_cpp talker
# 使用丢弃所有日志消息的空操作日志实现export RCL_LOGGING_IMPLEMENTATION=rcl_logging_noopros2 run demo_nodes_cpp talkerWindows
Section titled “Windows”# 使用 spdlog 进行日志记录(默认行为)set RCL_LOGGING_IMPLEMENTATION=rcl_logging_spdlogros2 run demo_nodes_cpp talker
# 使用丢弃所有日志消息的空操作日志实现set RCL_LOGGING_IMPLEMENTATION=rcl_logging_noopros2 run demo_nodes_cpp talker如果未设置环境变量,系统默认使用 rcl_logging_spdlog。
实现细节
rcl_logging_implementation 包:
- 使用
rcpputils::SharedLibrary进行跨平台动态库加载 - 在初始化时加载所有日志接口函数符号
- 维护符号引用直到进程退出
- 如果找不到请求的实现,则回退到默认实现
- 提供与底层实现相同的 API 接口,确保对客户端代码透明
此架构允许系统集成商:
- 为不同的部署场景选择合适的日志实现
- 在生产环境中禁用日志以提高性能,无需修改代码
- 为调试或开发目的切换日志后端
- 打包不同的日志实现并在运行时选择
rcl_logging_spdlog
Section titled “rcl_logging_spdlog”rcl_logging_spdlog 实现了 rcl_logging_interface API,从而为 rcl 层提供外部日志功能。具体而言,rcl_logging_spdlog 接收格式化的日志消息,并使用 spdlog 库将其写入磁盘上的日志文件,通常位于 ~/.ros/log(但这是可配置的;参见上文 配置 部分)。
rcl 中的日志子系统借助 rcutils 和 rcl_logging_spdlog 提供 ROS 2 日志服务的主要功能。当日志消息传入时,rcl 决定将它们发送到哪里。日志消息有 3 个主要的传递目的地;单个节点可以启用它们的任意组合:
- 通过
rcutils层输出到控制台 - 通过
rcl_logging_spdlog层写入磁盘 - 通过 RMW 层输出到 ROS 2 网络中的
/rosouttopic
rclcpp
Section titled “rclcpp”这是位于 rcl API 之上的主要 ROS 2 C++ API。在日志的上下文中,rclcpp 提供了 RCLCPP_ 日志宏;完整列表请参见上文 API 部分。当某个 RCLCPP_ 宏运行时,它会检查节点当前的严重级别与宏的严重级别。如果宏的严重级别大于或等于节点的严重级别,消息将被格式化并输出到当前配置的所有位置。需要注意的是,rclcpp 对日志调用使用全局互斥锁,因此同一进程中的所有日志调用最终都是串行执行的。
这是位于 rcl API 之上的主要 ROS 2 Python API。在日志的上下文中,rclpy 提供了 logger.debug 风格的函数;完整列表请参见上文 API 部分。当某个 logger.debug 函数运行时,它会检查节点当前的严重级别与函数的严重级别。如果函数的严重级别大于或等于节点的严重级别,消息将被格式化并输出到当前配置的所有位置。
- 参见 rclcpp logging demo 了解一些简单示例。
- 参见 logging demo 了解使用示例。
Python
Section titled “Python”- 参见 rclpy examples 了解节点日志记录器的使用示例。
- 参见 rclpy tests 了解关键字参数(例如
skip_first、once)的使用示例。