2. rclcpp日志API详解:RCLCPP_INFO、RCLCPP_WARN、RCLCPP_ERROR、RCLCPP_DEBUG的使用

日志,说白了就是自动驾驶系统的「黑匣子」。车在路上跑,传感器数据哗哗流,算法疯狂算,一旦出了诡异问题——比如规划路径突然跳变、控制指令延迟——你总得有个地方去查到底发生了什么。

我个人习惯,在写每个节点之前,先把日志框架搭好。为什么?因为调试的时候,日志就是你的眼睛。ROS2里最常用的日志API就是rclcpp提供的这几个宏:RCLCPP_INFO、RCLCPP_WARN、RCLCPP_ERROR、RCLCPP_DEBUG。今天咱们就把它们掰开揉碎了讲清楚。

2.1 四个核心日志级别

ROS2的日志系统分了几个等级,从低到高分别是:DEBUG、INFO、WARN、ERROR、FATAL。日常开发中,我们最常用的是前四个。每个级别都有明确的语义,别乱用。

宏名称 日志级别 典型使用场景
RCLCPP_DEBUG DEBUG 开发调试阶段,打印变量值、循环次数、中间计算结果
RCLCPP_INFO INFO 正常运行时,打印节点启动、参数加载、状态切换等关键信息
RCLCPP_WARN WARN 出现非致命异常,比如传感器短暂丢帧、通信超时但能重试
RCLCPP_ERROR ERROR 出现严重错误,比如关键传感器断连、算法崩溃、资源耗尽

核心原则:INFO打给运维看,DEBUG打给自己看,WARN打给排查看,ERROR打给老板看。

2.2 基本用法与参数格式

这些宏的用法非常统一。第一个参数是节点对象,第二个参数是格式化字符串,后面跟可变参数。嗯,这里要注意:ROS2的日志宏支持printf风格的格式化,不是C++的iostream风格。

// 基本用法
RCLCPP_INFO(node->get_logger(), "节点已启动,时间戳: %ld", now_ns);

// 多参数
RCLCPP_WARN(node->get_logger(), "激光雷达帧率异常: 当前 %.1f Hz, 期望 %.1f Hz", current_hz, expected_hz);

// 字符串参数
std::string sensor_name = "front_lidar";
RCLCPP_ERROR(node->get_logger(), "传感器 %s 连接超时", sensor_name.c_str());

我在项目中遇到过一个问题:有人直接用std::cout打印日志,结果在分布式系统里根本看不到输出。ROS2的日志系统是跟节点绑定的,它会自动加上时间戳、节点名、日志级别,还能通过命令行工具过滤。所以,千万别图省事绕过日志API。

2.3 条件日志:RCLCPP_INFO_THROTTLE

你想想看,如果某个传感器每10毫秒丢一帧,你用RCLCPP_WARN直接打印,控制台会被刷爆。这时候就需要限流日志。

// 每5秒最多打印一次
RCLCPP_WARN_THROTTLE(node->get_logger(), *node->get_clock(), 5000,
                     "GPS信号弱,当前卫星数: %d", satellite_count);

第三个参数是限流时间,单位毫秒。我建议在循环检测、高频回调函数里,能用THROTTLE就用THROTTLE。否则日志文件一天能涨到几个GB。

2.4 一次性日志:RCLCPP_INFO_ONCE

有些信息只需要打印一次,比如「参数加载成功」、「节点初始化完成」。用ONCE变体最合适。

RCLCPP_INFO_ONCE(node->get_logger(), "路径规划参数已加载,共 %d 个参数", param_count);

这个宏内部维护了一个静态布尔变量,第一次执行后就不再打印。说白了就是「打个招呼就够了」。

2.5 日志级别的编译时控制

ROS2允许你在编译时设置日志级别。比如发布Release版本时,可以把DEBUG日志全部去掉,减少运行时开销。

// 在CMakeLists.txt中设置
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DROS_LOG_MIN_SEVERITY=2")

数字对应关系:DEBUG=0, INFO=1, WARN=2, ERROR=3, FATAL=4。设置为2之后,所有RCLCPP_DEBUG和RCLCPP_INFO的代码在编译时就被跳过了,连字符串都不会生成。

我曾经踩过的坑:有一次在车上跑Release版本,忘了设置日志级别,结果DEBUG日志里有个巨大的字符串拼接操作,导致CPU占用率飙升。从那以后,我每次发版前都会检查日志级别的编译配置。

2.6 运行时动态调整日志级别

编译时控制虽然高效,但不够灵活。ROS2支持在运行时通过rqt_logger_level或命令行动态调整某个节点的日志级别。

# 将/my_node节点的日志级别设为DEBUG
ros2 run rqt_logger_level rqt_logger_level

或者用命令行:

ros2 param set /my_node use_sim_time false
ros2 param set /my_node /my_node.log_level debug

我个人习惯在开发阶段把日志级别设为DEBUG,部署时切到INFO。如果线上出了问题,再远程切到DEBUG去复现。这个能力在自动驾驶的远程诊断中非常实用。

2.7 日志输出格式与重定向

ROS2默认的日志输出格式是:[时间戳] [日志级别] [节点名]: 消息内容。你可以通过环境变量自定义格式。

export RCUTILS_CONSOLE_OUTPUT_FORMAT="[{severity}][{time}]: {message}"

更常用的做法是把日志重定向到文件:

ros2 run my_package my_node 2>&1 | tee log.txt

或者使用ROS2自带的rosbag记录日志话题:

ros2 bag record /rosout

/rosout话题会聚合所有节点的日志消息,回放bag时能完整重现当时的日志输出。这个在事故分析时简直是救命稻草。

2.8 知识体系结构图

下面这张图总结了rclcpp日志API的核心脉络,从四个基本宏到高级用法,再到运行时控制,一目了然。

rclcpp日志API知识体系 rclcpp 日志宏 RCLCPP_INFO RCLCPP_WARN RCLCPP_ERROR RCLCPP_DEBUG 高级变体 RCLCPP_*_THROTTLE RCLCPP_*_ONCE RCLCPP_*_SKIPFIRST 日志控制方式 编译时控制 ROS_LOG_MIN_SEVERITY 运行时控制 rqt_logger_level / param 输出重定向 rosbag / tee / 文件 最佳实践 • INFO:节点状态、参数加载、关键事件 • WARN:非致命异常、短暂丢帧、重试操作 • ERROR:致命错误、传感器断连、算法崩溃 • DEBUG:开发调试、变量追踪、性能分析

2.9 避坑指南与个人经验

最后分享几个我这些年积累下来的经验:

  • 日志字符串不要拼接:用格式化参数,别用+拼接字符串。否则即使日志级别被过滤掉,字符串拼接的开销依然存在。
  • 高频回调里慎用ERROR:我曾经见过有人在控制循环里每周期打ERROR日志,结果日志系统直接把CPU吃满,导致控制周期抖动。ERROR应该留给真正不可恢复的错误。
  • 善用SKIPFIRST:有些日志第一次打印时信息不完整,用RCLCPP_INFO_SKIPFIRST可以跳过第一次,从第二次开始打印。
  • 日志文件记得轮转:ROS2默认的日志输出到终端,如果重定向到文件,记得用logrotate做轮转,否则一个日志文件能撑爆硬盘。

小技巧:在自动驾驶系统中,我习惯把WARN和ERROR日志单独提取到一个文件,用grep或者awk实时过滤。这样在路测时,一眼就能看到当前系统有没有异常。

好了,rclcpp日志API的核心内容就这些。说白了就是四个宏加三个变体,再加上编译时和运行时的控制手段。把这些用好,你的自动驾驶系统就有了可靠的「黑匣子」。下次遇到诡异bug,翻日志就能找到线索。

可可精品资料库