4、日志与诊断:ROS2日志级别、rqt_console、诊断消息发布与自定义诊断
调试自动驾驶系统,说白了就是跟各种信息打交道。传感器数据、算法输出、节点状态……这些信息就像汽车的仪表盘,你得看得懂,才能知道车跑得稳不稳。
我个人习惯,拿到一个陌生的Autoware系统,第一件事不是看代码,而是打开日志和诊断工具。为什么?因为日志告诉你系统「说了什么」,诊断告诉你系统「感觉怎么样」。两者结合,问题往往一目了然。
4.1 ROS2日志级别:从INFO到FATAL
ROS2的日志系统,其实就五个级别,从低到高:DEBUG、INFO、WARN、ERROR、FATAL。每个级别代表信息的严重程度。
| 级别 | 含义 | 我什么时候用 |
|---|---|---|
| DEBUG | 调试信息,最详细 | 开发阶段,排查具体变量值 |
| INFO | 正常状态信息 | 节点启动、参数加载成功 |
| WARN | 警告,可能有问题 | 传感器数据偶尔丢失、参数超出推荐范围 |
| ERROR | 错误,功能受影响 | 节点崩溃、重要话题接收不到 |
| FATAL | 致命错误,系统无法继续 | 内存耗尽、硬件故障 |
在代码里怎么用?很简单。比如你在写一个激光雷达点云处理节点:
RCLCPP_INFO(this->get_logger(), "点云接收成功,共 %d 个点", point_count);
RCLCPP_WARN(this->get_logger(), "点云时间戳异常,差值 %f 秒", time_diff);
RCLCPP_ERROR(this->get_logger(), "无法打开激光雷达设备");
嗯,这里要注意:生产环境别开DEBUG。我曾经在一个项目中,DEBUG日志全开,结果日志文件每小时写几个GB,直接把磁盘撑爆了。自动驾驶系统跑在路上,磁盘满了可不是闹着玩的。
4.2 rqt_console:实时日志查看利器
光有日志输出还不够,你得有个好工具去看。rqt_console就是ROS2自带的日志查看器。启动命令很简单:
rqt_console
打开后,你会看到一个实时滚动的日志列表。每个日志前面有个颜色标记:灰色是DEBUG,白色是INFO,黄色是WARN,红色是ERROR,深红是FATAL。一眼就能看出问题严重性。
我个人最常用的功能是过滤。比如我只想看某个节点的ERROR日志,就在过滤框里输入节点名和ERROR级别。这样就不会被INFO信息刷屏了。
你想想看,如果整个系统几十个节点同时输出日志,没有过滤功能,你根本找不到重点。rqt_console还支持正则表达式过滤,这个我后面会讲到。
4.3 诊断消息发布:让系统告诉你它好不好
日志是「发生了什么」,诊断是「状态怎么样」。在Autoware里,我们通常用diagnostic_msgs包来发布诊断信息。
诊断消息的核心结构是这样的:
# 诊断状态
string OK=0
string WARN=1
string ERROR=2
string STALE=3
string level # 状态级别
string name # 诊断项名称
string message # 描述信息
string hardware_id # 硬件ID
举个例子,我要发布一个激光雷达的诊断状态:
auto diag_msg = diagnostic_msgs::msg::DiagnosticStatus();
diag_msg.level = diagnostic_msgs::msg::DiagnosticStatus::OK;
diag_msg.name = "LiDAR Front";
diag_msg.message = "正常运行,帧率10Hz";
diag_msg.hardware_id = "lidar_001";
// 还可以添加键值对
diagnostic_msgs::msg::KeyValue kv;
kv.key = "点云数量";
kv.value = "115200";
diag_msg.values.push_back(kv);
发布诊断消息,我建议用diagnostic_updater这个工具。它帮你管理频率、超时检测,比自己手动发布省心多了。
#include <diagnostic_updater/diagnostic_updater.hpp>
diagnostic_updater::Updater updater(this);
updater.setHardwareID("lidar_system");
// 注册一个定期检查的回调
updater.add("LiDAR Status", this, &MyNode::check_lidar);
void MyNode::check_lidar(diagnostic_updater::DiagnosticStatusWrapper &stat) {
if (lidar_ok) {
stat.summary(diagnostic_msgs::msg::DiagnosticStatus::OK, "正常运行");
stat.add("帧率", std::to_string(fps));
} else {
stat.summary(diagnostic_msgs::msg::DiagnosticStatus::ERROR, "连接断开");
}
}
为什么推荐用updater?因为它会自动检测诊断消息是否超时。如果某个传感器节点挂了,updater会发布一个STALE状态,告诉你「这个诊断项已经过期了」。这在自动驾驶里非常重要——你不仅要检测错误,还要检测「该有消息却没有消息」的情况。
4.4 自定义诊断:按需定制你的监控项
Autoware自带的诊断项可能不够用。比如你想监控「感知模块的推理延迟」,或者「车道线检测的置信度」。这时候就需要自定义诊断。
自定义诊断其实很简单,就是定义你自己的诊断项名称和状态判断逻辑。我一般会这样做:
- 确定监控目标(比如:推理延迟 < 50ms)
- 编写诊断回调函数
- 注册到diagnostic_updater
- 设置更新频率(通常1-10Hz)
举个例子,监控感知模块的推理延迟:
void MyNode::check_inference_latency(diagnostic_updater::DiagnosticStatusWrapper &stat) {
double latency = get_current_latency(); // 获取当前推理延迟
if (latency < 0.05) { // 小于50ms
stat.summary(diagnostic_msgs::msg::DiagnosticStatus::OK, "推理延迟正常");
} else if (latency < 0.1) { // 50-100ms
stat.summary(diagnostic_msgs::msg::DiagnosticStatus::WARN, "推理延迟偏高");
} else { // 超过100ms
stat.summary(diagnostic_msgs::msg::DiagnosticStatus::ERROR, "推理延迟超标");
}
stat.add("当前延迟(ms)", std::to_string(latency * 1000));
stat.add("阈值(ms)", "50");
}
然后注册:
updater.add("Perception Inference Latency", this, &MyNode::check_inference_latency);
这样,在rqt_runtime_monitor或者diagnostics_aggregator里,你就能看到这个自定义诊断项了。
4.5 知识体系总览
为了让你更直观地理解本章的知识结构,我画了一张图:
这张图把本章的核心内容串起来了。左边是日志系统,右边是诊断系统。两者相辅相成,缺一不可。
4.6 实战建议
最后,给你几个我在项目中总结的实战建议:
- 日志别乱打:每个日志都要有明确的目的。不要为了打日志而打日志,否则日志会变成噪音。
- 诊断项别太多:我见过有人给每个传感器定义了20多个诊断项,结果根本看不过来。建议每个模块3-5个核心诊断项就够了。
- 善用rqt_console的过滤功能:按节点名、按级别、按关键词过滤,能帮你快速定位问题。
- 诊断超时检测一定要开:这是诊断系统最强大的功能之一。节点挂了,诊断系统能立刻发现。
嗯,日志和诊断这部分,说白了就是给系统装上一双眼睛和一副听诊器。眼睛看状态,耳朵听日志。两者配合好了,调试效率能翻倍。