C++工业级日志项目(七)日志器核心

1:上几篇

前面我们已经把日志库所有底层基础模块全部完成:

  • 工具模块:util.hpp 跨平台时间、文件目录操作
  • 日志等级:level.hpp 七级枚举 + 字符串转换
  • 日志消息:message.hpp 封装日志所有元数据
  • 日志格式化:format.hpp 组合模式 + 自定义格式解析
  • 日志落地:sink.hpp 控制台 / 文件 / 滚动文件落地 + 工厂模式
  • 异步缓冲区:buffer.hpp 动态扩容非环形缓冲区
  • 异步调度器:looper.hpp 生产者消费者双缓冲区异步工作器

到目前为止,所有底层零件都准备好了,但是零散模块无法直接使用

本篇就是把所有模块进行整合,实现日志器 Logger 核心模块

统一封装「等级过滤、日志格式化、多落地输出、同步 / 异步双模式」,并且通过建造者模式 简化日志器创建,再用单例管理器全局管理所有日志器。

2:模块设计思路

1:核心职责

Logger 作为日志库业务中枢,要完成整套日志调用全流程:

  1. 日志等级过滤:低于设定等级的日志直接丢弃,不做后续处理
  2. 可变参数拼接:支持 printf 风格格式化日志内容
  3. 日志消息封装:构造 LogMsg 对象
  4. 日志格式拼接:调用格式化器,拼成标准日志字符串
  5. 日志落地分发:把日志同时输出到多个 Sink(控制台 + 文件)
  6. 区分同步 / 异步:同步直接写落地器,异步推入异步工作器后台处理

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;      // 多个日志落地器,支持一对多输出
    };

核心设计细节

  1. 模板方法模式:基类把等级过滤、参数拼接、格式化全部写好,只把最终落地留给子类
  2. _limit_levelatomic 原子变量,支持多线程动态修改日志等级,不用加锁
  3. 可变参数采用两次 vsnprintf:先求长度再格式化,避免内存浪费和越界
  4. 内置_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;  // 异步工作器智能指针
    };

核心细节

  1. 构造时std::bind绑定成员函数作为异步回调
  2. 日志落地只调用push推入缓冲区,业务线程无阻塞
  3. 后台线程消费缓冲区时,统一批量写入所有 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());
    }

设计思考

  1. 先做等级判断,无效日志直接截断,避免无用参数拼接和格式化,提升性能
  2. 五个接口逻辑完全一致,只是等级不同,复用基类 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来做封装!

相关推荐
2401_873479401 小时前
如何用IP离线库批量清洗订单IP,自动标注省市区?
开发语言·网络·python
郝学胜_神的一滴1 小时前
Qt 高级开发 019:从零定制登录窗口按钮、Logo 样式与交互悬浮效果
c++·qt
lcj25111 小时前
vector的基本使用 + 手搓成员变量 size capacity begin end operator[] reserve扩容 拷贝构造 赋值析构
开发语言·c++·笔记·面试
影寂ldy1 小时前
C#List泛型集合
windows·c#·list
满天星83035772 小时前
【Git】原理及使用(二) (版本回退)
linux·git
liulilittle2 小时前
C++ do_div 宏
c++
GHL2842710902 小时前
Qt Creator 19.0.0 (Community)下载
开发语言·qt
之歆2 小时前
Day21_电商详情页核心技术实战:从LESS预处理到复杂交互实现
开发语言·前端·javascript·css·交互·less