日志系统和log4cxx

我们都知道ROS有默认的日志工具,用rosclean check检查日志所在路径和占用的大小,一般放在~/.ros/log,可以定义环境变量ROS_LOG_DIR改变日志存放的位置。rosclean purge可以清除日志,日志记录了所有的输出信息,有时我们只需要看部分信息,看自带的日志太费劲了,所以本文讲一下怎么利用ROS实现自定义的日志。

log4cpp与log4cxx

log4..是基于log4j的一系列的c++移植版本,使用了log4j的模式结构,目前主要有:log4cxx,log4cplus,log4cpp.

一开始看到网上说ROS提供了log4cpp库的安装,而且还有对应的官方帮助页面,于是执行sudo apt-get install ros-kinetic-log4cpp顺利安装了,在CMakeList里的find package添加log4cpp,结果发现编译时报错:

执行catkin_make时,编译器会寻找find package中的package对应的-config.cmake文件,比如roscpp这个package,我们用roscd roscpp进入对应安装目录中,看到一个cmake目录,进入后发现几个文件,其中一个是roscppConfig.cmake。但是log4cxx对应的目录连cmake文件夹都没有,所以编译失败。后来看到网上一些人的讨论,说log4cpp涉及到Orocos环境,这个比较陌生,实在懒得研究了。

后来又看到log4cxx已经集成到了ROS里面,不过不是以package的形式,而是头文件的形式,在安装ROS时就安装到了/usr/local/log4cxx,这样就好办多了,不用担心编译问题了。

为什么使用log4cxx,因为它提供了强大的配置功能,好像ROS也能实现,但是我没有去研究,应该就是rosconsole这个东西,它在启动时会加载$ROS_ROOT/config/rosconsole.config作为配置文件。另外可以定义配置文件供log4cxx使用,定义环境变量ROSCONSOLE_CONFIG_FILE为配置文件的路径。

一般做成launch文件,这样使用方便,编辑灵活:

1
2
3
4
5
<launch>
<env name="ROSCONSOLE_CONFIG_FILE"
value="$(find log_test)/setting.conf"/>
<node pkg="log_test" type="log_test" name="log_test" output="screen"/>
</launch>

package和可执行文件的名称都是log_test,配置文件由环境变量指定路径。

一个典型的配置文件是这样的,看上去不复杂,但是和普通log4cxx配置不同,需要结合ROS的特性,查了好久才成功:

1
2
3
4
5
6
7
8
9
log4j.logger.ros.log_test=INFO,rosout_a    # log_test是package名
log4j.appender.rosout_a=org.apache.log4j.RollingFileAppender # 输出到文件,超过大小后自动创建下一个
log4j.appender.rosout_a.Threshold=INFO # 最低级别是INFO
log4j.appender.rosout_a.Append=true # 追加模式
log4j.appender.rosout_a.MaxFileSize=100KB # 每个日志文件的大小
log4j.appender.rosout_a.MaxBackupIndex=10 # 最多产生多少日志文件,默认2个
log4j.appender.rosout_a.File=/home/hlhp/output.txt # 输出文件,可结合环境变量${ROSOUT_LOG_PATH}
log4j.appender.rosout_a.layout=org.apache.log4j.PatternLayout # 常用PatternLayout
log4j.appender.rosout_a.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss SSS} %m%n # 输出格式

注意:

  • 第一行 log_test是package名称。
  • 如果没有设置文件最多产生个数,默认只生成2个日志文件。超过大小后,会从第一个开始覆盖。
  • 注意日志级别,否则不会记录

注意文件中就是log4j不是log4cxx,PatternLayout指可以灵活地指定布局模式,最后输出格式中的SSS指的是毫秒。log4j采用类似C语言中的printf函数的打印格式格式化日志信息:

1
2
3
4
5
6
7
%m   输出代码中指定的消息
%n 输出一个回车换行符
%p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL
%r 输出自应用启动到输出该log信息耗费的毫秒数
%c 输出所属的类目,通常就是所在类的全名
%t 输出产生该日志事件的线程名
%l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java: 10 )

日志打印宏以_ONCE结尾,表示只调用一次:ROS_DEBUG_STREAM_ONCE("This appears only once.");
日志打印宏以_THROTTLE结尾,表示按指定频率打印日志:ROS_DEBUG_STREAM_THROTTLE(0.1, "This appears every 0.1 seconds.");

日志消息有三个不同的目的地:控制台,话题/rosout,日志文件。相比于控制台输出,rosout话题输出的主要作用是它在一个流中包含了系统中所有节点的日志消息。查看话题/rosout中的消息:

1
rostopic echo /rosout

也可以使用图形界面查看:rqt_consolerqt_console订阅的是话题/rosout_agg,话题由节点rosout收集话题/rosout聚合之后发布。

我写的一个节点订阅了5个话题,这几个话题是单片机发布的,在回调函数里输出对应消息类型的变量信息,日志信息最终在output.txt中,注意输出信息是用逗号隔开,这样用EXCEL打开可以将各部分隔开,实现表格效果:


说白了,代码里没用到log4cxx的东西,当然也可以用,但是我这个例子没必要,关键用的是它的配置文件。

ROS_INFO中文显示问号

显然这是编码问题,在ROS_INFO之前加入下面代码中的一行:

1
2
3
// 设置区域为中文,或者直接取所有枚举的并集,不必仔细研究了
// setlocale(LC_CTYPE, "zh_CN.utf8");
setlocale(LC_ALL, "");

自定义ROS日志的格式

默认的日志输出是这样的:

1
2
[ INFO] [1556115082.590417546]: msg:91
[ INFO] [1556115083.591389486]: msg:12

感觉时间没用必要,希望能像log4cxx一样自定义,其实看源码rosconsole.cpp可知ROS日志就是基于log4cxx。我们可以修改环境变量ROSCONSOLE_FORMAT定制格式,格式包括以下选项:
1
2
3
4
5
6
7
8
9
severity
message
time
thread
logger
file
line
function
node

看一下rosconsole.cpp源码部分:

1
2
3
4
5
6
7
8
9
const char* g_format_string = "[${severity}] [${time}]: ${message}";
Formatter g_formatter;
format_string = getenv("ROSCONSOLE_FORMAT");
if (format_string)
{
g_format_string = format_string;
}

g_formatter.init(g_format_string);

从中可以发现源码里先给了个默认格式,我们也可以针对环境变量自己设置格式.

网上有人问能不能自定义日志信息的颜色,结果发现在函数Formatter::print里,各等级的日志信息的颜色已经写死了,比如levels::Fatal对应COLOR_RED,所以不能自定义了.

根据环境变量,我们可以这样修改:

1
export ROSCONSOLE_FORMAT='[${severity}][${node}] [${function}] line_${line}  ${message}'

只保留等级、节点名、函数、行号、信息,结果是这样的:
1
2
3
[ INFO][/Pub] [main] line_19  msg:13
[ INFO][/Pub] [main] line_19 msg:37
[ INFO][/Pub] [main] line_19 msg:73

界面日志工具

ROS提供两个带界面的日志工具:rqt_console和rqt_logger_level

命令如下:

1
2
rosrun rqt_console rqt_console
rosrun rqt_logger_level rqt_logger_level

使用很简单,就不详细解释了




参考:
rosconsole
log4j.properties配置详解
ROS 程序编写之日志
rosconsole.cpp