【Linux C++ 日志系统实战】Logger 日志器完整实现:级别控制、宏封装、动态输出、自动崩溃退出

张开发
2026/4/4 2:11:21 15 分钟阅读
【Linux C++ 日志系统实战】Logger 日志器完整实现:级别控制、宏封装、动态输出、自动崩溃退出
前言在之前的文章中我们实现了Timestamp时间戳、LogMessage日志消息体。今天我们实现日志系统最上层入口Logger 日志器。它承担以下核心功能对外提供极简使用方式LOG_INFO hello全局日志级别开关TRACE/DEBUG/INFO/ERROR…可动态替换输出控制台 / 文件 / 网络析构函数自动输出日志无需手动 flushFATAL 级别直接崩溃退出便于现场保留支持环境变量初始化日志级别本文基于你提供的完整工程代码逐行精讲可直接用于 Linux 服务端项目。一、整体架构Logger 是整个日志系统的入口类结构如下LogMessage负责拼装日志内容Logger负责输出、级别、宏封装宏定义 LOG_DEBUG / LOG_INFO极简调用输出回调 OutputFun可替换为控制台 / 文件刷新回调 FlushFun刷新缓冲区二、公共头文件 LogCommon.hpp日志级别、缓冲区大小、级别字符串全局共用#ifndef LOG_COMMON_HPP #define LOG_COMMON_HPP namespace tulun { static const int SMALL_BUFF_LEN 128; static const int MEDIAN_BUFF_LEN 512; static const int LARGE_BUFF_LEN 1024; // C11 强类型枚举 enum class LOG_LEVEL { TRACE 0, DEBUG, INFO, WARN, ERROR, FATAL, NUM_LOG_LEVELS, }; // 级别字符串映射 static const char * LLTOSTR[] { TRACE, DEBUG, INFO, WARN, ERROR, FATAL, NUM_LOG_LEVELS, }; } #endif三、Logger 类定义Logger.hpp对外提供日志宏、输出设置、级别设置、流接口。#include functional #include LogMessage.hpp #ifndef LOGGER_HPP #define LOGGER_HPP namespace tulun { class Logger { public: // 输出回调string → 输出位置 using OutputFun std::functionvoid(const std::string ); // 刷新回调 using FlushFun std::functionvoid(void); private: static OutputFun s_output_; // 全局输出 static FlushFun s_flush_; // 全局刷新 static LOG_LEVEL s_level_; // 全局级别 private: LogMessage impl_; // 内部日志消息对象 public: // 构造级别 文件 函数 行号 Logger(const LOG_LEVEL level, const std::string filename, const std::string funcname, int line); // 析构自动输出日志 刷新 ~Logger(); // 获取流用于 输入 LogMessage stream(); // 全局设置 static void setOutput(const OutputFun out); static void setFlush(const FlushFun flush); static LOG_LEVEL getLogLevel(); static void setLogLevel(const LOG_LEVEL level); }; // 日志宏定义 #define LOG_TRACE \ if (tulun::Logger::getLogLevel() tulun::LOG_LEVEL::TRACE) \ tulun::Logger(tulun::LOG_LEVEL::TRACE, __FILE__, __func__, __LINE__).stream() #define LOG_DEBUG \ if (tulun::Logger::getLogLevel() tulun::LOG_LEVEL::DEBUG) \ tulun::Logger(tulun::LOG_LEVEL::DEBUG, __FILE__, __func__, __LINE__).stream() #define LOG_INFO \ if (tulun::Logger::getLogLevel() tulun::LOG_LEVEL::INFO) \ tulun::Logger(tulun::LOG_LEVEL::INFO, __FILE__, __func__, __LINE__).stream() #define LOG_WARN \ if (tulun::Logger::getLogLevel() tulun::LOG_LEVEL::WARN) \ tulun::Logger(tulun::LOG_LEVEL::WARN, __FILE__, __func__, __LINE__).stream() #define LOG_ERROR \ tulun::Logger(tulun::LOG_LEVEL::ERROR, __FILE__, __func__, __LINE__).stream() #define LOG_FATAL \ tulun::Logger(tulun::LOG_LEVEL::FATAL, __FILE__, __func__, __LINE__).stream() #define LOG_SYSERR LOG_ERROR #define LOG_SYSFATAL LOG_FATAL } #endif四、Logger 核心实现Logger.cpp这是日志系统的心脏自动初始化级别析构自动输出支持替换输出FATAL 直接崩溃退出#include stdio.h #include iostream #include Logger.hpp namespace tulun { // 默认输出/刷新 void defaultOutput(const std::string msg) { fwrite(msg.c_str(), 1, msg.size(), stdout); } void defaultFlush() { fflush(stdout); } // 全局静态成员 typename Logger::OutputFun Logger::s_ouptut_ defaultOutput; typename Logger::FlushFun Logger::s_flush_ defaultFlush; tulun::LOG_LEVEL Logger::s_level_ InitLogLevel(); // 全局设置接口 void Logger::setOutput(const OutputFun out) { s_output_ out; } void Logger::setFlush(const FlushFun flush) { s_flush_ flush; } void Logger::setLogLevel(const LOG_LEVEL level) { s_level_ level; } LOG_LEVEL Logger::getLogLevel() { return s_level_; } // 环境变量初始化级别 LOG_LEVEL InitLogLevel() { if (::getenv(TULUN_LOG_TRACE)) { return LOG_LEVEL::TRACE; } else if (::getenv(TULUN_LOG_DEBUG)) { return LOG_LEVEL::DEBUG; } else { return LOG_LEVEL::INFO; } } // 构造/析构 Logger::Logger(const LOG_LEVEL level, const std::string filename, const std::string funcname, int line) : impl_{level, filename, funcname, line} { } // 核心析构时自动输出日志 Logger::~Logger() { // 换行 impl_ \n; // 调用输出回调 s_output_(impl_.toString()); // 刷新缓冲区 s_flush_(); // FATAL 级别直接崩溃退出 if (impl_.getLogLevel() LOG_LEVEL::FATAL) { fprintf(stderr, Process exit due to FATAL log\n); exit(EXIT_FAILURE); } } // 获取流支持 操作 LogMessage Logger::stream() { return impl_; } } // namespace tulun五、使用示例Test04_01_Log.cpp支持极简调用LOG_ERROR hello动态设置级别动态输出到文件自动崩溃#include stdio.h #include iostream #include Logger.hpp using namespace std; using namespace tulun; void func() { LOG_TRACE 1 ; LOG_DEBUG 2 ; LOG_INFO 3 ; LOG_WARN 4 ; LOG_ERROR 5 ; LOG_FATAL 6 ; LOG_ERROR hello; } // 输出到文件 FILE *fp fopen(yhping.log, w); void outputFile(const std::string msg) { fwrite(msg.c_str(), 1, msg.size(), fp); } void FlushFile() { fflush(fp); } int main() { // 设置只输出 ERROR 及以上级别 Logger::setLogLevel(LOG_LEVEL::ERROR); // 设置输出到文件 Logger::setOutput(outputFile); Logger::setFlush(FlushFile); func(); fclose(fp); fp nullptr; return 0; }六、输出日志格式示例2026/04/03-18:55:00.609469Z 4831 ERROR Test04_01_Log.cpp func 16 : 5 : 2026/04/03-18:55:00.609823Z 4831 FATAL Test04_01_Log.cpp func 17 : 6 : Process exit七、核心技术点精讲1. 为什么要在析构函数里输出日志这是C 日志系统经典设计利用临时对象生命周期一行写完LOG_INFO xxx后语句结束临时对象析构自动输出、自动 flush无需手动append()/write()2. 日志宏为什么要加 if 判断#define LOG_DEBUG \ if (level DEBUG) \ Logger(...).stream()级别不够时不构造 Logger / LogMessage低级别日志零开销线上可直接关闭 TRACE/DEBUG无性能损耗3. 为什么用 function 回调输出可动态替换输出控制台、文件、网络、kafka不修改 Logger 代码符合开闭原则灵活适配各种环境4. FATAL 日志为什么直接 exit致命错误必须立即停止防止程序异常继续运行导致数据损坏便于 coredump 抓现场5. 环境变量初始化日志级别好处启动程序前设置环境变量即可开启调试无需改代码、无需重启线上定位问题极快八、总结Logger 是整个日志系统的顶层入口实现了企业级日志必备的全部能力极简流式日志LOG_INFO hello全局级别控制运行时可切换低级别日志零开销输出 / 刷新可动态替换析构自动输出无需手动管理FATAL 级别自动崩溃退出支持环境变量初始化级别Linux 服务端生产可用

更多文章