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

大纲

  • 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.cppset_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 levelRCUTILS_LOG_SEVERITY_DEBUG = 10,  ///< The debug log levelRCUTILS_LOG_SEVERITY_INFO = 20,  ///< The info log levelRCUTILS_LOG_SEVERITY_WARN = 30,  ///< The warn log levelRCUTILS_LOG_SEVERITY_ERROR = 40,  ///< The error log levelRCUTILS_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.mzph.cn/web/50675.shtml

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

相关文章

【Opencv】色彩空间 color space

import os import cv2 img cv2.imread(os.path.join(.,dog.jpg)) # 在opencv中使用imread,读取的图片每个像素都是bgr色彩&#xff0c;蓝色&#xff0c;绿色&#xff0c;红色 cv2.imshow(img,img) cv2.waitKey(0) # 颜色空间转化&#xff1a;BGR2RGB img_rgb cv2.cvtC…

树深度对决策树性能的影响:深入分析

树深度对决策树性能的影响&#xff1a;深入分析 决策树是一种广泛应用于分类和回归任务的机器学习算法。它通过一系列决策规则将数据集划分为更小的子集&#xff0c;从而做出预测。决策树的深度是影响其性能的关键因素之一。本文将深入探讨树深度对决策树性能的影响&#xff0…

【Python学习手册(第四版)】学习笔记10-语句编写的通用规则

个人总结难免疏漏&#xff0c;请多包涵。更多内容请查看原文。本文以及学习笔记系列仅用于个人学习、研究交流。 本文较简单&#xff0c;5-10分钟即可阅读完成。介绍Python基本过程语句并讨论整体语法模型通用规则&#xff08;冒号、省略、终止、缩进、其他特殊情况&#xff0…

Python里lambda怎么使用呢?

lambda 函数在Python中是一种简洁定义单行小函数的方式。它允许你快速定义一个简单的、可以在需要函数对象的地方使用的匿名函数。lambda 函数的一般语法如下&#xff1a; python lambda 参数: 表达式 这里&#xff0c;参数 是传递给函数的参数&#xff0c;可以有一个或多个&a…

【CAN通讯系列5】CAN数据帧及其仲裁

在CAN通讯系列3-CAN通讯如何传递信号中&#xff0c;由于传递信号的分析需要&#xff0c;引出了CAN数据帧的ID&#xff0c;长度和数据段的概念&#xff0c;它们都与CAN协议帧相关。CAN协议帧有5种类型&#xff0c;如下表&#xff1a; 而我们当前使用到的是数据帧&#xff0c;故本…

常用命令git branch

Git Branch 命令总结 列出分支 git branch&#xff1a;显示本地分支&#xff0c;当前分支会被标记。git branch -r&#xff1a;显示远程分支。git branch -a&#xff1a;显示所有本地和远程分支。 创建分支 git branch <branch_name>&#xff1a;创建一个新分支但不自…

【WEB安全】 PHP基础与数据库教学下(超详细)

文章目录 php与mysql 简述 MySQLi 和 PDO 连接 MySQL 实例 Linux 和 Windows: 在 php5 mysql 包安装时 MySQLi 扩展多事情况下是自动安装的。 PDO 安装 实例 (MySQLi - 面向对象) 实例 (MySQLi - 面向过程) 实例 (PDO) 使用 MySQLi 和 PDO 向 MySQL 插入数据 实例 (M…

正向解析、反向解析、DNS主从、多区域、ntp时间同步

DNS配置回顾 编号主机名IP地址说明1web服务器192.168.1.17发布部署web服务2dns服务器192.168.1.20用于解析域名和IP地址3clien主机192.168.1.18用于模拟客户机 修改 client主机&#xff1a;修改了dns的访问主机&#xff1b;临时修改echo "nameserver IP地址"&…

docker 常用管理命令及数据备份

docker 常用管理命令及数据备份 常用管理命令 重启 cd share docker compose restart 停止 cd share docker compose stop 启动 cd share ./deploy.sh 升级 cd share ./deploy.sh 查看日志 cd share docker compose logs -f 数据备份 以下备份相关命令均要求在doc…

【Web开发手礼】探索Web开发的秘密(十三)-Vue(3)好友列表、登录

前言 主要介绍了好友列表、登录界面所涉及的vue知识点&#xff01;&#xff01;&#xff01; 好友列表 从云端API读取数据信息 地址 https://app165.acapp.acwing.com.cn/myspace/userlist/方法&#xff1a;GET是否验证jwt&#xff1a;否输入参数&#xff1a;无返回结果&…

【diffusers极速入门(四)】EMA 操作是什么?

系列文章目录 【diffusers 极速入门&#xff08;一&#xff09;】pipeline 实际调用的是什么&#xff1f; call 方法!【diffusers 极速入门&#xff08;二&#xff09;】如何得到扩散去噪的中间结果&#xff1f;Pipeline callbacks 管道回调函数【diffusers极速入门&#xff0…

基于okhttp3拦截器实现短时间内重复请求的拦截

基于okhttp3拦截器实现短时间内重复请求的拦截 背景 某次需求代码实现存在缺陷, 导致用户在点击某标签的时候发起了2次请求(即一次重复请求)。由于开发自测阶段没有盯着抓包软件看请求次数, 测试也没有关注接口请求次数问题, 最终将问题带上线。 影响面 导致被调用的接口QPS翻…

C#知识|文件与目录操作:文本读写操作

哈喽,你好啊,我是雷工! 今天学习文件与目录的操作,以下为文本读写操作的学习笔记。 01 文件操作说明 1.1、数据的存取方式 数据库:适合存取大量且关系复杂并有序的数据; 文件:适合存取大量但数据关系简单的数据,像系统的日志文件; 1.2、文件存取的优点 ①:读取操…

探索 GPT-4o mini:成本效益与开发效率的完美平衡

随着人工智能技术的飞速发展&#xff0c;OpenAI 最新发布的 GPT-4o mini 模型以其卓越的性能和极具竞争力的价格引发了广泛关注。作为一名在计算机行业深耕多年的专家&#xff0c;我已经开始深入探索这一“迄今为止最具成本效益的小模型”。本文将分享我在使用 GPT-4o mini 及其…

ECharts - 坐标轴刻度数值处理

写图表时&#xff0c;Y轴的数值过大&#xff0c;不太可能直接展示&#xff0c;这时候就得简写了&#xff0c;或者百分比展示的也要处理&#xff0c;如下图&#xff1a; yAxis: {type: value,// Y轴轴线axisLine: { show: false }, // 刻度线axisTick: { show: false },// 轴刻度…

收藏!2024年GPU算力最新排名

​GPU&#xff08;图形处理单元&#xff09;算力的提升是驱动当代科技革命的核心力量之一&#xff0c;尤其在人工智能、深度学习、科学计算和超级计算机领域展现出了前所未有的影响力。2024年的GPU技术发展&#xff0c;不仅体现在游戏和图形处理的传统优势上&#xff0c;更在跨…

House of Lore

House of Lore 概述&#xff1a; House of Lore 攻击与 Glibc 堆管理中的 Small Bin 的机制紧密相关。House of Lore 可以实现分配任意指定位置的 chunk&#xff0c;从而修改任意地址的内存。House of Lore 利用的前提是需要控制 Small Bin Chunk 的 bk 指针&#xff0c;并且…

Android中如何手动制造logcat各等级日志(VERBOSE、DEBUG、INFO、WARNING、ERROR、FATAL)

文章目录 1、logcat与log工具2、通过log生成logcat日志2.1、logcat日志等级2.2、log指令说明2.3、log生成日志指令 3、制作日志生成shell脚本4、增加日志生成控制5、附录 1、logcat与log工具 logcat&#xff1a;是Android操作系统中用于记录和查看系统日志的工具。它是Android…

Docker基础概念

Docker 是一个流行的容器化平台&#xff0c;它使开发者能够打包他们的应用程序及其依赖项到一个轻量级、可移植的容器中。这有助于确保应用程序无论在哪里运行都能获得一致的结果。以下是 Docker 的几个基础概念的详细解释&#xff1a; 1. Docker 镜像 (Image) 定义: Docker …