日志记录和记录器配置

概述

ROS 2 中的日志子系统旨在将日志消息传递到各种目标,包括:

  • 传递到控制台(如果已连接)

  • 记录磁盘上的文件(如果本地存储可用)

  • 传递到 ROS 2 网络上的 /rosout 主题

默认情况下,ROS 2 节点中的日志消息将传递到控制台(在 stderr 上)、记录磁盘上的文件以及 ROS 2 网络上的 /rosout 主题。

可以根据每个节点单独启用或禁用所有目标。

本文档的其余部分将介绍日志子系统背后的一些想法。

严重性级别

日志消息具有与之关联的严重性级别:按升序排列的 DEBUGINFOWARN``​​、``ERRORFATAL

记录器将仅处理严重性等于或高于为记录器选择的指定级别的日志消息。

每个节点都有一个与之关联的记录器,该记录器自动包含节点的名称和命名空间。 如果节点的名称在外部重新映射到源代码中定义以外的其他名称,它将反映在记录器名称中。 还可以创建使用特定名称的非节点记录器。

记录器名称表示层次结构。 如果名为“abc.def”的记录器的级别未设置,它将遵循其名为“abc”的父级的级别,如果该级别也未设置,则将使用默认记录器级别。 当记录器“abc”的级别更改时,其所有后代(例如“abc.def”、“abc.ghi.jkl”)的级别都将受到影响,除非其级别已明确设置。

API

这些是 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 - 仅当给定表达式为真时输出给定的 printf 样式消息

  • RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_FUNCTION - 仅当给定函数返回真时输出给定的 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++ 流样式消息

  • RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_STREAM_ONCE - 仅在第一次命中此行时输出给定的 C++ 流样式消息

  • RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_STREAM_EXPRESSION - 仅当给定表达式为真时才输出给定的 C++ 流式消息

  • RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_STREAM_FUNCTION - 仅当给定函数返回真时才输出给定的 C++ 流式消息

  • RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_STREAM_SKIPFIRST - 除第一次命中此行外,输出给定的 C++ 流式消息

  • RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_STREAM_THROTTLE - 以不超过给定速率(以整数毫秒为单位)的方式输出给定的 C++ 流式消息

  • RCLCPP_{DEBUG,INFO,WARN,ERROR,FATAL}_STREAM_SKIPFIRST_THROTTLE - 以不超过给定速率(整数毫秒)输出给定的 C++ 流式消息,但跳过第一个

上述每个 API 都将 rclcpp::Logger 对象作为第一个参数。

可以通过调用 node->get_logger()``(推荐)或构建独立的 ``rclcpp::Logger 对象从节点 API 中提取该对象。

  • rcutils_logging_set_logger_level - 将特定记录器名称的日志记录级别设置为给定的严重性级别

  • rcutils_logging_get_logger_effective_level - 给定记录器名称,返回记录器级别(可能未设置)

  • 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 - 给定记录器名称,返回记录器级别(可能未设置)

配置

由于 rclcpprclpy 使用相同的底层日志记录基础结构,因此配置选项相同。

环境变量

以下环境变量控制 ROS 2 记录器的某些方面。 对于每个环境设置,请注意这是一个进程范围的设置,因此它适用于该进程中的所有节点。

  • 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} - 调用此函数的文件名(可能为空)。

  • {time} - 自纪元以来的时间(以秒为单位)。

  • {time_as_nanoseconds} - 自纪元以来的时间(以纳秒为单位)。

  • {date_time_with_ms} - ISO 格式的时间,例如 2024-06-11 09:29:19.304

  • {line_number} - 调用此函数的行号(可能为空)。

如果没有指定格式,则使用默认值 [{severity}] [{time}] [{name}]: {message}

RCUTILS_CONSOLE_OUTPUT_FORMAT 还支持以下转义字符语法。

Escape character syntax

Character represented

\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:=DEBUG

  • external_log_config_file - 用于配置后端记录器的外部文件。如果为 NULL,则将使用默认配置。请注意,此文件的格式是后端特定的(目前尚未为 spdlog 的默认后端记录器实现)。可以使用以下命令设置:ros2 run demo_nodes_cpp talker --ros-args --log-config-file log-config.txt

  • log_stdout_disabled - 是否禁用将日志消息写入控制台。可以使用以下命令完成此操作:ros2 run demo_nodes_cpp talker --ros-args --disable-stdout-logs

  • log_rosout_disabled - 是否禁用将日志消息写入``/rosout``。这可以显著节省网络带宽,但外部观察者将无法监视日志记录。可以使用以下命令完成此操作:ros2 run demo_nodes_cpp talker --ros-args --disable-rosout-logs

  • log_ext_lib_disabled - 是否完全禁用外部记录器的使用。在某些情况下,这可能会更快,但意味着日志将不会写入磁盘。可以通过以下方式完成:ros2 run demo_nodes_cpp talker --ros-args --disable-external-lib-logs

日志子系统设计

下图显示了日志子系统的五个主要部分及其交互方式。

ROS 2 logging architecture

rcutils

rcutils 有一个日志实现,可以根据某种格式格式化日志消息(请参阅上面的“配置”),并将这些日志消息输出到控制台。 rcutils 实现了完整的日志解决方案,但允许更高级别的组件以依赖注入模型将自身插入到日志基础结构中。 当我们讨论下面的“rcl”层时,这将变得更加明显。

请注意,这是一个 每个进程 的日志实现,因此在此级别配置的任何内容都会影响整个进程,而不仅仅是单个节点。

rcl_logging_spdlog

rcl_logging_spdlog 实现了“rcl_logging_interface”API,从而为“rcl”层提供外部日志服务。 具体来说,rcl_logging_spdlog 实现采用格式化的日志消息,并使用 spdlog 库将它们写入磁盘上的日志文件,通常在 ~/.ros/log 中(尽管这是可配置的;请参阅上面的 配置)。

rcl

rcl 中的日志子系统使用 rcutilsrcl_logging_spdlog 来提供大部分 ROS 2 日志服务。 当日志消息进入时,rcl 决定将它们发送到哪里。 有 3 个主要位置可以传递日志消息;单个节点可以启用它们的任意组合:

  • 通过 rcutils 层传输到控制台

  • 通过 rcl_logging_spdlog 层传输到磁盘

  • 通过 RMW 层传输到 ROS 2 网络上的 /rosout 主题

rclcpp

这是位于 rcl API 之上的主要 ROS 2 C++ API。 在日志记录上下文中,rclcpp 提供 RCLCPP_ 日志记录宏;请参阅上面的 APIs 了解完整列表。 当其中一个 RCLCPP_ 宏运行时,它会根据宏的严重性级别检查节点的当前严重性级别。 如果宏的严重性级别大于或等于节点严重性级别,则消息将被格式化并输出到当前配置的所有位置。 请注意,“rclcpp”使用全局互斥锁进行日志调用,因此同一进程内的所有日志调用最终都是单线程的。

rclpy

这是位于“rcl”API 之上的主要 ROS 2 Python API。 在日志记录上下文中,“rclpy”提供“logger.debug”样式的函数;请参阅上面的“API”以获取完整列表。 当其中一个“logger.debug”函数运行时,它会根据宏的严重性级别检查节点的当前严重性级别。 如果宏的严重性级别大于或等于节点严重性级别,则消息将被格式化并输出到当前配置的所有位置。

日志记录用法