From 53c31bbf244f48c04de1d14499ed28f5a1e54488 Mon Sep 17 00:00:00 2001 From: lyq Date: Mon, 19 Jan 2026 16:58:33 +0800 Subject: [PATCH] Add high-performance logger system with asynchronous logging, terminal colors, and log throttling --- src/common/logger/CMakeLists.txt | 42 +++ src/common/logger/README.md | 236 +++++++++++++++ src/common/logger/include/logger/log_level.h | 80 ++++++ src/common/logger/include/logger/logger.h | 197 +++++++++++++ .../logger/include/logger/logger_impl.h | 140 +++++++++ src/common/logger/package.xml | 19 ++ src/common/logger/src/logger.cpp | 269 ++++++++++++++++++ src/common/logger/test_logger.cpp | 57 ++++ 8 files changed, 1040 insertions(+) create mode 100644 src/common/logger/CMakeLists.txt create mode 100644 src/common/logger/README.md create mode 100644 src/common/logger/include/logger/log_level.h create mode 100644 src/common/logger/include/logger/logger.h create mode 100644 src/common/logger/include/logger/logger_impl.h create mode 100644 src/common/logger/package.xml create mode 100644 src/common/logger/src/logger.cpp create mode 100644 src/common/logger/test_logger.cpp diff --git a/src/common/logger/CMakeLists.txt b/src/common/logger/CMakeLists.txt new file mode 100644 index 0000000..a34aa6c --- /dev/null +++ b/src/common/logger/CMakeLists.txt @@ -0,0 +1,42 @@ +cmake_minimum_required(VERSION 3.8) +project(logger) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic -O2) +endif() + +# ======== ROS2 dependencies ======== +find_package(ament_cmake REQUIRED) + +# ======== Build library ======== +add_library(logger SHARED + src/logger.cpp +) + +# ======== Include directories ======== +target_include_directories(logger PUBLIC + $ + $ +) + +# ======== Link libraries ======== +target_link_libraries(logger + pthread +) + +# ======== Install library ======== +install(TARGETS logger + EXPORT export_${PROJECT_NAME} + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) + +# ======== Install headers ======== +install(DIRECTORY include/ DESTINATION include) + +# ======== Export targets ======== +ament_export_targets(export_${PROJECT_NAME} HAS_LIBRARY_TARGET) +ament_export_dependencies(ament_cmake) + +ament_package() diff --git a/src/common/logger/README.md b/src/common/logger/README.md new file mode 100644 index 0000000..f1e70d3 --- /dev/null +++ b/src/common/logger/README.md @@ -0,0 +1,236 @@ +# 高性能日志系统 + +## 1. 设计概述 + +本日志系统是一个高性能、异步、线程安全的日志系统,参照ROS2日志设计,适用于C++项目。 + +### 1.1 核心特性 + +- **异步日志写入**:主线程将日志放入队列,独立线程负责写入文件 +- **多级别日志**:支持DEBUG、INFO、WARN、ERROR、FATAL五个级别 +- **双端输出**:同时输出到终端和文件 +- **终端颜色**:不同级别日志显示不同颜色,便于区分 +- **日志节流**:支持按时间节流,防止日志爆炸 +- **文件滚动**:按大小自动滚动日志文件(默认10MB) +- **线程安全**:支持多线程并发写入 +- **易用API**:提供简洁的宏定义接口 + +### 1.2 架构设计 + +``` ++----------------+ +----------------+ +----------------+ +| 业务线程 | | 日志队列 | | 日志写入线程 | +| (LOG_* 宏) | ---> | (线程安全) | ---> | (文件+终端输出)| ++----------------+ +----------------+ +----------------+ +``` + +## 2. 安装与集成 + +### 2.1 CMakeLists.txt配置 + +```cmake +# 添加依赖 +find_package(logger REQUIRED) + +# 链接库 +ament_target_dependencies(your_target + logger + # 其他依赖 +) +``` + +### 2.2 头文件包含 + +```cpp +#include "logger/logger.h" +``` + +## 3. 使用方法 + +### 3.1 初始化与关闭 + +```cpp +// 初始化日志系统 +logger::Logger::Init("node_name", "./log"); + +// 业务逻辑... + +// 关闭日志系统(可选,程序结束时会自动关闭) +logger::Logger::Shutdown(); +``` + +### 3.2 日志宏使用 + +#### 3.2.1 基本日志 + +```cpp +LOG_DEBUG("Debug message: %d", value); +LOG_INFO("Info message: %d", value); +LOG_WARN("Warn message: %d", value); +LOG_ERROR("Error message: %d", value); +LOG_FATAL("Fatal message: %d", value); +``` + +#### 3.2.2 带节流的日志 + +```cpp +// 每2000毫秒最多输出一次 +LOG_INFO_THROTTLE(2000, "Throttled info: %d", value); +LOG_WARN_THROTTLE(1000, "Throttled warn: %d", value); +``` + +### 3.3 日志级别控制 + +```cpp +// 设置日志级别 +logger::Logger::SetLogLevel(logger::LogLevel::DEBUG); +logger::Logger::SetLogLevel(logger::LogLevel::INFO); +logger::Logger::SetLogLevel(logger::LogLevel::WARN); +logger::Logger::SetLogLevel(logger::LogLevel::ERROR); +logger::Logger::SetLogLevel(logger::LogLevel::FATAL); + +// 获取当前日志级别 +logger::LogLevel level = logger::Logger::GetLogLevel(); +``` + +## 4. 配置参数 + +### 4.1 初始化参数 + +```cpp +/** + * @param name 日志记录器名称 + * @param log_dir 日志文件存储目录,默认值为"./log" + * @param max_file_size 单个日志文件最大大小(字节),默认值为10MB + * @param flush_interval 日志刷新间隔(毫秒),默认值为1000ms + */ +static void Init(const std::string& name, + const std::string& log_dir = "./log", + size_t max_file_size = 10 * 1024 * 1024, + size_t flush_interval = 1000); +``` + +## 5. 日志格式 + +``` +[YYYY-MM-DD HH:MM:SS.ms] [LEVEL] [THREAD_ID] [NAME] message +``` + +示例: +``` +[2026-01-19 16:00:00.123] [INFO] [123456789] [test_node] Hello, Logger! +``` + +## 6. 终端颜色输出 + +日志系统为不同级别的日志提供了不同的终端颜色,便于直观区分: + +| 日志级别 | 颜色 | ANSI 代码 | 描述 | +|---------|--------|-----------|------------| +| DEBUG | 蓝色 | \033[34m | 调试信息 | +| INFO | 无颜色 | 无 | 正常信息 | +| WARN | 黄色 | \033[33m | 警告信息 | +| ERROR | 红色 | \033[31m | 错误信息 | +| FATAL | 紫色 | \033[35m | 致命错误 | + +**说明**: +- 颜色仅在终端输出中显示,日志文件中为纯文本 +- 颜色输出使用ANSI转义序列,兼容大多数现代终端 +- 颜色设置会自动重置,不会影响后续终端输出 +- 可以通过修改代码禁用颜色输出(如需) + +## 7. 性能优化 + +1. **异步写入**:避免主线程阻塞 +2. **批量处理**:减少磁盘I/O次数 +3. **日志节流**:防止日志爆炸 +4. **线程安全队列**:高效的多线程通信 +5. **合理的刷新间隔**:平衡实时性和性能 + +## 8. 最佳实践 + +### 8.1 日志级别使用建议 + +- **DEBUG**:开发调试信息,生产环境建议关闭 +- **INFO**:正常运行状态信息 +- **WARN**:警告信息,需要关注但不影响正常运行 +- **ERROR**:错误信息,可能影响部分功能 +- **FATAL**:致命错误,会导致程序退出 + +### 8.2 日志内容建议 + +- 包含足够的上下文信息 +- 使用清晰的格式 +- 避免敏感信息 +- 合理使用节流功能 + +### 8.3 性能考虑 + +- 避免在高频调用的函数中使用详细日志 +- 对频繁输出的日志使用节流功能 +- 生产环境建议使用INFO或更高级别 + +## 9. 示例代码 + +```cpp +#include "logger/logger.h" +#include + +void TestThread(int id) { + for (int i = 0; i < 5; ++i) { + LOG_INFO_THROTTLE(1000, "Thread %d: info message %d", id, i); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } +} + +int main() { + // 初始化日志系统 + logger::Logger::Init("test_logger"); + + // 设置日志级别 + logger::Logger::SetLogLevel(logger::LogLevel::INFO); + + // 测试不同级别日志 + LOG_INFO("Logger initialized"); + LOG_WARN("This is a warning"); + LOG_ERROR("This is an error"); + + // 测试多线程日志 + std::thread t1(TestThread, 1); + std::thread t2(TestThread, 2); + + t1.join(); + t2.join(); + + LOG_INFO("Test completed"); + + return 0; +} +``` + +## 10. 版本变更 + +### 10.1 v1.0.0 (2026-01-19) + +- 初始版本 +- 实现基本日志功能 +- 支持五种日志级别 +- 异步日志写入 +- 日志文件滚动 + +### 10.2 v1.1.0 (2026-01-19) + +- 增加日志节流功能 +- 支持所有级别日志同时输出到终端和文件 +- 优化日志格式 +- 增加详细文档 + +### 10.3 v1.2.0 (2026-01-19) + +- 增加终端颜色输出功能 +- 不同级别日志显示不同颜色 +- 完善文档说明 + +## 11. 联系方式 + +如有问题或建议,请联系开发团队。 diff --git a/src/common/logger/include/logger/log_level.h b/src/common/logger/include/logger/log_level.h new file mode 100644 index 0000000..b5f04b8 --- /dev/null +++ b/src/common/logger/include/logger/log_level.h @@ -0,0 +1,80 @@ +#ifndef LOGGER_LOG_LEVEL_H +#define LOGGER_LOG_LEVEL_H + +#include + +namespace logger +{ + +/** + * @brief 日志级别枚举 + */ +enum class LogLevel +{ + DEBUG = 0, ///< 调试信息 + INFO = 1, ///< 普通信息 + WARN = 2, ///< 警告信息 + ERROR = 3, ///< 错误信息 + FATAL = 4 ///< 致命错误信息 +}; + +/** + * @brief 将日志级别转换为字符串 + * @param level 日志级别 + * @return 日志级别对应的字符串 + */ +inline std::string LogLevelToString(LogLevel level) +{ + switch (level) + { + case LogLevel::DEBUG: + return "DEBUG"; + case LogLevel::INFO: + return "INFO"; + case LogLevel::WARN: + return "WARN"; + case LogLevel::ERROR: + return "ERROR"; + case LogLevel::FATAL: + return "FATAL"; + default: + return "UNKNOWN"; + } +} + +/** + * @brief 将字符串转换为日志级别 + * @param level_str 日志级别字符串 + * @return 对应的日志级别 + */ +inline LogLevel StringToLogLevel(const std::string& level_str) +{ + if (level_str == "DEBUG") + { + return LogLevel::DEBUG; + } + else if (level_str == "INFO") + { + return LogLevel::INFO; + } + else if (level_str == "WARN") + { + return LogLevel::WARN; + } + else if (level_str == "ERROR") + { + return LogLevel::ERROR; + } + else if (level_str == "FATAL") + { + return LogLevel::FATAL; + } + else + { + return LogLevel::INFO; // 默认日志级别 + } +} + +} // namespace logger + +#endif // LOGGER_LOG_LEVEL_H diff --git a/src/common/logger/include/logger/logger.h b/src/common/logger/include/logger/logger.h new file mode 100644 index 0000000..85346db --- /dev/null +++ b/src/common/logger/include/logger/logger.h @@ -0,0 +1,197 @@ +#ifndef LOGGER_LOGGER_H +#define LOGGER_LOGGER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "logger/log_level.h" + +namespace logger +{ + +/** + * @brief 日志消息结构 + */ +struct LogMessage +{ + LogLevel level; ///< 日志级别 + std::string message; ///< 日志内容 + std::chrono::system_clock::time_point timestamp; ///< 日志时间戳 + std::thread::id thread_id; ///< 线程ID + std::string logger_name; ///< 日志记录器名称 +}; + +/** + * @brief 日志记录器类 + * 采用单例模式,支持异步日志写入 + */ +class Logger +{ + public: + /** + * @brief 初始化日志记录器 + * @param name 日志记录器名称 + * @param log_dir 日志文件存储目录,默认值为"./log" + * @param max_file_size 单个日志文件最大大小(字节),默认值为10MB + * @param flush_interval 日志刷新间隔(毫秒),默认值为1000ms + */ + static void Init(const std::string& name, const std::string& log_dir = "./log", + size_t max_file_size = 10 * 1024 * 1024, size_t flush_interval = 1000); + + /** + * @brief 关闭日志记录器 + */ + static void Shutdown(); + + /** + * @brief 设置日志级别 + * @param level 日志级别 + */ + static void SetLogLevel(LogLevel level); + + /** + * @brief 获取当前日志级别 + * @return 当前日志级别 + */ + static LogLevel GetLogLevel(); + + /** + * @brief 添加日志消息到队列 + * @param level 日志级别 + * @param format 日志格式化字符串 + * @param args 日志参数 + */ + template + static void Log(LogLevel level, const char* format, Args&&... args); + + /** + * @brief 添加带节流的日志消息到队列 + * @param level 日志级别 + * @param throttle_ms 节流时间(毫秒) + * @param file 文件路径 + * @param line 行号 + * @param format 日志格式化字符串 + * @param args 日志参数 + */ + template + static void LogThrottled(LogLevel level, uint64_t throttle_ms, const char* file, int line, const char* format, + Args&&... args); + + private: + /** + * @brief 构造函数 + */ + Logger(); + + /** + * @brief 析构函数 + */ + ~Logger(); + + /** + * @brief 日志写入线程函数 + */ + void WriteLogThread(); + + /** + * @brief 初始化日志文件 + */ + void InitLogFile(); + + /** + * @brief 滚动日志文件 + */ + void RotateLogFile(); + + /** + * @brief 格式化日志消息 + * @param msg 日志消息 + * @return 格式化后的日志字符串 + */ + std::string FormatLogMessage(const LogMessage& msg); + + /** + * @brief 获取单例实例 + * @return 日志记录器单例 + */ + static Logger& GetInstance(); + + private: + struct LogKey + { + std::string file; ///< 文件路径 + int line; ///< 行号 + LogLevel level; ///< 日志级别 + std::string format; ///< 日志格式化字符串 + + bool operator==(const LogKey& other) const + { + return file == other.file && line == other.line && level == other.level && format == other.format; + } + + // 自定义哈希函数支持 + struct Hash + { + std::size_t operator()(const LogKey& key) const + { + return std::hash{}(key.file) ^ std::hash{}(key.line) ^ + std::hash{}(static_cast(key.level)) ^ std::hash{}(key.format); + } + }; + }; + + using LogThrottleMap = std::unordered_map; + + std::string name_; ///< 日志记录器名称 + std::string log_dir_; ///< 日志文件目录 + size_t max_file_size_; ///< 单个日志文件最大大小 + size_t flush_interval_; ///< 日志刷新间隔(毫秒) + std::atomic log_level_; ///< 当前日志级别 + + std::queue log_queue_; ///< 日志消息队列 + std::mutex queue_mutex_; ///< 队列互斥锁 + std::condition_variable queue_cv_; ///< 队列条件变量 + std::atomic running_; ///< 运行状态标记 + std::thread write_thread_; ///< 日志写入线程 + + FILE* log_file_; ///< 当前日志文件指针 + std::mutex file_mutex_; ///< 文件互斥锁 + std::chrono::system_clock::time_point last_flush_time_; ///< 上次刷新时间 + + // 节流日志相关 + LogThrottleMap throttle_map_; ///< 存储每个日志语句的最后执行时间 + std::mutex throttle_mutex_; ///< 节流日志互斥锁 +}; + +// 日志宏定义 +#define LOG_DEBUG(format, ...) logger::Logger::Log(logger::LogLevel::DEBUG, format, ##__VA_ARGS__) +#define LOG_INFO(format, ...) logger::Logger::Log(logger::LogLevel::INFO, format, ##__VA_ARGS__) +#define LOG_WARN(format, ...) logger::Logger::Log(logger::LogLevel::WARN, format, ##__VA_ARGS__) +#define LOG_ERROR(format, ...) logger::Logger::Log(logger::LogLevel::ERROR, format, ##__VA_ARGS__) +#define LOG_FATAL(format, ...) logger::Logger::Log(logger::LogLevel::FATAL, format, ##__VA_ARGS__) + +// 带节流的日志宏定义(单位:毫秒) +#define LOG_DEBUG_THROTTLE(throttle_ms, format, ...) \ + logger::Logger::LogThrottled(logger::LogLevel::DEBUG, throttle_ms, __FILE__, __LINE__, format, ##__VA_ARGS__) +#define LOG_INFO_THROTTLE(throttle_ms, format, ...) \ + logger::Logger::LogThrottled(logger::LogLevel::INFO, throttle_ms, __FILE__, __LINE__, format, ##__VA_ARGS__) +#define LOG_WARN_THROTTLE(throttle_ms, format, ...) \ + logger::Logger::LogThrottled(logger::LogLevel::WARN, throttle_ms, __FILE__, __LINE__, format, ##__VA_ARGS__) +#define LOG_ERROR_THROTTLE(throttle_ms, format, ...) \ + logger::Logger::LogThrottled(logger::LogLevel::ERROR, throttle_ms, __FILE__, __LINE__, format, ##__VA_ARGS__) +#define LOG_FATAL_THROTTLE(throttle_ms, format, ...) \ + logger::Logger::LogThrottled(logger::LogLevel::FATAL, throttle_ms, __FILE__, __LINE__, format, ##__VA_ARGS__) + +} // namespace logger + +// 模板函数实现 +#include "logger/logger_impl.h" + +#endif // LOGGER_LOGGER_H diff --git a/src/common/logger/include/logger/logger_impl.h b/src/common/logger/include/logger/logger_impl.h new file mode 100644 index 0000000..23cd900 --- /dev/null +++ b/src/common/logger/include/logger/logger_impl.h @@ -0,0 +1,140 @@ +#ifndef LOGGER_LOGGER_IMPL_H +#define LOGGER_LOGGER_IMPL_H + +#include +#include +#include +#include +#include + +#include "logger/logger.h" + +// 抑制格式安全警告,因为format参数是来自用户可控的字符串字面量 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-security" + +/** + * @brief 字符串格式化辅助函数 + * @param format 格式化字符串 + * @param args 可变参数 + * @return 格式化后的字符串 + */ +template +inline std::string StringFormat(const char* format, Args&&... args) +{ + // 计算所需缓冲区大小 + int size = snprintf(nullptr, 0, format, std::forward(args)...); + if (size <= 0) + { + return ""; + } + + // 分配缓冲区并格式化 + std::array small_buf; + if (size < static_cast(small_buf.size())) + { + // 使用__attribute__((format(printf, 2, 3)))抑制格式安全警告 + // 因为format参数是来自用户可控的字符串字面量 + snprintf(small_buf.data(), small_buf.size(), format, std::forward(args)...); + return small_buf.data(); + } + else + { + std::string buf(size + 1, '\0'); + snprintf(buf.data(), buf.size(), format, std::forward(args)...); + return buf; + } +} + +// 恢复诊断设置 +#pragma GCC diagnostic pop + +namespace logger +{ + +/** + * @brief 日志记录模板函数实现 + */ +template +inline void Logger::Log(LogLevel level, const char* format, Args&&... args) +{ + // 检查日志级别 + if (level < GetInstance().log_level_) + { + return; + } + + // 创建日志消息 + LogMessage msg; + msg.level = level; + msg.timestamp = std::chrono::system_clock::now(); + msg.thread_id = std::this_thread::get_id(); + msg.logger_name = GetInstance().name_; + msg.message = ::StringFormat(format, std::forward(args)...); + + // 添加到队列 + { + std::lock_guard lock(GetInstance().queue_mutex_); + GetInstance().log_queue_.push(std::move(msg)); + } + + // 通知写入线程 + GetInstance().queue_cv_.notify_one(); +} + +/** + * @brief 带节流的日志记录模板函数实现 + */ +template +inline void Logger::LogThrottled(LogLevel level, uint64_t throttle_ms, const char* file, int line, const char* format, + Args&&... args) +{ + // 检查日志级别 + if (level < GetInstance().log_level_) + { + return; + } + + // 检查是否需要执行日志 + bool should_log = false; + { + std::lock_guard lock(GetInstance().throttle_mutex_); + + // 创建日志唯一标识 + LogKey key; + key.file = file; + key.line = line; + key.level = level; + key.format = format; + + auto now = std::chrono::system_clock::now(); + auto it = GetInstance().throttle_map_.find(key); + + if (it == GetInstance().throttle_map_.end()) + { + // 第一次执行该日志 + should_log = true; + GetInstance().throttle_map_[key] = now; + } + else + { + // 检查时间差 + auto duration = std::chrono::duration_cast(now - it->second); + if (duration.count() >= throttle_ms) + { + should_log = true; + it->second = now; + } + } + } + + // 如果需要执行日志 + if (should_log) + { + Log(level, format, std::forward(args)...); + } +} + +} // namespace logger + +#endif // LOGGER_LOGGER_IMPL_H diff --git a/src/common/logger/package.xml b/src/common/logger/package.xml new file mode 100644 index 0000000..5abdf4c --- /dev/null +++ b/src/common/logger/package.xml @@ -0,0 +1,19 @@ + + + + logger + 1.0.0 + High performance logging system for sweeper project + admin + Apache License 2.0 + + ament_cmake + + rclcpp + + rclcpp + + + ament_cmake + + diff --git a/src/common/logger/src/logger.cpp b/src/common/logger/src/logger.cpp new file mode 100644 index 0000000..c3afaa9 --- /dev/null +++ b/src/common/logger/src/logger.cpp @@ -0,0 +1,269 @@ +#include "logger/logger.h" + +#include +#include +#include +#include +#include + +namespace logger +{ + +/** + * @brief 构造函数 + */ +Logger::Logger() + : name_(""), + log_dir_("./log"), + max_file_size_(10 * 1024 * 1024), + flush_interval_(1000), + log_level_(LogLevel::INFO), + running_(false), + log_file_(nullptr) +{ +} + +/** + * @brief 析构函数 + */ +Logger::~Logger() { Shutdown(); } + +/** + * @brief 初始化日志记录器 + */ +void Logger::Init(const std::string& name, const std::string& log_dir, size_t max_file_size, size_t flush_interval) +{ + Logger& logger = GetInstance(); + logger.name_ = name; + logger.log_dir_ = log_dir; + logger.max_file_size_ = max_file_size; + logger.flush_interval_ = flush_interval; + + // 创建日志目录 + std::filesystem::create_directories(logger.log_dir_); + + // 初始化日志文件 + logger.InitLogFile(); + + // 启动日志写入线程 + logger.running_ = true; + logger.write_thread_ = std::thread(&Logger::WriteLogThread, &logger); + + LOG_INFO("Logger initialized: name=%s, log_dir=%s, max_file_size=%zu, flush_interval=%zu", name.c_str(), + log_dir.c_str(), max_file_size, flush_interval); +} + +/** + * @brief 关闭日志记录器 + */ +void Logger::Shutdown() +{ + Logger& logger = GetInstance(); + if (logger.running_) + { + logger.running_ = false; + logger.queue_cv_.notify_one(); + + if (logger.write_thread_.joinable()) + { + logger.write_thread_.join(); + } + + // 刷新并关闭日志文件 + if (logger.log_file_) + { + fflush(logger.log_file_); + fclose(logger.log_file_); + logger.log_file_ = nullptr; + } + + LOG_INFO("Logger shutdown"); + } +} + +/** + * @brief 设置日志级别 + */ +void Logger::SetLogLevel(LogLevel level) +{ + GetInstance().log_level_ = level; + LOG_INFO("Log level set to %s", LogLevelToString(level).c_str()); +} + +/** + * @brief 获取当前日志级别 + */ +LogLevel Logger::GetLogLevel() { return GetInstance().log_level_; } + +/** + * @brief 初始化日志文件 + */ +void Logger::InitLogFile() +{ + // 生成日志文件名 + auto now = std::chrono::system_clock::now(); + auto now_c = std::chrono::system_clock::to_time_t(now); + struct tm tm_info; + localtime_r(&now_c, &tm_info); + + char filename[256]; + snprintf(filename, sizeof(filename), "%s/%s_%04d%02d%02d_%02d%02d%02d.log", log_dir_.c_str(), name_.c_str(), + tm_info.tm_year + 1900, tm_info.tm_mon + 1, tm_info.tm_mday, tm_info.tm_hour, tm_info.tm_min, + tm_info.tm_sec); + + // 打开日志文件 + log_file_ = fopen(filename, "a"); + if (!log_file_) + { + std::cerr << "Failed to open log file: " << filename << std::endl; + exit(EXIT_FAILURE); + } + + last_flush_time_ = std::chrono::system_clock::now(); + + LOG_INFO("Log file initialized: %s", filename); +} + +/** + * @brief 滚动日志文件 + */ +void Logger::RotateLogFile() +{ + if (log_file_) + { + fflush(log_file_); + fclose(log_file_); + log_file_ = nullptr; + } + + InitLogFile(); +} + +/** + * @brief 格式化日志消息 + */ +std::string Logger::FormatLogMessage(const LogMessage& msg) +{ + // 格式化时间 + auto now = msg.timestamp; + auto now_c = std::chrono::system_clock::to_time_t(now); + auto now_ms = std::chrono::duration_cast(now.time_since_epoch()) % 1000; + + struct tm tm_info; + localtime_r(&now_c, &tm_info); + + char time_buf[64]; + snprintf(time_buf, sizeof(time_buf), "%04d-%02d-%02d %02d:%02d:%02d.%03d", tm_info.tm_year + 1900, + tm_info.tm_mon + 1, tm_info.tm_mday, tm_info.tm_hour, tm_info.tm_min, tm_info.tm_sec, + static_cast(now_ms.count())); + + // 格式化线程ID + char thread_buf[32]; + snprintf(thread_buf, sizeof(thread_buf), "%lu", std::hash{}(msg.thread_id)); + + // 组合日志消息 + char log_buf[2048]; + snprintf(log_buf, sizeof(log_buf), "[%s] [%s] [%s] [%s] %s\n", time_buf, LogLevelToString(msg.level).c_str(), + thread_buf, msg.logger_name.c_str(), msg.message.c_str()); + + return log_buf; +} + +/** + * @brief 日志写入线程函数 + */ +void Logger::WriteLogThread() +{ + std::queue local_queue; + + while (running_ || !log_queue_.empty()) + { + { // 从队列获取日志消息 + std::unique_lock lock(queue_mutex_); + + // 等待新的日志消息或超时 + queue_cv_.wait_for(lock, std::chrono::milliseconds(flush_interval_), + [this]() { return !log_queue_.empty() || !running_; }); + + // 将队列中的消息移动到本地队列,减少锁持有时间 + if (!log_queue_.empty()) + { + local_queue.swap(log_queue_); + } + } + + // 写入本地队列中的所有日志 + while (!local_queue.empty()) + { + LogMessage msg = std::move(local_queue.front()); + local_queue.pop(); + + std::string formatted_msg = FormatLogMessage(msg); + + { // 写入日志文件 + std::lock_guard lock(file_mutex_); + if (log_file_) + { + fwrite(formatted_msg.c_str(), 1, formatted_msg.size(), log_file_); + + // 检查文件大小是否需要滚动 + if (ftell(log_file_) >= static_cast(max_file_size_)) + { + RotateLogFile(); + } + + // 定期刷新缓冲区 + auto now = std::chrono::system_clock::now(); + if (std::chrono::duration_cast(now - last_flush_time_) >= + std::chrono::milliseconds(flush_interval_)) + { + fflush(log_file_); + last_flush_time_ = now; + } + } + } + + // 同时输出到控制台(带颜色,INFO级别无颜色) + std::string colored_msg; + switch (msg.level) + { + case LogLevel::DEBUG: + colored_msg = "\033[34m" + formatted_msg + "\033[0m"; // 蓝色 + break; + case LogLevel::INFO: + colored_msg = formatted_msg; // INFO级别无颜色 + break; + case LogLevel::WARN: + colored_msg = "\033[33m" + formatted_msg + "\033[0m"; // 黄色 + break; + case LogLevel::ERROR: + colored_msg = "\033[31m" + formatted_msg + "\033[0m"; // 红色 + break; + case LogLevel::FATAL: + colored_msg = "\033[35m" + formatted_msg + "\033[0m"; // 紫色 + break; + default: + colored_msg = formatted_msg; + break; + } + std::cerr << colored_msg; + } + } + + // 最后刷新日志文件 + if (log_file_) + { + fflush(log_file_); + } +} + +/** + * @brief 获取单例实例 + */ +Logger& Logger::GetInstance() +{ + static Logger instance; + return instance; +} + +} // namespace logger diff --git a/src/common/logger/test_logger.cpp b/src/common/logger/test_logger.cpp new file mode 100644 index 0000000..d4d26e5 --- /dev/null +++ b/src/common/logger/test_logger.cpp @@ -0,0 +1,57 @@ +#include +#include + +#include "logger/logger.h" + +void TestThread(int id) +{ + for (int i = 0; i < 5; ++i) + { + LOG_DEBUG("Thread %d: debug message %d", id, i); + LOG_INFO("Thread %d: info message %d", id, i); + LOG_WARN("Thread %d: warn message %d", id, i); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } +} + +int main() +{ + // 初始化日志系统 + logger::Logger::Init("test_logger", "./test_logs", 1 * 1024 * 1024); + + // 设置日志级别为DEBUG + logger::Logger::SetLogLevel(logger::LogLevel::DEBUG); + + // 测试不同级别的日志 + LOG_DEBUG("Debug message"); + LOG_INFO("Info message"); + LOG_WARN("Warn message"); + LOG_ERROR("Error message"); + LOG_FATAL("Fatal message"); + + // 测试带参数的日志 + LOG_INFO("Test with integers: %d, %d, %d", 1, 2, 3); + LOG_INFO("Test with floats: %f, %f", 1.1, 2.2); + LOG_INFO("Test with strings: %s, %s", "hello", "world"); + + // 测试多线程日志 + std::thread t1(TestThread, 1); + std::thread t2(TestThread, 2); + std::thread t3(TestThread, 3); + + t1.join(); + t2.join(); + t3.join(); + + // 测试日志文件滚动(生成大量日志) + for (int i = 0; i < 2000; ++i) + { + LOG_INFO("Large log message %d: This is a test to generate large amount of log data to test log file rotation.", + i); + } + + // 关闭日志系统 + logger::Logger::Shutdown(); + + return 0; +}