Robot Operating System——深度解析日志功能的实现

news2024/9/8 20:49:53

大纲

  • enable_logger_service的作用
    • 创建获取日志等级的Service
    • 创建设置日志等级的Service
  • 不同等级日志的底层实现
  • 总结

在《Robot Operating System——远程修改日志等级》中,我们学习了日志相关的功能,但是没有进行深入分析。本文将分析下列几个课题

  • enable_logger_service的作用
  • 不同等级日志的底层实现

enable_logger_service的作用

当我们需要Node可以被远程修改日志等级时,可以通过在构造Node时,开启enable_logger_service功能。

class LoggerServiceNode : public rclcpp::Node
{
public:
  explicit LoggerServiceNode(const std::string & node_name)
  : Node(node_name, rclcpp::NodeOptions().enable_logger_service(true))
  {

这样Node就会调用create_logger_services创建两个Service,分别用于获取和设置日志等级。

  if (options.enable_logger_service()) {
    node_logging_->create_logger_services(node_services_);
  }

创建获取日志等级的Service

在create_logger_services的底层,首先创建的是获取日志等级的Service。

// https://github.com/ros2/rclcpp/blob/jazzy/rclcpp/src/rclcpp/node_interfaces/node_logging.cpp
void NodeLogging::create_logger_services(
  node_interfaces::NodeServicesInterface::SharedPtr node_services)
{
  rclcpp::ServicesQoS qos_profile;
  const std::string node_name = node_base_->get_name();
  auto callback_group = node_base_->get_default_callback_group();

  get_loggers_service_ = rclcpp::create_service<rcl_interfaces::srv::GetLoggerLevels>(
    node_base_, node_services,
    node_name + "/get_logger_levels",
    [](
      const std::shared_ptr<rmw_request_id_t>,
      const std::shared_ptr<rcl_interfaces::srv::GetLoggerLevels::Request> request,
      std::shared_ptr<rcl_interfaces::srv::GetLoggerLevels::Response> response)
    {
      for (auto & name : request->names) {
        rcl_interfaces::msg::LoggerLevel logger_level;
        logger_level.name = name;
        auto ret = rcutils_logging_get_logger_level(name.c_str());
        if (ret < 0) {
          logger_level.level = 0;
        } else {
          logger_level.level = static_cast<uint8_t>(ret);
        }
        response->levels.push_back(std::move(logger_level));
      }
    },
    qos_profile, callback_group);

在回调中,ROS2会通过向rcutils_logging_get_logger_level传递Node的名称来查询它对应的等级。它的底层又会调用get_severity_level方法

// https://github.com/ros2/rcutils/blob/jazzy/src/logging.c
int rcutils_logging_get_logger_leveln(const char * name, size_t name_length)
{
……
  char * short_name = rcutils_strndup(name, name_length, g_rcutils_logging_allocator);
  if (short_name == NULL) {
    RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING(
      "Failed to allocate memory when looking up logger level for '%s'", name);
    return -1;
  }

  int severity;
  rcutils_ret_t ret = get_severity_level(short_name, &severity);
// https://github.com/ros2/rcutils/blob/jazzy/src/logging.c
static rcutils_ret_t get_severity_level(const char * name, int * severity)
{
  rcutils_ret_t ret =
    rcutils_hash_map_get(&g_rcutils_logging_severities_map, &name, severity);

最后我们追踪到,静态变量g_rcutils_logging_severities_map保存了不同Node的日志等级信息。不出意外的话,我们在设置日志等级时也会看到它的身影。

创建设置日志等级的Service

这个Service底层调用的是rcutils_logging_set_logger_level方法来设置Node的日志等级。

// https://github.com/ros2/rclcpp/blob/jazzy/rclcpp/src/rclcpp/node_interfaces/node_logging.cpp
  set_loggers_service_ = rclcpp::create_service<rcl_interfaces::srv::SetLoggerLevels>(
    node_base_, node_services,
    node_name + "/set_logger_levels",
    [](
      const std::shared_ptr<rmw_request_id_t>,
      const std::shared_ptr<rcl_interfaces::srv::SetLoggerLevels::Request> request,
      std::shared_ptr<rcl_interfaces::srv::SetLoggerLevels::Response> response)
    {
      rcl_interfaces::msg::SetLoggerLevelsResult result;
      for (auto & level : request->levels) {
        auto ret = rcutils_logging_set_logger_level(level.name.c_str(), level.level);
        if (ret != RCUTILS_RET_OK) {
          result.successful = false;
          result.reason = rcutils_get_error_string().str;
        } else {
          result.successful = true;
        }
        response->results.push_back(std::move(result));
      }
    },
    qos_profile, callback_group);
}
rcutils_ret_t rcutils_logging_set_logger_level(const char * name, int level)
{
……
  rcutils_ret_t add_key_ret = add_key_to_hash_map(name, level, true);
  ……

static rcutils_ret_t add_key_to_hash_map(const char * name, int level, bool set_by_user)
{
……
  rcutils_ret_t hash_map_ret =
    rcutils_hash_map_set(&g_rcutils_logging_severities_map, &copy_name, &level);

不同等级日志的底层实现

我们追踪下RCLCPP_DEBUG、RCLCPP_INFO、RCLCPP_WARN、RCLCPP_ERROR和RCLCPP_FATAL的底层实现。

// /opt/ros/jazzy/include/rclcpp/rclcpp/logging.hpp
#define RCLCPP_DEBUG(logger, ...) \
  do { \
    static_assert( \
      ::std::is_same<typename std::remove_cv_t<typename std::remove_reference_t<decltype(logger)>>, \
      typename ::rclcpp::Logger>::value, \
      "First argument to logging macros must be an rclcpp::Logger"); \
 \
    RCUTILS_LOG_DEBUG_NAMED( \
      (logger).get_name(), \
      __VA_ARGS__); \
  } while (0)

#define RCLCPP_INFO(logger, ...) \
  do { \
    static_assert( \
      ::std::is_same<typename std::remove_cv_t<typename std::remove_reference_t<decltype(logger)>>, \
      typename ::rclcpp::Logger>::value, \
      "First argument to logging macros must be an rclcpp::Logger"); \
 \
    RCUTILS_LOG_INFO_NAMED( \
      (logger).get_name(), \
      __VA_ARGS__); \
  } while (0)

#define RCLCPP_WARN(logger, ...) \
  do { \
    static_assert( \
      ::std::is_same<typename std::remove_cv_t<typename std::remove_reference_t<decltype(logger)>>, \
      typename ::rclcpp::Logger>::value, \
      "First argument to logging macros must be an rclcpp::Logger"); \
 \
    RCUTILS_LOG_WARN_NAMED( \
      (logger).get_name(), \
      __VA_ARGS__); \
  } while (0)

#define RCLCPP_ERROR(logger, ...) \
  do { \
    static_assert( \
      ::std::is_same<typename std::remove_cv_t<typename std::remove_reference_t<decltype(logger)>>, \
      typename ::rclcpp::Logger>::value, \
      "First argument to logging macros must be an rclcpp::Logger"); \
 \
    RCUTILS_LOG_ERROR_NAMED( \
      (logger).get_name(), \
      __VA_ARGS__); \
  } while (0)

#define RCLCPP_FATAL(logger, ...) \
  do { \
    static_assert( \
      ::std::is_same<typename std::remove_cv_t<typename std::remove_reference_t<decltype(logger)>>, \
      typename ::rclcpp::Logger>::value, \
      "First argument to logging macros must be an rclcpp::Logger"); \
 \
    RCUTILS_LOG_FATAL_NAMED( \
      (logger).get_name(), \
      __VA_ARGS__); \
  } while (0)

它们底层调用的都是RCUTILS_LOG_COND_NAMED,只是等级不同。

// /opt/ros/jazzy/include/rcutils/rcutils/logging_macros.h
# define RCUTILS_LOG_DEBUG_NAMED(name, ...) \
  RCUTILS_LOG_COND_NAMED( \
    RCUTILS_LOG_SEVERITY_DEBUG, \
    RCUTILS_LOG_CONDITION_EMPTY, RCUTILS_LOG_CONDITION_EMPTY, name, \
    __VA_ARGS__)
    
# define RCUTILS_LOG_INFO_NAMED(name, ...) \
  RCUTILS_LOG_COND_NAMED( \
    RCUTILS_LOG_SEVERITY_INFO, \
    RCUTILS_LOG_CONDITION_EMPTY, RCUTILS_LOG_CONDITION_EMPTY, name, \
    __VA_ARGS__)

# define RCUTILS_LOG_ERROR(...) \
  RCUTILS_LOG_COND_NAMED( \
    RCUTILS_LOG_SEVERITY_ERROR, \
    RCUTILS_LOG_CONDITION_EMPTY, RCUTILS_LOG_CONDITION_EMPTY, NULL, \
    __VA_ARGS__)
    
# define RCUTILS_LOG_ERROR_NAMED(name, ...) \
  RCUTILS_LOG_COND_NAMED( \
    RCUTILS_LOG_SEVERITY_ERROR, \
    RCUTILS_LOG_CONDITION_EMPTY, RCUTILS_LOG_CONDITION_EMPTY, name, \
    __VA_ARGS__)

# define RCUTILS_LOG_FATAL_NAMED(name, ...) \
  RCUTILS_LOG_COND_NAMED( \
    RCUTILS_LOG_SEVERITY_FATAL, \
    RCUTILS_LOG_CONDITION_EMPTY, RCUTILS_LOG_CONDITION_EMPTY, name, \
    __VA_ARGS__)

这些等级的枚举值是

// /opt/ros/jazzy/include/rcutils/rcutils/logging.h
enum RCUTILS_LOG_SEVERITY
{
  RCUTILS_LOG_SEVERITY_UNSET = 0,  ///< The unset log level
  RCUTILS_LOG_SEVERITY_DEBUG = 10,  ///< The debug log level
  RCUTILS_LOG_SEVERITY_INFO = 20,  ///< The info log level
  RCUTILS_LOG_SEVERITY_WARN = 30,  ///< The warn log level
  RCUTILS_LOG_SEVERITY_ERROR = 40,  ///< The error log level
  RCUTILS_LOG_SEVERITY_FATAL = 50,  ///< The fatal log level
};

RCUTILS_LOG_COND_NAMED的逻辑是:先通过日志级别severity判断该日志是否需要输出,如果要输出则调用rcutils_log_internal进行输出。比如我们设置的日志级别时WARN,那么RCLCPP_DEBUG、RCLCPP_INFO不会输出日志。

// /opt/ros/jazzy/include/rcutils/rcutils/logging_macros.h
#define RCUTILS_LOG_COND_NAMED(severity, condition_before, condition_after, name, ...) \
  do { \
    RCUTILS_LOGGING_AUTOINIT; \
    static rcutils_log_location_t __rcutils_logging_location = {__func__, __FILE__, __LINE__}; \
    if (rcutils_logging_logger_is_enabled_for(name, severity)) { \
      condition_before \
      rcutils_log_internal(&__rcutils_logging_location, severity, name, __VA_ARGS__); \
      condition_after \
    } \
  } while (0)

我们继续追踪rcutils_logging_logger_is_enabled_for的实现

// https://github.com/ros2/rcutils/blob/jazzy/src/logging.c
bool rcutils_logging_logger_is_enabled_for(const char * name, int severity)
{
  RCUTILS_LOGGING_AUTOINIT;
  int logger_level = g_rcutils_logging_default_logger_level;
  if (name) {
    logger_level = rcutils_logging_get_logger_effective_level(name);
    if (-1 == logger_level) {
      RCUTILS_SAFE_FWRITE_TO_STDERR_WITH_FORMAT_STRING(
        "Error determining if logger '%s' is enabled for severity '%d'\n",
        name, severity);
      return false;
    }
  }
  return severity >= logger_level;
}
int rcutils_logging_get_logger_effective_level(const char * name)
{
……
  // Start by trying to find the exact name.
  int severity;
  rcutils_ret_t ret = get_severity_level(name, &severity);
static rcutils_ret_t get_severity_level(const char * name, int * severity)
{
  rcutils_ret_t ret =
    rcutils_hash_map_get(&g_rcutils_logging_severities_map, &name, severity);
  if (ret != RCUTILS_RET_OK) {
    // One possible response is RCUTILS_RET_NOT_FOUND, but the higher layers may be OK with that.
    return ret;
  }

  // See the comment in add_key_to_hash_map() on why we remove the bottom bit.
  (*severity) &= ~(0x1);

  return RCUTILS_RET_OK;
}

可以看到,兜兜转转,我们还是回到静态变量g_rcutils_logging_severities_maps上。

总结

  • enable_logger_service会让Node启动两个Service,分别用于获取和设置Node的日志等级。
  • 在ROS2中,会通过静态变量g_rcutils_logging_severities_mapb保存不同Node的日志等级。
  • RCLCPP_DEBUG、RCLCPP_INFO、RCLCPP_WARN、RCLCPP_ERROR和RCLCPP_FATAL第一个参数要传递Node的rclcpp::Logger的原因是:ROS2需要通过它获取名称,进而判断该Node的日志等级。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1958441.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

入门 PyQt6 看过来(案例)14~ 分组

本文分享一个分组框功能&#xff0c;采用pyqt6里的QGroupBox​控件&#xff0c;效果如下&#xff1a;性别和专业分开为两个分组框内&#xff1a; ​ 1 功能实现思路 ui页面布局设计 性别和专业要设计成两个分组框&#xff1a; ​ 逻辑实现 引入信号和槽函数来实现点击单选…

Cybersecurity ASPICE实施策略-基于ISO/SAE 21434-亚远景科技

近几年&#xff0c;随着软件定义汽车和汽车的智能化和网联化&#xff0c;使得汽车融合了现代通信与网络通信技术&#xff0c;实现了车与人、车与车、车与道路、车与云端等智能信息交互和共享&#xff0c;也让车具备了环境感知、协同控制、智能决策等功能&#xff1b;与此同时&a…

构建可定制的表情选择器组件

你好呀&#xff0c;我是小邹。 概述 在当今的交互式Web应用中&#xff0c;表情符号&#xff08;Emoji&#xff09;已成为一种流行的沟通方式。为了提升用户体验并简化开发流程&#xff0c;本教程将引导您如何构建一个可高度定制的表情选择器组件。此组件将允许用户在Web表单中…

力扣621.任务调度器

力扣621.任务调度器 桶思想当桶放不满时 答案为桶面积 maxcount(最后一行) (max - 1)(n1)当桶放的满时 答案为任务总数 tasks.size()最终两者取大即可 class Solution {public:int leastInterval(vector<char>& tasks, int n) {int len tasks.size();vector<…

QT--聊天室

一、设计要求 用QT做一个聊天室&#xff0c; 制作一个服务器和客户端。可以进行注册、登录&#xff0c; 登陆成功后可以使用昵称进行发送、接收消息。 能根据昵称、聊天内容查询历史记录&#xff0c;也可以查询全部聊天记录。 。 二、客户端三级ui界面 三、项目代码 //在…

测试用例:确保软件质量的基石

大家好&#xff0c;我是一名测试开发工程师&#xff0c;已经开源一套【自动化测试框架】和【测试管理平台】&#xff0c;欢迎大家联系我&#xff0c;一起【分享测试知识&#xff0c;交流测试技术】 在当今这个数字化时代&#xff0c;软件已经成为人们日常生活、工作和学习中不可…

Hive3:Centos7环境部署Hive服务

一、安装说明 1、Hadoop集群情况 3台机器&#xff1a;4G2C、2G2C、2G2C 安装教程&#xff1a;Centos7环境安装Hadoop集群 2、安装MySQL&#xff0c;用于存储Hive的元数据 在102机器上安装MySQL 安装MySQL使用服务器的root账号 3、最后安装Hive 安装hive过程使用服务器的atgu…

fatal: Could not read from remote repository. 解决方法

问题描述&#xff1a; Git : fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists。 解决方法&#xff1a; 当在网上尝试大量方法仍然失败的时候&#xff0c;不妨试试这个方法。 在 github 上&…

ROUND() Function in SQL(四舍五入)

ROUND() Function in SQL ROUND() 函数用于将数值四舍五入到指定的小数位数或者整数位数。 不同的数据库系统可能会有一些细微的语法差异&#xff0c;但基本功能都是一致的。 1. 基本语法 ROUND(number, decimal_places)number: 要进行四舍五入的数值。decimal_places: 可选参…

2024年7月30日 十二生肖 今日运势

小运播报&#xff1a;2024年7月30日&#xff0c;星期二&#xff0c;农历六月廿五 &#xff08;甲辰年辛未月乙未日&#xff09;&#xff0c;法定工作日。 红榜生肖&#xff1a;兔、马、猴 需要注意&#xff1a;狗、鼠、牛 喜神方位&#xff1a;西北方 财神方位&#xff1a;…

基于SpringBoot+Vue的游戏攻略分享平台(带1w+文档)

基于SpringBootVue的游戏攻略分享平台(带1w文档) 本系统为了数据库结构的灵活性所以打算采用MySQL来设计数据库&#xff0c;而java技术&#xff0c;B/S架构则保证了较高的平台适应性。本文主要介绍了本系统的开发背景&#xff0c;所要完成的功能和开发的过程&#xff0c;主要说…

大数据-54 Kafka 安装配置 环境变量配置 启动服务 Ubuntu配置 ZooKeeper

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

零基础入门转录组数据分析——加权基因共表达网络分析(WGCNA,Weighted correlation network analysis)

零基础入门转录组数据分析——加权基因共表达网络分析&#xff08;WGCNA&#xff0c;Weighted correlation network analysis&#xff09; 目录 零基础入门转录组数据分析——加权基因共表达网络分析&#xff08;WGCNA&#xff0c;Weighted correlation network analysis&#…

c语言代码运行不成功,如何解决?

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

GEE数据:Sentinel-2数据更新新增两个云和雪波段(MSK_CLDPRB和MSK_SNWPRB)

目录 简介 数据时间 数据提供者 Collection Snippet 波段名称 Class Table: SCL 影像属性 代码 结果 简介 2022年1月25日之后&#xff0c;PROCESSING_BASELINE为“04.00”或以上的Sentinel-2场景的DN&#xff08;值&#xff09;范围移动了1000。HARMONIZED集合将新场…

【C++】std::shared_ptr智能指针详解和示例

在C中&#xff0c;智能指针是一种用于自动管理动态分配内存的机制&#xff0c;旨在减少内存泄漏和野指针的风险。std::shared_ptr 是C标准库提供的几种智能指针之一&#xff0c;它通过共享所有权的机制来管理动态分配的对象。本文将详细解析 std::shared_ptr 的工作原理、特性&…

【电路笔记】-共源JFET放大器

共源JFET放大器 文章目录 共源JFET放大器1、概述2、共源JFET放大器3、JFET放大器电流和功率增益共源JFET放大器使用结场效应晶体管作为其主要有源器件,提供高输入阻抗特性。 1、概述 普通源JFET放大器与共射极BJT放大器相比有一个重要优点,即FET具有极高的输入阻抗,再加上低…

工业三防平板,高效能与轻便性的结合

在当今数字化、智能化的工业时代&#xff0c;工业三防平板作为一种创新的设备&#xff0c;正以其独特的优势在各个领域发挥着重要作用。它不仅具备高效能的处理能力&#xff0c;还拥有出色的轻便性&#xff0c;为工业生产和管理带来了前所未有的便利。 一、高效能的核心动力 工…

2024年中职云计算实验室建设及云计算实训平台整体解决方案

随着信息技术的飞速发展&#xff0c;云计算作为新一代信息技术的核心&#xff0c;正逐步渗透到各行各业&#xff0c;成为推动数字化转型的重要力量。为了适应这一趋势&#xff0c;中职教育作为技能型人才培养的重要阵地&#xff0c;亟需加强云计算实验室建设与云计算实训平台的…

web,apache,nginx

web基本概念和常识 Web:为用户提供的一种在互联网上浏览信息的服务&#xff0c;Web 服务 是动态的、可交 互的、跨平台的和图形化的。 Web 服务为用户提供各种互联网服务&#xff0c;这些服务包括信息浏览服务&#xff0c;以及各种交互式服务&#xff0c;包括聊天、购物、学习…