Cartographer源码解析(3)——gflags和glog简介

文章介绍了gflags库在解析命令行参数时的灵活性和复用性,以及如何通过DEFINE_type宏定义和访问参数。同时,文章阐述了glog日志系统的特点,如严重性分级、条件记录和线程安全,以及如何通过CHECK宏进行错误检测和程序终止。文章还展示了如何在ROS环境中自定义日志输出格式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.gflags简介

gflags 是 google 开源的命令行标记处理库

主要用于解析用命令行执行可执行文件时传入的参数。与getops()不同的是,在gflags中flag可以分散的定义在各个文件之中,而不用定义在一起,这就意味着在我们可以在一个单独的文件中只定义这个文件所需要用到的一些flag。

链接了该文件的应用都可以使用该文件中的flag,这样就能非常方便的实现代码的复用。

如果不同的文件定义了相同的flag,则会产生错误,所以需要明确规范gflags的使用规范。

命令行标记,顾名思义就是当运行一个可执行文件时, 由用户为其指定的标记, 形如:

fgrep -l -f ./test ccc jjj

-l 与-f 是命令行标记, 而 ccc 与 jjj 是命令行参数, 因为这两者不是以-开头的.

一般的一个可执行文件, 允许用户为其传入命令行标记以及参数.

如上述例子, -l 是一个不带参数的标记, -f 是一个带了参数./test 的标记, 而 gflags 可以解析这些标记以及参数并将其存储在某些数据结构中.

gflags 主要支持的参数类型包括 bool, int32, int64, uint64, double, string 等

定义参数通过 DEFINE_type 宏实现, 该宏的三个参数含义分别为命令行参数名, 参数默认值, 以及参数的帮助信息

当参数被定义后, 通过 FLAGS_name 就可访问到对应的参数

引用于http:// https://zhuanlan.zhihu.com/p/108477489

如下所示为carto源码里面定义的一些命令行参数,如何定义可参考DEFINE_type 宏实现。

DEFINE_bool(collect_metrics, false,
            "Activates the collection of runtime metrics. If activated, the "
            "metrics can be accessed via a ROS service.");
DEFINE_string(configuration_directory, "",
              "First directory in which configuration files are searched, "
              "second is always the Cartographer installation to allow "
              "including files from there.");
DEFINE_string(configuration_basename, "",
              "Basename, i.e. not containing any directory prefix, of the "
              "configuration file.");
DEFINE_string(load_state_filename, "",
              "If non-empty, filename of a .pbstream file to load, containing "
              "a saved SLAM state.");
DEFINE_bool(load_frozen_state, true,
            "Load the saved state as frozen (non-optimized) trajectories.");
DEFINE_bool(
    start_trajectory_with_default_topics, true,
    "Enable to immediately start the first trajectory with default topics.");
DEFINE_string(
    save_state_filename, "",
    "If non-empty, serialize state and write it to disk before shutting down.");

如下所示,在launch文件中有两个参数会在执行 cartographer_node 程序的时候传入给 node_main.cc 文文件中的 configuration_directory,configuration_basename,另外,roslaunch 指令还会把 remap(重映射)操作也当作参数传入到 main 函数的 **argv 中。

  <node name="cartographer_node" pkg="cartographer_ros"  
      type="cartographer_node" args="
          -configuration_directory $(find cartographer_ros)/configuration_files
          -configuration_basename test01.lua"
      output="screen"> 
    <remap from="scan" to="scan" />  
  </node>  

通过下面这个函数去进行解析命令行标记和参数,最后存储在这个argv[]数组里面。

 google::ParseCommandLineFlags(&argc, &argv, true);

最后再来看下DEFINE_xxx 的宏定义变量:

(1) collect_metrics→默认为false,激活运行时度量的集合.如果激活, 可以通过ROS服务访问度量。.launch文件中没有配置,所以其使用的是默认值。
(2) configuration_directory→在.launch中被被配置为 $(find cartographer_ros)/configuration_files。
(3) configuration_basename→在.launch中被被配置为 test01.lua

(4) load_state_filename→加载pbstream文件的路径,默认为空
(5) load_frozen_state→默认为true,.launch中未配置,使用默认值。将保存的状态加载为冻结(非优化)轨迹。
(6) start_trajectory_with_default_topics→使用默认主题(话题)开始第一个轨迹
(7) save_state_filename→保存轨迹文件的路径,默认为空

2.glog简介

Google glog 是一个应用级别的日志系统库.它提供基于 C++风格的流和各种辅助宏的 日志 API.支持以下功能:

  • 参数设置, 以命令行参数的方式设置标志参数来控制日志记录行为
  • 严重性分级, 根据日志严重性分级记录日志 - INFO WARNING ERROR FATAL
  • 可有条件地记录日志信息 - LOG_IF LOG_EVERY_N LOG_IF_EVERY_N LOG_FIRST_N
  • 条件中止程序。丰富的条件判定宏, 可预设程序终止条件 - CHECK 宏
  • 异常信号处理。程序异常情况, 可自定义异常处理过程
  • 支持 debug 功能
  • 自定义日志信息
  • 线程安全日志记录方式
  • 系统级日志记录
  • google perror 风格日志信息
  • 精简日志字符串信息

最终的结果不仅会在屏幕终端显示出来, 同时会将 log 日志写入到/tmp/...log....这个文件中 

glog 比较语句的源码

 #define CHECK_EQ(val1, val2) CHECK_OP(_EQ, ==, val1, val2)
 #define CHECK_NE(val1, val2) CHECK_OP(_NE, !=, val1, val2)
 #define CHECK_LE(val1, val2) CHECK_OP(_LE, <=, val1, val2)
 #define CHECK_LT(val1, val2) CHECK_OP(_LT, < , val1, val2)
 #define CHECK_GE(val1, val2) CHECK_OP(_GE, >=, val1, val2)
 #define CHECK_GT(val1, val2) CHECK_OP(_GT, > , val1, val2)

这些语句能够很方便的把信息输入到日志中,如源码中的:

  CHECK(!FLAGS_configuration_directory.empty())
      << "-configuration_directory is missing.";
  CHECK(!FLAGS_configuration_basename.empty())
      << "-configuration_basename is missing.";

如果输入参数 configuration_directory 或 FLAGS_configuration_basename 为空,则会记录错误信息,并且终止程序。

那么我们应该如何使用 glog,自定义一些我们想要的信息呢?源码中可以看到如下代码(位于node_main.cc):

  // 使用ROS_INFO进行glog消息的输出
  cartographer_ros::ScopedRosLogSink ros_log_sink;

其中的 ScopedRosLogSink 的定义如下(位于/home/lxy/carto_ws/cartographer_detailed_comments_ws/src/cartographer_ros/cartographer_ros/cartographer_ros/ros_log_sink.h)

#ifndef CARTOGRAPHER_ROS_CARTOGRAPHER_ROS_ROS_LOG_SINK_H
#define CARTOGRAPHER_ROS_CARTOGRAPHER_ROS_ROS_LOG_SINK_H

#include <ctime>

#include "glog/logging.h"

namespace cartographer_ros {

// Makes Google logging use ROS logging for output while an instance of this
// class exists.
/**
 * @brief 自定义的输出日志的方式: 使用ROS_INFO进行glog消息的输出
 */
class ScopedRosLogSink : public ::google::LogSink {
 public:
  ScopedRosLogSink();
  ~ScopedRosLogSink() override;

  void send(::google::LogSeverity severity, const char* filename,
            const char* base_filename, int line, const struct std::tm* tm_time,
            const char* message, size_t message_len) override;

  void WaitTillSent() override;

 private:
  bool will_die_;
};

}  // namespace cartographer_ros

#endif  // CARTOGRAPHER_ROS_CARTOGRAPHER_ROS_ROS_LOG_SINK_H

首先其继承了 google::LogSink,只需要实现 send() 、WaitTillSent() 两个函数即可,在/home/lxy/carto_ws/cartographer_detailed_comments_ws/src/cartographer_ros/cartographer_ros/cartographer_ros/ros_log_sink.cc 中实现,如下:

#include "cartographer_ros/ros_log_sink.h"

#include <chrono>
#include <cstring>
#include <string>
#include <thread>

#include "glog/log_severity.h"
#include "ros/console.h"

namespace cartographer_ros {

namespace {

/**
 * @brief 根据给定的文件全路径名, 获取文件名
 * 
 * @param[in] filepath 
 * @return const char* 返回文件名
 */
const char* GetBasename(const char* filepath) {
  // 找到 '/' 最后一次在filepath中出现的位置
  const char* base = std::strrchr(filepath, '/');
  // 找到'/',就将'/'之后的字符串返回;找不到'/', 就将整个filepath返回
  return base ? (base + 1) : filepath;
}

}  // namespace


/**
 * @brief 在构造函数中调用AddLogSink(), 将ScopedRosLogSink类注册到glog中
 */
ScopedRosLogSink::ScopedRosLogSink() : will_die_(false) { AddLogSink(this); }
ScopedRosLogSink::~ScopedRosLogSink() { RemoveLogSink(this); }

/**
 * @brief 重载了send()方法, 使用ROS_INFO进行glog消息的输出
 * 
 * @param[in] severity 消息级别
 * @param[in] filename 全路径文件名
 * @param[in] base_filename 文件名
 * @param[in] line 消息所在的文件行数
 * @param[in] tm_time 消息的时间
 * @param[in] message 消息数据本体
 * @param[in] message_len 消息长度
 */
void ScopedRosLogSink::send(const ::google::LogSeverity severity,
                            const char* const filename,
                            const char* const base_filename, 
                            const int line,
                            const struct std::tm* const tm_time,
                            const char* const message,
                            const size_t message_len) {
  const std::string message_string = ::google::LogSink::ToString(
      severity, GetBasename(filename), line, tm_time, message, message_len);
  switch (severity) {
    case ::google::GLOG_INFO:
      ROS_INFO_STREAM(message_string);
      break;

    case ::google::GLOG_WARNING:
      ROS_WARN_STREAM(message_string);
      break;

    case ::google::GLOG_ERROR:
      ROS_ERROR_STREAM(message_string);
      break;

    case ::google::GLOG_FATAL:
      ROS_FATAL_STREAM(message_string);
      will_die_ = true;
      break;
  }
}

// WaitTillSent()会在每次send后调用, 用于一些异步写的场景
void ScopedRosLogSink::WaitTillSent() {
  if (will_die_) {
    // Give ROS some time to actually publish our message.
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
  }
}

}  // namespace cartographer_ros

核心的语句为:

const std::string message_string = ::google::LogSink::ToString(
      severity, GetBasename(filename), line, tm_time, message, message_len);

ROS_xxx_STREAM(message_string);

::google::LogSink::ToString 函数会把输入的参数如 severity,line,message 等汇聚成一条字符串信息,然后根据信息等级通过 ROS_xxxx_STREAM 输出。这样输出,最终就保持了ROS 格式,如下就是一条打印信息:

[ INFO] [1667016236.198881881, 1606808687.533274071]: I1029 12:03:56.000000 24790 collated_trajectory_builder.cc:115] points2 rate: 19.29 Hz 5.18e-02 s +/- 5.36e-04 s (pulsed at 1.79% real time)

含有信息安全级别、时间戳、文件名,消息长度等。每次消息发送完成之后会调用 void ScopedRosLogSink::WaitTillSent() 函数,如果发生致命(FATAL)错误,则会通过延时,给ROS一些时间来发布我们的消息。

结语 :

一是介绍了gflags如何去定义和使用参数;二是介绍了谷歌的glog日志系统,并如何去利用google的日志输出去自定义我们想要的在ros下的消息格式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值