#include "topic_logger_ext/topic_logger_driver.h"
#include "cpp/util/json_type_support.h"
#include "base/string_util.h"
namespace YAML {
template <>
struct convert<ibmw::extensions::topic_logger_extension::TopicLoggerDriver::Options> {
using Options = ibmw::extensions::topic_logger_extension::TopicLoggerDriver::Options;
static Node encode(const Options& rhs) {
Node node;
node["module_filter"] = rhs.module_filter;
node["interval_ms"] = rhs.interval_ms;
node["timer_executor_name"] = rhs.timer_executor_name;
node["topic"] = rhs.topic_name;
node["max_msg_size"] = rhs.max_msg_size;
node["max_msg_count"] = rhs.max_msg_count;
return node;
}
static bool decode(const Node& node, Options& rhs) {
if (!node.IsMap()) return false;
if (node["interval_ms"])
rhs.interval_ms = node["interval_ms"].as<uint32_t>();
if (node["module_filter"])
rhs.module_filter = node["module_filter"].as<std::string>();
if (node["max_msg_size"])
rhs.max_msg_size = node["max_msg_size"].as<size_t>();
if (node["max_msg_count"])
rhs.max_msg_count = node["max_msg_count"].as<size_t>();
rhs.topic_name = node["topic"].as<std::string>();
rhs.timer_executor_name = node["timer_executor_name"].as<std::string>();
return true;
}
};
}
namespace ibmw::extensions::topic_logger_extension {
void TopicLoggerDriver::Initialize(YAML::Node options_node) {
if (options_node && !options_node.IsNull())
options_ = options_node.as<Options>();
MW_ASSERT(!options_.timer_executor_name.empty(), "Timer executor name is empty.");
timer_executor_ = get_executor_func_(options_.timer_executor_name);
MW_ASSERT(timer_executor_, "Invalid timer executor name: {}", options_.timer_executor_name);
MW_ASSERT(timer_executor_.SupportsTimedDispatch(),
"Timer executor {} must support timer schedule.", options_.timer_executor_name);
using LogData = ibmw::protocols::topic_logger::LogData;
static const std::string kMsgTypeName =
"json:" + std::string(LogData::kTypeName);
auto timer_task = [this]() {
std::unique_ptr<LogData> log_data_ptr;
{
std::unique_lock<std::mutex> lck(mutex_);
if (!current_log_data_) [[unlikely]] {
publish_flag_ = false;
timer_ptr->Cancel();
return;
}
current_log_data_.swap(log_data_ptr);
log_data_ptr->header.time_stamp = ibmw::common::util::GetCurTimestampNs();
log_data_ptr->header.sequence_num = sequence_num_++;
log_data_ptr->dropped_count = dropped_msg_count_;
dropped_msg_count_ = 0;
}
ibmw::transport::Context ctx;
ctx.SetSerializationType("json");
log_publisher_.Send(kMsgTypeName, ctx, static_cast<const void*>(log_data_ptr.get()));
};
timer_ptr = ibmw::executor::CreateTimer(timer_executor_,
std::chrono::milliseconds(options_.interval_ms),
std::move(timer_task),
false);
options_node = options_;
run_flag_.store(true);
}
void TopicLoggerDriver::Log(const runtime::core::logger::LogDataWrapper& log_data_wrapper) noexcept {
try {
if (!run_flag_.load()) [[unlikely]] {
return;
}
if (!CheckLog(log_data_wrapper)) [[unlikely]] {
return;
}
using SingleLogData = ibmw::protocols::topic_logger::SingleLogData;
using LogData = ibmw::protocols::topic_logger::LogData;
SingleLogData single_log_data;
single_log_data.module_name = std::string(log_data_wrapper.module_name);
single_log_data.thread_id = log_data_wrapper.thread_id;
single_log_data.time_point = log_data_wrapper.t.time_since_epoch().count();
single_log_data.level = static_cast<ibmw::protocols::topic_logger::LogLevel>(log_data_wrapper.lvl);
single_log_data.line = log_data_wrapper.line;
single_log_data.file_name = log_data_wrapper.file_name;
single_log_data.function_name = log_data_wrapper.function_name;
const auto* log_data_ptr_raw = log_data_wrapper.log_data;
size_t used_msg_size = common::util::SafeUtf8TruncationLength(log_data_ptr_raw, log_data_wrapper.log_data_size, max_msg_size_);
single_log_data.message = std::string(log_data_ptr_raw, used_msg_size);
{
std::unique_lock<std::mutex> lck(mutex_);
if (!current_log_data_) [[unlikely]] {
current_log_data_ = std::make_unique<LogData>();
}
if (current_log_data_->logs.size() >= options_.max_msg_count) [[unlikely]] {
dropped_msg_count_++;
return;
}
current_log_data_->logs.emplace_back(std::move(single_log_data));
if (!publish_flag_) [[unlikely]] {
publish_flag_ = true;
timer_ptr->Reset();
}
}
} catch (const std::exception& e) {
(void)fprintf(stderr, "Log get exception: %s\n", e.what());
}
}
bool TopicLoggerDriver::CheckLog(const runtime::core::logger::LogDataWrapper& log_data_wrapper) {
{
std::shared_lock lock(module_filter_map_mutex_);
auto find_itr = module_filter_map_.find(log_data_wrapper.module_name);
if (find_itr != module_filter_map_.end()) {
return find_itr->second;
}
}
bool if_log = false;
try {
if (std::regex_match(
log_data_wrapper.module_name.begin(),
log_data_wrapper.module_name.end(),
std::regex(options_.module_filter, std::regex::ECMAScript))) {
if_log = true;
}
} catch (const std::exception& e) {
(void)fprintf(stderr, "Regex get exception, expr: %s, string: %s, exception info: %s\n",
options_.module_filter.c_str(), log_data_wrapper.module_name.data(), e.what());
}
std::unique_lock lock(module_filter_map_mutex_);
module_filter_map_.emplace(log_data_wrapper.module_name, if_log);
return if_log;
}
void TopicLoggerDriver::RegisterLogPublisher() {
using LogData = ibmw::protocols::topic_logger::LogData;
log_publisher_ = get_publisher_ref_func_(options_.topic_name);
MW_ASSERT(log_publisher_, "Failed to get publisher for topic: {}", options_.topic_name);
bool ret = log_publisher_.RegisterSendType(
ibmw::json::GetJsonMessageTypeSupport<LogData>());
MW_ASSERT(ret, "Register publish type failed.");
}
}