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:
parent
a1fda52c37
commit
929a105bd0
@ -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" | 日志的输出格式 |
|
||||
|
||||
|
||||
使用注意点如下:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
|
274
src/runtime/core/logger/formatter.h
Normal file
274
src/runtime/core/logger/formatter.h
Normal 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
|
64
src/runtime/core/logger/formatter_test.cc
Normal file
64
src/runtime/core/logger/formatter_test.cc
Normal 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
|
@ -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));
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user