高性能日志系统 日志器模块

概述

  • 作用:整合输出模块和格式化模块,创建日志器,通过该日志器对日志进行输出
  • 成员
    • 格式化模块对象管理
    • 输出模块对象管理,数组管理(日志器可能会向多个位置进行日志输出)
    • 默认日志输出限制等级,只有大于限制等级的日志才可以进行输出
    • 互斥锁保证多线程下日志输出的安全性
    • 日志器唯一标志,便于调用时查找
  • 具体操作
    • debug等级日志输出
    • info等级日志输出
    • warn等级日志输出
    • error等级日志输出
    • fatal等级日志输出
  • 实现
    • 设计日志基类,在日志基类基础上创建同步日志器和异步日志器
      • 异步日志器是先写入到内存中,然后异步线程再写入到磁盘中
    • 通过Logger::Builder构建者模式,可以根据需求构建出同步和异步日志器

同步日志器与其他模块总体逻辑分析 

 

异步日志器与其他模块逻辑分析 

 

架构设计

  • 模块设计        
    • Logger类:日志系统核心类,定义日志记录的基本功能和接口。借助基类和派生类的设计模式,通过SyncLogger和AsyncLogger两个子类,分别实现同步和异步日志记录
    • Builder类:构造器模式进行日志器创建,其中的功能也可以根据自己的需要进行拓展
    • Singleton模式:该类采用单例模式集中管理所有的日志器,确保日志器的唯一性和全局访问性
  • 关键组件设计
    • 日志等级:使用LogLevel::value表示日志的不同等级,控制日志的输出范围
    • Formaotter和LogSink:Formatter 主要用于格式化日志消息,LogSink 则用于定义日志输出的目的地,也就是是输出到文件还是直接输出到显示器
    • SyncLogger 和 AsyncLogger:前者是同步日志器,直接在主线程中处理日志记录;后者是异步日志器,使用AsyncLogger将日志任务推送到后台线程处理
    • Logger::Builder:提供灵活日志器构建方式,其内部支持日志名称、等级、格式化器、日志输出目的地等
  • 工作流程
    • 创建日志器:通过Builder设置日志器的各种属性,然后调用build方法创建日志器
    • 日志记录:调用Logger类debug\info\warn\error\fatal方法记录日志,然后根据日志等级和日志器类型选择处理方法

模块之间逻辑分析

  •  Loggger类
    • 作用:定义日志记录的基本接口和功能,所有日志记录操作都通过该类及其内部子类完成
    • 基类与派生类关系:根据Logger基类,派生出两个子类,SyncLogger和AsyncLogger,从而实现同步日志记录和异步日志记录
  • SyncLogger和AsyncLogger
    • 同步日志器:主线程中同步处理日志,日志消息产生后立即输出
    • 异步日志器:利用后台线程异步处理日志,减少主线程负担,主要用在高并发场景
    • 与基类的关系:重写基类中logIt方法,从而实现不同日志的输出格式,其中异步日志器还依赖于AsyncLooper模块,来处理异步的日志任务
  • Formatter 和 LogSink
    • Formatter:负责格式化日志消息,将日志消息转换为指定的格式
    • LogSink:将格式化后的日志消息输出到具体的地方(例如文件或者直接输出到屏幕上)
    • 模块联系:Logger类依赖Formatter格式化日志消息,以来LogSink实际输出日志,日志设计中LogSink设计的是数组,所以可以同时接收同一个日志器的输出,从而实现多种输出方式的并行
  • AsyncLooper
    • 作用:异步日志器的辅助类,主要用于在后台线程中异步处理任务,对于AsyncLogger,主要用于异步处理日志消息的输出,将日志消息推送到任务队列中,后台线程则负责从队列中取出消息并输出
    • 模块关系:AsyncLogger结合使用,AsyncLogger将日志消息推送给AsyncLooper中进行异步处理,从而实现异步日志记录功能

  •  Logger::Builder和其子类LocalLoggerBuilder、GlobalLoggerBuilder)
    • 作用:使用建筑者模式,主要目的是更灵活的方式创建和Logger对象。
      • 具体实现:通过Builder模式,用户设置Logger属性,最后调用build()方法来创建配置完成Logger对象
    • 模块关系:Builder类定义了创建Logger对象的步骤;LocalLoggerBuilder用于创建本地日志器;GlobalLoggerBuilder创建日志器,然后将其注册到 loggerManager中,便于全局管理
  • loggerManager
    • 作用:单例类,用于集中管理所有的Logger对象;主要是为了保证在应用程序中每个日志器都有唯一的名称,并提供接口来获取、添加或者删除日志器
      • 任何位置获取相同的单例对象,通过该单例对象获取指定日志器进行日志输出
    • 模块关系:loggerManager和GlobalLoggerBuilder两者配合使用,从而确保所有通过GlobalLoggerBuilder创建的日志器都可以在全局范围内访问;
      • loggerManager内部维护着一个日志器映射表,确保日志器名称的唯一性

具体实现

Logger类

日志系统核心类,主要是日志记录的基本功能和接口,设计是一个父类,其下有两个子类,分别是SynclLogger 和 AsyncLogger 分别实现同步和异步日志记录功能

构造函数

  • _name:日志器的名称
  • _level:日志等级,默认初始化DEBUG等级,限制等级
  • _formatter:日志格式化器,格式化日志信息
  • _sinks:日志输出目的地设计
Logger(const std::string &name, 
    Formatter::ptr formatter,
    std::vector<LogSink::ptr> &sinks, 
    LogLevel::value level = LogLevel::value::DEBUG): 
    _name(name),  _level(level), _formatter(formatter),
    _sinks(sinks.begin(), sinks.end()){
}

返回日志器的名称日志等级 

std::string loggerName() { return _name; }
LogLevel::value loggerLevel() { return _level; }

 日志记录函数,该处只分析一个debug函数

  • 作用:通过传入的参数构造日志消息对象(LogMsg)过程并进行格式化,得到格式化后的日志消息字符串,然后对该字符串进行输出(输出是由log函数来完成)
  • 实现逻辑
    • 判断当前日志是否达到了输出等级,如果没有达到对应等级则直接退出
    • 功能统一封装到 log 函数中实现
      • 对fmt格式化字符串和不定参进行字符串组织,从而得到日志消息字符串
      • 构造LogMsg对象
      • 通过格式化工具对LogMsg进行格式化,得到格式化后的日志字符串
      • 将日志信息输出到指定位置
  • shouldLog:检查当前日志等级是否应该输出debug级别的日志
  • va_list ; va_start :初始化可变参数列表 al 
  • log:使用log方法格式化和记录日志
  • va_end:结束可变参数列表
void debug(const char *file, size_t line, const char *fmt, ...) {
    if (shouldLog(LogLevel::value::DEBUG) == false) {
        return ;
    }
    va_list al;
    va_start(al, fmt);
    log(LogLevel::value::DEBUG, file, line, fmt, al);
    va_end(al);
}

shouldLog: 检查当前日志等级是否应该输出指定级别的日志

bool shouldLog(LogLevel::value level) { return level >= _level; }

 Log:格式化记录日志(与日志记录函数搭配使用)

  • vasprintf:使用可变参数列表格式化日志消息
  • LogMsg lm :创建一个LogMsg对象,其中包含日志器名称、文件名、行号、消息内容和日志等级
  • _formatter->sormat(ss,lm):使用格式化器将日志消息格式化为字符串流 ss
  • logIt:调用logIt方法,将格式化后的日志消息输出到日志接收器
void log(LogLevel::value level, const char *file, size_t line, const char *fmt, va_list al) {
    char *buf;
    std::string msg;
    int len = vasprintf(&buf, fmt, al);
    if (len < 0) {
        msg = "格式化日志消息失败!!";
    }else {
        msg.assign(buf, len);
        free(buf);
    }
    LogMsg lm(_name, file, line, std::move(msg), level);
    std::stringstream ss;
    _formatter->format(ss, lm);
    logIt(std::move(ss.str()));
}

虚函数 void logIt(const std::string &msg)

  • logIt 是一个纯虚函数,由子类 SyncLoggerAsyncLogger 实现,用于实际输出日志

SyncLogger类

同步日志器,所有日志记录操作都是在主线程同步完成

class SyncLogger : public Logger {
public:
    using ptr = std::shared_ptr<SyncLogger>;

    // 构造函数
    SyncLogger(const std::string &name, 
               Formatter::ptr formatter,
               std::vector<LogSink::ptr> &sinks, 
               LogLevel::value level = LogLevel::value::DEBUG);

private:
    // 实现日志的同步输出逻辑
    virtual void logIt(const std::string &msg) override;
};

构造函数

  • Logger(name, formatter, sinks, level): 调用基类 Logger 的构造函数
  • std::cout: 打印日志器创建成功的消息
SyncLogger(const std::string &name, 
    Formatter::ptr formatter,
    std::vector<LogSink::ptr> &sinks, 
    LogLevel::value level = LogLevel::value::DEBUG): 
    Logger(name, formatter, sinks, level){ 
    std::cout << LogLevel::toString(level) << " 同步日志器: " << name << "创建成功...\n";
}

日志输出

  • std::unique_lock<std::mutex> lock(_mutex);: 锁定 _mutex,确保多个线程同时记录日志时的安全性。
  • for (auto &it : _sinks): 遍历日志接收器,将日志消息输出到每个接收
void logIt(const std::string &msg) {
    std::unique_lock<std::mutex> lock(_mutex);
    if (_sinks.empty()) { return ; }
    for (auto &it : _sinks) {
        it->log(msg.c_str(), msg.size());
    }
}

AsyncLogger类

 构造函数

  • _looper: 创建一个 AsyncLooper 对象,并将 backendLogIt 方法绑定为回调函数。
  • std::cout: 打印日志器创建成功的消息
AsyncLogger(const std::string &name, 
    Formatter::ptr formatter, 
    std::vector<LogSink::ptr> &sinks, 
    LogLevel::value level = LogLevel::value::DEBUG): 
    Logger(name, formatter, sinks, level),
    _looper(std::make_shared<AsyncLooper>(std::bind(&AsyncLogger::backendLogIt, this, std::placeholders::_1))) {
    std::cout << LogLevel::toString(level) << "异步日志器: " << name << "创建成功...\n";
}

 日志消息推送到_looper中,由后台线程异步处理

void logIt(const std::string &msg) {
    _looper->push(msg);
}

遍历日志接收器,将从缓冲区读取的日志消息输出到每一个接收器中 

void backendLogIt(Buffer &msg) {
    if (_sinks.empty()) { return; }
    for (auto &it : _sinks) {
        it->log(msg.begin(), msg.readAbleSize());
    }
}

Logger::Builder

日志器的构造器,通过链式调用设置日志器的各种属性

构造函数,初始化日志器等级,默认等级为DEBUG ;默认为同步日志器

Builder():_level(LogLevel::value::DEBUG), 
    _logger_type(Logger::Type::LOGGER_SYNC) {}

设置日志器名称和日志等级,注意必须赋予日志器名称

void buildLoggerName(const std::string &name) { _logger_name = name; }
void buildLoggerLevel(LogLevel::value level) { _level = level; }

设置日志器类型,同步还是异步 ;使用指定的模式字符串创建日志格式化器(日志输出规则),通过智能指针对其生命周期进行管理

void buildLoggerType(Logger::Type type) { _logger_type = type; }
void buildFormatter(const std::string pattern) { _formatter = std::make_shared<Formatter>(pattern); }

设置日志格式化器 

void buildFormatter(const Formatter::ptr &formatter) { _formatter = formatter; }

使用SinkFactory创建日志接收器,并将其添加到_sinks(日志器数组)中 

template<typename SinkType, typename ...Args>
void buildSink(Args &&...args) { 
    auto sink = SinkFactory::create<SinkType>(std::forward<Args>(args)...);
    _sinks.push_back(sink); 
}

 LocalLoggerBuilder类GlobalLoggerBuilder类

继承自Logger::Builder,分别用于构建本地和全局日志器

  • 创建并返回一个日志器对象
  • 如果是全局构建者模式,会将日志器添加到loggerManager中
Logger::ptr build() {
    if (_logger_name.empty()) {
        std::cout << "日志器名称不可以为空";
        abort();
    }
    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 (_logger_type == Logger::Type::LOGGER_ASYNC) {
        lp = std::make_shared<AsyncLogger>(_logger_name, _formatter, _sinks, _level);
    }else {
        lp = std::make_shared<SyncLogger>(_logger_name, _formatter, _sinks, _level);
    }
    return lp;
}

 loggerManager类

该类管理多个Logger对象,使用单例模式 (懒汉型)确保日志器的全局唯一性和可访问性

设计思路分析

  • 作用
    • 默认创建一个日志器不创建任何日志器的情况下,也可以进行日志标准输出打印
    • 对创建的管理器进行统一管理
    • 保证程序调用日志器的时候是同一个日志器,设置成单例模式最主要就是防止频繁创建日志器对象,从而对造成资源浪费和性能消耗
  • 成员
    • 默认日志器
    • 管理同步以及异步日志器组
    • 互斥锁
  • 方法
    • 添加日志器进行管理
    • 判断是否已经管理的某个日志器
    • 获取指定名称的日志器
    • 获取默认日志器

 构造函数,先创建一个root的根日志器,然后将其加入到_loggers中

loggerManager(){ 
    std::unique_ptr<LocalLoggerBuilder> slb(new LocalLoggerBuilder());
    slb->buildLoggerName("root");
    slb->buildLoggerType(Logger::Type::LOGGER_SYNC);
    _root_logger = slb->build();
    _loggers.insert(std::make_pair("root", _root_logger));
}

 单例模式,返回一个loggerManage(C++11后支持,静态局部变量没有构造完之前,其他线程进入阻塞状态)

static loggerManager& getInstance() {
    static loggerManager lm;
    return lm;
}

检查是否存在指定名称的日志器 

bool 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;
}

添加日志器到_loggers中进行管理

void addLogger(const std::string &name, const Logger::ptr logger) {
    std::unique_lock<std::mutex> lock(_mutex);
    _loggers.insert(std::make_pair(name, logger));
}

根据日志器名称获取日志器,如果不存在则返回空指针 

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() {
    std::unique_lock<std::mutex> lock(_mutex);
    return _root_logger;
}

AsyncLooper异步处理器与Buffer

架构分析 

该类是配合创建异步日志器使用,本质是一个异步任务处理器设计的目的是将任务推送到队列中,并由后台线程异步进行处理。核心思想则是将时间密集型和I/O密集型任务(更详细的来说是为了避免写日志过程中阻塞)放到后台线程中执行,从而减少主线程的任务负担,从而提高性能。

设计实现总体思路,首先是需要创建一个安全的缓冲区,其次需要创建一个异步工作线程,专门负责缓冲区中日志消息的输出落地操作。总体逻辑遵循生产者消费者模型

缓冲区设计,因为需要缓存日志信息,然后对日志信息进行逐条处理,所以不能够让空间频繁申请释放,这样会降低效率,所以使用环形队列,提前申请好空间,然后达到堆空间的循环利用。

缓冲区设计的线程安全问题,因为缓冲区操作的时候会涉及到多线程,所以缓冲区操作必须保证是线程安全的。实现线程安全的方法采用读写加锁。

线程分配问题,因为日志记录不需要占用太多的资源,所以工作线程只需要保证一个日志器一个线程即可。

锁冲突问题,线程之间向缓冲区中写入数据,必定会导致抢夺资源,那么与之会产生锁冲突,也就是生产者和生产者之间互斥以及生产者和消费者之间的互斥

线程冲突采用双缓冲区进行处理,即业务线程写入日志到一个写入缓冲区中,而异步线程则负责用任务处理缓冲区中读取任务执行。写入缓冲区和任务处理缓冲区两者建立交换数据机制,从而避免线程冲突。

双缓冲区设计目的,降低生产者和消费者锁冲突的次数,只有在任务写入缓冲区和任务处理缓冲区才会产生一次锁冲突,从而降低锁的冲突。

执行流程分析

  • 构造函数中启动一个后台线程,循环执行的worker_loop方法
  • push方法将任务推送到_tasks_push缓冲区中,同时唤醒后台线程处理这些数据
  • _worker_loop循环检查是否有新任务到来,如果有新任务到来,则将其从_tasks_push交换到_tasks_pop,然后调用回调函数进行处理

模块中关键组件分析

  • Functor:用于定义回到函数,用于处理任务队列中的任务
  • 线程和同步机制:thread创建后台线程,mutex和condition_variable进行线程同步,确保线程安全的访问任务队列
  • Buffer:存储任务数据的缓冲区对象,通过push方法将任务推送到队列,然后通过worker_loop方法在后台线程职工处理队列中的任务 

双缓冲区设计

设计思想

  • 作用:直接存放格式化后的日志消息字符串
  • 优点
    • 减少LogMsg对象频繁构造所造成的性能消耗
    • 降低频繁对缓冲区消息I/O操作,从而减少I/O次数,提高效率
  • 设计思路
    • 空间管理使用Vector来存放字符串的数据缓冲区
    • 写入位置指针:指向位置是可写区域的起始位置,避免数据的写入覆盖
    • 读取位置指针:指向可读的起始位置
    • 当读写位置指针相遇的时候,数据缓冲区空了
  • 接口设计
    • 缓冲区中写入数据
    • 获取可读数据起始地址接口(避免频繁拷贝降低系统性能)
    • 获取可读数据长度
    • 移动读写位置
    • 初始化缓冲区
    • 交换缓冲区

 双缓冲区工作原理分析

  • 两个缓冲区:维护两个缓冲区,一个缓冲区中存放当前正在处理的数据,而另一个缓冲区则存放下一批处理的数据
  • 数据填充和处理的分离
    • 工作线程将数据读取到B缓冲区中
    • 另一个工作线程从A缓冲区中读取数据并进行处理
    • 当一个缓冲区的数据处理完成后且另一个缓冲区数据填充完毕的时候,两个缓冲区就会交换角色
  • 缓冲区交换
    • 数据填充和处理过程中,两个缓冲区是交替使用
    • 由于一个缓冲区总是在处理另一个缓冲区在填充,这样就避免了在同一个缓冲区上进行读写操作,也就减少了数据竞争和冲突

具体实现

class Buffer {
public:
    // 构造函数
    Buffer();

    // 公共接口
    bool empty();               // 检查缓冲区是否为空
    size_t readAbleSize();       // 获取可读数据的大小
    size_t writeAbleSize();      // 获取可写空间的大小
    void reset();                // 重置缓冲区
    void swap(Buffer &buf);      // 交换两个缓冲区的数据
    void push(const char *data, size_t len); // 向缓冲区写入数据
    const char* begin();         // 获取缓冲区开始位置的指针
    void pop(size_t len);        // 从缓冲区弹出数据

protected:
    // 保护的辅助函数
    void ensureEnoughSpace(size_t len); // 确保有足够的空间存储数据

private:
    size_t _reader_idx;          // 读指针位置
    size_t _writer_idx;          // 写指针位置
    std::vector<char> _v;        // 存储数据的缓冲区
};

}

双缓冲区在异步处理器上实现分析

  • 初始化:_tasks_push 和 _tasks_pop两个Buffer对象,初始化成空缓冲区
  • 数据写入:外部调用Push方法的时候,数据被写入tasks_push缓冲区中,另一个线程则正在处理_tasks_pop缓冲区,两个线程处理过程中不会出现冲突
    • 写入日志的时候,放入格式化,目的是防止构造产生的性能开销
  • 缓冲区交换
    • worker_loop方法,当一个线程检测到_tasks_push中有数据需要处理的时候,首先锁定互斥量_mutex,然后两个缓冲区的数据进行交换
    • 缓冲区数据交换后,_task_pop也就存放了新的数据,_tasks_push缓冲区则就为空了,可以安全的接收新的数据
  • 数据处理
    • 缓冲区交换后,任务处理线程从_tasks_pop缓冲区中取出任务,然后调用回调函数进行处理(异步线程如何处理缓冲区中的数据),同事_tasks_push缓冲区也是可以继续接收新的数据,而不会与正在处理的数据发生冲突
  • 互斥锁和条件变量的作用
    • 互斥锁:任务线程和异步线程会对缓冲区进行读写操作,使用互斥锁的目的就是保护缓冲区,从而确保只有一个线程可以在任意时刻修改缓冲区的数据,防止数据竞争的不一致性
    • 条件变量:任务和异步线程同步
      • 异步线程在向_task_push缓冲区写入数据前等待_push_cond条件变量,从而确保拥有足够的空间写入数据
      • 任务线程处理完_tasks_pop中的数据后,会等待_pop_cond,确保有新的数据可以进行处理
class AsyncLooper {
public:
    // 类型定义
    using Functor = std::function<void(Buffer &buffer)>;
    using ptr = std::shared_ptr<AsyncLooper>;

    // 构造函数与析构函数
    AsyncLooper(const Functor &cb);
    ~AsyncLooper();

    // 公共接口
    void stop();                 // 停止异步循环器
    void push(const std::string &msg); // 将消息推入缓冲区

private:
    // 线程处理函数
    void worker_loop();

private:
    Functor _looper_callback;    // 回调函数,用于处理任务
    std::mutex _mutex;           // 互斥锁,保护共享资源
    std::atomic<bool> _running;  // 控制异步循环器运行状态的标志
    std::condition_variable _push_cond;  // 用于通知的条件变量(生产者)
    std::condition_variable _pop_cond;   // 用于通知的条件变量(消费者)
    Buffer _tasks_push;          // 缓冲区,用于存储待处理的任务
    Buffer _tasks_pop;           // 缓冲区,用于存储处理中的任务
    std::thread _thread;         // 工作线程,用于异步任务处理
};

}

构造初始化

  • cb: 回调函数,异步线程处理缓冲区数据的逻辑
  • _running(true): 初始化 _runningtrue(默认运行),表示后台线程应继续运行
  • _looper_callback(cb): 将传入的回调函数保存到 _looper_callback
  • _thread(std::thread(&AsyncLooper::worker_loop, this)): 启动一个新的线程,该线程执行 worker_loop 函数,处理任务队列(核心逻辑实现)
AsyncLooper(const Functor &cb): _running(true), _looper_callback(cb),
    _thread(std::thread(&AsyncLooper::worker_loop, this)) {
}

终止异步工作器

  • _running = false: 将 _running 设置为 false通知后台线程停止运行
  • _pop_cond.notify_all(): 唤醒可能在等待任务的后台线程
  • _thread.join(): 等待后台线程结束,确保线程安全地终止
void stop(){ 
    _running = false; 
    _pop_cond.notify_all();
    _thread.join();
}

向缓冲区中添加数据

  • if (_running == false) return;: 如果后台线程已停止,停止接受新任务
  • std::unique_lock: 锁定 _mutex,以确保线程安全地访问任务队列
  • _push_cond.wait:等待直到 _tasks_push 缓冲区有足够的空间容纳新的任务(缓冲区剩余空间大小大于数据长度时)
  • _tasks_push.push(msg.c_str(), msg.size());: 将任务推送到 _tasks_push 缓冲区
  • _pop_cond.notify_all();: 唤醒后台线程,通知其有新任务可以处理(唤醒消费者对缓冲区中的任务进行处理)
            void push(const std::string &msg){
                if (_running == false) return;
                {
                    std::unique_lock<std::mutex> lock(_mutex);
                    _push_cond.wait(lock, [&]{ return _tasks_push.writeAbleSize() >= msg.size(); });
                    _tasks_push.push(msg.c_str(), msg.size());
                }
                _pop_cond.notify_all();
            }

对消费缓冲区中的数据进行处理,处理完毕后初始化缓冲区,交换两个缓冲区

  • 下述加锁操作设置生命周期,缓冲区交换完毕后解锁
  • std::unique_lock: 锁定 _mutex确保线程安全地访问任务队列
  • if (_running == false && _tasks_push.empty()) { return; }: 如果 _runningfalse() 任务队列为空,则退出循环,终止线程(线程停止且队列为空时)
  • _pop_cond.wait: 判断生产缓冲区中是否有数据,有则交换,没有则阻塞
  • _tasks_push.swap(_tasks_pop);: 交换 (两个缓冲区)_tasks_push_tasks_pop,以便在锁定的情况下将新任务移交给 _tasks_pop,然后在解锁后处理这些任务。

线程唤醒对消费缓冲区数据处理,重新初始化消费缓冲区,唤醒生产者

  • _push_cond.notify_all(): 唤醒所有线程
  • _looper_callback(_tasks_pop): 调用回调函数处理任务。
  • _tasks_pop.reset(): 重置 _tasks_pop 缓冲区,清空已处理的任务
void worker_loop(){
    while(1){
        {
            std::unique_lock<std::mutex> lock(_mutex);
            if (_running == false && _tasks_push.empty()) { return; }
            _pop_cond.wait(lock, [&]{ return !_tasks_push.empty() || !_running; });
            _tasks_push.swap(_tasks_pop);
        }
        _push_cond.notify_all();
        _looper_callback(_tasks_pop);
        _tasks_pop.reset();
    }
    return;
}

单元测试

异步日志器测试

简单功能测试

#include <iostream>
#include "logger.hpp"
#include "sink.hpp"
#include "formatter.hpp"
#include "level.hpp"

int main() {
    // 直接实例化 LocalLoggerBuilder
    bitlog::LocalLoggerBuilder builder;

    // 使用建造者模式来设置Logger的各个属性
    builder.buildLoggerName("sync_logger");
    builder.buildLoggerLevel(bitlog::LogLevel::value::WARN);
    builder.buildFormatter("%m%n");  // 设置日志格式
    builder.buildLoggerType(bitlog::Logger::Type::LOGGER_SYNC);  // 设置为同步日志器
    builder.buildSink<bitlog::FileSink>("./logfile/test.log");  // 输出到文件
    builder.buildSink<bitlog::StdoutSink>();  // 输出到标准输出

    // 构建Logger
    bitlog::Logger::ptr logger = builder.build();

    // 记录各种级别的日志
    logger->debug(__FILE__, __LINE__, "%s", "这是一条DEBUG日志,不会被记录,因为日志级别为WARN");
    logger->info(__FILE__, __LINE__, "%s", "这是一条INFO日志,不会被记录,因为日志级别为WARN");
    logger->warn(__FILE__, __LINE__, "%s", "这是一条WARN日志");
    logger->error(__FILE__, __LINE__, "%s", "这是一条ERROR日志");
    logger->fatal(__FILE__, __LINE__, "%s", "这是一条FATAL日志");

    // 进行批量日志记录测试
    size_t cursize = 0, count = 0;
    while (cursize < 1024 * 1024 * 10) {  // 10 MB
        logger->fatal(__FILE__, __LINE__, "批量日志测试 - 日志条目: %d", count++);
        cursize += 20;  // 假设每条日志约占 20 字节
    }

    std::cout << "日志测试完成,日志已写入 ./logfile/test.log 和标准输出" << std::endl;

    return 0;
}

 复杂测试

  • 多个日志器,不同级别名称以及输出目的地
  • 并发记录,多个线程并发记录日志,测试日志器在多线程环境下的性能和线程安全性
  • 同时创建同步和异步线程,验证两者共同工作
  • 使用不同日志级别触发条件记录

 

#include <iostream>
#include <thread>
#include <vector>
#include "logger.hpp"
#include "sink.hpp"
#include "formatter.hpp"
#include "level.hpp"

void logMessages(bitlog::Logger::ptr logger, const std::string &prefix, int count) {
    for (int i = 0; i < count; ++i) {
        logger->debug(__FILE__, __LINE__, "%s DEBUG 日志 %d", prefix.c_str(), i);
        logger->info(__FILE__, __LINE__, "%s INFO 日志 %d", prefix.c_str(), i);
        logger->warn(__FILE__, __LINE__, "%s WARN 日志 %d", prefix.c_str(), i);
        logger->error(__FILE__, __LINE__, "%s ERROR 日志 %d", prefix.c_str(), i);
        logger->fatal(__FILE__, __LINE__, "%s FATAL 日志 %d", prefix.c_str(), i);
    }
}

int main() {
    // 创建第一个同步日志器
    bitlog::LocalLoggerBuilder syncBuilder;
    syncBuilder.buildLoggerName("sync_logger1");
    syncBuilder.buildLoggerLevel(bitlog::LogLevel::value::DEBUG);
    syncBuilder.buildFormatter("[%d{%H:%M:%S}][%t][%p][%c][%f:%l] %m%n");  // 传递格式化字符串
    syncBuilder.buildLoggerType(bitlog::Logger::Type::LOGGER_SYNC);
    syncBuilder.buildSink<bitlog::FileSink>("./logfile/sync_log1.log");
    syncBuilder.buildSink<bitlog::StdoutSink>();
    bitlog::Logger::ptr syncLogger1 = syncBuilder.build();

    // 创建第二个异步日志器
    bitlog::LocalLoggerBuilder asyncBuilder;
    asyncBuilder.buildLoggerName("async_logger1");
    asyncBuilder.buildLoggerLevel(bitlog::LogLevel::value::INFO);
    asyncBuilder.buildFormatter("[%d{%H:%M:%S}][%t][%p][%c][%f:%l] %m%n");  // 传递格式化字符串
    asyncBuilder.buildLoggerType(bitlog::Logger::Type::LOGGER_ASYNC);
    asyncBuilder.buildSink<bitlog::FileSink>("./logfile/async_log1.log");
    bitlog::Logger::ptr asyncLogger1 = asyncBuilder.build();

    // 创建第三个同步日志器,用于文件输出
    bitlog::LocalLoggerBuilder syncBuilder2;
    syncBuilder2.buildLoggerName("sync_logger2");
    syncBuilder2.buildLoggerLevel(bitlog::LogLevel::value::WARN);
    syncBuilder2.buildFormatter("[%d{%H:%M:%S}][%t][%p][%c][%f:%l] %m%n");  // 传递格式化字符串
    syncBuilder2.buildLoggerType(bitlog::Logger::Type::LOGGER_SYNC);
    syncBuilder2.buildSink<bitlog::FileSink>("./logfile/sync_log2.log");
    bitlog::Logger::ptr syncLogger2 = syncBuilder2.build();

    // 并发记录日志
    const int numThreads = 4;
    const int logCountPerThread = 1000;
    std::vector<std::thread> threads;
    for (int i = 0; i < numThreads; ++i) {
        threads.emplace_back(logMessages, syncLogger1, "Thread" + std::to_string(i), logCountPerThread);
        threads.emplace_back(logMessages, asyncLogger1, "Thread" + std::to_string(i), logCountPerThread);
        threads.emplace_back(logMessages, syncLogger2, "Thread" + std::to_string(i), logCountPerThread);
    }

    // 等待所有线程完成
    for (auto &t : threads) {
        t.join();
    }

    std::cout << "复杂日志测试完成,日志已写入相应的文件和标准输出。" << std::endl;

    return 0;
}

 同步日志器测试

创建一个同步日志器,记录几条日志,然后将日志输出到文件和标准输出

 

#include <iostream>
#include "logger.hpp"
#include "sink.hpp"
#include "formatter.hpp"
#include "level.hpp"

int main() {
    // 创建一个LocalLoggerBuilder实例用于同步日志器
    bitlog::LocalLoggerBuilder builder;
    builder.buildLoggerName("simple_sync_logger");
    builder.buildLoggerLevel(bitlog::LogLevel::value::DEBUG);
    builder.buildFormatter("[%d{%H:%M:%S}][%t][%p][%c][%f:%l] %m%n");  // 设置格式化字符串
    builder.buildLoggerType(bitlog::Logger::Type::LOGGER_SYNC);
    builder.buildSink<bitlog::FileSink>("./logfile/simple_sync_log.log");  // 输出到文件
    builder.buildSink<bitlog::StdoutSink>();  // 输出到标准输出

    // 构建同步Logger
    bitlog::Logger::ptr logger = builder.build();

    // 记录几条不同级别的日志
    logger->debug(__FILE__, __LINE__, "这是一条DEBUG级别的日志");
    logger->info(__FILE__, __LINE__, "这是一条INFO级别的日志");
    logger->warn(__FILE__, __LINE__, "这是一条WARN级别的日志");
    logger->error(__FILE__, __LINE__, "这是一条ERROR级别的日志");
    logger->fatal(__FILE__, __LINE__, "这是一条FATAL级别的日志");

    std::cout << "简单同步日志测试完成,日志已写入 ./logfile/simple_sync_log.log 和标准输出。" << std::endl;

    return 0;
}

 复杂测试

  • 创建两个同步日志器和一个异步日志器
  • 创建4个线程并发,每个线程都向日志器中记录日志,且日志级别不同,从而测试在多线程下是否正常运行
  • 日志文件大小限制和切分,验证运行的时候调整日志级别效果,重新配置日志器的行为是否符合预期
  • 屏幕输出验证一次,同时在文件中也保存着日志记录信息

 

 

#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
#include "logger.hpp"
#include "sink.hpp"
#include "formatter.hpp"
#include "level.hpp"

void logMessages(bitlog::Logger::ptr logger, const std::string &prefix, int count, std::atomic<size_t> &log_size_limit) {
    size_t logged_size = 0;
    for (int i = 0; i < count; ++i) {
        std::string msg = prefix + " 日志 " + std::to_string(i);
        logger->debug(__FILE__, __LINE__, "%s DEBUG %s", prefix.c_str(), msg.c_str());
        logger->info(__FILE__, __LINE__, "%s INFO %s", prefix.c_str(), msg.c_str());
        logger->warn(__FILE__, __LINE__, "%s WARN %s", prefix.c_str(), msg.c_str());
        logger->error(__FILE__, __LINE__, "%s ERROR %s", prefix.c_str(), msg.c_str());
        logger->fatal(__FILE__, __LINE__, "%s FATAL %s", prefix.c_str(), msg.c_str());

        // 模拟日志文件大小限制
        logged_size += msg.size();
        if (logged_size >= log_size_limit.load()) {
            std::cout << "日志文件大小达到限制,切分日志文件。" << std::endl;
            log_size_limit.store(log_size_limit.load() * 2);  // 简单的模拟,实际可以是切换到新的文件
        }
    }
}

int main() {
    // 创建同步日志器
    bitlog::LocalLoggerBuilder syncBuilder1;
    syncBuilder1.buildLoggerName("sync_logger1");
    syncBuilder1.buildLoggerLevel(bitlog::LogLevel::value::DEBUG);
    syncBuilder1.buildFormatter("[%d{%H:%M:%S}][%t][%p][%c][%f:%l] %m%n");
    syncBuilder1.buildLoggerType(bitlog::Logger::Type::LOGGER_SYNC);
    syncBuilder1.buildSink<bitlog::FileSink>("./logfile/sync_log1.log");
    syncBuilder1.buildSink<bitlog::StdoutSink>();
    bitlog::Logger::ptr syncLogger1 = syncBuilder1.build();

    // 创建异步日志器
    bitlog::LocalLoggerBuilder asyncBuilder1;
    asyncBuilder1.buildLoggerName("async_logger1");
    asyncBuilder1.buildLoggerLevel(bitlog::LogLevel::value::INFO);
    asyncBuilder1.buildFormatter("[%d{%H:%M:%S}][%t][%p][%c][%f:%l] %m%n");
    asyncBuilder1.buildLoggerType(bitlog::Logger::Type::LOGGER_ASYNC);
    asyncBuilder1.buildSink<bitlog::FileSink>("./logfile/async_log1.log");
    bitlog::Logger::ptr asyncLogger1 = asyncBuilder1.build();

    // 创建第二个同步日志器
    bitlog::LocalLoggerBuilder syncBuilder2;
    syncBuilder2.buildLoggerName("sync_logger2");
    syncBuilder2.buildLoggerLevel(bitlog::LogLevel::value::WARN);
    syncBuilder2.buildFormatter("[%d{%H:%M:%S}][%t][%p][%c][%f:%l] %m%n");
    syncBuilder2.buildLoggerType(bitlog::Logger::Type::LOGGER_SYNC);
    syncBuilder2.buildSink<bitlog::FileSink>("./logfile/sync_log2.log");
    bitlog::Logger::ptr syncLogger2 = syncBuilder2.build();

    // 设置日志文件大小限制
    std::atomic<size_t> log_size_limit(1024 * 10);  // 10KB

    // 并发记录日志
    const int numThreads = 4;
    const int logCountPerThread = 1000;
    std::vector<std::thread> threads;
    for (int i = 0; i < numThreads; ++i) {
        threads.emplace_back(logMessages, syncLogger1, "Thread" + std::to_string(i), logCountPerThread, std::ref(log_size_limit));
        threads.emplace_back(logMessages, asyncLogger1, "Thread" + std::to_string(i), logCountPerThread, std::ref(log_size_limit));
        threads.emplace_back(logMessages, syncLogger2, "Thread" + std::to_string(i), logCountPerThread, std::ref(log_size_limit));
    }

    // 模拟动态调整日志级别
    std::this_thread::sleep_for(std::chrono::seconds(5));
    std::cout << "重新创建sync_logger1,日志级别调整为ERROR" << std::endl;

    // 重新创建 sync_logger1,并设置日志级别为 ERROR
    bitlog::LocalLoggerBuilder newSyncBuilder;
    newSyncBuilder.buildLoggerName("sync_logger1");
    newSyncBuilder.buildLoggerLevel(bitlog::LogLevel::value::ERROR);
    newSyncBuilder.buildFormatter("[%d{%H:%M:%S}][%t][%p][%c][%f:%l] %m%n");
    newSyncBuilder.buildLoggerType(bitlog::Logger::Type::LOGGER_SYNC);
    newSyncBuilder.buildSink<bitlog::FileSink>("./logfile/sync_log1.log");
    newSyncBuilder.buildSink<bitlog::StdoutSink>();
    syncLogger1 = newSyncBuilder.build();

    // 再次并发记录日志
    for (int i = 0; i < numThreads; ++i) {
        threads.emplace_back(logMessages, syncLogger1, "Thread" + std::to_string(i), logCountPerThread, std::ref(log_size_limit));
    }

    // 等待所有线程完成
    for (auto &t : threads) {
        t.join();
    }

    std::cout << "复杂日志测试完成,日志已写入相应的文件和标准输出。" << std::endl;

    return 0;
}

缓冲区测试

功能性测试

  • 测试初始化,检查Buffer对象在初始化的时候是否为空
  • 测试写入数据,Push方法写入数据
  • 读取数据,使用begin 和 readAblesize方法读取缓冲区数据,同时使用pop方法模拟数据读取后的弹出
  • 测试数据交换,创建新缓冲区然后交换数据

#include <iostream>
#include "buffer.hpp"  // 假设你将上述代码保存为 buffer.hpp 文件

void testBuffer() {
    // 创建一个Buffer对象
    bitlog::Buffer buffer;

    // 测试缓冲区是否为空
    if (buffer.empty()) {
        std::cout << "缓冲区初始化为空" << std::endl;
    } else {
        std::cerr << "缓冲区初始化时应为空" << std::endl;
    }

    // 向缓冲区写入数据
    const char *data = "Hello, Buffer!";
    size_t len = std::strlen(data);
    buffer.push(data, len);
    std::cout << "已向缓冲区写入数据: " << data << std::endl;

    // 测试缓冲区是否不为空
    if (!buffer.empty()) {
        std::cout << "缓冲区现在非空,数据长度: " << buffer.readAbleSize() << std::endl;
    } else {
        std::cerr << "缓冲区应当为非空" << std::endl;
    }

    // 读取并输出缓冲区内容
    std::cout << "缓冲区内容: " << std::string(buffer.begin(), buffer.readAbleSize()) << std::endl;

    // 模拟读取数据,将数据从缓冲区弹出
    buffer.pop(len);
    std::cout << "已从缓冲区弹出数据" << std::endl;

    // 测试缓冲区是否为空
    if (buffer.empty()) {
        std::cout << "缓冲区已为空" << std::endl;
    } else {
        std::cerr << "缓冲区应当为空" << std::endl;
    }

    // 测试缓冲区交换功能
    bitlog::Buffer anotherBuffer;
    const char *moreData = "More data to buffer!";
    size_t moreLen = std::strlen(moreData);
    anotherBuffer.push(moreData, moreLen);
    std::cout << "已向另一缓冲区写入数据: " << moreData << std::endl;

    // 交换两个缓冲区
    buffer.swap(anotherBuffer);
    std::cout << "已交换两个缓冲区" << std::endl;

    // 测试交换后的缓冲区内容
    std::cout << "当前缓冲区内容: " << std::string(buffer.begin(), buffer.readAbleSize()) << std::endl;
}

int main() {
    testBuffer();
    return 0;
}

复杂测试

  • 多线程并发,同时对Buffer进行写入、读取和交换操作
  • 模式大数据处理,创建一个大数据写入到缓冲区,验证其是否正确处理
  • 异常处理,检查如果缓冲区不足的情况是否会拓展缓冲区

#include <iostream>
#include <cstring>
#include <thread>
#include <vector>
#include <mutex>
#include "buffer.hpp"

std::mutex buffer_mutex;

void writerThread(bitlog::Buffer &buffer, const char *data, size_t len) {
    for (int i = 0; i < 1000; ++i) {
        std::lock_guard<std::mutex> guard(buffer_mutex);
        if (buffer.writeAbleSize() < len) {
            std::cout << "缓冲区空间不足,扩展缓冲区" << std::endl;
        }
        buffer.push(data, len);
    }
}

void readerThread(bitlog::Buffer &buffer) {
    while (true) {
        std::lock_guard<std::mutex> guard(buffer_mutex);
        if (!buffer.empty()) {
            size_t readableSize = buffer.readAbleSize();
            std::string data(buffer.begin(), readableSize);
            std::cout << "从缓冲区读取数据: " << data << std::endl;
            buffer.pop(readableSize);
        }
    }
}

void swapThread(bitlog::Buffer &buffer1, bitlog::Buffer &buffer2) {
    std::lock_guard<std::mutex> guard(buffer_mutex);
    buffer1.swap(buffer2);
    std::cout << "交换了两个缓冲区的内容" << std::endl;
}

int main() {
    bitlog::Buffer buffer1;
    bitlog::Buffer buffer2;

    const char *data1 = "Data from writer 1";
    size_t len1 = strlen(data1);
    const char *data2 = "Data from writer 2";
    size_t len2 = strlen(data2);

    std::thread writer1(writerThread, std::ref(buffer1), data1, len1);
    std::thread writer2(writerThread, std::ref(buffer2), data2, len2);

    std::thread reader1(readerThread, std::ref(buffer1));
    std::thread reader2(readerThread, std::ref(buffer2));

    std::thread swapper(swapThread, std::ref(buffer1), std::ref(buffer2));

    writer1.join();
    writer2.join();
    reader1.detach();
    reader2.detach();
    swapper.join();

    std::cout << "复杂缓冲区测试完成" << std::endl;

    return 0;
}

  • 28
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值