log format (#57)

* log format

* Modify the logic of log formatting output

* Remove redundant parts.

* log format:

-Improve the log configuration documentation

-More flexible log formatting output structure

* log fotmat

- Use std::string instead of fmt to achieve formatted output,;

- add unit test

- add exception checking.

* Add estimated_size  & optimize unit tests

---------

Co-authored-by: hanjun <hanjun@agibot.com>
This commit is contained in:
han J 2024-10-31 19:39:35 +08:00 committed by GitHub
parent a1fda52c37
commit 929a105bd0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 563 additions and 51 deletions

View File

@ -5,13 +5,13 @@
`aimrt.log`配置项用于配置日志。其中的细节配置项说明如下:
| 节点 | 类型 | 是否可选| 默认值 | 作用 |
| ---- | ---- | ---- | ---- | ---- |
| core_lvl | string | 可选 | "Info" | 框架日志等级 |
| default_module_lvl | string | 可选 | "Info" | 默认的模块日志等级 |
| backends | array | 可选 | "" | 日志后端列表 |
| backends[i].type | string | 必选 | "" | 日志后端类型 |
| backends[i].options | map | 可选 | - | 具体日志后端的配置 |
| 节点 | 类型 | 是否可选 | 默认值 | 作用 |
| ------------------- | ------ | -------- | ------ | ------------------ |
| core_lvl | string | 可选 | "Info" | 框架日志等级 |
| default_module_lvl | string | 可选 | "Info" | 默认的模块日志等级 |
| backends | array | 可选 | "" | 日志后端列表 |
| backends[i].type | string | 必选 | "" | 日志后端类型 |
| backends[i].options | map | 可选 | - | 具体日志后端的配置 |
其中,日志等级可选项包括以下几种(不区分大小写):
- Trace
@ -53,11 +53,12 @@ aimrt:
`console`日志后端是 AimRT 官方提供的一种日志后端,用于将日志打印到控制台上。其所有的配置项如下:
| 节点 | 类型 | 是否可选| 默认值 | 作用 |
| ---- | ---- | ---- | ---- | ---- |
| color | bool | 可选 | true | 是否要彩色打印 |
| module_filter | string | 可选 | "(.*)"| 模块过滤器 |
| log_executor_name | string | 可选 | "" | 日志执行器。默认使用主线程 |
| 节点 | 类型 | 是否可选 | 默认值 | 作用 |
| ----------------- | ------ | -------- | ------------------------------------- | -------------------------- |
| color | bool | 可选 | true | 是否要彩色打印 |
| module_filter | string | 可选 | "(.*)" | 模块过滤器 |
| log_executor_name | string | 可选 | "" | 日志执行器。默认使用主线程 |
| pattern | string | 可选 | "[%c.%f][%l][%t][%n][%g:%R:%C @%F]%v" | 日志的输出格式 |
使用注意点如下:
@ -65,6 +66,32 @@ aimrt:
- `color`配置了是否要彩色打印。此项配置有可能在一些操作系统不支持。
- `module_filter`支持以正则表达式的形式,来配置哪些模块的日志可以通过本后端处理。这与模块日志等级不同,模块日志等级是全局的、先决的、影响所有日志后端的,而这里的配置只影响本后端。
- `log_executor_name`配置了日志执行器。要求日志执行器是线程安全的,如果没有配置,则默认使用主线程打印日志。
- `pattern` 通过 `"%" + 字符` 的形式来进行格式化输出, 具体可输出格式如下:
| 格式 | 解释 | 举例 |
| ----- | ------------------------ | ------------------------------- |
| %c | 完整日期和时间 | 2024-03-15 14:30:45 |
| %Y | 年份 | 2024 |
| %m | 月份 | 03 |
| %d | 日期 | 15 |
| %H | 小时 | 14 |
| %M | 分钟 | 30 |
| %S | 秒 | 45 |
| %D | 仅日期 | 2024-03-15 |
| %T | 仅时间 | 14:30:45 |
| %f | 微秒 | 123456 |
| %A | 星期几 | Sunday |
| %a | 星期几(简写) | Sun |
| %l | 日志级别 | Info |
| %t | 线程ID | 1234 |
| %n | 模块名 | test_module |
| %G | 文件名(只显示最后一级) | test_file.cpp |
| %g | 文件名(完整路径) | /XX/YY/ZZ/test_file.cpp |
| %R | 行号 | 20 |
| %C | 列号 | 20 |
| %F | 函数名 | TestFunc |
| %v | 日志消息内容 | "This is a log message" |
| %其他 | 只会把其他内容显示 | 如`%q` 只显示 `q`, `%%`只显示%` |
以下是一个简单的示例:
@ -93,15 +120,15 @@ aimrt:
`rotate_file`日志后端是 AimRT 官方提供的一种日志后端,用于将日志打印到文件中。其所有的配置项如下:
| 节点 | 类型 | 是否可选| 默认值 | 作用 |
| ---- | ---- | ---- | ---- | ---- |
| path | string | 必选 | "./log" | 日志文件存放目录 |
| filename | string | 必选 | "aimrt.log" | 日志文件基础名称 |
| max_file_size_m | unsigned int | 可选 | 16 | 日志文件最大尺寸单位Mb |
| max_file_num | unsigned int | 可选 | 100 | 日志文件最大数量。0表示无上限 |
| module_filter | string | 可选 | "(.*)"| 模块过滤器 |
| log_executor_name | string | 可选 | "" | 日志执行器。默认使用主线程 |
| 节点 | 类型 | 是否可选 | 默认值 | 作用 |
| ----------------- | ------------ | -------- | ------------------------------------- | ----------------------------- |
| path | string | 必选 | "./log" | 日志文件存放目录 |
| filename | string | 必选 | "aimrt.log" | 日志文件基础名称 |
| max_file_size_m | unsigned int | 可选 | 16 | 日志文件最大尺寸单位Mb |
| max_file_num | unsigned int | 可选 | 100 | 日志文件最大数量。0表示无上限 |
| module_filter | string | 可选 | "(.*)" | 模块过滤器 |
| log_executor_name | string | 可选 | "" | 日志执行器。默认使用主线程 |
| pattern | string | 可选 | "[%c.%f][%l][%t][%n][%g:%R:%C @%F]%v" | 日志的输出格式 |
使用注意点如下:

View File

@ -360,4 +360,140 @@ inline int32_t GetDayCount(time_t l_time, time_t r_time, int32_t time_zone) {
return (l_time + time_zone - r_time) / kSecondPerDay;
}
/**
* @brief get week day string
*
* @param t
* @return std::string_view
*/
inline std::string_view GetWeekDayStr(time_t t) {
static constexpr std::string_view kWeekDays[] = {
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
struct tm st = TimeT2TmLocal(t);
return kWeekDays[st.tm_wday];
}
/**
* @brief get week-short day string
*
* @param t
* @return std::string_view
*/
inline std::string_view GetWeekDayStrShort(time_t t) {
static constexpr std::string_view kWeekDaysShort[] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
struct tm st = TimeT2TmLocal(t);
return kWeekDaysShort[st.tm_wday];
}
/**
* @brief Get year string (4 digits)
*
* @param t timestamp
* @return std::string_view Format like "2024"
*/
inline std::string_view GetYearStr(time_t t) {
thread_local char buf[5];
auto tm = TimeT2TmLocal(t);
snprintf(buf, sizeof(buf), "%04d", (tm.tm_year + 1900) % 10000u);
return std::string_view(buf);
}
/**
* @brief Get month string (2 digits)
*
* @param t timestamp
* @return std::string_view Format like "03"
*/
inline std::string_view GetMonthStr(time_t t) {
thread_local char buf[3];
auto tm = TimeT2TmLocal(t);
snprintf(buf, sizeof(buf), "%02d", (tm.tm_mon + 1) % 100u);
return std::string_view(buf);
}
/**
* @brief Get day string (2 digits)
*
* @param t timestamp
* @return std::string_view Format like "15"
*/
inline std::string_view GetDayStr(time_t t) {
thread_local char buf[3];
auto tm = TimeT2TmLocal(t);
snprintf(buf, sizeof(buf), "%02d", tm.tm_mday % 100u);
return std::string_view(buf);
}
/**
* @brief Get hour string (2 digits, 24-hour format)
*
* @param t timestamp
* @return std::string_view Format like "14"
*/
inline std::string_view GetHourStr(time_t t) {
thread_local char buf[3];
auto tm = TimeT2TmLocal(t);
snprintf(buf, sizeof(buf), "%02d", tm.tm_hour % 100u);
return std::string_view(buf);
}
/**
* @brief Get minute string (2 digits)
*
* @param t timestamp
* @return std::string_view Format like "30"
*/
inline std::string_view GetMinuteStr(time_t t) {
thread_local char buf[3];
auto tm = TimeT2TmLocal(t);
snprintf(buf, sizeof(buf), "%02d", tm.tm_min % 100u);
return std::string_view(buf);
}
/**
* @brief Get second string (2 digits)
*
* @param t timestamp
* @return std::string_view Format like "45"
*/
inline std::string_view GetSecondStr(time_t t) {
thread_local char buf[3];
auto tm = TimeT2TmLocal(t);
snprintf(buf, sizeof(buf), "%02d", tm.tm_sec % 100u);
return std::string_view(buf);
}
/**
* @brief Get complete date string
*
* @param t timestamp
* @return std::string_view Format like "2024-03-15"
*/
inline std::string_view GetDateStr(time_t t) {
thread_local char buf[11];
auto tm = TimeT2TmLocal(t);
snprintf(buf, sizeof(buf), "%04d-%02d-%02d",
(tm.tm_year + 1900) % 10000u,
(tm.tm_mon + 1) % 100u,
tm.tm_mday % 100u);
return std::string_view(buf);
}
/**
* @brief Get complete time string
*
* @param t timestamp
* @return std::string_view Format like "14:30:45"
*/
inline std::string_view GetClockStr(time_t t) {
thread_local char buf[9];
auto tm = TimeT2TmLocal(t);
snprintf(buf, sizeof(buf), "%02d:%02d:%02d",
tm.tm_hour % 100u,
tm.tm_min % 100u,
tm.tm_sec % 100u);
return std::string_view(buf);
}
} // namespace aimrt::common::util

View File

@ -26,6 +26,9 @@
说明:
- 此示例创建了一个 `LoggerModule`,会在其 `Start` 的阶段循环打印各种等级的日志;
- 此示例将 `LoggerModule` 集成到 `logger_pkg` 中,并在配置文件中加载此 Pkg
- 在该例程的[配置文件]((./install/linux/bin/cfg/examples_cpp_logger_cfg.yaml))中,配置了 pattern 选项为 `"[%c.%f][%A][%l][%t][%n][%G] %v"` 这是一个可以自定义输出格式的配置项。
## logger rotate file

View File

@ -6,6 +6,8 @@ aimrt:
core_lvl: INFO # Trace/Debug/Info/Warn/Error/Fatal/Off
backends:
- type: console
options:
pattern: "[%c.%f][%A][%l][%t][%n][%G] %v"
executor:
executors:
- name: work_executor

View File

@ -8,6 +8,7 @@
#include <mutex>
#include <regex>
#include "core/logger/formatter.h"
#include "core/logger/log_level_tool.h"
#include "util/exception.h"
#include "util/format.h"
@ -95,6 +96,7 @@ struct convert<aimrt::runtime::core::logger::ConsoleLoggerBackend::Options> {
node["color"] = rhs.print_color;
node["module_filter"] = rhs.module_filter;
node["log_executor_name"] = rhs.log_executor_name;
node["pattern"] = rhs.pattern;
return node;
}
@ -107,6 +109,8 @@ struct convert<aimrt::runtime::core::logger::ConsoleLoggerBackend::Options> {
rhs.module_filter = node["module_filter"].as<std::string>();
if (node["log_executor_name"])
rhs.log_executor_name = node["log_executor_name"].as<std::string>();
if (node["pattern"])
rhs.pattern = node["pattern"].as<std::string>();
return true;
}
@ -134,6 +138,11 @@ void ConsoleLoggerBackend::Initialize(YAML::Node options_node) {
"Log executor must be thread safe. Log executor name: " + options_.log_executor_name);
}
if (!options_.pattern.empty()) {
pattern_ = options_.pattern;
}
formatter_.SetPattern(pattern_);
options_node = options_;
run_flag_.store(true);
@ -147,18 +156,7 @@ void ConsoleLoggerBackend::Log(const LogDataWrapper& log_data_wrapper) noexcept
if (!CheckLog(log_data_wrapper)) [[unlikely]]
return;
auto log_data_str = ::aimrt_fmt::format(
"[{}.{:0>6}][{}][{}][{}][{}:{}:{} @{}]{}",
aimrt::common::util::GetTimeStr(std::chrono::system_clock::to_time_t(log_data_wrapper.t)),
(aimrt::common::util::GetTimestampUs(log_data_wrapper.t) % 1000000),
LogLevelTool::GetLogLevelName(log_data_wrapper.lvl),
log_data_wrapper.thread_id,
log_data_wrapper.module_name,
log_data_wrapper.file_name,
log_data_wrapper.line,
log_data_wrapper.column,
log_data_wrapper.function_name,
std::string_view(log_data_wrapper.log_data, log_data_wrapper.log_data_size));
std::string log_data_str = formatter_.Format(log_data_wrapper);
auto log_work = [this, lvl = log_data_wrapper.lvl, log_data_str{std::move(log_data_str)}]() {
if (options_.print_color) {
@ -168,10 +166,10 @@ void ConsoleLoggerBackend::Log(const LogDataWrapper& log_data_wrapper) noexcept
0, CC_DBG, CC_INF, CC_WRN, CC_ERR, CC_FATAL};
if (color_array[lvl] == 0) {
std::cout << log_data_str;
std::cout.write(log_data_str.data(), log_data_str.size());
} else {
SetConsoleTextAttribute(g_hConsole, color_array[lvl]);
std::cout << log_data_str;
std::cout.write(log_data_str.data(), log_data_str.size());
SetConsoleTextAttribute(g_hConsole, CC_DEFAULT);
}
@ -181,13 +179,15 @@ void ConsoleLoggerBackend::Log(const LogDataWrapper& log_data_wrapper) noexcept
"", CC_DBG, CC_INF, CC_WRN, CC_ERR, CC_FATAL};
if (kColorArray[lvl].empty()) {
std::cout << log_data_str;
std::cout.write(log_data_str.data(), log_data_str.size());
} else {
std::cout << kColorArray[lvl] << log_data_str << CC_NONE;
std::cout.write(kColorArray[lvl].data(), kColorArray[lvl].size())
.write(log_data_str.data(), log_data_str.size())
.write(CC_NONE, sizeof(CC_NONE) - 1);
}
#endif
} else {
std::cout << log_data_str;
std::cout.write(log_data_str.data(), log_data_str.size());
}
std::cout << std::endl;
};

View File

@ -7,6 +7,7 @@
#include <unordered_map>
#include "aimrt_module_cpp_interface/executor/executor.h"
#include "core/logger/formatter.h"
#include "core/logger/logger_backend_base.h"
#include "util/string_util.h"
@ -18,6 +19,7 @@ class ConsoleLoggerBackend : public LoggerBackendBase {
bool print_color = true;
std::string module_filter = "(.*)";
std::string log_executor_name = "";
std::string pattern;
};
public:
@ -52,6 +54,9 @@ class ConsoleLoggerBackend : public LoggerBackendBase {
std::unordered_map<
std::string, bool, aimrt::common::util::StringHash, std::equal_to<>>
module_filter_map_;
LogFormatter formatter_;
std::string pattern_ = "[%c.%f][%l][%t][%n][%g:%R:%C @%F]%v";
};
} // namespace aimrt::runtime::core::logger

View File

@ -0,0 +1,274 @@
// Copyright (c) 2023, AgiBot Inc.
// All rights reserved.
#pragma once
#include <cassert>
#include <functional>
#include <iostream>
#include "core/logger/log_data_wrapper.h"
#include "core/logger/log_level_tool.h"
#include "util/exception.h"
#include "util/time_util.h"
namespace aimrt::runtime::core::logger {
class LogFormatter {
public:
std::string Format(const LogDataWrapper& log_data_wrapper) {
std::string buffer;
buffer.reserve(log_data_wrapper.log_data_size + estimated_size_); // reserve space for log data
for (const auto& handler : format_handlers_) {
handler(log_data_wrapper, buffer);
}
return buffer;
}
void SetPattern(const std::string& pattern) {
if (pattern.empty()) [[unlikely]] {
throw aimrt::common::util::AimRTException("Invalid argument: Logger's pattern cannot be empty");
}
try {
format_handlers_.clear();
size_t pos = 0;
size_t last_text_pos = 0;
while (pos < pattern.length()) {
if (pattern[pos] == '%' && pos + 1 < pattern.length()) {
// deal with normal text
if (pos > 0 && pattern[pos - 1] != '%') {
auto text = std::string_view(pattern.data() + last_text_pos, pos - last_text_pos);
if (!text.empty()) {
estimated_size_ += text.length();
format_handlers_.emplace_back(
[text = std::string(text)](auto&, std::string& buffer) {
buffer.append(text);
});
}
}
// deal with format specifier
switch (pattern[pos + 1]) {
case 'c': // data and time (2024-03-15 14:30:45)
estimated_size_ += 19;
format_handlers_.emplace_back(format_time);
break;
case 'Y': // year (2024)
estimated_size_ += 4;
format_handlers_.emplace_back(format_year);
break;
case 'm': // month (03)
estimated_size_ += 2;
format_handlers_.emplace_back(format_month);
break;
case 'd': // day (15)
estimated_size_ += 2;
format_handlers_.emplace_back(format_day);
break;
case 'H': // hour (14)
estimated_size_ += 2;
format_handlers_.emplace_back(format_hour);
break;
case 'M': // minute (30)
estimated_size_ += 2;
format_handlers_.emplace_back(format_minute);
break;
case 'S': // second (45)
estimated_size_ += 2;
format_handlers_.emplace_back(format_second);
break;
case 'D': // date only (2024-03-15)
estimated_size_ += 10;
format_handlers_.emplace_back(format_date);
break;
case 'T': // clock only (14:30:45)
estimated_size_ += 8;
format_handlers_.emplace_back(format_clock);
break;
case 'f': // microseconds (123456)
estimated_size_ += 6;
format_handlers_.emplace_back(format_microseconds);
break;
case 'A': // weekay (Sunday)
estimated_size_ += 9;
format_handlers_.emplace_back(format_weekday);
break;
case 'a': // weekay-short (Sun)
estimated_size_ += 3;
format_handlers_.emplace_back(format_weekday_short);
break;
case 'l': // log level (Info)
estimated_size_ += 5;
format_handlers_.emplace_back(format_level);
break;
case 't': // thread id (1234)
estimated_size_ += 10;
format_handlers_.emplace_back(format_thread_id);
break;
case 'n': // module name (test_module)
estimated_size_ += 35;
format_handlers_.emplace_back(format_module);
break;
case 'G': // file name_short (test_module.cpp)
estimated_size_ += 35;
format_handlers_.emplace_back(format_file_short);
break;
case 'g': // file name (/XX/YY/ZZ/test_module.cpp)
estimated_size_ += 500;
format_handlers_.emplace_back(format_file);
break;
case 'R': // row number (20)
estimated_size_ += 6;
format_handlers_.emplace_back(format_line);
break;
case 'C': // column number (20)
estimated_size_ += 6;
format_handlers_.emplace_back(format_column);
break;
case 'F': // function name (TestFunc)
estimated_size_ += 30;
format_handlers_.emplace_back(format_function);
break;
case 'v': // message
format_handlers_.emplace_back(format_message);
break;
default:
format_handlers_.emplace_back(
[c = pattern[pos + 1]](const LogDataWrapper&, std::string& buffer) {
buffer.push_back(c);
});
break;
}
pos += 2;
last_text_pos = pos;
} else {
++pos;
}
}
// deal with the last text
if (last_text_pos < pattern.length()) {
std::string_view text(pattern.data() + last_text_pos, pattern.length() - last_text_pos);
estimated_size_ += text.length();
format_handlers_.emplace_back(
[text = std::string(text)](const LogDataWrapper&, std::string& buffer) {
buffer.append(text);
});
}
} catch (const std::exception& e) {
throw aimrt::common::util::AimRTException("Error in LogFormatter::SetPattern: " + std::string(e.what()));
}
}
private:
// format time
static void format_time(const LogDataWrapper& data, std::string& buffer) {
buffer.append(aimrt::common::util::GetTimeStr(std::chrono::system_clock::to_time_t(data.t)));
}
// format microseconds
static void format_microseconds(const LogDataWrapper& data, std::string& buffer) {
buffer.append(std::to_string(aimrt::common::util::GetTimestampUs(data.t) % 1000000));
}
static void format_year(const LogDataWrapper& data, std::string& buffer) {
buffer.append(aimrt::common::util::GetYearStr(std::chrono::system_clock::to_time_t(data.t)));
}
static void format_month(const LogDataWrapper& data, std::string& buffer) {
buffer.append(aimrt::common::util::GetMonthStr(std::chrono::system_clock::to_time_t(data.t)));
}
static void format_day(const LogDataWrapper& data, std::string& buffer) {
buffer.append(aimrt::common::util::GetDayStr(std::chrono::system_clock::to_time_t(data.t)));
}
static void format_hour(const LogDataWrapper& data, std::string& buffer) {
buffer.append(aimrt::common::util::GetHourStr(std::chrono::system_clock::to_time_t(data.t)));
}
static void format_minute(const LogDataWrapper& data, std::string& buffer) {
buffer.append(aimrt::common::util::GetMinuteStr(std::chrono::system_clock::to_time_t(data.t)));
}
static void format_second(const LogDataWrapper& data, std::string& buffer) {
buffer.append(aimrt::common::util::GetSecondStr(std::chrono::system_clock::to_time_t(data.t)));
}
static void format_date(const LogDataWrapper& data, std::string& buffer) {
buffer.append(aimrt::common::util::GetDateStr(std::chrono::system_clock::to_time_t(data.t)));
}
static void format_clock(const LogDataWrapper& data, std::string& buffer) {
buffer.append(aimrt::common::util::GetClockStr(std::chrono::system_clock::to_time_t(data.t)));
}
// format weekday
static void format_weekday(const LogDataWrapper& data, std::string& buffer) {
buffer.append(aimrt::common::util::GetWeekDayStr(std::chrono::system_clock::to_time_t(data.t)));
}
// format weekday-short
static void format_weekday_short(const LogDataWrapper& data, std::string& buffer) {
buffer.append(aimrt::common::util::GetWeekDayStrShort(std::chrono::system_clock::to_time_t(data.t)));
}
// format log level
static void format_level(const LogDataWrapper& data, std::string& buffer) {
buffer.append(LogLevelTool::GetLogLevelName(data.lvl));
}
// format message
static void format_message(const LogDataWrapper& data, std::string& buffer) {
buffer.append(data.log_data, data.log_data_size);
}
// format thread id
static void format_thread_id(const LogDataWrapper& data, std::string& buffer) {
buffer.append(std::to_string(data.thread_id));
}
// format module name
static void format_module(const LogDataWrapper& data, std::string& buffer) {
buffer.append(data.module_name);
}
// format file name
static void format_file(const LogDataWrapper& data, std::string& buffer) {
buffer.append(data.file_name);
}
// format file name (short)
static void format_file_short(const LogDataWrapper& data, std::string& buffer) {
const char* last_slash = strrchr(data.file_name, '/');
if (last_slash) {
buffer.append(last_slash + 1);
} else {
buffer.append(data.file_name);
}
}
// format line number
static void format_line(const LogDataWrapper& data, std::string& buffer) {
buffer.append(std::to_string(data.line));
}
// format column number
static void format_column(const LogDataWrapper& data, std::string& buffer) {
buffer.append(std::to_string(data.column));
}
// format function name
static void format_function(const LogDataWrapper& data, std::string& buffer) {
buffer.append(data.function_name);
}
using FormatHandler = std::function<void(const LogDataWrapper&, std::string&)>;
std::vector<FormatHandler> format_handlers_;
size_t estimated_size_ = 0;
};
} // namespace aimrt::runtime::core::logger

View File

@ -0,0 +1,64 @@
// Copyright (c) 2023, AgiBot Inc.
// All rights reserved.
#include "formatter.h"
#include <gtest/gtest.h>
namespace aimrt::runtime::core::logger {
// Test the GetRemappedFuncName function
TEST(FORMATTER_TEST, Format_test) {
auto date = std::chrono::year(2024) / std::chrono::month(10) / std::chrono::day(1);
auto time = std::chrono::hours(10) +
std::chrono::minutes(10) +
std::chrono::seconds(10) +
std::chrono::microseconds(123456);
// // 2024-10-01 10:10:10.123456
auto datatime = std::chrono::sys_days(date) + time - std::chrono::seconds(common::util::GetLocalTimeZone());
const char* test_msg = "test log message";
LogDataWrapper log_data_wrapper{
.module_name = "test_module",
.thread_id = 1234,
.t = datatime,
.lvl = AIMRT_LOG_LEVEL_INFO,
.line = 20,
.column = 10,
.file_name = "XX/YY/ZZ/test_file.cpp",
.function_name = "test_function",
.log_data = test_msg,
.log_data_size = strlen(test_msg)
};
LogFormatter formatter;
std::string actual_output;
std::string expected_output;
// Test the default pattern
formatter.SetPattern("[%c.%f][%l][%t][%n][%g:%R:%C @%F]%v");
expected_output = "[2024-10-01 10:10:10.123456][Info][1234][test_module][XX/YY/ZZ/test_file.cpp:20:10 @test_function]test log message";
actual_output = formatter.Format(log_data_wrapper);
EXPECT_EQ(actual_output, expected_output);
// Test dividing the time into year, month, day, hour, minute, second, and weekday
actual_output.clear();
formatter.SetPattern("[%Y/%m/%d %H:%M:%S.%f][%A][%a]%v");
expected_output = "[2024/10/01 10:10:10.123456][Tuesday][Tue]test log message";
actual_output = formatter.Format(log_data_wrapper);
EXPECT_EQ(actual_output, expected_output);
// Test custom pattern
actual_output.clear();
formatter.SetPattern("%%123456%%%q!@#$%^&*()+-*/12345678%%");
expected_output = "%123456%q!@#$^&*()+-*/12345678%";
actual_output = formatter.Format(log_data_wrapper);
EXPECT_EQ(actual_output, expected_output);
// Test blank pattern
actual_output.clear();
EXPECT_ANY_THROW(formatter.SetPattern(""));
}
} // namespace aimrt::runtime::core::logger

View File

@ -28,6 +28,7 @@ struct convert<aimrt::runtime::core::logger::RotateFileLoggerBackend::Options> {
node["max_file_num"] = rhs.max_file_num;
node["module_filter"] = rhs.module_filter;
node["log_executor_name"] = rhs.log_executor_name;
node["pattern"] = rhs.pattern;
return node;
}
@ -45,6 +46,8 @@ struct convert<aimrt::runtime::core::logger::RotateFileLoggerBackend::Options> {
rhs.module_filter = node["module_filter"].as<std::string>();
if (node["log_executor_name"])
rhs.log_executor_name = node["log_executor_name"].as<std::string>();
if (node["pattern"])
rhs.pattern = node["pattern"].as<std::string>();
return true;
}
@ -83,6 +86,11 @@ void RotateFileLoggerBackend::Initialize(YAML::Node options_node) {
"Log executor must be thread safe. Log executor name: " + options_.log_executor_name);
}
if (!options_.pattern.empty()) {
pattern_ = options_.pattern;
}
formatter_.SetPattern(pattern_);
options_node = options_;
run_flag_.store(true);
@ -96,24 +104,13 @@ void RotateFileLoggerBackend::Log(const LogDataWrapper& log_data_wrapper) noexce
if (!CheckLog(log_data_wrapper)) [[unlikely]]
return;
auto log_data_str = ::aimrt_fmt::format(
"[{}.{:0>6}][{}][{}][{}][{}:{}:{} @{}]{}",
aimrt::common::util::GetTimeStr(std::chrono::system_clock::to_time_t(log_data_wrapper.t)),
(aimrt::common::util::GetTimestampUs(log_data_wrapper.t) % 1000000),
LogLevelTool::GetLogLevelName(log_data_wrapper.lvl),
log_data_wrapper.thread_id,
log_data_wrapper.module_name,
log_data_wrapper.file_name,
log_data_wrapper.line,
log_data_wrapper.column,
log_data_wrapper.function_name,
std::string_view(log_data_wrapper.log_data, log_data_wrapper.log_data_size));
std::string log_data_str = formatter_.Format(log_data_wrapper);
auto log_work = [this, log_data_str{std::move(log_data_str)}]() {
if (!ofs_.is_open() || ofs_.tellp() > options_.max_file_size_m * 1024 * 1024) {
if (!OpenNewFile()) return;
}
ofs_ << log_data_str << std::endl;
ofs_.write(log_data_str.data(), log_data_str.size()) << std::endl;
};
log_executor_.Execute(std::move(log_work));

View File

@ -8,6 +8,7 @@
#include <unordered_map>
#include "aimrt_module_cpp_interface/executor/executor.h"
#include "core/logger/formatter.h"
#include "core/logger/logger_backend_base.h"
#include "util/string_util.h"
@ -22,6 +23,7 @@ class RotateFileLoggerBackend : public LoggerBackendBase {
uint32_t max_file_num = 100;
std::string module_filter = "(.*)";
std::string log_executor_name = "";
std::string pattern;
};
public:
@ -63,6 +65,8 @@ class RotateFileLoggerBackend : public LoggerBackendBase {
std::unordered_map<
std::string, bool, aimrt::common::util::StringHash, std::equal_to<>>
module_filter_map_;
LogFormatter formatter_;
std::string pattern_ = "[%c.%f][%l][%t][%n][%g:%R:%C @%F]%v";
};
} // namespace aimrt::runtime::core::logger