Skip to content

日志与 Logger 配置

更多日志相关内容,请参阅日志页面。

以下代码会从 ROS 2 节点输出一条 DEBUG 级别的日志:

C++

// printf 风格
RCLCPP_DEBUG(node->get_logger(), "My log message %d", 4);
// C++ stream 风格
RCLCPP_DEBUG_STREAM(node->get_logger(), "My log message " << 4);

Python

node.get_logger().debug('My log message %d' % (4))

注意,以上两种写法都无需手动添加换行符,日志基础设施会自动处理。

以下代码会从 ROS 2 节点输出一条 INFO 级别的日志,但仅在首次执行时输出:

C++

// printf 风格
RCLCPP_INFO_ONCE(node->get_logger(), "My log message %d", 4);
// C++ stream 风格
RCLCPP_INFO_STREAM_ONCE(node->get_logger(), "My log message " << 4);

Python

num = 4
node.get_logger().info(f'My log message {num}', once=True)

以下代码会从 ROS 2 节点输出一条 WARN 级别的日志,但会跳过首次执行:

C++

// printf 风格
RCLCPP_WARN_SKIPFIRST(node->get_logger(), "My log message %d", 4);
// C++ stream 风格
RCLCPP_WARN_STREAM_SKIPFIRST(node->get_logger(), "My log message " << 4);

Python

num = 4
node.get_logger().warning('My log message {0}'.format(num), skip_first=True)

以下代码会从 ROS 2 节点输出一条 ERROR 级别的日志,但每秒最多输出一次。

用于指定消息间隔(单位毫秒)的 interval 参数应为整数类型,以便正确转换为 rcutils_duration_value_t(即 int64_t):

C++

// printf 风格
RCLCPP_ERROR_THROTTLE(node->get_logger(), *node->get_clock(), 1000, "My log message %d", 4);
// C++ stream 风格
RCLCPP_ERROR_STREAM_THROTTLE(node->get_logger(), *node->get_clock(), 1000, "My log message " << 4);
// 目前,使用 nanoseconds() 方法来使用现有的 rclcpp::Duration 值,参见 https://github.com/ros2/rclcpp/issues/1929
RCLCPP_ERROR_STREAM_THROTTLE(node->get_logger(), *node->get_clock(), msg_interval.nanoseconds()/1000000, "My log message " << 4);

Python

num = 4
node.get_logger().error(f'My log message {num}', throttle_duration_sec=1)

以下代码会从 ROS 2 节点输出一条 DEBUG 级别的日志,每秒最多输出一次,且会跳过首次执行:

C++

// printf 风格
RCLCPP_DEBUG_SKIPFIRST_THROTTLE(node->get_logger(), *node->get_clock(), 1000, "My log message %d", 4);
RCLCPP_DEBUG_SKIPFIRST_THROTTLE(node->get_logger(), *node->get_clock(), 1000, "My log message " << 4);

Python

num = 4
node.get_logger().debug(f'My log message {num}', skip_first=True, throttle_duration_sec=1.0)

这个演示展示了各类日志调用方式,以及如何在代码内部和外部配置各 logger 的日志级别。

使用以下命令启动演示:

Terminal window
$ ros2 run logging_demo logging_demo_main

程序运行后,你会看到不同类型的日志输出。起初只会显示 INFO 及以上级别(WARN、ERROR、FATAL)的日志。注意,第一条消息只会被记录一次——尽管该行代码在每次迭代中都会执行,这是该日志调用本身的特性决定的。

日志目录通过两个环境变量配置:ROS_LOG_DIR 和 ROS_HOME。规则如下:

  • 如果设置了 ROS_LOG_DIR 且值不为空,则使用 $ROS_LOG_DIR。
  • 否则使用 $ROS_HOME/log;若 ROS_HOME 未设置或为空,则默认将 ~/.ros 作为 ROS_HOME。

例如,要将日志目录设置为 ~/my_logs:

Linux

Terminal window
$ export ROS_LOG_DIR=~/my_logs
$ ros2 run logging_demo logging_demo_main

macOS

Terminal window
$ export ROS_LOG_DIR=~/my_logs
$ ros2 run logging_demo logging_demo_main

Windows

Terminal window
$ set "ROS_LOG_DIR=~/my_logs"
$ ros2 run logging_demo logging_demo_main

运行后,日志文件将位于 ~/my_logs/ 目录下。

你也可以设置 ROS_HOME,日志目录随之变为 $ROS_HOME/log。ROS_HOME 的设计初衷是为所有需要基础目录的功能提供统一的根路径。注意,此方式要求 ROS_LOG_DIR 未设置或为空。例如,将 ROS_HOME 设置为 ~/my_ros_home:

Linux

Terminal window
$ export ROS_HOME=~/my_ros_home
$ ros2 run logging_demo logging_demo_main

macOS

Terminal window
$ export ROS_HOME=~/my_ros_home
$ ros2 run logging_demo logging_demo_main

Windows

Terminal window
$ set "ROS_HOME=~/my_ros_home"
$ ros2 run logging_demo logging_demo_main

运行后,日志文件将位于 ~/my_ros_home/log/ 目录下。

迭代 10 次后,logger 级别会切换为 DEBUG,从而输出更多调试消息。

部分调试消息会触发一些额外的函数调用或表达式求值,这些函数或表达式此前因 DEBUG 日志未启用而被跳过。有关所用调用的详细说明,请参阅该演示的源代码;有关支持的日志调用完整列表,请参阅 rclcpp 日志文档。

ROS 2 节点提供了可在运行时从外部配置日志级别的服务,默认处于禁用状态。以下代码展示了创建节点时如何启用 logger 服务。

C++

// 创建一个启用 logger 服务的节点
auto node = std::make_shared<rclcpp::Node>("NodeWithLoggerService", rclcpp::NodeOptions().enable_logger_service(true));

Python

# 创建一个启用 logger 服务的节点
node = Node('NodeWithLoggerService', enable_logger_service=True)

如果按上述配置运行某个节点,执行 ros2 service list 时会看到两个服务:

Terminal window
$ ros2 service list
...
/NodeWithLoggerService/get_logger_levels
/NodeWithLoggerService/set_logger_levels
...
  • get_logger_levels

    通过此服务获取指定 logger 的日志级别。

    运行 ros2 service call 获取 NodeWithLoggerService 和 rcl 的日志级别。

    Terminal window
    $ ros2 service call /NodeWithLoggerService/get_logger_levels rcl_interfaces/srv/GetLoggerLevels '{names: ["NodeWithLoggerService", "rcl"]}'
    requester: making request: rcl_interfaces.srv.GetLoggerLevels_Request(names=['NodeWithLoggerService', 'rcl'])
    response:
    rcl_interfaces.srv.GetLoggerLevels_Response(levels=[rcl_interfaces.msg.LoggerLevel(name='NodeWithLoggerService', level=0), rcl_interfaces.msg.LoggerLevel(name='rcl', level=0)])
  • set_logger_levels

    通过此服务设置指定 logger 的日志级别。

    运行 ros2 service call 设置 NodeWithLoggerService 和 rcl 的日志级别。

    Terminal window
    $ ros2 service call /NodeWithLoggerService/set_logger_levels rcl_interfaces/srv/SetLoggerLevels '{levels: [{name: "NodeWithLoggerService", level: 20}, {name: "rcl", level: 10}]}'
    requester: making request: rcl_interfaces.srv.SetLoggerLevels_Request(levels=[rcl_interfaces.msg.LoggerLevel(name='NodeWithLoggerService', level=20), rcl_interfaces.msg.LoggerLevel(name='rcl', level=10)])
    response:
    rcl_interfaces.srv.SetLoggerLevels_Response(results=[rcl_interfaces.msg.SetLoggerLevelsResult(successful=True, reason=''), rcl_interfaces.msg.SetLoggerLevelsResult(successful=True, reason='')])

此外,还有演示代码展示了如何通过 logger 服务设置或获取日志级别。

  • rclcpp:演示代码

    Terminal window
    $ ros2 run demo_nodes_cpp use_logger_service
  • rclpy:演示代码

    Terminal window
    $ ros2 run demo_nodes_py use_logger_service

警告:当前存在一个已知限制:get_logger_levels 和 set_logger_levels 服务不是线程安全的,即同一时刻只能有一个线程调用这些服务。详情请参阅 https://github.com/ros2/rcutils/issues/397

响应日志配置请求的服务端已封装为组件,可直接集成到现有的 composition 系统中。例如,如果你正在使用容器运行节点,只需在容器中额外加载 logging_demo::LoggerConfig 组件,即可配置 logger。

以调试 composition::Talker 演示为例,首先像平常一样启动 talker:

终端 1:

Terminal window
$ ros2 run rclcpp_components component_container

终端 2:

Terminal window
$ ros2 component load /ComponentManager composition composition::Talker

然后,需要启用调试日志时,使用以下命令加载 LoggerConfig 组件:

终端 2:

Terminal window
$ ros2 component load /ComponentManager logging_demo logging_demo::LoggerConfig

最后,通过指定空名称的 logger,将所有尚未设置的 logger 统一配置为 debug 级别。注意,已被显式设置为特定级别的 logger 不受此调用影响。

终端 2:

Terminal window
$ ros2 service call /config_logger logging_demo/srv/ConfigLogger "{logger_name: '', level: DEBUG}"

你应该会看到进程中所有此前未设置的 logger(包括 ROS 2 核心)开始输出调试信息。

从 Bouncy 版本起,ROS 2 支持通过命令行为尚未显式配置级别的 logger 设置日志级别。使用以下命令行参数重新启动演示:

Terminal window
$ ros2 run logging_demo logging_demo_main --ros-args --log-level debug

这样会将所有未设置的 logger 的默认级别配置为 debug。你应该会看到来自演示本身以及 ROS 2 核心 logger 的调试输出。

你也可以通过命令行配置个别 logger。使用以下命令行参数重新启动演示:

Terminal window
$ ros2 run logging_demo logging_demo_main --ros-args --log-level logger_usage_demo:=debug

如果想调整输出的详细程度,可以使用 RCUTILS_CONSOLE_OUTPUT_FORMAT 环境变量自定义格式。例如,想在输出中额外显示时间戳和调用位置,可以先停止演示,设置环境变量后重新启动:

Linux

Terminal window
$ export RCUTILS_CONSOLE_OUTPUT_FORMAT="[{severity} {time}] [{name}]: {message} ({function_name}() at {file_name}:{line_number})"
$ ros2 run logging_demo logging_demo_main

macOS

Terminal window
$ export RCUTILS_CONSOLE_OUTPUT_FORMAT="[{severity} {time}] [{name}]: {message} ({function_name}() at {file_name}:{line_number})"
$ ros2 run logging_demo logging_demo_main

Windows

Terminal window
$ set "RCUTILS_CONSOLE_OUTPUT_FORMAT=[{severity} {time}] [{name}]: {message} ({function_name}() at {file_name}:{line_number})"
$ ros2 run logging_demo logging_demo_main

你应该会看到每条消息都会额外打印以秒为单位的时间戳,以及函数名、文件名和行号。

有关控制台输出格式的更多配置选项,请参阅 logger 控制台配置文档。

默认情况下,日志输出到终端时会自动着色。如果想强制启用或禁用着色,可以使用 RCUTILS_COLORIZED_OUTPUT 环境变量。例如:

Linux

Terminal window
$ export RCUTILS_COLORIZED_OUTPUT=0 # 1 为强制启用
$ ros2 run logging_demo logging_demo_main

macOS

Terminal window
$ export RCUTILS_COLORIZED_OUTPUT=0 # 1 为强制启用
$ ros2 run logging_demo logging_demo_main

Windows

Terminal window
$ set "RCUTILS_COLORIZED_OUTPUT=0" :: 1 为强制启用
$ ros2 run logging_demo logging_demo_main

你应该会看到 debug、warn、error 和 fatal 日志不再着色。

注意:在 Linux 和 macOS 上,强制着色意味着输出重定向到文件时,文件中会包含 ANSI 颜色转义码。在 Windows 上,着色功能依赖控制台 API,如果在不支持的环境中被强制着色,会收到着色失败的警告。由于默认行为已经会自动检测输出目标是否为控制台,通常不建议强制设置着色。

注意:如果通过 ros2 launch 启动多个节点,这些节点不会直接附加到当前终端(除非设置了 emulate_tty=True)。因此,要想在 ros2 launch 中看到着色输出,需要显式设置 RCUTILS_COLORIZED_OUTPUT=1。

在 Foxy 及更高版本中,所有调试级别的输出默认发送到 stderr。可以通过将 RCUTILS_LOGGING_USE_STDOUT 环境变量设置为 1,强制所有输出发送到 stdout。例如:

Linux

Terminal window
$ export RCUTILS_LOGGING_USE_STDOUT=1

macOS

Terminal window
$ export RCUTILS_LOGGING_USE_STDOUT=1

Windows

Terminal window
$ set "RCUTILS_LOGGING_USE_STDOUT=1"

默认情况下,所有日志输出都是无缓冲的。可以通过将 RCUTILS_LOGGING_BUFFERED_STREAM 环境变量设置为 1 来强制启用缓冲。例如:

Linux

Terminal window
$ export RCUTILS_LOGGING_BUFFERED_STREAM=1

macOS

Terminal window
$ export RCUTILS_LOGGING_BUFFERED_STREAM=1

Windows

Terminal window
$ set "RCUTILS_LOGGING_BUFFERED_STREAM=1"

然后运行:

Terminal window
$ ros2 run logging_demo logging_demo_main

默认情况下,日志文件名基于可执行文件名,后跟进程 ID 和文件创建时的系统时间戳。可以使用 --log-file-name 命令行参数将日志文件名前缀更改为自定义名称:

Terminal window
$ ros2 run demo_nodes_cpp talker --ros-args --log-file-name filename

这会将日志文件名前缀配置为 filename,而不是可执行文件名(在本例中为 talker)。