ROS日志(log
)系统:
通过显示进程的运行状态是好的习惯,但需要确定这样做不会影响到软件的运行效率和输出的清晰度。
ROS
日志 (log
) 系统的功能是让程序生成一些日志消息,显示在屏幕上、发送到特定 topic
或者储存在特定 log
文件中,以方便调试、记录、报警等。
下面简单介绍如何生成和查看日志消息。
1. 日志消息
在ROS
中,有一个特殊的话题叫作/rosout
,它承载着所有节点的所有日志消息。/rosout
消息的类型是rosgraph_msgs/Log
:
rosgraph_msgs/Log
消息用来让各个节点发布日志消息,这样一来就能让网络上的任何一个人都看到。
可以认为/rosout
是一个加强版的print()
:它不是向终端输出字符串,而是可以将字符串和元数据放到一个消息中,发送给网络上的任何一个人。
ROS
节点应该向/rosout
发布日志消息,这样一来这些消息就能被所有人看到。
rospy
客户端提供了多个函数来发布rosgraph_msgs/Log
消息:
if battery_voltage < 11.0:
rospy.logwarn('Battery voltage low: %f'%(battery_voltage))
rospy.logwarn()
函数实现了三件事情:
- 输出一个格式化的字符串到终端;
- 输出更详细的警告到日志文档中,这个文档一般在
~/.ros/log
中; - 构建并发布一条消息到
/rosout
话题,其中包括警告以及节点元数据;
2. 日志等级
ROS
有5个日志记录标准级别,这些名称是输出信息的函数的一部分,他们遵循以下语法:
ROS_<LEVEL>[_<OTHER>]
日志消息按照严重性由低到高可以分为 5 级:
DEBUG
INFO
WARN
ERROR
FATAL
每个消息级别用于不同的目的:
-
DEBUG
(调试):只在调试时用,此消息不出现在部署的应用中,仅用于测试; -
INFO
(信息):标准消息,说明重要步骤或节点所正在执行的操作; -
WARN
(警告):提醒一些错误、缺失或者不正常,但进程仍能运行; -
ERROR
(错误):提示错误,尽管节点仍可在这里恢复,但对节点的行为设置了一定期望; FATAR
(致命):这些消息通常表示阻止节点继续运行的错误。
3. 生成基本的日志消息
由五个 C++ 宏来产生日志消息,每个宏对应一个级别:
ROS_DEBUG_STREAM(message);
ROS_INFO_STREAM(message);
ROS_WARN_STREAM(message);
ROS_ERROR_STREAM(message);
ROS_FATAL_STREAM(message);
其实这个宏的定义就把格式输出包含到里面,其中各个宏的参数message
可以处理C++
中标准输出流(ostream
)中的各种表达式,比如:std::cout
。这包括在 int
或者 double
这种基本数据类型上使用插入操作符(<<
),以及已经重载这个操作符的复合数据类型。
我们经常也能看到不带_STREAM
的消息,它们的区别如下:
ROS_INFO(“INFO message %d”,k); // 相当于c中的printf;
ROS_INFO_STREAM ( "INFO message." << k); // 相当于c++中的cout;
编写如下 C++ 进程:
int (int argc,char **argv)
{
ros::init(argc,argv,"count_and_log");
ros::NodeHandle nh;
ros::Rate.rate(10);
for(int i=1; ros::ok(); i++)
{
ROS_DEBUG_STREAM("Counted?to?" << i);
if((i%3)==0){
ROS_INFO_STREAM(i<<"?is?divisible?by?3.");
}
if((i%5)==0){
ROS_INFO_STREAM(i<<"string">"?is?divisible?by?5.");
}
if((i%10)==0){
ROS_INFO_STREAM(i<<"?is?divisible?by?10.");
}
if((i%20)==0){
ROS_INFO_STREAM(i<<"?is?divisible?by?20.");
}
rate.sleep();
}
}
编译、执行之后结果如下:
4. 生成一次性日志消息
生成单次消息,其实就是在程序中加入了一个静态局部变量来检测,进入一次后就把变量改为false
则下次检测到后则不输出该信息。
// Don't do this directly. Use ROS_..._STREAM_ONCE instead.
{
static bool first_time = true ;
if (first_time) {
ROS_INFO_STREAM( "Here's some important information"
<<" that will only appear once.");
first_time = false;
}
}
ROS
提供了可以仅仅生成一次日志消息的宏:
ROS_DEBUG_STREAM_ONCE(message);
ROS_INFO_STREAM_ONCE(message);
ROS_WARN_STREAM_ONCE(message);
ROS_ERROR_STREAM_ONCE(message);
ROS_FATAL_STREAM_ONCE(message);
将上述 C++
进程中的 log
命令替换一下,得到如下的执行结果:
可以看到每个日志只生成了一次。
5. 生成频率受控的日志消息
ROS_DEBUG_STREAM_THROTTLE(interval, message);
ROS_INFO_STREAM_THROTTLE(interval, message);
ROS_WARN_STREAM_THROTTLE(interval, message);
ROS_ERROR_STREAM_THROTTLE(interval, messge);
ROS_FATAL_STREAM_THROTTLE(interval, message);
参数 interval
是 double
型,表示相邻日志消息出现的最小时间间隔,以秒为单位。ROS_..._STREAM_THROTTLE
宏的每一个实例在第一次执行时都会生成日志消息(与不带_THROTTLE
后缀版本宏的日志消息相同),随后的执行都会被忽略,直到经过了指定的时间间隔。每个宏的实例的时间被单独跟踪,方法是使用一个局部静态变量来存储上一次生成日志的时间。
得到如下的执行结果:
6. 查看日志消息
日志消息有三个不同的输出目的地,包括屏幕、rosout topic
、log
文档。其中发布到 rosout topic
的 msg
类型是 rosgraph_msgs/Log
。
我们可以通过rostopic echo /rosout
来查看消息,也可以通过一个节点来订阅日志话题,还可以通过指令rqt_console
来通过图形界面来显示日志消息:
7. 日志文件
作为/rosout
话题回调函数的一部分,rosout
节点可以将日志消息作为一行写入到一个日志文件,文件名类似于:~/.ros/log/run_id/rosout.log
有时日志过多时需要我们清除系统日志,利用指令:
rosclean check
如果日志正在消耗过多的硬盘空间,可以通过下面的命令删除所有已经存在的日志:
rosclean purge
8. 设置日志级别
ROS
默认只处理 INFO
或者更高级别消息,DEBUG
级别的消息会被忽略。
-
terminal
终端界面配置:
rosservice call /node-name/set_logger_level package-name level
其中:
-
node-name
期望设置日志级别的节点名称; -
set_logger_level
服务由各个节点自动提供; -
package-name
拥有这个节点的package
名称; -
level
是五个级别中的一个;
!!!注意:由于这条命令直接与节点进行交互,我们不能在节点启动之前使用它。如果一切正常,这个对 rosservice
的调用将输出一个空行。
-
rqt
界面配置:
rosrun rqt_logger_level rqt_logger_level
rosrun rqt_console rqt_console
图中列出了节点列表、日志记录器列表、日志级别列表。在图中操作与 rosservice
命令的效果一致。
- 代码中配置:
ROS node
改变自身日志级别最直接的方式是使用 log4cxx
提供的接口:
#include <log4cxx/logger.h>
log4cxx::Logger::getLogger(ROSCONSOLE_DEFAULT_NAME)->setLevel(
ros::console::g_level_lookup[ros::console::levels::Debug]);
ros::console::notifyLoggerLevelsChanged();
其中 Debug
可以替换为 Info
、Warn
、Error
、Fatal
。
或者
#include <ros/console.h>
if( ros::console::set_logger_level(ROSCONSOLE_DEFAULT_NAME, ros::console::levels::Debug) ) {
ros::console::notifyLoggerLevelsChanged();
}
-
config
文件中配置:
可以在launch
文件里指定一个config
文件, 在这个config
文件里制定哪些pkg
输出什么级别的log
示例rosconsole.config
配置如下:
# Set the default ros output to warning and higher
log4j.logger.ros=WARN
# Override my_package to output everything
log4j.logger.ros.move_base=DEBUG # WARN INFO ...
在launch
文件里添加如下配置:
<env name="ROSCONSOLE_CONFIG_FILE"
value="_myfolder_path_/rosconsole.config"/> //_myfolder_path_是rosconsole.config 所在的文件夹路径
可参考:ROS中log4j的学习记录
后记
对于大型ROS
项目的调试必须要利用到日志系统,所有成熟的框架都为开发者提供了代码进程的调试工具,学会这些工具能够很大程度上帮助我们少走弯路节省时间,所以我们要能够利用这些辅助工具来作为开发过程中的左膀右臂,达到事半功倍的效果。