同步/异步日志系统:日志器管理器模块\全局接口\性能测试

本期我们进行最后一步:接口设置与性能测试

相关代码已经实现在我的gitee:同步_异步日志: 本项⽬主要实现⼀个⽇志系统, 其主要⽀持以下功能: 1、⽀持多级别⽇志消息 2、⽀持同步⽇志和异步⽇志 3、⽀持可靠写⼊⽇志到控制台、⽂件以及滚动⽂件中 4、⽀持多线程程序并发写⽇志 5、⽀持扩展不同的⽇志落地⽬标地喜欢请点个赞谢谢

目录

日志器管理类

设计框架

全局接口

全局日志器

全局接口

性能测试

性能测试函数

性能测试:


日志器管理类

设计框架

作用1:对所有创建的日志器进行管理

特性:将管理器设计为单例

作用2:可以在程序的任意位置,获取相同的单例对象,获取其中的日志器进行日志输出

拓展:单例管理器创建的时候,默认先创建一个日志器(用于进行标准输出的打印)

目的:让用户在不创建任何日志器的情况下,也能进行标准输出的打印,方便用户使用

设计

管理的成员:

  1. 默认日志器

  2. 所管理的日志器数组

  3. 互斥锁

提供的接口:

  1. 添加日志器管理

  2. 判断是否管理了指定名称的日志器

  3. 获取指定名称的日志器

  4. 获取默认日志器

Logger.hpp

cpp 复制代码
/*
    日志器管理器模块:
        作用1:对所有创建的日志器进行管理  
        特性:将管理器设计为单例  

            作用2:可以在程序的任意位置,获取相同的单例对象,获取其中的日志器进行日志输出  
        拓展:单例管理器创建的时候,默认先创建一个日志器(用于进行标准输出的打印)  
    目的:让用户在不创建任何日志器的情况下,也能进行标准输出的打印,方便用户使用  
*/ 
class LoggerManager
{
    public:
        static LoggerManager& getInstance();//懒汉单例模式
        void addLogger(Logger::Ptr &logger);
        bool hasLogger(const std::string &name);
        Logger::Ptr getLogger(const std::string &name);
        Logger::Ptr rootLogger();
    private:
        LoggerManager();
    private:
        std::mutex mutex_;
        Logger::Ptr default_logger_;//默认日志器,提供标准输出功能
        std::unordered_map<std::string, Logger::Ptr> loggers_;//所有日志的日志器
};

Logger.cpp

cpp 复制代码
LoggerManager::LoggerManager()
{
    std::unique_ptr<LoggerBuilder> builder = std::make_unique<LoggerBuilder>();
    builder->BuildLoggerName("root");
    default_logger_ = builder->build();
    loggers_.insert(std::make_pair(default_logger_->GetName(), default_logger_));
}
LoggerManager& LoggerManager::getInstance()
{
    //C++11后针对静态局部变量编译器实现了线程安全,每个构造完成前其他线程均会阻塞等待,
    //直到构造完成后才会继续执行,因此不需要额外的锁机制来保证单例实例的线程安全性
    // 使用局部静态变量实现线程安全的单例模式
    static LoggerManager instance;
    return instance;
}
void LoggerManager::addLogger(Logger::Ptr &logger)
{
    if(hasLogger(logger->GetName())) return ;//已经存在就不需要重复添加了

    std::unique_lock<std::mutex> lock(mutex_);
    loggers_.insert(std::make_pair(logger->GetName(), logger));
}
bool LoggerManager::hasLogger(const std::string &name)
{
    std::unique_lock<std::mutex> lock(mutex_);
    auto it = loggers_.find(name);
    if(it== loggers_.end())
    {
        return false;
    }
    return true;
}
Logger::Ptr LoggerManager::getLogger(const std::string &name)
{
    std::unique_lock<std::mutex> lock(mutex_);
    auto it = loggers_.find(name);
    if(it== loggers_.end())
    {
        return nullptr;
    }
    return it->second;
}
Logger::Ptr LoggerManager::rootLogger()
{
    return default_logger_;
}

全局接口

全局日志器

Logger.hpp

cpp 复制代码
/*
    日志器管理器模块:
        作用1:对所有创建的日志器进行管理  
        特性:将管理器设计为单例  

            作用2:可以在程序的任意位置,获取相同的单例对象,获取其中的日志器进行日志输出  
        拓展:单例管理器创建的时候,默认先创建一个日志器(用于进行标准输出的打印)  
    目的:让用户在不创建任何日志器的情况下,也能进行标准输出的打印,方便用户使用  
*/ 
class LoggerManager
{
    public:
        static LoggerManager& getInstance();//懒汉单例模式
        void addLogger(Logger::Ptr &logger);
        bool hasLogger(const std::string &name);
        Logger::Ptr getLogger(const std::string &name);
        Logger::Ptr rootLogger();
    private:
        LoggerManager();
    private:
        std::mutex mutex_;
        Logger::Ptr default_logger_;//默认日志器,提供标准输出功能
        std::unordered_map<std::string, Logger::Ptr> loggers_;//所有日志的日志器
};

Logger.cpp

cpp 复制代码
LoggerManager::LoggerManager()
{
    std::unique_ptr<LocalLoggerBuilder> builder = std::make_unique<LocalLoggerBuilder>();
    builder->BuildLoggerName("root").BuildLoggerType(LoggerType::SYNC);
    default_logger_ = builder->build();
    loggers_.insert(std::make_pair(default_logger_->GetName(), default_logger_));
}
LoggerManager& LoggerManager::getInstance()
{
    //C++11后针对静态局部变量编译器实现了线程安全,每个构造完成前其他线程均会阻塞等待,
    //直到构造完成后才会继续执行,因此不需要额外的锁机制来保证单例实例的线程安全性
    // 使用局部静态变量实现线程安全的单例模式
    static LoggerManager instance;
    return instance;
}
void LoggerManager::addLogger(Logger::Ptr &logger)
{
    if(hasLogger(logger->GetName())) return ;//已经存在就不需要重复添加了

    std::unique_lock<std::mutex> lock(mutex_);
    loggers_.insert(std::make_pair(logger->GetName(), logger));
}
bool LoggerManager::hasLogger(const std::string &name)
{
    std::unique_lock<std::mutex> lock(mutex_);
    auto it = loggers_.find(name);
    if(it== loggers_.end())
    {
        return false;
    }
    return true;
}
Logger::Ptr LoggerManager::getLogger(const std::string &name)
{
    std::unique_lock<std::mutex> lock(mutex_);
    auto it = loggers_.find(name);
    if(it== loggers_.end())
    {
        return nullptr;
    }
    return it->second;
}
Logger::Ptr LoggerManager::rootLogger()
{
    return default_logger_;
}

全局接口

提供全局接口&宏函数,对日志系统接口进行使用便捷性优化

思想:

  1. 提供获取指定日志器的全局接口(避免用户自己操作单例对象)

  2. 使用宏函数对日志器的接口进行代理(代理模式) I

  3. 提供宏函数,直接通过默认日志器进行日志的标准输出打印(不用获取日志器了)

Logger.hpp

cpp 复制代码
/*
    全局日志器管理器模块:
    将日志器添加到单例对象
*/ 
// 全局日志器的建造者,建造的日志器会添加到全局日志器管理器中
class GlobalLoggerBuilder : public LoggerBuilder
{
    public:
        Logger::Ptr build() override;
};

Logger.cpp

cpp 复制代码
Logger::Ptr GlobalLoggerBuilder::build()
{
    if (logger_name_.empty()) 
    {
            std::cout << "日志器名称不能为空!!";
            abort();
    }
    assert(LoggerManager::getInstance().hasLogger(logger_name_) == false);
    if (formatter_.get() == nullptr) {
        std::cout << "当前日志器:" << logger_name_ << " 未检测到日志格式,默认设置为[ %d{%H:%M:%S}%T%t%T[%p]%T[%c]%T%f:%l%T%m%n ]!\n";
        formatter_ = std::make_shared<Formatter>();
    }
    if (sinks_.empty()) {
        std::cout << "当前日志器:" << logger_name_ << " 未检测到落地方向,默认设置为标准输出!\n";
        sinks_.push_back(std::make_shared<StdoutSink>());
    }
    Logger::Ptr lp;
    if (type_ == LoggerType::ASYNC) 
    {
        lp = std::make_shared<AsyncLogger>(logger_name_, level_, formatter_, looper_type_, sinks_);
    }
    else 
    {
        lp = std::make_shared<SyncLogger>(logger_name_, level_, formatter_, sinks_);
    }
    LoggerManager::getInstance().addLogger(lp);
    return lp;
}

性能测试

测试三要素:

  1. 测试环境
  2. 测试方法
  3. 测试结果

测试工具的编写:

  1. 可以控制写日志线程数量
  2. 可以控制写日志的总数量分别对于同步日志器 & 异步日志器进行各自的性能测试需要测试单写日志线程的性能需要测试多写日志线程的性能

实现:封装一个接口,传入日志器名称,线程数量,日志数量,单条日志大小在接口内,创建指定数量的线程,各自负责一部分日志的输出,在输出之前计时开始,在输出完毕后计时结束,所耗时间 = 结束时间 - 起始时间每秒输出量 = 日志数量 / 总耗时每秒输出大小 = 日志数量 * 单条日志大小 / 总耗时注意:异步日志输出这里,我们启动非安全模式,纯内存写入(不去考虑实际落地的时间)

性能测试函数

cpp 复制代码
#pragma once
#include "Log.hpp"
namespace Logger
{
    void bench(const std::string &loger_name, size_t thread_num, size_t msglen, size_t msg_count)
    {
        Logger::Ptr lp = getLogger(loger_name);
        if (lp.get() == nullptr) return;
        std::string msg(msglen, '1');
        size_t msg_count_per_thread = msg_count / thread_num;
        std::vector<double> cost_time(thread_num);
        std::vector<std::thread> threads;
        std::cout << "输入线程数量: " << thread_num << std::endl;
        std::cout << "输出日志数量: " << msg_count << std::endl;
        std::cout << "输出日志大小: " << msglen * msg_count / 1024  << "KB" << std::endl;
        for (int i = 0; i < thread_num; i++) 
        {
            threads.emplace_back([&, i]()
            {
                auto start = std::chrono::high_resolution_clock::now();
                for(size_t j = 0; j < msg_count_per_thread; j++) 
                {
                    lp->fatal("%s", msg.c_str());
                }
                auto end = std::chrono::high_resolution_clock::now();
                auto cost = std::chrono::duration_cast<std::chrono::duration<double>>(end - start);
                cost_time[i] = cost.count();
                auto avg = msg_count_per_thread / cost_time[i];
                std::cout << "线程" << i << "耗时: " << cost.count() << "s" << " 平均:" << (size_t)avg << "/s\n";
            });
        }
        for(auto &thr : threads) 
        {
            thr.join();
        }
        double max_cost = 0;
        for (auto cost : cost_time) max_cost = max_cost < cost ? cost : max_cost;
        std::cout << "总消耗时间: " << max_cost << std::endl;
        std::cout << "平均每秒输出: " << (size_t)(msg_count / max_cost) << "条日志" << std::endl;
        std::cout << "平均每秒输出: " << (size_t)(msglen * msg_count / max_cost / 1024 / 1024) << "MB" << std::endl;
    }
    void file_bench(const std::string &logger_name,
                    LoggerType type,
                    const std::string &file_path,
                    size_t thread_num,
                    size_t msglen,
                    size_t msg_count,
                    bool enable_unsafe = false)
    {
        // 确保日志目录存在
        std::string dir = util::File::GetPath(file_path);
        if (!dir.empty() && !util::File::IsExist(dir)) 
        {
            if (!util::File::CreateDirectory(dir)) 
            {
                std::cerr << "创建日志目录失败: " << dir << std::endl;
                return;
            }
        }

        // 构建日志器
        std::unique_ptr<LoggerBuilder> builder = std::make_unique<GlobalLoggerBuilder>();
        builder->BuildLoggerName(logger_name)
            .BuildLoggerType(type)
            .BuildLogLevel(LogLevel::Value::DEBUG)
            .BuildFormatter("[%d{%Y-%m-%d %H:%M:%S}] [%c] [%l] %m%n")
            .BuildSinks<RollingFileSink>(file_path, 100 * 1024 * 1024); // 100MB 滚动文件

        if (type == LoggerType::ASYNC && enable_unsafe) 
        {
            builder->BuildEnableUnsafe();
        }

        builder->build();

        // 调用原有的性能测试函数
        bench(logger_name, thread_num, msglen, msg_count);
    }
}

性能测试:

cpp 复制代码
#include "Log.hpp"
#include "bench.hpp"

void sync_bench()
{
    // 1. 构建并创建日志器(必须调用 build())
    std::unique_ptr<Logger::LoggerBuilder> builder = std::make_unique<Logger::GlobalLoggerBuilder>();
    builder->BuildLoggerName("sync_logger")
        .BuildLoggerType(Logger::LoggerType::SYNC)
        .BuildLogLevel(Logger::LogLevel::Value::DEBUG)
        // 使用正确的格式占位符:%d 日期,%c 日志器名,%l 行号,%m 消息,%n 换行
        .BuildFormatter("[%d{%Y-%m-%d %H:%M:%S}] [%c] [%l] %m%n")
        .BuildSinks<Logger::StdoutSink>()
        .build();   // ← 必须调用 build() 创建日志器

    // 2. 运行性能测试(消息长度调整为 100 字节,避免过大)
    Logger::bench("sync_logger", 10, 100, 1024);
}

void async_bench()
{
    std::unique_ptr<Logger::LoggerBuilder> builder = std::make_unique<Logger::GlobalLoggerBuilder>();
    builder->BuildLoggerName("async_logger")
        .BuildLoggerType(Logger::LoggerType::ASYNC)
        .BuildLogLevel(Logger::LogLevel::Value::DEBUG)
        .BuildFormatter("[%d{%Y-%m-%d %H:%M:%S}] [%c] [%l] %m%n")
        .BuildSinks<Logger::StdoutSink>();
    
    // BuildEnableUnsafe 返回 void,不能链式调用,需单独写一行
    builder->BuildEnableUnsafe();
    builder->build();   // ← 创建日志器

    Logger::bench("async_logger", 10, 100, 1024);
}
void file_sync_bench()
{
    Logger::file_bench("file_sync_logger", Logger::LoggerType::SYNC,
                       "logs/sync_bench.log", 10, 100, 1024);
}

void file_async_bench()
{
    Logger::file_bench("file_async_logger", Logger::LoggerType::ASYNC,
                       "logs/async_bench.log", 10, 100, 1024, true);
}

void FileTest()
{
    std::cout << "\n========== 文件输出性能测试 ==========" << std::endl;
    std::cout << "同步文件日志性能测试:" << std::endl;
    file_sync_bench();
    std::cout << "\n异步文件日志性能测试:" << std::endl;
    file_async_bench();
}

void Test()
{
    std::cout << "同步日志性能测试:" << std::endl;
    sync_bench();
    std::cout << "异步日志性能测试:" << std::endl;
    async_bench();
}

int main()
{
    Test();
    FileTest();
    return 0;
}

文件测试结果:

本项目到这里就结束了,喜欢请点个赞谢谢

封面图自取:

相关推荐
geNE GENT2 小时前
Spring Boot管理用户数据
java·spring boot·后端
故事和你912 小时前
洛谷-数据结构-1-3-集合3
数据结构·c++·算法·leetcode·贪心算法·动态规划·图论
人邮异步社区2 小时前
文科生零基础学 Python 难吗?真不难,难的是找对书!
开发语言·python
怒放吧德德2 小时前
Spring Boot实战:Event事件机制解析与实战
java·spring boot·后端
奇妙之二进制2 小时前
zmq源码分析之io_thread_t
linux·服务器
春栀怡铃声2 小时前
【C++修仙录02】筑基篇:类和对象(上)
开发语言·c++·算法
cui_ruicheng2 小时前
Linux IO入门(三):手写一个简易的 mystdio 库
linux·运维·服务器
telllong2 小时前
MCP协议实战:30分钟给Claude接上你公司的内部API
linux·运维·服务器
悟空爬虫-彪哥2 小时前
2026 Python UI 框架选择指南:从 Streamlit 到 Pyside6 的四层体系
开发语言·python·ui