3、日志过滤与条件输出:使用条件宏、日志级别阈值设置
日志这东西,说白了就是自动驾驶系统的「黑匣子」。但黑匣子要是啥都记,那跟没记一样。我见过太多团队,一开日志就是海量输出,刷屏刷到连关键错误都找不着。今天咱们聊聊怎么让日志「该出声时出声,该闭嘴时闭嘴」。
3.1 日志级别:你的第一道过滤器
ROS2 的日志系统自带一套级别体系。我个人习惯把这套级别理解成「警报等级」——从 DEBUG 到 FATAL,越往后越严重。
| 级别 | 宏名称 | 典型用途 |
|---|---|---|
| DEBUG | RCLCPP_DEBUG | 调试信息,比如传感器原始数据 |
| INFO | RCLCPP_INFO | 正常流程提示,比如节点启动成功 |
| WARN | RCLCPP_WARN | 异常但可恢复,比如丢了一帧数据 |
| ERROR | RCLCPP_ERROR | 功能受损,比如传感器断连 |
| FATAL | RCLCPP_FATAL | 系统崩溃,比如内存溢出 |
你想想看,如果生产环境开着 DEBUG 级别跑,那日志文件一天能撑爆硬盘。我在项目中遇到过,同事把日志级别设成 DEBUG 就上线了,结果跑了半天,磁盘直接写满,整个系统挂了。嗯,从那以后我每次上线前都会检查日志级别配置。
3.2 条件宏:只在需要时打印
有时候我们只想在特定条件下打日志。比如,只在速度超过 80km/h 时打印警告。ROS2 提供了条件宏,用起来很顺手。
// 只在条件为 true 时打印
RCLCPP_WARN_THROTTLE(rclcpp::get_logger("controller"),
get_clock(), 1000,
"速度过高: %.2f km/h", speed);
// 或者用更直接的条件宏
if (speed > 80.0) {
RCLCPP_WARN(rclcpp::get_logger("controller"),
"超速警告: %.2f km/h", speed);
}
这里有个小技巧:RCLCPP_WARN_THROTTLE 后面的 1000 是节流时间(毫秒)。意思是同样的警告,1 秒内最多打印一次。我在调试路径规划时,经常用这个宏——不然控制频率一高,日志刷得你根本看不清。
3.3 日志级别阈值:运行时动态调整
ROS2 允许你在运行时修改日志级别。这个功能太实用了。比如,车在正常跑时我只想看 ERROR,但出了事故,我想立刻切到 DEBUG 看细节。
// 设置全局日志级别
rclcpp::Logger::set_level(rclcpp::Logger::Level::WARN);
// 或者只针对某个节点
auto logger = rclcpp::get_logger("sensor_fusion");
logger.set_level(rclcpp::Logger::Level::DEBUG);
我曾经在实车测试时遇到一个诡异问题:传感器融合偶尔输出错误位姿。但 ERROR 级别日志啥也没记,因为错误被内部处理了。后来我远程把 sensor_fusion 节点的日志级别降到 DEBUG,立刻抓到了问题——原来是某个滤波器参数在特定工况下发散。你看,动态调级别就是救命稻草。
3.4 条件宏的高级用法:频率控制与采样
除了基本的条件宏,ROS2 还提供了几种「聪明」的打印方式:
- RCLCPP_*_ONCE:整个生命周期只打印一次。适合初始化信息。
- RCLCPP_*_SKIPFIRST:跳过第一次,从第二次开始打印。适合过滤启动时的瞬态数据。
- RCLCPP_*_THROTTLE:按频率节流,避免刷屏。
// 只打印一次
RCLCPP_INFO_ONCE(rclcpp::get_logger("system"),
"系统初始化完成");
// 跳过前 5 次
static int count = 0;
if (++count > 5) {
RCLCPP_WARN(rclcpp::get_logger("planner"),
"规划失败次数: %d", count);
}
3.5 实战:搭建日志过滤系统
下面是我个人在项目中常用的日志配置模板。它结合了级别阈值和条件宏,适合自动驾驶这种多节点、高频场景。
// 日志配置结构体
struct LogConfig {
rclcpp::Logger::Level level;
int throttle_ms;
bool enable_once;
};
// 示例:传感器节点日志配置
LogConfig sensor_config = {
.level = rclcpp::Logger::Level::WARN,
.throttle_ms = 500,
.enable_once = false
};
// 应用配置
auto logger = rclcpp::get_logger("sensor_node");
logger.set_level(sensor_config.level);
// 条件打印示例
if (sensor_config.enable_once) {
RCLCPP_INFO_ONCE(logger, "传感器节点已启动");
} else {
RCLCPP_INFO_THROTTLE(logger, get_clock(),
sensor_config.throttle_ms,
"传感器数据帧率: %d Hz", fps);
}
这个模板的好处是:所有日志行为集中管理。你改一个配置文件,整个节点的日志行为就变了。我在做多传感器融合时,用这套方案同时管理了激光雷达、摄像头、毫米波雷达三个节点的日志,调试效率提升不少。
3.6 知识体系图
下面这张图总结了本章的核心逻辑。说白了就是:日志级别决定「能不能打」,条件宏决定「什么时候打」,两者配合,才能做到精准输出。
嗯,到这里你应该明白了。日志过滤不是「要不要打」的问题,而是「怎么打得聪明」的问题。级别阈值是粗筛,条件宏是精筛,两者配合,你才能在自动驾驶这种复杂系统里,快速定位问题,而不是被日志淹没。
可可精品资料库