前言
在之前的文章中,我们实现了 Timestamp 时间戳、LogMessage 日志消息体。今天,我们实现日志系统最上层入口:Logger 日志器。
它承担以下核心功能:
-
对外提供极简使用方式:
LOG_INFO << "hello" -
全局日志级别开关(TRACE/DEBUG/INFO/ERROR...)
-
可动态替换输出(控制台 / 文件 / 网络)
-
析构函数自动输出日志(无需手动 flush)
-
FATAL 级别直接崩溃退出,便于现场保留
-
支持环境变量初始化日志级别
本文基于你提供的完整工程代码,逐行精讲,可直接用于 Linux 服务端项目。
一、整体架构
Logger 是整个日志系统的入口类,结构如下:
-
LogMessage:负责拼装日志内容
-
Logger:负责输出、级别、宏封装
-
宏定义 LOG_DEBUG / LOG_INFO:极简调用
-
输出回调 OutputFun:可替换为控制台 / 文件
-
刷新回调 FlushFun:刷新缓冲区
二、公共头文件 LogCommon.hpp
日志级别、缓冲区大小、级别字符串,全局共用
cpp
#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;
// C++11 强类型枚举
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)
对外提供日志宏、输出设置、级别设置、流接口。
cpp
#include <functional>
#include "LogMessage.hpp"
#ifndef LOGGER_HPP
#define LOGGER_HPP
namespace tulun
{
class Logger
{
public:
// 输出回调:string → 输出位置
using OutputFun = std::function<void(const std::string &)>;
// 刷新回调
using FlushFun = std::function<void(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 直接崩溃退出
cpp
#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"动态设置级别
动态输出到文件
自动崩溃
cpp
#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;
}
六、输出日志格式示例
bash
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 判断?
cpp
#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 服务端生产可用