如何使用g3log实现按函数或按模块分别生成日志文件
g3log按模块/函数拆分日志文件实现示例
核心实现逻辑:g3log通过自定义Sink扩展日志输出能力,要实现按模块/函数拆分日志文件,只需要在自定义Sink中根据日志携带的模块标记、函数名信息,将日志路由到对应文件即可。
步骤1:实现自定义Sink类
CustomSink.h
#pragma once #include <string> #include <fstream> #include <unordered_map> #include <mutex> #include <g2log.hpp> class CustomSink { public: // 拆分模式枚举:按模块拆分 / 按函数拆分 enum class SplitMode : int { BY_MODULE = 0, BY_FUNCTION = 1 }; explicit CustomSink(SplitMode mode, const std::string& log_dir = "./logs/"); ~CustomSink(); // g3log日志回调入口,所有日志都会传入该函数处理 void callback(g2::LogMessageMover message); private: SplitMode m_split_mode; std::string m_log_dir; std::unordered_map<std::string, std::ofstream> m_file_stream_map; std::mutex m_file_op_mutex; // 根据拆分key获取对应的文件流,不存在则自动创建 std::ofstream& get_file_stream(const std::string& split_key); };
CustomSink.cpp
#include "CustomSink.h" #include <filesystem> #include <stdexcept> #include <algorithm> namespace fs = std::filesystem; CustomSink::CustomSink(SplitMode mode, const std::string& log_dir) : m_split_mode(mode), m_log_dir(log_dir) { // 自动创建日志目录 if (!fs::exists(m_log_dir)) { fs::create_directories(m_log_dir); } if (!fs::is_directory(m_log_dir)) { throw std::runtime_error("无效的日志存储路径: " + m_log_dir); } } CustomSink::~CustomSink() { std::lock_guard<std::mutex> lock(m_file_op_mutex); for (auto& [key, file] : m_file_stream_map) { if (file.is_open()) { file.close(); } } } std::ofstream& CustomSink::get_file_stream(const std::string& split_key) { auto iter = m_file_stream_map.find(split_key); if (iter == m_file_stream_map.end()) { std::string file_path = m_log_dir + split_key + ".log"; auto [new_iter, insert_success] = m_file_stream_map.emplace( split_key, std::ofstream(file_path, std::ios::app | std::ios::out) ); if (!insert_success || !new_iter->second.is_open()) { throw std::runtime_error("日志文件创建失败: " + file_path); } iter = new_iter; } return iter->second; } void CustomSink::callback(g2::LogMessageMover message) { auto log_msg = message.get(); std::string split_key; if (m_split_mode == SplitMode::BY_MODULE) { // 解析日志前缀的[模块名]标记,无标记则归入default.log size_t tag_start = log_msg.message().find('['); size_t tag_end = log_msg.message().find(']', tag_start); if (tag_start != std::string::npos && tag_end != std::string::npos) { split_key = log_msg.message().substr(tag_start + 1, tag_end - tag_start - 1); } else { split_key = "default"; } } else if (m_split_mode == SplitMode::BY_FUNCTION) { // 直接取函数名作为拆分key,替换非法字符避免文件名错误 split_key = log_msg.function(); std::replace(split_key.begin(), split_key.end(), ':', '_'); std::replace(split_key.begin(), split_key.end(), '<', '_'); std::replace(split_key.begin(), split_key.end(), '>', '_'); } std::lock_guard<std::mutex> lock(m_file_op_mutex); auto& output_file = get_file_stream(split_key); // 自定义日志输出格式,可根据需求调整 output_file << log_msg.timestamp() << " [" << log_msg.level() << "] " << log_msg.message() << " (" << log_msg.file() << ":" << log_msg.line() << ")\n"; output_file.flush(); }
步骤2:定义模块日志宏(按模块分类时需要)
在公共头文件中添加如下宏,打日志时指定模块即可自动标记:
// 参数1:模块名,参数2:日志等级(DEBUG/INFO/WARNING/ERROR/FATAL) #define LOG_MODULE(module, level) G2_LOG_##level(__FILE__, __LINE__, __FUNCTION__, "") << "[" #module "] "
步骤3:修改日志初始化代码
// main.cpp #include<g2log.hpp> #include<g2logworker.hpp> #include <std2_make_unique.hpp> #include "CustomSink.h" int main(int argc, char**argv) { using namespace g2; // 创建无默认Sink的日志worker,如果需要保留全局默认日志,也可以用createWithDefaultLogger创建后再加自定义Sink auto log_worker = LogWorker::createWithNoSink(); // 初始化自定义Sink:可选择按模块拆分SplitMode::BY_MODULE,或按函数拆分SplitMode::BY_FUNCTION auto custom_sink = std2::make_unique<CustomSink>(CustomSink::SplitMode::BY_MODULE, "./logs/"); // 绑定Sink回调 log_worker->addSink(std::move(custom_sink), &CustomSink::callback); // 初始化全局日志 g2::initializeLogging(log_worker.get()); // 测试按模块打日志 LOG_MODULE(DB, INFO) << "数据库连接成功,端口3306"; LOG_MODULE(NETWORK, DEBUG) << "收到客户端请求,IP=192.168.1.10"; LOG_MODULE(UI, WARNING) << "图标资源加载失败,使用默认占位图"; // 按函数分类时直接用原生LOG宏即可 // LOG(INFO) << "函数日志测试"; // 程序退出时worker会自动释放资源,无需手动关闭 return 0; }
注意事项
- 按模块分类时必须使用
LOG_MODULE宏打日志,未标记模块的日志会默认存入default.log - 按函数分类时如果函数名过长、包含特殊字符,可额外添加映射表将函数名转换为短别名,避免文件名异常
- 高并发场景下可在Sink中添加异步写队列,避免日志写盘阻塞业务线程
- 若需要同时保留全局统一日志,可保留原有
createWithDefaultLogger初始化逻辑,再额外添加自定义Sink即可,多个Sink之间互不影响
内容的提问来源于stack exchange,提问作者chan
相关产品推荐
相关产品推荐

