【ROS2】演示:日志记录

 日志记录 

 目录

  • 在代码中使用日志语句

    •  基本日志记录

    • 仅记录第一次

    • 记录除第一次之外的所有时间

    •  记录被限制

    • 记录限制了除第一次之外的所有时间

  •  日志演示

  • 日志目录配置

  • 记录器级别配置:以编程方式

  • 记录器级别配置:外部

    • 使用记录器配置组件

  • 记录器级别配置:命令行

    • 控制台输出格式

    • 控制台输出着色

    • 默认的控制台输出流

    • 行缓冲控制台输出

  • 设置日志文件名前缀

请参阅日志页面以获取有关可用功能的详细信息。

在代码中使用日志语句 

593af6f9adca04b7e559b81e79771996.png

基础日志记录 

以下代码将输出来自 ROS 2 节点的 DEBUG 严重性日志消息:

C++:

// printf style
RCLCPP_DEBUG(node->get_logger(), "My log message %d", 4);


// C++ stream style
RCLCPP_DEBUG_STREAM(node->get_logger(), "My log message " << 4);

请注意,在这两种情况下,都不会添加尾随换行符,因为日志记录infrastructure会自动添加一个。

Python:

node.get_logger().debug('My log message %d' % (4))

551e0e6f7557ef4210cd6e03885e16be.png

仅记录第一次 

以下代码将从 ROS 2 节点输出一个 INFO 严重级别的日志消息,但仅在第一次命中时输出:

C++:

// printf style
RCLCPP_INFO_ONCE(node->get_logger(), "My log message %d", 4);


// C++ stream style
RCLCPP_INFO_STREAM_ONCE(node->get_logger(), "My log message " << 4);

Python:

num = 4
node.get_logger().info(f'My log message {num}', once=True)

a02c09d59656f97cd1dbcd2dc827f7c9.png

记录所有但非第一次 

以下代码将从 ROS 2 节点输出 WARN 严重性级别的日志消息,但不会在第一次命中时输出:

C++:

// printf style
RCLCPP_WARN_SKIPFIRST(node->get_logger(), "My log message %d", 4);


// C++ stream style
RCLCPP_WARN_STREAM_SKIPFIRST(node->get_logger(), "My log message " << 4);

Python:

num = 4
node.get_logger().warning('My log message {0}'.format(num), skip_first=True)

b2b297a1cb7e756c57d06bcbf21909b3.png

 记录被限制 

以下代码将从 ROS 2 节点输出 ERROR 严重性级别的日志消息,但每秒不超过一次。

间隔参数指定消息之间的毫秒数应具有整数数据类型,以便可以将其转换为 rcutils_duration_value_t ( int64_t )

C++:

// printf style
RCLCPP_ERROR_THROTTLE(node->get_logger(), *node->get_clock(), 1000, "My log message %d", 4);


// C++ stream style
RCLCPP_ERROR_STREAM_THROTTLE(node->get_logger(), *node->get_clock(), 1000, "My log message " << 4);


// For now, use the nanoseconds() method to use an existing rclcpp::Duration value, see https://github.com/ros2/rclcpp/issues/1929
RCLCPP_ERROR_STREAM_THROTTLE(node->get_logger(), *node->get_clock(), msg_interval.nanoseconds()/1000000, "My log message " << 4);

Python:

num = 4
node.get_logger().error(f'My log message {num}', throttle_duration_sec=1)

eb28fa833a7c3876e3d8eacf96803433.png

eaea818355e8970bec8fe8782e1d606b.png

记录限制了除第一次之外的所有时间 

以下代码将从 ROS 2 节点输出一个日志消息,严重性为 DEBUG ,每秒不超过一次,跳过第一次命中时的输出:

C++:

// printf style
RCLCPP_DEBUG_SKIPFIRST_THROTTLE(node->get_logger(), *node->get_clock(), 1000, "My log message %d", 4);


RCLCPP_DEBUG_STREAM_SKIPFIRST_THROTTLE(node->get_logger(), *node->get_clock(), 1000, "My log message " << 4);

Python:

num = 4
node.get_logger().debug(f'My log message {num}', skip_first=True, throttle_duration_sec=1.0)

0a442d36505d7ca1ad37dcbbf182cb56.png

 日志演示 

在此演示中,显示了不同类型的日志调用,并且不同日志记录器的严重性级别在本地和外部进行了配置。

开始演示:

ros2 run logging_demo logging_demo_main

随着时间的推移,您将看到具有不同属性的各种日志调用的输出。首先,您只会看到严重性为 INFO 及以上的日志调用的输出( WARN 、 ERROR 、 FATAL )。请注意,尽管每次迭代都会到达该行,但第一条消息只会记录一次,因为这是用于该消息的日志调用的属性

a0ca3803052c215991289afedf31366d.png

日志目录配置 

日志目录可以通过两个环境变量配置: ROS_LOG_DIR 和 ROS_HOME 。逻辑如下:

  • 如果 ROS_LOG_DIR 已设置且不为空,则使用 $ROS_LOG_DIR 。

  • 否则,使用 $ROS_HOME/log ,如果未设置或为空,则使用 ~/.ros 用于 ROS_HOME 。

例如,要将日志目录设置为 ~/my_logs :

Linux:

export ROS_LOG_DIR=~/my_logs
ros2 run logging_demo logging_demo_main

然后,您将在 ~/my_logs/ 下找到日志。

或者,您可以设置 ROS_HOME ,日志目录将相对于它( $ROS_HOME/log )。 ROS_HOME 旨在用于需要基本目录的任何内容。请注意, ROS_LOG_DIR 必须未设置或为空。例如,将 ROS_HOME 设置为 ~/my_ros_home :

Linux:

export ROS_HOME=~/my_ros_home
ros2 run logging_demo logging_demo_main

然后你会在 ~/my_ros_home/log/ 下找到日志。

记录器级别配置:以编程方式 

经过 10 次迭代后,记录器的级别将设置为 DEBUG ,这将导致记录其他消息。

其中一些调试消息会导致额外的函数/表达式被评估,这些函数/表达式之前被跳过,因为 DEBUG 日志调用未启用。有关所使用调用的进一步解释,请参阅演示的源代码 https://github.com/ros2/demos/blob/jazzy/logging_demo/src/logger_usage_component.cpp ,并参阅 rclcpp 日志记录文档以获取支持的日志记录调用的完整列表。

记录器级别配置:外部 

ROS 2 节点具有可用于在运行时外部配置日志级别的服务。这些服务默认情况下是禁用的。以下代码显示了如何在创建节点时启用日志服务。

C++:

// Create a node with logger service enabled
auto node = std::make_shared<rclcpp::Node>("NodeWithLoggerService", rclcpp::NodeOptions().enable_logger_service(true))

如果按照上述配置运行其中一个节点,运行 ros2 service list 时会发现 2 个服务:

$ ros2 service list
...
/NodeWithLoggerService/get_logger_levels
/NodeWithLoggerService/set_logger_levels
...

 获取记录器级别

使用此服务获取指定记录器名称的记录器级别

运行 ros2 service call 以获取 NodeWithLoggerService 和 rcl 的记录器级别。

$ ros2 service call /NodeWithLoggerService/get_logger_levels rcl_interfaces/srv/GetLoggerLevels '{names: ["NodeWithLoggerService", "rcl"]}'


requester: making request: rcl_interfaces.srv.GetLoggerLevels_Request(names=['NodeWithLoggerService', 'rcl'])


response:
rcl_interfaces.srv.GetLoggerLevels_Response(levels=[rcl_interfaces.msg.LoggerLevel(name='NodeWithLoggerService', level=0), rcl_interfaces.msg.LoggerLevel(name='rcl', level=0)])

 设置记录器级别

使用此服务为指定的记录器名称设置记录器级别。

运行 ros2 service call 以设置 NodeWithLoggerService 和 rcl 的记录器级别。

$ ros2 service call /NodeWithLoggerService/set_logger_levels rcl_interfaces/srv/SetLoggerLevels '{levels: [{name: "NodeWithLoggerService", level: 20}, {name: "rcl", level: 10}]}'


requester: making request: rcl_interfaces.srv.SetLoggerLevels_Request(levels=[rcl_interfaces.msg.LoggerLevel(name='NodeWithLoggerService', level=20), rcl_interfaces.msg.LoggerLevel(name='rcl', level=10)])


response:
rcl_interfaces.srv.SetLoggerLevels_Response(results=[rcl_interfaces.msg.SetLoggerLevelsResult(successful=True, reason=''), rcl_interfaces.msg.SetLoggerLevelsResult(successful=True, reason='')])

还有演示代码显示如何通过记录器服务设置或获取记录器级别。

  •  rclcpp:演示代码

$ ros2 run demo_nodes_cpp use_logger_service
  • rclpy:演示代码

$ ros2 run demo_nodes_py use_logger_service

 警告

目前, get_logger_levels 和 set_logger_levels 服务存在线程不安全的限制。这意味着您需要确保一次只有一个线程在调用这些服务。详情请参见 https://github.com/ros2/rcutils/issues/397

使用记录器配置组件 

响应记录器配置请求的服务器已开发为一个组件,以便可以将其添加到现有的基于组合的系统中。例如,如果您使用容器运行节点,为了能够配置记录器,您只需要请求它另外将 logging_demo::LoggerConfig 组件加载到容器中。

例如,如果您想调试 composition::Talker 演示,可以像平常一样启动 talker:

Shell 1:

ros2 run rclcpp_components component_container

Shell 2:

ros2 component load /ComponentManager composition composition::Talker

最后,通过处理空名称记录器,将所有未设置的记录器配置为调试级别。请注意,已专门配置为使用特定级别的记录器不会受到此调用的影响。

Shell 2:

ros2 service call /config_logger logging_demo/srv/ConfigLogger "{logger_name: '', level: DEBUG}"

您应该会看到进程启动时任何先前未设置的记录器的调试输出开始出现,包括来自 ROS 2 核心的调试输出。

记录器级别配置:命令行 

从 Bouncy ROS 2 版本开始,未明确设置严重性级别的记录器的严重性级别可以从命令行进行配置。重新启动演示,包括以下命令行参数:

ros2 run logging_demo logging_demo_main --ros-args --log-level debug

这将配置任何未设置记录器的默认严重性为调试严重性级别。您应该看到来自演示本身和 ROS 2 核心的记录器的调试输出。

可以从命令行配置单个记录器的严重级别。重新启动演示,包括以下命令行参数:

ros2 run logging_demo logging_demo_main --ros-args --log-level logger_usage_demo:=debug

控制台输出格式化 

如果您想要更多或更少的详细格式,可以使用 RCUTILS_CONSOLE_OUTPUT_FORMAT 环境变量。例如,要另外获取日志调用的时间戳和位置,请停止演示并在设置环境变量后重新启动它:

Linux:

export RCUTILS_CONSOLE_OUTPUT_FORMAT="[{severity} {time}] [{name}]: {message} ({function_name}() at {file_name}:{line_number})"
ros2 run logging_demo logging_demo_main

您应该看到每条消息附带的时间戳(以秒为单位)、函数名称、文件名和行号。

有关配置控制台记录器格式的更多信息,请参阅记录器控制台配置

控制台输出着色 

默认情况下,当输出目标是终端时,输出是彩色的。如果您想强制启用或禁用它,可以使用 RCUTILS_COLORIZED_OUTPUT 环境变量。例如:

export RCUTILS_COLORIZED_OUTPUT=0  # 1 for forcing it
ros2 run logging_demo logging_demo_main

你应该看到调试、警告、错误和致命日志现在没有颜色了。

 注意

在 Linux 和 MacOS 中,强制彩色输出意味着如果将输出重定向到文件,ansi 转义颜色代码将出现在文件中。在 Windows 中,彩色化方法依赖于控制台 API。如果强制彩色化,您将收到一个新的警告,提示彩色化失败。默认行为已经检查输出是否为控制台,因此不推荐强制彩色化。

默认流用于控制台输出 

在 Foxy 及以后版本中,所有调试级别的输出默认都会发送到 stderr。可以通过设置 RCUTILS_LOGGING_USE_STDOUT 环境变量为 1 来强制所有输出发送到 stdout。例如:

export RCUTILS_LOGGING_USE_STDOUT=1

行缓冲控制台输出 

默认情况下,所有日志输出都是非缓冲的。您可以通过将 RCUTILS_LOGGING_BUFFERED_STREAM 环境变量设置为 1 来强制其缓冲。例如:

export RCUTILS_LOGGING_BUFFERED_STREAM=1

 然后运行:

ros2 run logging_demo logging_demo_main

设置日志文件名前缀 

默认情况下,日志文件名基于可执行文件名,后跟进程 ID 和文件创建时的系统时间戳。您可以使用 --log-file-name 命令行参数将日志文件名前缀更改为您选择的名称:

ros2 run demo_nodes_cpp talker --ros-args --log-file-name filename

附录 logging_demo

cxy@cxy-Ubuntu2404:~/second_ros2_ws/src/demos/logging_demo$ tree
.
├── CHANGELOG.rst
├── CMakeLists.txt
├── Doxyfile
├── include
│   └── logging_demo
│       ├── logger_config_component.hpp
│       ├── logger_usage_component.hpp
│       └── visibility_control.h
├── package.xml
├── src
│   ├── logger_config_component.cpp
│   ├── logger_usage_component.cpp
│   └── logging_demo_main.cpp
├── srv
│   └── ConfigLogger.srv
└── test
    ├── logging_demo_main_debug_severity.txt
    ├── logging_demo_main_default_severity.txt
    └── test_logging_demo.py.in


6 directories, 14 files

aa3d6b0265316570040ac5f7ff9a8240.png

CMakeLists.txt

cmake_minimum_required(VERSION 3.5) // 设置CMake的最低版本要求为3.5


project(logging_demo) // 定义项目名称为logging_demo


# 默认使用C++17标准
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 17) // 设置C++标准为17
  set(CMAKE_CXX_STANDARD_REQUIRED ON) // 强制要求使用C++17标准
endif()


if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic) // 如果使用GCC或Clang编译器,添加编译选项
endif()


find_package(ament_cmake REQUIRED) // 查找ament_cmake包
find_package(rclcpp REQUIRED) // 查找rclcpp包
find_package(rclcpp_components REQUIRED) // 查找rclcpp_components包
find_package(rcutils REQUIRED) // 查找rcutils包
find_package(std_msgs REQUIRED) // 查找std_msgs包


find_package(rosidl_default_generators REQUIRED) // 查找rosidl_default_generators包
rosidl_generate_interfaces(${PROJECT_NAME} // 生成服务接口
  "srv/ConfigLogger.srv"
)


include_directories(include) // 包含头文件目录


add_library(logger_config_component SHARED // 添加共享库logger_config_component
  src/logger_config_component.cpp)
target_compile_definitions(logger_config_component
  PRIVATE "LOGGING_DEMO_BUILDING_DLL") // 定义编译宏
ament_target_dependencies(logger_config_component
  "rclcpp"
  "rclcpp_components") // 设置目标依赖项
rosidl_get_typesupport_target(cpp_typesupport_target "${PROJECT_NAME}" "rosidl_typesupport_cpp")
target_link_libraries(logger_config_component "${cpp_typesupport_target}") // 链接类型支持库
rclcpp_components_register_nodes(logger_config_component "logging_demo::LoggerConfig") // 注册节点


add_library(logger_usage_component SHARED // 添加共享库logger_usage_component
  src/logger_usage_component.cpp)
target_compile_definitions(logger_usage_component
  PRIVATE "LOGGING_DEMO_BUILDING_DLL") // 定义编译宏
ament_target_dependencies(logger_usage_component
  "rclcpp"
  "rclcpp_components"
  "std_msgs") // 设置目标依赖项
rclcpp_components_register_nodes(logger_usage_component "logging_demo::LoggerUsage") // 注册节点


add_executable(logging_demo_main // 添加可执行文件logging_demo_main
  src/logging_demo_main.cpp)
target_link_libraries(logging_demo_main
  logger_config_component
  logger_usage_component) // 链接库
ament_target_dependencies(logging_demo_main
  "rclcpp") // 设置目标依赖项


install(TARGETS // 安装目标
  logger_config_component
  logger_usage_component
  ARCHIVE DESTINATION lib
  LIBRARY DESTINATION lib
  RUNTIME DESTINATION bin)


install(TARGETS
  logging_demo_main
  DESTINATION lib/${PROJECT_NAME})


if(BUILD_TESTING) // 如果启用测试
  find_package(ament_lint_auto REQUIRED) // 查找ament_lint_auto包
  ament_lint_auto_find_test_dependencies() // 查找测试依赖项


  find_package(ament_cmake_pytest REQUIRED) // 查找ament_cmake_pytest包
  find_package(launch_testing_ament_cmake REQUIRED) // 查找launch_testing_ament_cmake包
  find_package(rmw_implementation_cmake REQUIRED) // 查找rmw_implementation_cmake包


  set(generated_python_files) // 设置生成的Python文件
  macro(tests) // 定义宏tests
    set(LOGGING_DEMO_MAIN_EXECUTABLE $<TARGET_FILE:logging_demo_main>) // 设置可执行文件路径
    set(
      EXPECTED_OUTPUT_LOGGING_DEMO_MAIN_DEFAULT_SEVERITY
      "${CMAKE_CURRENT_SOURCE_DIR}/test/logging_demo_main_default_severity") // 设置默认日志级别的预期输出
    set(
      EXPECTED_OUTPUT_LOGGING_DEMO_MAIN_DEBUG_SEVERITY
      "${CMAKE_CURRENT_SOURCE_DIR}/test/logging_demo_main_debug_severity") // 设置调试日志级别的预期输出


    configure_file(
      test/test_logging_demo.py.in
      test_logging_demo${target_suffix}.py.genexp
      @ONLY
    ) // 配置文件
    file(GENERATE
      OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/test_logging_demo${target_suffix}_$<CONFIG>.py"
      INPUT "${CMAKE_CURRENT_BINARY_DIR}/test_logging_demo${target_suffix}.py.genexp"
    ) // 生成文件
    add_launch_test(
      "${CMAKE_CURRENT_BINARY_DIR}/test_logging_demo${target_suffix}_$<CONFIG>.py"
      TARGET test_logging_demo${target_suffix}
      ENV RMW_IMPLEMENTATION=${rmw_implementation}
      APPEND_LIBRARY_DIRS "${append_library_dirs}"
      TIMEOUT 30
    ) // 添加启动测试
    list(
      APPEND generated_python_files
      "${CMAKE_CURRENT_BINARY_DIR}/test_logging_demo${target_suffix}_$<CONFIG>.py") // 添加生成的Python文件到列表
  endmacro()


  set(append_library_dirs "${CMAKE_CURRENT_BINARY_DIR}") // 设置附加库目录
  if(WIN32)
    set(append_library_dirs "${append_library_dirs}/$<CONFIG>") // 如果是Windows系统,设置配置目录
  endif()


  call_for_each_rmw_implementation(tests) // 为每个RMW实现调用tests宏


  find_package(ament_cmake_flake8 REQUIRED) // 查找ament_cmake_flake8包
  ament_flake8(
    TESTNAME "flake8_generated_launch"
    # 生成的代码可能包含较长的行,如果来自模板
    MAX_LINE_LENGTH 999
    ${generated_python_files}) // 设置flake8测试
endif()


ament_package() // 定义ament包

logging_demo_main.cpp

#include <memory> // 包含智能指针库


#include "logging_demo/logger_config_component.hpp" // 包含日志配置组件的头文件
#include "logging_demo/logger_usage_component.hpp" // 包含日志使用组件的头文件
#include "rclcpp/rclcpp.hpp" // 包含ROS 2的核心库


int main(int argc, char * argv[]) // 主函数入口
{
  // 强制刷新标准输出缓冲区
  setvbuf(stdout, NULL, _IONBF, BUFSIZ);


  rclcpp::init(argc, argv); // 初始化ROS 2
  rclcpp::executors::SingleThreadedExecutor exec; // 创建单线程执行器
  rclcpp::NodeOptions options; // 创建节点选项


  // 创建一个处理日志配置请求的节点
  auto logger_config = std::make_shared<logging_demo::LoggerConfig>(options);
  exec.add_node(logger_config); // 将日志配置节点添加到执行器


  // 创建一个包含不同日志使用示例的节点
  auto logger_usage = std::make_shared<logging_demo::LoggerUsage>(options);
  exec.add_node(logger_usage); // 将日志使用节点添加到执行器


  exec.spin(); // 开始执行器循环


  rclcpp::shutdown(); // 关闭ROS 2
  return 0; // 返回0表示程序正常结束
}

logger_config_component.cpp

#include "logging_demo/logger_config_component.hpp" // 包含日志配置组件的头文件


#include <cinttypes> // 包含固定宽度整数类型的头文件
#include <iostream> // 包含输入输出流的头文件
#include <memory> // 包含智能指针库


#include "logging_demo/srv/config_logger.hpp" // 包含配置日志服务的头文件
#include "rclcpp/rclcpp.hpp" // 包含ROS 2的核心库
#include "rcutils/error_handling.h" // 包含错误处理的头文件
#include "rcutils/logging.h" // 包含日志记录的头文件


namespace logging_demo // 定义命名空间logging_demo
{


LoggerConfig::LoggerConfig(rclcpp::NodeOptions options) // LoggerConfig构造函数
: Node("logger_config", options) // 调用基类Node的构造函数,初始化节点名称为"logger_config"
{
  srv_ = create_service<logging_demo::srv::ConfigLogger>( // 创建配置日志服务
    "config_logger", this { // 响应参数
      return this->handle_logger_config_request(request, response); // 调用处理请求的函数
    }
  );
}


void
LoggerConfig::handle_logger_config_request( // 处理日志配置请求的函数
  const std::shared_ptr<logging_demo::srv::ConfigLogger::Request> request, // 请求参数
  std::shared_ptr<logging_demo::srv::ConfigLogger::Response> response) // 响应参数
{
  const char * severity_string = request->level.c_str(); // 获取请求中的日志级别字符串
  RCLCPP_INFO( // 打印日志信息
    this->get_logger(), "Incoming request: logger '%s', severity '%s'",
    request->logger_name.c_str(), severity_string);
  std::flush(std::cout); // 刷新标准输出缓冲区


  int severity; // 定义日志级别变量
  rcutils_ret_t ret = rcutils_logging_severity_level_from_string( // 将字符串转换为日志级别
    severity_string, rcl_get_default_allocator(), &severity);
  if (RCUTILS_RET_LOGGING_SEVERITY_STRING_INVALID == ret) { // 如果日志级别字符串无效
    RCLCPP_ERROR( // 打印错误信息
      this->get_logger(), "Unknown severity '%s'", severity_string);
    response->success = false; // 设置响应为失败
    return;
  }
  if (RCUTILS_RET_OK != ret) { // 如果转换过程中出现错误
    RCLCPP_ERROR( // 打印错误信息
      this->get_logger(), "Error %d getting severity level from request: %s", ret,
      rcl_get_error_string().str);
    rcl_reset_error(); // 重置错误状态
    response->success = false; // 设置响应为失败
    return;
  }


  // TODO(dhood): allow configuration through rclcpp
  ret = rcutils_logging_set_logger_level(request->logger_name.c_str(), severity); // 设置日志级别
  if (ret != RCUTILS_RET_OK) { // 如果设置日志级别失败
    RCLCPP_ERROR( // 打印错误信息
      this->get_logger(), "Error setting severity: %s", rcutils_get_error_string().str);
    rcutils_reset_error(); // 重置错误状态
    response->success = false; // 设置响应为失败
  }
  response->success = true; // 设置响应为成功
}


}  // namespace logging_demo // 命名空间结束


#include "rclcpp_components/register_node_macro.hpp" // 包含注册节点宏的头文件


// 注册组件到class_loader
// 这相当于一个入口点,当库被加载到运行进程时,使组件可被发现
RCLCPP_COMPONENTS_REGISTER_NODE(logging_demo::LoggerConfig)

logger_usage_component.cpp

#include "logging_demo/logger_usage_component.hpp" // 包含日志使用组件的头文件


#include <cinttypes> // 包含固定宽度整数类型的头文件
#include <iostream> // 包含输入输出流的头文件
#include <memory> // 包含智能指针库
#include <string> // 包含字符串库
#include <utility> // 包含工具库


#include "rclcpp/rclcpp.hpp" // 包含ROS 2的核心库
#include "rcutils/error_handling.h" // 包含错误处理的头文件
#include "std_msgs/msg/string.hpp" // 包含标准消息类型String的头文件


using namespace std::chrono_literals; // 使用chrono命名空间中的字面量


namespace logging_demo // 定义命名空间logging_demo
{


LoggerUsage::LoggerUsage(rclcpp::NodeOptions options) // LoggerUsage构造函数
: Node("logger_usage_demo", options), count_(0) // 初始化节点名称为"logger_usage_demo",计数器为0
{
  pub_ = create_publisher<std_msgs::msg::String>("logging_demo_count", 10); // 创建发布者,主题为"logging_demo_count",队列大小为10
  timer_ = create_wall_timer(500ms, this {return this->on_timer();}); // 创建定时器,每500毫秒调用一次on_timer函数
  debug_function_to_evaluate_ = this { // 定义调试函数
      return is_divisor_of_twelve(std::cref(this->count_), this->get_logger());
    };


  // 在10次迭代后,将日志级别设置为DEBUG
  auto on_one_shot_timer =
    this -> void {
      one_shot_timer_->cancel(); // 取消一次性定时器
      RCLCPP_INFO(get_logger(), "Setting severity threshold to DEBUG"); // 打印信息日志
      // TODO(dhood): 允许通过rclcpp进行配置
      auto ret = rcutils_logging_set_logger_level( // 设置日志级别为DEBUG
        get_logger().get_name(), RCUTILS_LOG_SEVERITY_DEBUG);
      if (ret != RCUTILS_RET_OK) { // 如果设置日志级别失败
        RCLCPP_ERROR(get_logger(), "Error setting severity: %s", rcutils_get_error_string().str); // 打印错误日志
        rcutils_reset_error(); // 重置错误状态
      }
    };
  one_shot_timer_ = create_wall_timer(5500ms, on_one_shot_timer); // 创建一次性定时器,5.5秒后调用on_one_shot_timer函数
}


void LoggerUsage::on_timer() // 定时器回调函数
{
  // 这条消息只会在第一次调用时记录
  RCLCPP_INFO_ONCE(get_logger(), "Timer callback called (this will only log once)");


  auto msg = std::make_unique<std_msgs::msg::String>(); // 创建一个String消息
  msg->data = "Current count: " + std::to_string(count_); // 设置消息数据


  // 这条消息每次调用时都会记录
  RCLCPP_INFO(get_logger(), "Publishing: '%s'", msg->data.c_str()); // 打印信息日志
  pub_->publish(std::move(msg)); // 发布消息


  // 当函数返回true时,这条消息会被记录
  // 只有在启用DEBUG级别日志时,函数才会被调用
  // 如果调试输出的计算开销很大,这种方式很有用
  RCLCPP_DEBUG_FUNCTION(
    get_logger(), &debug_function_to_evaluate_,
    "Count divides into 12 (function evaluated to true)");


  // 当表达式返回true时,这条消息会被记录
  // 只有在启用DEBUG级别日志时,表达式才会被计算
  RCLCPP_DEBUG_EXPRESSION(
    get_logger(), (count_ % 2) == 0, "Count is even (expression evaluated to true)");
  if (count_++ >= 15) { // 如果计数器大于等于15
    RCLCPP_WARN(get_logger(), "Reseting count to 0"); // 打印警告日志
    count_ = 0; // 重置计数器
  }
}


bool is_divisor_of_twelve(size_t val, rclcpp::Logger logger) // 判断值是否为12的因数
{
  // 这个方法在RCLCPP_DEBUG_FUNCTION()调用中被调用
  // 因此只有在启用DEBUG日志时才会被调用


  if (val == 0) { // 如果值为0
    RCLCPP_ERROR(logger, "Modulo divisor cannot be 0"); // 打印错误日志
    return false; // 返回false
  }
  return (12 % val) == 0; // 返回是否为12的因数
}


}  // namespace logging_demo // 命名空间结束


#include "rclcpp_components/register_node_macro.hpp" // 包含注册节点宏的头文件


// 注册组件到class_loader
// 这相当于一个入口点,当库被加载到运行进程时,使组件可被发现
RCLCPP_COMPONENTS_REGISTER_NODE(logging_demo::LoggerUsage)

附录 demo_nodes_cpp

cxy@cxy-Ubuntu2404:~/second_ros2_ws/src/demos/demo_nodes_cpp$ tree
.
├── CHANGELOG.rst
├── CMakeLists.txt
├── img
│   ├── allocator_tutorial.png
│   ├── content_filtering_messaging.png
│   ├── even_parameters_node.png
│   ├── one_off_timer.png
│   ├── parameter_blackboard.png
│   ├── serialized_messaging.png
│   ├── server_client.png
│   └── talker_listener.png
├── include
│   └── demo_nodes_cpp
│       └── visibility_control.h
├── launch
│   ├── services
│   │   ├── add_two_ints_async_launch.py
│   │   ├── add_two_ints_async_launch.xml
│   │   ├── add_two_ints_launch.py
│   │   ├── add_two_ints_launch.xml
│   │   └── introspect_services_launch.py
│   └── topics
│       ├── talker_listener_best_effort_launch.py
│       ├── talker_listener_best_effort_launch.xml
│       ├── talker_listener_best_effort_launch.yaml
│       ├── talker_listener_launch.py
│       ├── talker_listener_launch.xml
│       └── talker_listener_launch.yaml
├── package.xml
├── README.md
├── src
│   ├── events
│   │   └── matched_event_detect.cpp
│   ├── logging
│   │   └── use_logger_service.cpp
│   ├── parameters
│   │   ├── even_parameters_node.cpp
│   │   ├── list_parameters_async.cpp
│   │   ├── list_parameters.cpp
│   │   ├── parameter_blackboard.cpp
│   │   ├── parameter_event_handler.cpp
│   │   ├── parameter_events_async.cpp
│   │   ├── parameter_events.cpp
│   │   ├── set_and_get_parameters_async.cpp
│   │   ├── set_and_get_parameters.cpp
│   │   └── set_parameters_callback.cpp
│   ├── services
│   │   ├── add_two_ints_client_async.cpp
│   │   ├── add_two_ints_client.cpp
│   │   ├── add_two_ints_server.cpp
│   │   ├── introspection_client.cpp
│   │   └── introspection_service.cpp
│   ├── timers
│   │   ├── one_off_timer.cpp
│   │   └── reuse_timer.cpp
│   └── topics
│       ├── allocator_tutorial_custom.cpp
│       ├── allocator_tutorial_pmr.cpp
│       ├── content_filtering_publisher.cpp
│       ├── content_filtering_subscriber.cpp
│       ├── listener_best_effort.cpp
│       ├── listener.cpp
│       ├── listener_serialized_message.cpp
│       ├── talker.cpp
│       ├── talker_loaned_message.cpp
│       └── talker_serialized_message.cpp
└── test
    ├── add_two_ints_client_async.txt
    ├── add_two_ints_client.txt
    ├── add_two_ints_server.txt
    ├── content_filtering_publisher.txt
    ├── content_filtering_subscriber-rmw_connextdds.txt
    ├── content_filtering_subscriber-rmw_fastrtps_cpp.txt
    ├── content_filtering_subscriber.txt
    ├── listener.regex
    ├── list_parameters_async.txt
    ├── list_parameters.txt
    ├── matched_event_detect.txt
    ├── parameter_events_async.txt
    ├── parameter_events.txt
    ├── set_and_get_parameters_async.txt
    ├── set_and_get_parameters.txt
    ├── talker.txt
    ├── test_executables_tutorial.py.in
    └── use_logger_service.txt


15 directories, 71 files

use_logger_service.cpp

#include <chrono> // 包含时间库
#include <memory> // 包含智能指针库
#include <string> // 包含字符串库


#include "rclcpp/rclcpp.hpp" // 包含ROS 2的核心库


#include "std_msgs/msg/string.hpp" // 包含标准消息类型String的头文件


#include "rcl_interfaces/srv/get_logger_levels.hpp" // 包含获取日志级别服务的头文件
#include "rcl_interfaces/srv/set_logger_levels.hpp" // 包含设置日志级别服务的头文件


using namespace std::chrono_literals; // 使用chrono命名空间中的字面量


// 这个演示程序展示了如何启用日志服务并通过日志服务控制日志级别。
// LoggerServiceNode类启用日志服务并创建一个订阅。订阅的回调函数通过不同的日志函数输出接收到的消息。
// TestNode类可以设置/获取LoggerServiceNode的日志级别并向其发送消息。


class LoggerServiceNode : public rclcpp::Node // 定义LoggerServiceNode类,继承自rclcpp::Node
{
public:
  explicit LoggerServiceNode(const std::string & node_name) // 构造函数,接受节点名称作为参数
  : Node(node_name, rclcpp::NodeOptions().enable_logger_service(true)) // 调用基类构造函数,启用日志服务
  {
    auto callback = this-> void { // 定义订阅的回调函数
        RCLCPP_DEBUG(this->get_logger(), "%s with DEBUG logger level.", msg->data.c_str()); // 打印DEBUG级别日志
        RCLCPP_INFO(this->get_logger(), "%s with INFO logger level.", msg->data.c_str()); // 打印INFO级别日志
        RCLCPP_WARN(this->get_logger(), "%s with WARN logger level.", msg->data.c_str()); // 打印WARN级别日志
        RCLCPP_ERROR(this->get_logger(), "%s with ERROR logger level.", msg->data.c_str()); // 打印ERROR级别日志
      };


    sub_ = this->create_subscription<std_msgs::msg::String>("output", 10, callback); // 创建订阅,主题为"output",队列大小为10
  }


private:
  rclcpp::Subscription<std_msgs::msg::String>::SharedPtr sub_; // 订阅者指针
};


class TestNode : public rclcpp::Node // 定义TestNode类,继承自rclcpp::Node
{
public:
  explicit TestNode(const std::string & remote_node_name) // 构造函数,接受远程节点名称作为参数
  : Node("TestNode"), // 调用基类构造函数,设置节点名称为"TestNode"
    remote_node_name_(remote_node_name) // 初始化远程节点名称
  {
    pub_ = this->create_publisher<std_msgs::msg::String>("output", 10); // 创建发布者,主题为"output",队列大小为10
    logger_set_client_ = this->create_client<rcl_interfaces::srv::SetLoggerLevels>( // 创建设置日志级别的客户端
      remote_node_name + "/set_logger_levels");
    logger_get_client_ = this->create_client<rcl_interfaces::srv::GetLoggerLevels>( // 创建获取日志级别的客户端
      remote_node_name + "/get_logger_levels");
  }


  rclcpp::Publisher<std_msgs::msg::String>::SharedPtr get_pub() // 获取发布者指针
  {
    return pub_;
  }


  bool set_logger_level_on_remote_node( // 设置远程节点的日志级别
    rclcpp::Logger::Level logger_level)
  {
    if (!logger_set_client_->wait_for_service(2s)) { // 等待服务可用
      return false;
    }


    auto request = std::make_shared<rcl_interfaces::srv::SetLoggerLevels::Request>(); // 创建请求
    auto set_logger_level = rcl_interfaces::msg::LoggerLevel(); // 创建日志级别消息
    set_logger_level.name = remote_node_name_; // 设置日志级别消息的名称
    set_logger_level.level = static_cast<uint32_t>(logger_level); // 设置日志级别
    request->levels.emplace_back(set_logger_level); // 将日志级别消息添加到请求中


    auto result = logger_set_client_->async_send_request(request); // 异步发送请求


    if (rclcpp::spin_until_future_complete(this->shared_from_this(), result) != // 等待请求完成
      rclcpp::FutureReturnCode::SUCCESS)
    {
      return false;
    }


    auto ret_result = result.get(); // 获取结果
    if (!ret_result->results[0].successful) { // 如果设置日志级别失败
      RCLCPP_ERROR(
        this->get_logger(), "Failed to change logger level: %s",
        ret_result->results[0].reason.c_str()); // 打印错误日志
      return false;
    }
    return true;
  }


  bool get_logger_level_on_remote_node(uint32_t & level) // 获取远程节点的日志级别
  {
    if (!logger_get_client_->wait_for_service(2s)) { // 等待服务可用
      return false;
    }


    auto request = std::make_shared<rcl_interfaces::srv::GetLoggerLevels::Request>(); // 创建请求
    request->names.emplace_back(remote_node_name_); // 设置请求的名称
    auto result = logger_get_client_->async_send_request(request); // 异步发送请求
    if (rclcpp::spin_until_future_complete(shared_from_this(), result) != // 等待请求完成
      rclcpp::FutureReturnCode::SUCCESS)
    {
      return false;
    }


    auto ret_result = result.get(); // 获取结果
    level = ret_result->levels[0].level; // 设置日志级别
    return true;
  }


private:
  const std::string remote_node_name_; // 远程节点名称
  rclcpp::Publisher<std_msgs::msg::String>::SharedPtr pub_; // 发布者指针
  rclcpp::Client<rcl_interfaces::srv::SetLoggerLevels>::SharedPtr logger_set_client_; // 设置日志级别的客户端指针
  rclcpp::Client<rcl_interfaces::srv::GetLoggerLevels>::SharedPtr logger_get_client_; // 获取日志级别的客户端指针
};




int main(int argc, char ** argv) // 主函数
{
  rclcpp::init(argc, argv); // 初始化ROS 2


  const std::string node_name = "LoggerServiceNode"; // 定义节点名称
  auto logger_service_node = std::make_shared<LoggerServiceNode>( // 创建LoggerServiceNode节点
    node_name);
  auto test_node = std::make_shared<TestNode>(node_name); // 创建TestNode节点


  rclcpp::executors::SingleThreadedExecutor executor; // 创建单线程执行器


  executor.add_node(logger_service_node); // 将LoggerServiceNode节点添加到执行器


  std::thread thread(&executor { // 创建线程,执行器循环
      executor.spin();
    });


  auto get_logger_level_func = [&test_node] { // 定义获取日志级别的函数
      uint32_t get_logger_level = 0;
      if (test_node->get_logger_level_on_remote_node(get_logger_level)) { // 获取远程节点的日志级别
        RCLCPP_INFO(test_node->get_logger(), "Current logger level: %u", get_logger_level); // 打印信息日志
      } else {
        RCLCPP_ERROR(
          test_node->get_logger(),
          "Failed to get logger level via logger service !"); // 打印错误日志
      }
    };


  // 使用默认日志级别输出
  RCLCPP_INFO(test_node->get_logger(), "Output with default logger level:");
  {
    auto msg = std::make_unique<std_msgs::msg::String>(); // 创建消息
    msg->data = "Output 1"; // 设置消息数据
    test_node->get_pub()->publish(std::move(msg)); // 发布消息
  }
  std::this_thread::sleep_for(200ms); // 休眠200毫秒


  // 获取日志级别。日志级别应为0(未设置)
  get_logger_level_func();


  // 使用调试日志级别输出
  RCLCPP_INFO(test_node->get_logger(), "Output with debug logger level:");
  if (test_node->set_logger_level_on_remote_node(rclcpp::Logger::Level::Debug)) { // 设置远程节点的日志级别为调试
    auto msg = std::make_unique<std_msgs::msg::String>(); // 创建消息
    msg->data = "Output 2"; // 设置消息数据
    test_node->get_pub()->publish(std::move(msg)); // 发布消息
    std::this_thread::sleep_for(200ms); // 休眠200毫秒
  } else {
    RCLCPP_ERROR(test_node->get_logger(), "Failed to set debug logger level via logger service !"); // 打印错误日志
  }


  // 获取日志级别。日志级别应为10(调试)
  get_logger_level_func();


  // 使用警告日志级别输出
  RCLCPP_INFO(test_node->get_logger(), "Output with warn logger level:");
  if (test_node->set_logger_level_on_remote_node(rclcpp::Logger::Level::Warn)) { // 设置远程节点的日志级别为警告
    auto msg = std::make_unique<std_msgs::msg::String>(); // 创建消息
    msg->data = "Output 3"; // 设置消息数据
    test_node->get_pub()->publish(std::move(msg)); // 发布消息
    std::this_thread::sleep_for(200ms); // 休眠200毫秒
  } else {
    RCLCPP_ERROR(test_node->get_logger(), "Failed to set warn logger level via logger service !"); // 打印错误日志
  }


  // 获取日志级别。日志级别应为30(警告)
  get_logger_level_func();


  // 使用错误日志级别输出
  RCLCPP_INFO(test_node->get_logger(), "Output with error logger level:");
  if (test_node->set_logger_level_on_remote_node(rclcpp::Logger::Level::Error)) { // 设置远程节点的日志级别为错误
    auto msg = std::make_unique<std_msgs::msg::String>(); // 创建消息
    msg->data = "Output 4"; // 设置消息数据
    test_node->get_pub()->publish(std::move(msg)); // 发布消息
    std::this_thread::sleep_for(200ms); // 休眠200毫秒
  } else {
    RCLCPP_ERROR(test_node->get_logger(), "Failed to set error logger level via logger service !"); // 打印错误日志
  }


  // 获取日志级别。日志级别应为40(错误)
  get_logger_level_func();


  executor.cancel(); // 取消执行器
  if (thread.joinable()) { // 如果线程可加入
    thread.join(); // 加入线程
  }


  rclcpp::shutdown(); // 关闭ROS 2
  return 0; // 返回0表示程序正常结束
}
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ROS2中,std::make_unique函数用于创建一个唯一指针。它是C++14中引入的一个函数模板,用于在堆上创建一个对象,并返回一个指向该对象的唯一指针。在ROS2中,可以使用std::make_unique函数来创建ROS2节点。例如,可以使用以下代码创建一个继承自rclcpp::Node的类的实例: ```cpp auto node = std::make_unique<MinimalPublisher>(); ``` 这将创建一个MinimalPublisher类的实例,并返回一个指向该实例的唯一指针。然后,可以使用该指针来访问该节点的成员函数和变量。 引用: \[1\] 截至目前,galactic虽然对以上过程进行了一定程度的封装,但封装也极为简化,同时read的流程仍没有封装,使用还是很麻烦。节点的建立ROS2使用了完全的面向对象设计,以C++为例,大量的ROS API都封装在了rclcpp::Node这个类中,也就意味着需要使用这些API,你必须定义一个继承自Node的类,这也就强制用户必须使用类的方式构建整个系统,官方的一个简单的例子如下:class MinimalPublisher : public rclcpp::Node { public: MinimalPublisher() : Node("minimal_publisher"), count_(0) { publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10); timer_ = this->create_wall_timer( 500ms, std::bind(&MinimalPublisher::timer_callback, this)); } private: rclcpp::TimerBase::SharedPtr timer_; rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_; size_t count_; } \[2\] 只要在同一ID的机器就可以接受到彼此的消息。yaml配置文件比较坑的一个细节,ROS2在yaml使用上有两个变化,如下:test_node: ros__parameters: common: topic: "your_topic" \[3\] 主要是下面三个部分:名称重定向日志配置参数使用命令行传参在eloquent之后需要加 --ros-args标志,dashing版本不需要eloquent:ros2 run my_package node_executable --ros-args ... #### 引用[.reference_title] - *1* *2* [ROS2实践总结](https://blog.csdn.net/liu3612162/article/details/121906612)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [[ros2学习]-学习ROS 2工具集](https://blog.csdn.net/weixin_36628778/article/details/106375420)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值