1:上几篇
前面我们已经把日志库所有底层基础模块全部完成:
- 工具模块:
util.hpp跨平台时间、文件目录操作 - 日志等级:
level.hpp七级枚举 + 字符串转换 - 日志消息:
message.hpp封装日志所有元数据 - 日志格式化:
format.hpp组合模式 + 自定义格式解析 - 日志落地:
sink.hpp控制台 / 文件 / 滚动文件落地 + 工厂模式 - 异步缓冲区:
buffer.hpp动态扩容非环形缓冲区 - 异步调度器:
looper.hpp生产者消费者双缓冲区异步工作器
到目前为止,所有底层零件都准备好了,但是零散模块无法直接使用。
本篇就是把所有模块进行整合,实现日志器 Logger 核心模块
统一封装「等级过滤、日志格式化、多落地输出、同步 / 异步双模式」,并且通过建造者模式 简化日志器创建,再用单例管理器全局管理所有日志器。
2:模块设计思路
1:核心职责
Logger 作为日志库业务中枢,要完成整套日志调用全流程:
- 日志等级过滤:低于设定等级的日志直接丢弃,不做后续处理
- 可变参数拼接:支持
printf风格格式化日志内容 - 日志消息封装:构造
LogMsg对象 - 日志格式拼接:调用格式化器,拼成标准日志字符串
- 日志落地分发:把日志同时输出到多个 Sink(控制台 + 文件)
- 区分同步 / 异步:同步直接写落地器,异步推入异步工作器后台处理
2:类结构设计
采用抽象基类 + 派生子类的方式:
- 抽象基类
Logger:模板方法模式,封装所有公共逻辑(等级判断、参数拼接、格式化),仅预留一个纯虚接口给子类实现落地逻辑; - 同步日志器
SyncLogger:继承基类,重写落地方法,业务线程直接写 IO,会阻塞; - 异步日志器
AsyncLogger:继承基类,重写落地方法,业务线程只写缓冲区,后台线程异步写 IO,无阻塞; - 建造者
LoggerBuilder:解决日志器构造参数过多的问题,支持链式配置创建日志器; - 全局管理器
LoggerManager:单例模式,全局统一管理所有日志器 ,内置默认root日志器。
3:完整流程
业务调用日志接口 → 日志等级过滤(无效直接丢弃)→ 可变参数拼接 → 构造LogMsg → 格式化日志 → 同步直接输出 / 异步入缓冲区 → 多落地器批量输出
3:代码详解
1:头文件与前置声明
cpp
/*完成日志器模块
1抽象日志器基类
2派生出不同子类(同步日志器和异步日志器)
3统一调度:等级过滤->格式化->日志落地
核心流程:调用日志接口 → 等级判断 → 格式化字符串 → 构造日志消息 → 输出到落地器
*/
#pragma once
// 标准库依赖
#include <iostream>
#include <string>
#include <atomic> // 原子变量,保证日志等级线程安全
#include <mutex> // 互斥锁,多线程日志输出防错乱
#include <vector> // 存储多个落地器
#include <cstdarg> // 可变参数支持
#include <cstdio> // vsnprintf 格式化
#include <cstdlib>
#include <sstream> // 字符串流格式化
#include <memory> // 智能指针
#include <unordered_map> // 哈希表管理日志器
#include <cassert> // 断言调试
// 依赖所有前置自定义模块
#include "util.hpp"
#include "level.hpp"
#include "format.hpp"
#include "message.hpp"
#include "sink.hpp"
#include "looper.hpp"
namespace my_log {
// 前置声明:日志管理器,后面用到
class LoggerManager;
日志器是顶层整合模块,必须依赖前面所有基础头文件。
引入cstdarg是为了支持 printf 风格可变参数日志,atomic用于线程安全的日志等级控制。
2:抽象日志类Logger
cpp
/**
* @brief 日志器抽象基类
* 采用模板方法模式:固定日志执行流程,同步/异步子类只实现落地逻辑
* 封装公共:等级过滤、可变参数拼接、消息格式化
* 预留纯虚log接口,由同步/异步子类实现
*/
class Logger {
public:
// 智能指针别名,简化代码书写
using ptr = std::shared_ptr<Logger>;
/**
* @brief 构造函数:初始化日志器所有核心属性
* @param logger_name 日志器名称,用于区分不同业务模块日志
* @param level 日志输出等级阈值,低于该等级直接丢弃
* @param formatter 格式化器对象,定制日志输出格式
* @param sinks 落地器数组,支持同时输出到控制台+文件+滚动文件
*/
Logger(const std::string& logger_name, LogLevel::value level,
const Formatter::ptr& formatter, const std::vector<LogSink::ptr>& sinks)
: _logger_name(logger_name)
, _limit_level(level)
, _formatter(formatter)
, _sinks(sinks.begin(), sinks.end())
{
}
// 虚析构:保证派生类同步/异步日志器正确析构
virtual ~Logger() = default;
// 获取日志器名称
const std::string& name() { return _logger_name; }
// 对外五级日志接口:debug/info/warn/err/fatal
void debug(const std::string& file, size_t line, const char* fmt, ...);
void info(const std::string& file, size_t line, const char* fmt, ...);
void warn(const std::string& file, size_t line, const char* fmt, ...);
void error(const std::string& file, size_t line, const char* fmt, ...);
void fatal(const std::string& file, size_t line, const char* fmt, ...);
protected:
/**
* @brief 纯虚函数:日志落地统一接口
* 模板方法关键点:基类固定流程,子类重写实现同步/异步不同落地方式
* @param data 格式化完成的日志字符串起始地址
* @param len 日志字符串长度
*/
virtual void log(const char* data, size_t) = 0;
/**
* @brief 日志统一序列化核心流程
* 1 构造LogMsg日志消息对象
* 2 调用格式化器生成标准日志行
* 3 调用纯虚log接口,交给子类落地
*/
void serialize(LogLevel::value level, const std::string& file, size_t line, const char* str) {
// 构造日志消息:填充等级、文件名、行号、日志器名、日志内容
LogMsg msg(level, line, file, _logger_name, str);
// 字符串流承载格式化结果
std::stringstream ss;
// 按照自定义格式格式化日志消息
_formatter->format(ss, msg);
// 交给子类完成同步/异步输出
log(ss.str().c_str(), ss.str().size());
}
/**
* @brief 可变参数格式化工具函数
* 适配 printf 风格日志,将可变参数拼接成完整日志正文
* 采用两次vsnprintf:先算长度、再分配内存、最后格式化
*/
std::string formatString(const std::string& fmt, va_list ap) {
va_list ap_copy;
// 拷贝可变参数,避免原参数被修改
va_copy(ap_copy, ap);
// 第一次调用:只计算需要的字符长度,不填充内容
int len = vsnprintf(nullptr, 0, fmt.c_str(), ap_copy);
va_end(ap_copy);
// 根据长度分配字符串内存
std::string result(len + 1, '\0');
// 第二次调用:真正格式化填充内容
vsnprintf(&result[0], len + 1, fmt.c_str(), ap);
return result;
}
protected:
std::mutex _mutex; // 多线程输出互斥锁,防止日志错乱
std::string _logger_name; // 日志器名称
std::atomic<LogLevel::value> _limit_level; // 原子类型日志等级,多线程无锁安全读写
Formatter::ptr _formatter; // 格式化器智能指针
std::vector<LogSink::ptr> _sinks; // 多个日志落地器,支持一对多输出
};
核心设计细节
- 模板方法模式:基类把等级过滤、参数拼接、格式化全部写好,只把最终落地留给子类
_limit_level用atomic原子变量,支持多线程动态修改日志等级,不用加锁- 可变参数采用两次 vsnprintf:先求长度再格式化,避免内存浪费和越界
- 内置
_sinks数组,支持一个日志器同时输出到控制台 + 多个文件
3:同步日志器SyncLogger
cpp
/**
* @brief 同步日志器:继承抽象Logger基类
* 日志调用时直接阻塞,当场写入落地器
*/
class SyncLogger : public Logger {
public:
using ptr = std::shared_ptr<SyncLogger>;
// 直接调用父类构造初始化
SyncLogger(const std::string& logger_name, LogLevel::value level,
const Formatter::ptr formatter, const std::vector<LogSink::ptr>& sinks)
: Logger(logger_name, level, formatter, sinks)
{
}
protected:
/**
* @brief 重写落地接口:同步直接输出
* 加锁遍历所有落地器,依次写入日志
*/
void log(const char* data, size_t len) override {
// 加锁:多线程下防止多个日志穿插错乱
std::unique_lock<std::mutex> lock(_mutex);
// 没有落地器直接返回
if (_sinks.empty()) return;
// 遍历所有落地器,一对多输出
for (auto& sink : _sinks) {
sink->log(data, len);
}
}
};
同步日志器非常轻量,只需要重写纯虚log方法。
核心:加锁遍历所有 Sink,直接写 IO,会阻塞业务线程。
4:异步日志器AsyncLogger
cpp
/**
* @brief 异步日志器:继承抽象Logger基类
* 日志不直接写IO,推入异步缓冲区,后台线程负责落地
*/
class AsyncLogger : public Logger {
public:
/**
* @brief 构造函数
* 初始化父类,同时创建异步工作器,绑定回调函数
*/
AsyncLogger(const std::string& logger_name, LogLevel::value level,
const Formatter::ptr formatter, const std::vector<LogSink::ptr>& sinks,
AsyncType looper_type)
: Logger(logger_name, level, formatter, sinks)
// 绑定异步回调:后台线程消费缓冲区时调用readlog
, _looper(std::make_shared<AsyncLooper>(
std::bind(&AsyncLogger::readlog, this, std::placeholders::_1),
looper_type))
{
}
/**
* @brief 重写落地接口:异步推入缓冲区
* 业务线程只做内存写入,不阻塞磁盘IO
*/
void log(const char* data, size_t len) override {
_looper->push(data, len);
}
/**
* @brief 异步回调函数
* 后台线程取出完整缓冲区数据,遍历所有落地器写入
*/
void readlog(Buffer& buf) {
if (_sinks.empty()) return;
// 批量把缓冲区日志分发到各个落地器
for (auto& sink : _sinks) {
sink->log(buf.begin(), buf.readAbleSize());
}
}
private:
AsyncLooper::ptr _looper; // 异步工作器智能指针
};
核心细节
- 构造时
std::bind绑定成员函数作为异步回调 - 日志落地只调用
push推入缓冲区,业务线程无阻塞 - 后台线程消费缓冲区时,统一批量写入所有 Sink
5:五级日志接口实现
cpp
// DEBUG级别日志接口
inline void Logger::debug(const std::string& file, size_t line, const char* fmt, ...) {
// 等级过滤:当前日志等级低于阈值,直接跳过,不做任何处理
if (LogLevel::value::DEBUG < _limit_level.load()) return;
va_list ap;
va_start(ap, fmt);
// 拼接可变参数
std::string str = formatString(fmt, ap);
va_end(ap);
// 进入统一序列化流程
serialize(LogLevel::value::DEBUG, file, line, str.c_str());
}
// INFO级别日志接口
inline void Logger::info(const std::string& file, size_t line, const char* fmt, ...) {
if (LogLevel::value::INFO < _limit_level.load()) return;
va_list ap;
va_start(ap, fmt);
std::string str = formatString(fmt, ap);
va_end(ap);
serialize(LogLevel::value::INFO, file, line, str.c_str());
}
// WARN级别日志接口
inline void Logger::warn(const std::string& file, size_t line, const char* fmt, ...) {
if (LogLevel::value::WARN < _limit_level.load()) return;
va_list ap;
va_start(ap, fmt);
std::string str = formatString(fmt, ap);
va_end(ap);
serialize(LogLevel::value::WARN, file, line, str.c_str());
}
// ERR级别日志接口
inline void Logger::error(const std::string& file, size_t line, const char* fmt, ...) {
if (LogLevel::value::ERR < _limit_level.load()) return;
va_list ap;
va_start(ap, fmt);
std::string str = formatString(fmt, ap);
va_end(ap);
serialize(LogLevel::value::ERR, file, str.c_str());
}
// FATAL级别日志接口
inline void Logger::fatal(const std::string& file, size_t line, const char* fmt, ...) {
if (LogLevel::value::FATAL < _limit_level.load()) return;
va_list ap;
va_start(ap, fmt);
std::string str = formatString(fmt, ap);
va_end(ap);
serialize(LogLevel::value::FATAL, file, line, str.c_str());
}
设计思考
- 先做等级判断,无效日志直接截断,避免无用参数拼接和格式化,提升性能
- 五个接口逻辑完全一致,只是等级不同,复用基类 serialize 流程
6:建造者类
日志器需要配置名称、等级、格式、落地器、同步 / 异步 ,参数太多,构造函数臃肿,用建造者模式实现链式配置。
cpp
// 日志器类型枚举:同步/异步
enum class LoggerType {
LOGGER_SYNC,
LOGGER_ASYNC
};
/**
* @brief 日志器建造者抽象类
* 解决日志器构造参数过多问题,链式配置
*/
class LoggerBuilder {
public:
// 构造初始化默认配置
LoggerBuilder()
: _logger_type(LoggerType::LOGGER_SYNC) // 默认同步
, _limit_level(LogLevel::value::DEBUG) // 默认最低等级
, _looper_type(AsyncType::ASYNC_SAFE) // 默认异步安全模式
{
}
// 配置日志器类型:同步/异步
void buildLoggerType(LoggerType type) { _logger_type = type; }
// 配置异步为不安全扩容模式
void buildEnableUnsafe() { _looper_type = AsyncType::ASYNC_UNSAFE; }
// 配置日志器名称
void buildLoggerName(const std::string& name) { _logger_name = name; }
// 配置日志过滤等级
void buildLoggerLevel(LogLevel::value level) { _limit_level = level; }
// 配置自定义格式化模板
void buildFormatter(const std::string& pattern) {
_formatter = std::make_shared<Formatter>(pattern);
}
/**
* @brief 模板方法:任意类型落地器都能链式添加
* 复用SinkFactory工厂创建落地器
*/
template<typename SinkType, typename ...Args>
void buildSink(Args &&...args) {
LogSink::ptr psink = SinkFactory::create<SinkType>(std::forward<Args>(args)...);
_sinks.push_back(psink);
}
// 纯虚构建方法:由子类实现创建日志器
virtual Logger::ptr build() = 0;
protected:
AsyncType _looper_type;
LoggerType _logger_type;
std::string _logger_name;
LogLevel::value _limit_level;
Formatter::ptr _formatter;
std::vector<LogSink::ptr> _sinks;
};
/**
* @brief 局部日志器建造者
* 构建日志器但不自动注册到全局管理器,由用户自主管理
*/
class LocalLoggerBuilder : public LoggerBuilder {
public:
virtual Logger::ptr build() override;
};
7:全局日志管理器
全局唯一的日志器管理中心,支持注册、获取、判断 日志器,内置默认root日志器。
cpp
/**
* @brief 全局日志管理器 单例模式
* 统一管理所有命名日志器,内置默认root日志器
* 线程安全注册、查找日志器
*/
class LoggerManager {
public:
// 懒汉式单例:局部静态变量,线程安全
static LoggerManager& getInstance() {
static LoggerManager eton;
return eton;
}
// 注册日志器,加锁防止并发插入
void addLogger(const std::string& name, const Logger::ptr& logger) {
std::unique_lock<std::mutex> lock(_mutex);
if (_loggers.find(name) != _loggers.end()) return;
_loggers[name] = logger;
}
// 判断日志器是否存在
bool hasLogger(const std::string& name) {
std::unique_lock<std::mutex> lock(_mutex);
return _loggers.find(name) != _loggers.end();
}
// 根据名称获取日志器
Logger::ptr getLogger(const std::string& name) {
std::unique_lock<std::mutex> lock(_mutex);
auto it = _loggers.find(name);
if (it == _loggers.end()) return Logger::ptr();
return it->second;
}
// 获取默认根日志器
Logger::ptr rootLogger() {
return _root_logger;
}
private:
// 构造函数:程序启动自动创建root默认日志器
LoggerManager() {
std::unique_ptr<my_log::LoggerBuilder> builder(new my_log::LocalLoggerBuilder());
builder->buildLoggerName("root");
_root_logger = builder->build();
_loggers["root"] = _root_logger;
}
private:
std::mutex _mutex; // 管理哈希表并发访问锁
Logger::ptr _root_logger; // 默认根日志器
std::unordered_map<std::string, Logger::ptr> _loggers; // 存放所有命名日志器
};
/**
* @brief 局部建造者构建实现
* 无配置则填充默认值:默认格式、默认控制台落地
*/
inline Logger::ptr LocalLoggerBuilder::build() {
// 日志器名称不能为空
assert(!_logger_name.empty());
// 没有指定格式,使用默认格式化模板
if (_formatter.get() == nullptr) {
_formatter = std::make_shared<Formatter>();
}
// 没有配置落地器,默认添加控制台输出
if (_sinks.empty()) {
buildSink<StdoutSink>();
}
// 根据同步/异步类型创建对应日志器
Logger::ptr logger;
if (_logger_type == LoggerType::LOGGER_ASYNC) {
logger = std::make_shared<AsyncLogger>(_logger_name, _limit_level, _formatter, _sinks, _looper_type);
}
else {
logger = std::make_shared<SyncLogger>(_logger_name, _formatter, _sinks);
}
return logger;
}
}
4:测试(AI生成)
cpp
#include "logger.hpp"
#include <iostream>
#include <thread>
// 多线程测试函数
void thread_func(int id, my_log::Logger::ptr logger)
{
for (int i = 0; i < 5; i++)
{
logger->info(__FILE__, __LINE__, "线程%d 日志测试 第%d条", id, i);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
int main()
{
// 1. 测试建造者创建同步日志器
std::cout << "========== 同步日志器测试 ==========\n";
auto sync_builder = std::make_shared<my_log::LocalLoggerBuilder>();
sync_builder->buildLoggerName("sync_logger");
sync_builder->buildLoggerLevel(my_log::LogLevel::value::DEBUG);
// 添加控制台 + 文件双落地
sync_builder->buildSink<my_log::StdoutSink>();
sync_builder->buildSink<my_log::FileSink>("./logs/sync_app.log");
auto sync_logger = sync_builder->build();
// 输出不同等级日志
sync_logger->debug(__FILE__, __LINE__, "这是DEBUG日志");
sync_logger->info(__FILE__, __LINE__, "这是INFO日志");
sync_logger->warn(__FILE__, __LINE__, "这是WARN日志");
sync_logger->error(__FILE__, __LINE__, "这是ERR日志");
sync_logger->fatal(__FILE__, __LINE__, "这是FATAL日志");
// 2. 测试日志等级过滤
std::cout << "\n========== 日志等级过滤测试 ==========\n";
auto filter_builder = std::make_shared<my_log::LocalLoggerBuilder>();
filter_builder->buildLoggerName("filter_logger");
filter_builder->buildLoggerLevel(my_log::LogLevel::value::WARN); // 只输出WARN及以上
filter_builder->buildSink<my_log::StdoutSink>();
auto filter_logger = filter_builder->build();
// DEBUG、INFO会被过滤
filter_logger->debug(__FILE__, __LINE__, "我是DEBUG,应该被过滤");
filter_logger->info(__FILE__, __LINE__, "我是INFO,应该被过滤");
filter_logger->warn(__FILE__, __LINE__, "我是WARN,正常输出");
// 3. 测试异步日志器
std::cout << "\n========== 异步日志器测试 ==========\n";
auto async_builder = std::make_shared<my_log::LocalLoggerBuilder>();
async_builder->buildLoggerType(my_log::LoggerType::LOGGER_ASYNC);
async_builder->buildLoggerName("async_logger");
async_builder->buildLoggerLevel(my_log::LogLevel::value::DEBUG);
async_builder->buildSink<my_log::StdoutSink>();
async_builder->buildSink<my_log::RollSizeSink>("./logs/roll_", 1024 * 1024); // 1MB滚动
auto async_logger = async_builder->build();
async_logger->info(__FILE__, __LINE__, "异步日志测试第一条");
async_logger->fatal(__FILE__, __LINE__, "异步日志严重错误");
// 4. 多线程日志测试
std::cout << "\n========== 多线程日志测试 ==========\n";
std::thread t1(thread_func, 1, async_logger);
std::thread t2(thread_func, 2, async_logger);
t1.join();
t2.join();
std::cout << "\n所有测试完成,日志已输出控制台+本地logs目录\n";
return 0;
}
5:总结
- 基于模板方法模式设计抽象日志器基类
- 实现同步 / 异步两种日志器子类
- 封装五级日志接口,内置等级快速过滤
- 实现建造者模式,优雅配置创建日志器
- 实现单例全局日志管理器
- 编写完整测试用例,覆盖同步、异步、等级过滤、多线程、多落地场景
下一篇也就是最后一篇了,我们会将Logger来做封装!
