Robot Operating System——远程修改日志等级

在任何系统中,日志功能的重要性都是不容忽视的。日志功能为开发者、维护者甚至用户提供了一个关键的工具,以监控、理解和调试系统的行为。所以我们数量掌握日志功能非常必要。

本文我们将结合demo_nodes_cpp/src/logging/use_logger_service.cpp来熟悉如何在ROS2中,远程修改日志等级,以方便工作中问题的排查。

在use_logger_service中,我们将在一个叫做TestNode的Node中查询/设置另外一个叫LoggerServiceNode的Node的日志等级,然后观察LoggerServiceNode的日志输出情况。

日志输出Node

和以往例子中创建的Node不同,LoggerServiceNode在构造函数中指定了要通过rclcpp::NodeOptions().enable_logger_service(true)来开启查询/设置日志等级的Service。这样TestNode才可以远程修改LoggerServiceNode的等级。

在LoggerServiceNode的构造函数中,它创建了一个监听"output"主题的订阅者。当收到发布者的消息时,它会使用RCLCPP_DEBUG、RCLCPP_INFO、RCLCPP_WARN和RCLCPP_ERROR打印消息。在本例中TestNode修改完LoggerServiceNode的日志等级后,会作为发布者发布"output"主题消息。这样我们就可以观察不同日志等级时,RCLCPP_DEBUG、RCLCPP_INFO、RCLCPP_WARN和RCLCPP_ERROR的输出情况。

// This demo program shows how to enable logger service and control logger level via logger service.
// Class LoggerServiceNode enable logger service and create a subscription. The callback of
// subscription output received message by different log functions.
// Class TestNode can set/get logger level of LoggerServiceNode and send message to it.

class LoggerServiceNode : public rclcpp::Node
{
public:
  explicit LoggerServiceNode(const std::string & node_name)
  : Node(node_name, rclcpp::NodeOptions().enable_logger_service(true))
  {
    auto callback = [this](std_msgs::msg::String::ConstSharedPtr msg)-> void {
        RCLCPP_DEBUG(this->get_logger(), "%s with DEBUG logger level.", msg->data.c_str());
        RCLCPP_INFO(this->get_logger(), "%s with INFO logger level.", msg->data.c_str());
        RCLCPP_WARN(this->get_logger(), "%s with WARN logger level.", msg->data.c_str());
        RCLCPP_ERROR(this->get_logger(), "%s with ERROR logger level.", msg->data.c_str());
      };

    sub_ = this->create_subscription<std_msgs::msg::String>("output", 10, callback);
  }

private:
  rclcpp::Subscription<std_msgs::msg::String>::SharedPtr sub_;
};

int main(int argc, char ** argv)
{
  rclcpp::init(argc, argv);

  const std::string node_name = "LoggerServiceNode";
  auto logger_service_node = std::make_shared<LoggerServiceNode>(
    node_name);

修改其他Node日志等级的Node

在本例中,我们使用一个自定义类TestNode来实现相关功能。

  auto test_node = std::make_shared<TestNode>(node_name);

在构造函数中,会把各种需要的的工具对象都创建好。其中包括:

  • 通知LoggerServiceNode打印日志的Topic发布者。
  • LoggerServiceNode暴露出来的Service(“/set_logger_levels"和”/get_logger_levels")的客户端。用于设置和获取LoggerServiceNode的日志等级。
class TestNode : public rclcpp::Node
{
public:
  explicit TestNode(const std::string & remote_node_name)
  : Node("TestNode"),
    remote_node_name_(remote_node_name)
  {
    pub_ = this->create_publisher<std_msgs::msg::String>("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");
  }
  ……
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_;
};

获取日志等级

我们通过上一步创建的返回日志等级Service(rcl_interfaces::srv::SetLoggerLevels)的客户端调用async_send_request查询LoggerServiceNode的日志等级。

可以看到async_send_request是一个异步方法,它需要我们调用rclcpp::spin_until_future_complete进行同步等待。只有等消息成功返回时,我们才从返回结果中提取日志等级。

  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;
  }

设置日志等级

这一步我们通过之前创建的设置日志等级的Service(rcl_interfaces::srv::SetLoggerLevels)的客户端调用async_send_request来设置LoggerServiceNode的日志等级。

  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;
  }

测试

然后我们分别查看各种日志等级的日志的输出情况。

默认等级

  // Output with default logger level
  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);

可以看到,默认等级时,DEBUG级别日志是不输出的。INFO、WARN、ERROR级别日志会输出。
在这里插入图片描述

Debug等级

  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);
  } else {
    RCLCPP_ERROR(test_node->get_logger(), "Failed to set debug logger level via logger service !");
  }

可以看到,DEBUG等级时,所有级别的日志都会输出。
在这里插入图片描述

Warn等级

  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);
  } else {
    RCLCPP_ERROR(test_node->get_logger(), "Failed to set warn logger level via logger service !");
  }

可以看到,WARN等级时,INFO和DEBUG级别日志不会输出,但是WARN、ERROR级别日志会输出。
在这里插入图片描述

Error等级

  // Output with error logger level
  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);
  } else {
    RCLCPP_ERROR(test_node->get_logger(), "Failed to set error logger level via logger service !");
  }

可以看到,ERROR等级时,只有ERROR级别日志会输出,而INFO、DEBUG、WARN级别日志不会输出。
在这里插入图片描述

完整代码

// Copyright 2023 Sony Group Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <chrono>
#include <memory>
#include <string>

#include "rclcpp/rclcpp.hpp"

#include "std_msgs/msg/string.hpp"

#include "rcl_interfaces/srv/get_logger_levels.hpp"
#include "rcl_interfaces/srv/set_logger_levels.hpp"

using namespace std::chrono_literals;

// This demo program shows how to enable logger service and control logger level via logger service.
// Class LoggerServiceNode enable logger service and create a subscription. The callback of
// subscription output received message by different log functions.
// Class TestNode can set/get logger level of LoggerServiceNode and send message to it.

class LoggerServiceNode : public rclcpp::Node
{
public:
  explicit LoggerServiceNode(const std::string & node_name)
  : Node(node_name, rclcpp::NodeOptions().enable_logger_service(true))
  {
    auto callback = [this](std_msgs::msg::String::ConstSharedPtr msg)-> void {
        RCLCPP_DEBUG(this->get_logger(), "%s with DEBUG logger level.", msg->data.c_str());
        RCLCPP_INFO(this->get_logger(), "%s with INFO logger level.", msg->data.c_str());
        RCLCPP_WARN(this->get_logger(), "%s with WARN logger level.", msg->data.c_str());
        RCLCPP_ERROR(this->get_logger(), "%s with ERROR logger level.", msg->data.c_str());
      };

    sub_ = this->create_subscription<std_msgs::msg::String>("output", 10, callback);
  }

private:
  rclcpp::Subscription<std_msgs::msg::String>::SharedPtr sub_;
};

class TestNode : public rclcpp::Node
{
public:
  explicit TestNode(const std::string & remote_node_name)
  : Node("TestNode"),
    remote_node_name_(remote_node_name)
  {
    pub_ = this->create_publisher<std_msgs::msg::String>("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);

  const std::string node_name = "LoggerServiceNode";
  auto logger_service_node = std::make_shared<LoggerServiceNode>(
    node_name);
  auto test_node = std::make_shared<TestNode>(node_name);

  rclcpp::executors::SingleThreadedExecutor executor;

  executor.add_node(logger_service_node);

  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 !");
      }
    };

  // Output with default logger level
  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);

  // Get logger level. Logger level should be 0 (Unset)
  get_logger_level_func();

  // Output with debug logger level
  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);
  } else {
    RCLCPP_ERROR(test_node->get_logger(), "Failed to set debug logger level via logger service !");
  }

  // Get logger level. Logger level should be 10 (Debug)
  get_logger_level_func();

  // Output with warn logger level
  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);
  } else {
    RCLCPP_ERROR(test_node->get_logger(), "Failed to set warn logger level via logger service !");
  }

  // Get logger level. Logger level should be 30 (Warn)
  get_logger_level_func();

  // Output with error logger level
  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);
  } else {
    RCLCPP_ERROR(test_node->get_logger(), "Failed to set error logger level via logger service !");
  }

  // Get logger level. Logger level should be 40 (Error)
  get_logger_level_func();

  executor.cancel();
  if (thread.joinable()) {
    thread.join();
  }

  rclcpp::shutdown();
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

breaksoftware

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值