C++日志系统:高效异步日志实现解析
本文介绍了一个功能完善的多日志器日志系统,支持同步/异步日志输出,具备以下核心特性: 系统架构 采用模块化设计,包含日志等级、消息格式化、落地处理和日志器等模块 支持同步日志(直接写入)和异步日志(双缓冲队列) 提供多种日志落地方式:控制台、文件和滚动文件 关键技术 基于C++11实现线程安全日志记录 采用生产者-消费者模型处理异步日志 运用多种设计模式:单例、工厂、建造者和代理模式 实现可变参数
目录
8.7 双缓冲区异步任务处理器(AsyncLooper)设计
什么是日志
日志简单来说就是记录系统、应用程序、设备或服务在运行过程中发生的事件、状态变化、用户操作、错误信息等详细信息的文件或数据流。
你可以把它想象成一部详细的运行日记或飞行数据记录仪(黑匣子)。
目的:
-
记录发生了什么事件(例如:用户登录、文件被修改、系统启动、网络连接建立、程序崩溃)。
-
记录事件发生的时间(精确到毫秒甚至微秒)。
-
记录事件相关的详细信息(例如:哪个用户、来自哪个IP地址、操作了什么对象、错误代码是什么)。
-
记录系统的状态(例如:CPU使用率、内存占用、磁盘空间)。
作用:
- 故障排查
- 安全审计
- 性能监控与优化
- 行为分析
- 合规性
- 历史记录
总结来说:
日志就是系统和应用的“黑匣子”和“日记本”。它忠实、详细地记录下运行过程中的点点滴滴,是工程师进行故障诊断、性能优化、安全防护、行为分析和合规审计的基石。
1. 项目介绍
本项目主要实现一个日志系统,其主要支持以下功能:
- 支持多级别日志消息
- 支持同步日志和异步日志
- 支持可靠写入日志到控制台、文件以及滚动文件中
- 支持多线程程序并发写日志
- 支持扩展不同的日志落地目的地
2. 开发环境
- CentOS 7
- vscode/vim
- g++/gdb
- Makefile
3. 核心技术
- 类层次设计(继承和多态的应用)
- C++11(多线程、auto、智能指针、右值引用等)
- 双缓冲区
- 生产消费模型
- 多线程
- 设计模式(单例、工厂、代理、建造者等)
4. 环境搭建
本项目不依赖其他任何第三方库,只需要安装好CentOS/Ubuntu + vscode/vim环境即可开发。
5. 日志系统介绍
5.1 为什么需要日志系统
- 生产的产品为了保证其稳定性及安全性是不允许开发人员附加调试器去排查问题,可以借助日志系统来打印一些日志帮助开发人员解决问题
- 上线客户端的产品出现bug无法复现并解决,可以借助日志系统打印日志并上传到服务端帮助开发人员进行分析
- 对于一些高频操作(如定时器、心跳包)在少量调试次数下可能无法触发我们想要的行为,通过断点的暂停方式,我们不得不重复操作几十次、上百次甚至更多,导致排查问题效率是非常低下,可以借助打印日志的方式查问题
- 在分布式、多线程/多进程代码中,出现bug比较难以定位,可以借助日志系统打印log帮助定位bug
- 帮助首次接触项目代码的新开发人员理解代码的运行流程
5.2 日志系统技术实现
日志系统的技术实现主要包括三种类型:
- 利用printf、std::cout等输出函数将日志信息打印到控制台
- 对于大型商业化项目,为了方便排查问题,我们一般会将日志输出到文件或者是数据库系统方便查询和分析日志,主要分为同步日志和异步日志方式。
- 同步写日志
- 异步写日志
5.2.1 同步写日志
同步日志是指当输出日志时,必须等待日志输出语句执行完毕后,才能执行后面的业务逻辑语句,日志输出语句与程序的业务逻辑语句将在同一个线程运行。每次调用一次打印日志API就对应一次系统调用write写日志文件。
在高并发场景下,随着日志数量不断增加,同步日志系统容易产生系统瓶颈:
- 一方面,大量的日志打印陷入等量的write系统调用,有一定系统开销。
- 另一方面,使得打印日志的进程附带了大量同步的磁盘IO,影响程序性能
5.2.2 异步写日志
异步日志是指在进行日志输出时,日志输出语句与业务逻辑语句并不是在同一个线程中运行,而是有专门的线程用于进行日志的输出操作。业务线程只需要将日志放到一个内存缓冲区中不用等待即可继续执行后续业务逻辑(作为日志的生产者),而日志的落地操作交给单独的日志线程去完成(作为日志的消费者),这是⼀个典型的生产-消费模型。
这样做的好处是即使日志没有真的地完成输出也不会影响程序的主业务,可以提高程序的性能:
- 主线程调用日志打印接口成为非阻塞操作
- 同步的磁盘IO从主线程中剥离出来交给单独的线程完成
6. 相关技术知识补充
6.1 不定参函数
在初学C语言的时候,我们都用过printf函数进行打印。其中printf函数就是一个不定参函数,在函数内部可以根据格式化字符串中格式化字符分别获取不同的参数进行数据的格式化。
而这种不定参函数在实际的使用中也非常多见,在这里简单做⼀介绍:
不定参宏函数
#include<stdio.h>
#define LOG(fmt,...) printf("[%s:%d]" fmt,__FILE__,__LINE__,##__VA_ARGS__)
int main()
{
LOG("%s-%d\n","我的日志",666);
return 0;
}
C风格不定参函数
#include<iostream>
#include<cstdarg>
void printNum(int n, ...)
{
va_list la;
va_start(la,n);
for (int i = 0; i < n; i++)
{
int num = va_arg(la, int);
std::cout << num << std::endl;
}
va_end(la);
}
int main()
{
printNum(3, 11, 22, 33);
return 0;
}
#define _GNU_SOURCE
#include<iostream>
#include<stdarg.h>
void Myprintf(const char *fmt,...)
{
char *res;
va_list al;
va_start(al,fmt);
int len = _vscprintf(fmt, al);
va_end(al);
}
int main()
{
Myprintf("%s-%d", "小明", 18);
return 0;
}
C++风格不定参函数
#include<iostream>
#include<cstdarg>
#include<memory>
#include<functional>
void xprintf() {
std::cout << std::endl;
}
template<typename T ,typename ...Args>
void xprintf(const T& value, Args &&...args) {
std::cout << value << " ";
if((sizeof ...(args))>0){
xprintf(std::forward<Args>(args)...);
}
else {
xprintf();
}
}
int main()
{
xprintf("日志");
xprintf("日志",666);
xprintf("日志","项目",666);
return 0;
}
6.2 设计模式
设计模式是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。
六大原则:
- 单一职责原则(Single Responsibility Principle);
- 类的职责应该单一,一个方法只做一件事。职责划分清晰了,每次改动到最小单位的方法或类。
- 使用建议:两个完全不一样的功能不应该放一个类中,一个类中应该是一组相关性很高的函
数、数据的封装 - 用例:网络聊天:网络通信 & 聊天,应该分割成为网络通信类 & 聊天类
- 开闭原则(Open Closed Principle);
- 对扩展开放,对修改封闭
- 使用建议:对软件实体的改动,最好用扩展而非修改的方式
- 用例:超时卖货:商品价格---不是修改商品的原来价格,而是新增促销价格。
- 里氏替换原则(Liskov Substitution Principle);
- 通俗点讲,就是只要父类能出现的地方,子类就可以出现,而且替换为子类也不会产生任何错误或异常。
- 在继承类时,务必重写父类中所有的方法,尤其需要注意父类的protected方法,子类尽量不要暴露自己的public方法供外界调用。
- 使用建议:子类必须完全实现父类的方法,孩子类可以有自己的个性。覆盖或实现父类的方法时,输入参数可以被放大,输出可以缩小
- 用例:跑步运动员类-会跑步,子类长跑运动员-会跑步且擅长长跑,子类短跑运动员-会跑步且擅长短跑
- 依赖倒置原则(Dependence Inversion Principle)
- 高层模块不应该依赖低层模块,两者都应该依赖其抽象。不可分割的原子逻辑就是低层模式,原子逻辑组装成的就是高层模块。
- 模块间依赖通过抽象(接口)发生,具体类之间不直接依赖
- 使用建议:每个类都尽量有抽象类,任何类都不应该从具体类派生。尽量不要重写基类的方法。结合里氏替换原则使用。
- 用例:奔驰车司机类--只能开奔驰; 司机类 -- 给什么车,就开什么车; 开车的人:司机--依赖于抽象
- 迪米特法则(Law of Demeter),又叫“最少知道法则”;
- 尽量减少对象之间的交互,从而减小类之间的耦合。一个对象应该对其他对象有最少的了解。对类的低耦合提出了明确的要求:
- 只和直接的朋友交流, 朋友之间也是有距离的。自己的就是自己的(如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中)。
2.用例:老师让班长点名--老师给班长一个名单,班长完成点名勾选,返回结果,而不是班长点
名,老师勾选
- 接口隔离原则(Interface Segregation Principle);
- 客户端不应该依赖它不需要的接口,类间的依赖关系应该奖励在最小的接口上
- 使用建议:接口设计尽量精简单一,但是不要对外暴露没有实际意义的接口。
- 用例:修改密码,不应该提供修改用户信息接口,而就是单一的最小修改密码接口,更不要暴露数据库操作
从整体上来理解六大设计原则,可以简要的概括为一句话,用抽象构建框架,用实现扩展细节,具体到每一条设计原则,则对应一条注意事项:
- 单一职责原则告诉我们实现类要职责单一;
- 里氏替换原则告诉我们不要破坏继承体系;
- 依赖倒置原则告诉我们要面向接口编程;
- 接口隔离原则告诉我们在设计接口的时候要精简单一;
- 迪米特法则告诉我们要降低耦合;
- 开闭原则是总纲,告诉我们要对扩展开放,对修改关闭
单例模式
一个类只能创建一个对象,即单例模式,该设计模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
单例模式有两种实现模式:饿汉模式和懒汉模式
- 饿汉模式:程序启动时就会创建一个唯一的实例对象。因为单例对象已经确定,所以比较适用于多线程环境中,多线程获取单例对象不需要加锁,可以有效的避免资源竞争,提高性能。
- 懒汉模式:第一次使用要使用单例对象的时候创建实例对象。如果单例对象构造特别耗时或者耗费资源(加载插件、加载网络资源等),可以选择懒汉模式,在第一次使用的时候才创建对象。
- 这里介绍的是《Effective C++》一书作者 Scott Meyers 提出的一种更加优雅简便的单例模式 Meyers' Singleton in C++。
- C++11 Static local variables 特性以确保C++11起,静态变量将能够在满足 thread-safe 的前提下唯一地被构造和析构
工厂模式
工厂模式是⼀种创建型设计模式, 它提供了一种创建对象的最佳方式。在工厂模式中,我们创建对象时不会对上层暴露创建逻辑,而是通过使用一个共同结构来指向新创建的对象,以此实现创建-使用的分离。
工厂模式可以分为:
- 简单工厂模式:简单工厂模式实现由一个工厂对象通过类型决定创建出来指定产品类的实例。假设有个工厂能生产水果,当客户需要产品的时候明确告知工厂生产哪类水果,工厂需要接收用户提供的类别信息,当新增产品的时候,工厂内部去添加新产品的生产方式。
//简单工厂模式:通过参数控制可以生产任何产品
// 优点:简单粗暴,直观易懂。使用一个工厂生产同一等级结构下的任意产品
// 缺点:
// 1. 所有东西生产在⼀起,产品太多会导致代码量庞大
// 2. 开闭原则遵循(开放拓展,关闭修改)的不是太好,要新增产品就必须修改工厂方法。
#include<iostream>
#include<string>
#include<memory>
class Fruit{
public:
Fruit(){}
virtual void show() = 0;
};
class Apple : public Fruit{
public:
Apple(){}
virtual void show()
{
std::cout<<"我是一个苹果"<<std::endl;
}
};
class Banana : public Fruit{
public:
Banana(){}
virtual void show(){
std::cout<<"我是一个香蕉"<<std::endl;
}
};
class FruitFactory{
public:
static std::shared_ptr<Fruit>create(const std::string &name){
if(name == "苹果"){
return std::make_shared<Apple>();
}
else if(name == "香蕉"){
return std::make_shared<Banana>();
}
return std::shared_ptr<Fruit>();
}
};
这个模式的结构和管理产品对象的方式十分简单,但是它的扩展性非常差,当我们需要新增产品的时候,就需要去修改工厂类新增一个类型的产品创建逻辑,违背了开闭原则。
- 工厂方法模式:在简单工厂模式下新增多个工厂,多个产品,每个产品对应一个工厂。假设现在有A、B两种产品,则开两个工厂,工厂A负责生产产品A,工厂B负责生产产品B,用户只知道产品的工厂名,而不知道具体的产品信息,工厂不需要再接收客户的产品类别,而只负责生产产品。
//工厂方法:定义⼀个创建对象的接口,但是由⼦类来决定创建哪种对象,使用多个工厂分别生产指定的固定产品
// 优点:
// 1. 减轻了工厂类的负担,将某类产品的生产交给指定的工厂来进行
// 2. 开闭原则遵循较好,添加新产品只需要新增产品的工厂即可,不需要修改原先的工厂类
// 缺点:对于某种可以形成一组产品族的情况处理较为复杂,需要创建大量的工厂类
#include<iostream>
#include<string>
#include<memory>
class Fruit{
public:
Fruit(){}
virtual void show() = 0;
};
class Apple : public Fruit{
public:
Apple(){}
virtual void show()
{
std::cout<<"我是一个苹果"<<std::endl;
}
private:
std::string _color;
};
class Banana : public Fruit{
public:
Banana(){}
virtual void show(){
std::cout<<"我是一个香蕉"<<std::endl;
}
};
class FruitFactory{
public:
virtual std::shared_ptr<Fruit> create() = 0;
};
class AppleFactory : public FruitFactory{
public:
virtual std::shared_ptr<Fruit> create(){
return std::make_shared<Apple>();
}
};
class BananaFactory : public FruitFactory{
public:
virtual std::shared_ptr<Fruit> create(){
return std::make_shared<Banana>();
}
};
int main()
{
std::shared_ptr<FruitFactory>factory(new AppleFactory());
fruit = factory->create();
fruit->show();
factor.reset(new BananaFactory);
fruit = factory->create();
fruit->show();
return 0;
}
工厂方法模式每次增加一个产品时,都需要增加一个具体产品类和工厂类,这会使得系统中类的个数成倍增加,在一定程度上增加了系统的耦合度。
抽象工厂模式:工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问
题,但由于工厂方法模式中的每个工厂只生产一类产品,可能会导致系统中存在大量的工厂类,势
必会增加系统的开销。此时,我们可以考虑将一些相关的产品组成一个产品族(位于不同产品等级
结构中功能相关联的产品组成的家族),由同一个工厂来统一生产,这就是抽象工厂模式的基本思
想。
#include <iostream>
#include <string>
#include <memory>
// 抽象工厂:围绕⼀个超级工厂创建其他工厂。每个生成的工厂按照工厂模式提供对象。
// 思想:将工厂抽象成两层,抽象工厂 & 具体工厂子类, 在工厂子类种生产不同类型的子产品
class Fruit
{
public:
Fruit() {}
virtual void show() = 0;
};
class Apple : public Fruit
{
public:
Apple() {}
virtual void show()
{
std::cout << "我是一个苹果" << std::endl;
}
private:
std::string _color;
};
class Banana : public Fruit
{
public:
Banana() {}
virtual void show()
{
std::cout << "我是一个香蕉" << std::endl;
}
};
class Animal
{
public:
virtual void voice() = 0;
};
class Lamp : public Animal
{
public:
void voice()
{
std::cout << "咩咩咩\n";
}
};
class Dog : public Animal
{
public:
void voice()
{
std::cout << "汪汪汪\n";
}
};
class Factory
{
public:
virtual std::shared_ptr<Fruit> getFruit(const std::string &name) = 0;
virtual std::shared_ptr<Animal> getAnimal(const std::string &name) = 0;
};
class FruitFactory : public Factory
{
public:
virtual std::shared_ptr<Animal> getAnimal(sonst std::string &name)
{
return std::shared_ptr<Animal>();
}
virtual std::shared_ptr<Fruit> getFruit(const std::string &name)
{
if (name == "苹果")
{
return std::make_shared<Apple>();
}
else if (name == "香蕉")
{
return std::make_shared<Banana>();
}
return std::shared_ptr<Fruit>();
}
};
class AnimalFactory : public Factory
{
public:
virtual std::shared_ptr<Fruit> getFruit(const std::string &name)
{
return std::shared_ptr<Fruit>();
}
virtual std::shared_ptr<Fruit> getFruit(const std::string &name)
{
if (name == "小羊")
{
return std::make_shared<Lamp>();
}
else if (name == "小狗")
{
return std::make_shared<Dog>();
}
return std::shared_ptr<Animal>();
}
};
class FactoryProducer
{
public:
static std::shared_ptr<Factory> getFactory(const std::string &name)
{
if (name == "动物")
{
return std::make_shared<AnimalFactory>();
}
else
{
return std::make_shared<FruitFactory>();
}
}
};
int main()
{
std::shared_ptr<Factory> fruit_factory = FactoryProducer::getFactory("水果");
std::shared_ptr<Fruit> fruit = fruit_factory->getFruit("苹果");
fruit->show();
fruit = fruit_factory->getFruit("香蕉");
fruit->show();
std::shared_ptr<Factory> animal_factory = FactoryProducer::getFactory("动物");
std::shared_ptr<Animal> animal = animal_factory->getAnimal("小羊");
animal->voice();
animal = animal_factory->getAnimal("小狗");
animal->voice();
return 0;
}
抽象工厂模式适用于生产多个工厂系列产品衍生的设计模式,增加新的产品等级结构复杂,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,违背了“开闭原则”。
建造者模式
建造者模式是一种创建型设计模式,使用多个简单的对象一步一步构建成一个复杂的对象,能够将一个复杂的对象的构建与它的表示分离,提供一种创建对象的最佳方式。主要用于解决对象的构建过于复杂的问题。
建造者模式主要基于四个核心类实现:
- 抽象产品类
- 具体产品类:一个具体的产品对象类
- 抽象Builder类:创建一个产品对象所需的各个部件的抽象接口
- 具体产品的Builder类:实现抽象接口,构建各个部件
- 指挥者Director类:统一组建过程,提供给调用者使用,通过指挥者来构造产品
#include<iostream>
#include<memory>
/*抽象电脑类*/
class Computer{
public:
using ptr = std::shared_ptr<Computer>;
Computer(){}
void setBoard(const std::string &board) {_board = board;}
void setDisplay(const std::string &display) {_display = display;}
virtual void setOs() = 0;
std::string toString(){
std::string computer = "Computer:{\n";
computer += "\tboard=" + _board + ",\n";
computer += "\tdisplay" + _display + ",\n";
computer += "\tOs" +_os + ",\n";
computer += "}\n";
return computer;
}
protected:
std::string _board;
std::string _display;
std::string _os;
};
/*具体产品类*/
class MacBook : public Computer
{
public:
using ptr = std::shared_ptr<MacBook>;
MacBook(){}
virtual void setOS(){
_os = "Max Os X12";
}
};
/*抽象建造者类:包含创建⼀个产品对象的各个部件的抽象接⼝*/
class Builder {
public:
using ptr = std::shared_ptr<Builder>;
virtual void buildBoard(const std::string &board) = 0;
virtual void bulidDisplay(const std::string &display) = 0;
virtual void buildOs() = 0;
virtual Computer::ptr build() = 0;
};
/*具体产品的具体建造者类:实现抽象接口,构建和组装各个部件*/
class MacBookBuilder : public Builder{
public:
using ptr = std::shared_ptr<MacBookBuilder>;
MacBookBuilder():_computer(new MacBook()){}
virtual void buildBoard(const std::string &board){
_computer->setBoard(board);
}
virtual void buildDisplay(const std::string &display){
_computer->setDisplay(display);
}
virtual void buildOs(){
_computer->setOs();
}
virtual Computer::ptr build(){
return _computer;
}
private:
Computer::ptr _computer;
};
/*指挥者类,提供给调用者使用,通过指挥者来构造复杂产品*/
class Director{
public:
Director(Builder *builder):_builder(builder){}
void construct(const std::string &board,const std::string &display){
_builder->buildBoard(board);
_builder->bulidDisplay(display);
_builder->buildOs();
}
private:
Builder::ptr _builder;
};
int main()
{
Builder *builder = nem MacBookBuilder();
std::unique_ptr<Director> pd(new Director(builder));
pd->construct("英特尔主板","VOC显示器");
Computer::ptr computer = builder->build();
std::cout<<computer->toString();
return 0;
}
代理模式
代理模式指代理控制对其他对象的访问,也就是代理对象控制对原对象的引用。在某些情况下,一个对象不适合或者不能直接被引用访问,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式的结构包括一个是真正的你要访问的对象(目标类)、一个是代理对象。目标对象与代理对象实现同一个接口,先访问代理类再通过代理类访问目标对象。代理模式分为静态代理、动态代理:
- 静态代理指的是,在编译时就已经确定好了代理类和被代理类的关系。也就是说,在编译时就已经确定了代理类要代理的是哪个被代理类。
- 动态代理指的是,在运行时才动态生成代理类,并将其与被代理类绑定。这意味着,在运行时才能确定代理类要代理的是哪个被代理类。
以租房为例,房东将房子租出去,但是要租房子出去,需要发布招租启示,带人看房,负责维修,这些工作中有些操作并非房东能完成,因此房东为了图省事,将房子委托给中介进行租赁。代理模式实现:
/*房东要把⼀个房⼦通过中介租出去理解代理模式*/
#include<iostream>
#include<string>
class RentHouse{
public:
virtual void rentHouse = 0;
};
/*房东类:将房⼦租出去*/
class Landlord : public RentHouse{
public:
void rentHouse(){
std::cout<<"将房子租出去\n";
}
};
/*中介代理类:对租房⼦进⾏功能加强,实现租房以外的其他功能*/
class Intermediary : public RentHouse{
public:
void rentHouse{
std::cout<<"发布招租启示\n";
std::cout<<"带人看房\n";
_landlord.rentHouse();
std::cout<<"负责租后维修\n";
}
private:
Landlord _landlord;
};
int main()
{
Intermediary intermediary;
intermediary.rentHouse();
return 0;
}
7. 日志系统框架设计
本项目实现的是一个多日志器日志系统,主要实现的功能是让程序员能够轻松的将程序运行日志信息落地到指定的位置,且支持同步与异步两种方式的日志落地方式。
项目的框架设计将项目分为以下几个模块来实现。
7.1 模块划分
日志等级模块:对输出日志的等级进行划分,以便于控制日志的输出,并提供等级枚举转字符串功
能。
- OFF:关闭
- DEBUG:调试,调试时的关键信息输出。
- INFO:提示,普通的提示型日志信息。
- WARN:警告,不影响运行,但是需要注意一下的日志。
- ERROR:错误,程序运行出现错误的日志
- FATAL:致命,一般是代码异常导致程序无法继续推进运行的日志
日志消息模块:中间存储日志输出所需的各项要素信息
- 时间:描述本条日志的输出时间。
- 线程ID:描述本条日志是哪个线程输出的。
- 日志等级:描述本条日志的等级。
- 日志数据:本条日志的有效载荷数据。
- 日志文件名:描述本条日志在哪个源码文件中输出的。
- 日志行号:描述本条日志在源码文件的哪一行输出的。
日志消息格式化模块:设置日志输出格式,并提供对日志消息进行格式化功能。
- 系统的默认日志输出格式:%d{%H:%M:%S}%T[%t]%T[%p]%T[%c]%T%f:%l%T%m%n
- -> 13:26:32 [2343223321] [FATAL] [root] main.c:76 套接字创建失败\n
- %d{%H:%M:%S}:表示日期时间,花括号中的内容表示日期时间的格式。
- %T:表示制表符缩进。
- %t:表示线程ID
- %p:表示日志级别
- %c:表示日志器名称,不同的开发组可以创建自己的日志器进行日志输出,小组之间互不影
响。 - %f:表示日志输出时的源代码文件名。
- %l:表示日志输出时的源代码行号。
- %m:表示给与的日志有效载荷数据
- %n:表示换行
- 设计思想:设计不同的子类,不同的子类从日志消息中取出不同的数据进行处理。
日志消息落地模块:决定了日志的落地方向,可以是标准输出,也可以是日志文件,也可以滚动文件输出....
- 标准输出:表示将日志进行标准输出的打印。
- 日志文件输出:表示将日志写入指定的文件末尾。
- 滚动文件输出:当前以文件大小进行控制,当一个日志文件大小达到指定大小,则切换下一个
文件进行输出 - 后期,也可以扩展远程日志输出,创建客户端,将日志消息发送给远程的日志分析服务器。
- 设计思想:设计不同的子类,不同的子类控制不同的日志落地方向。
日志器模块:
- 此模块是对以上几个模块的整合模块,用户通过日志器进行日志的输出,有效降低用户的使用难度。
- 包含有:日志消息落地模块对象,日志消息格式化模块对象,日志输出等级
日志器管理模块:
- 为了降低项目开发的日志耦合,不同的项目组可以有自己的日志器来控制输出格式以及落地方向,因此本项目是一个多日志器的日志系统。
- 管理模块就是对创建的所有日志器进行统一管理。并提供一个默认日志器提供标准输出的日志输出
异步线程模块:
- 实现对日志的异步输出功能,用户只需要将输出日志任务放入任务池,异步线程负责日志的落地输出功能,以此提供更加高效的非阻塞日志输出。
7.2 模块关系图
8. 代码设计
8.1 实用类设计
提前完成一些零碎的功能接口,以便于项目中会用到。
- 获取系统时间
- 判断文件是否存在
- 获取文件的所在目录路径
- 创建目录
/*
通⽤功能类,与业务⽆关的功能实现
1. 获取系统时间
2. 获取⽂件⼤⼩
3. 创建⽬录
4. 获取⽂件所在⽬录
*/
#ifndef _M_UTIL_H_
#define _M_UTIL_M_
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <ctime>
#include <cassert>
#include <sys/stat.h>
namespace Mylogs
{
namespace util
{
class date
{
public:
static size_t now() { return (size_t)time(nullptr); }
};
class file
{
public:
static bool exists(const std::string &name)
{
struct stat st;
return stat(name.c_str(), &st) == 0;
}
static std::string path(const std::string &name)
{
if (name.empty())
return ".";
size_t pos = name.find_last_of("/\\");
if (pos == std::string::npos)
return ".";
return name.substr(0, pos + 1);
}
static void create_directory(const std::string &path)
{
if (path.empty())
return;
if (exists(path))
return;
size_t pos, idx = 0;
while (idx < path.size())
{
pos = path.find_first_of("/\\", idx);
if (pos == std::string::npos)
{
mkdir(path.c_str(), 0777);
return;
}
if (pos == idx)
{
idx = pos + 1;
continue;
}
std::string subdir = path.substr(0, pos);
if (subdir == "." || subdir == "..")
{
idx = pos + 1;
continue;
}
if (exists(subdir))
{
idx = pos + 1;
continue;
}
mkdir(subdir.c_str(), 0777);
idx = pos + 1;
}
}
};
}
}
#endif
8.2 日志等级类设计
日志等级总共分为7个等级,分别为:
- OFF 关闭所有日志输出
- DRBUG 进行debug时候打印日志的等级
- INFO 打印一些用户提示信息
- WARN 打印警告信息
- ERROR 打印错误信息
- FATAL 打印致命信息- 导致程序崩溃的信息
#ifndef _M_LEVEL_H_
#define _M_LEVEL_H_
namespace Mylogs
{
class LogLevel
{
public:
enum class value
{
UNKNOW = 0,
DEBUG,
INFO,
WARN,
ERROR,
FATAL,
OFF
};
static const char *toString(LogLevel::value level)
{
switch (level)
{
#define TOSTRING(name) #name
case LogLevel::value::DEBUG:
TOSTRING(DEBUG);
case LogLevel::value::INFO:
return TOSTRING(INFO);
case LogLevel::value::WARN:
return TOSTRING(WARN);
case LogLevel::value::ERROR:
return TOSTRING(ERROR);
case LogLevel::value::FATAL:
return TOSTRING(FATAL);
default:
return TOSTRING(UNKNOW);
#undef TOSTRING
}
}
};
}
#endif
8.3 日志消息类设计
日志消息类主要是封装一条完整的日志消息所需的内容,其中包括日志等级、对应的loggername、打印日志源文件的位置信息(包括文件名和行号)、线程ID、时间戳信息、具体的日志信息等内容。
#ifndef _M_MSG_H_
#define _M_MSG_H_
#include"util.hpp"
#include"level.hpp"
#include<thread>
#include<memory>
namespace Mylogs{
struct LogMsg
{
using ptr = std::share_ptr<LogMsg>;
size_t _line;
size_t _ctime;
std::thread::id _tid;
std::string _name;
std::string _file;
std::string _payload;
Loglevel::value _level;
LogMsg(std::string &name,std::string file,size_t line,std::string &&payload,
Loglevel::value level):_name(name),_file(file),_line(line),_level(level),_payload(std::move(payload)),
_ctime(util::data::now()),_tid(std::this_thread::get_id()){}
};
}
#endif
8.4 日志输出格式化类设计
日志格式化(Formatter)类主要负责格式化日志消息。其主要包含以下内容
pattern成员:保存日志输出的格式字符串。
- %d 日期
- %T 缩进
- %t 线程id
- %p 日志级别
- %c 日志器名称
- %f 文件名
- %l 行号
- %m 日志消息
- %n 换行
- std::vector<FormatItem::ptr> items成员:用于按序保存格式化字符串对应的子格式化对象。FormatItem类主要负责日志消息子项的获取及格式化。其包含以下子类
- MsgFormatItem :表示要从LogMsg中取出有效日志数据
- LevelFormatItem :表示要从LogMsg中取出日志等级
- NameFormatItem :表示要从LogMsg中取出日志器名称
- ThreadFormatItem :表示要从LogMsg中取出线程ID
- TimeFormatItem :表示要从LogMsg中取出时间戳并按照指定格式进行格式化
- CFileFormatItem :表示要从LogMsg中取出源码所在文件名
- CLineFormatItem :表示要从LogMsg中取出源码所在行号
- TabFormatItem :表示一个制表符缩进
- NLineFormatItem :表示一个换行
- OtherFormatItem :表示非格式化的原始字符串
示例:"[%d{%H:%M:%S}] %m%n"
pattern = "[%d{%H:%M:%S}] %m%n"
items = {
{OtherFormatItem(), "["},
{TimeFormatItem(), "%H:%M:%S"},
{OtherFormatItem(), "]"},
{MsgFormatItem (), ""},
{NLineFormatItem (), ""}
}
LogMsg msg = {
size_t _line = 22;
size_t _ctime = 12345678;
std::thread::id _tid = 0x12345678;
std::string _name = "logger";
std::string _file = "main.cpp";
std::string _payload = "创建套接字失败";
LogLevel::value _level = ERROR;
};
格式化的过程其实就是按次序从Msg中取出需要的数据进行字符串的连接的过程。
最终组织出来的格式化消息:"[22:32:54] 创建套接字失败\n"
代码实现:
#ifndef _M_FMT_H
#define _M_FMT_H
#include "util.hpp"
#include "level.hpp"
#include "message.hpp"
#include <memory>
#include <vector>
#include <tuple>
namespace Mylogs
{
class FormatItem
{
public:
using ptr = std::share_ptr<FormatItem>;
virtual ~FormatItem() {}
virtual void format(std::ostream &os, const LogMsg &msg) = 0;
};
class MsgFormatItem : public FormatItem
{
public:
MsgFormatItem(const std::string &str = "") {}
virtual void format(std::ostream &os, const LogMsg &msg)
{
os << msg._payload;
}
};
class LevelFormatItem : public FormatItem
{
public:
LevelFormatItem(const std::string &str = "") {}
virtual void format(std::ostream &os, const LogMsg &msg)
{
os << LogLevel::toString(msg._level);
}
};
class NameFormatItem : public FormatItem
{
public:
NameFormatItem(const std::string &str = "") {}
virtual void format(std::ostream &os, const LogMsg &msg)
{
os << msg._name;
}
};
class ThreadFormatItem : public FormatItem
{
public:
ThreadFormatItem(const std::string &str = "") {}
virtual void format(std::ostream &os, LogMsg &msg)
{
os << msg._tid;
}
};
class TimeFormatItem : public FormatItem
{
private:
std::string _format;
public:
TimeFormatItem(const std::string &format = "%H:%M:%S") : _format(format)
{
if (format.empty())
_format = "%H:%M:%S";
}
virtual void format(std::ostream &os, LogMsg &msg)
{
time_t t = msg._ctime;
struct tm lt;
localtime_r(&t, <);
char tmp[128];
strftime(tmp, 127, _format.str(), <);
os << tmp;
}
};
class CFileFormatItem : public FormatItem
{
public:
CFileFormatItem(const std::string &str = "") {}
virtual void format(std::ostream &os, const LogMsg &msg)
{
os << msg._file;
}
};
class CLineFormatItem : public FormatItem
{
public:
CLineFormatItem(const std::string &str = "") {}
virtual void format(std::ostream &os, const LogMsg &msg)
{
os << msg._line;
}
};
class TabFormatItem : public FormatItem
{
public:
TabFormatItem(const std::string &str = "") {}
virtual void format(std::ostream &os, const LogMsg &msg)
{
os << "\t";
}
};
class NLineFormatItem : public FormatItem
{
public:
NLineFormatItem(const std::string &str = "") {}
virtual void format(std::ostream &os, const LogMsg &msg)
{
os << "\n";
}
};
class OtherFormatItem : public FormatItem
{
private:
std::string _str;
public:
OtherFormatItem(const std::string &str = "") {}
virtual void format(std::ostream &os, const LogMsg msg)
{
os << _str;
}
};
class Formatter
{
public:
using ptr = std::share_ptr<Formatter>;
/*
%d 日期
%T 缩进
%t 线程id
%p 日志级别
%c 日志器名称
%f 文件名
%l 行号
%m 日志消息
%n 换行
*/
// 时间{年-月-日 时:分:秒}缩进 线程ID 缩进 [日志级别] 缩进 [日志名称] 缩进 文件名:行号 缩进 消息换行
Formatter(const std::string &pattern = "[%d{%H:%M:%S}][%t][%p][%c][%f:%l] %m%n") : _pattern(pattern)
{
assert(parsePattern());
}
const std::string pattern()
{
return _pattern;
}
std::string format(const LogMsg &msg)
{
std::stringstream ss;
for (auto &it : _items)
{
it->format(ss, msg);
}
return ss.str();
}
std::ostream &format(std::ostream os, const LogMsg &msg)
{
for (auto &it : _items)
{
it->format(os, msg);
}
return os;
}
FormatItem::ptr createItem(const std::string &fc, const std::string &subfmt)
{
if (fc == "m")
return FormatItem::ptr(new MsgFormatItem(subfmt));
if (fc == "p")
return FormatItem::ptr(new LevelFormatItem(subfmt));
if (fc == "c")
return FormatItem::ptr(new NameFormatItem(subfmt));
if (fc == "t")
return FormatItem::ptr(new ThreadFormatItem(subfmt));
if (fc == "n")
return FormatItem::ptr(new NLineFormatItem(subfmt));
if (fc == "d")
return FormatItem::ptr(new TimeFormatItem(subfmt));
if (fc == "f")
return FormatItem::ptr(new CFileFormatItem(subfmt));
if (fc == "l")
return FormatItem::ptr(new CLineFormatItem(subfmt));
if (fc == "T")
return FormatItem::ptr(new TabFormatItem(subfmt));
return FormatItem::ptr();
}
// pattern解析
bool parsePattern()
{
// 每个要素分为三部分:
// 格式化字符 : %d %T %p...
// 对应的输出子格式 : {%H:%M:%S}
// 对应数据的类型 : 0-表示原始字符串,也就是非格式化字符,1-表示格式化数据类型
// 默认格式 "%d{%H:%M:%S}%T%t%T[%p]%T[%c]%T%f:%l%T%m%n"
std::vector<std::tuple<std::string, std::string, int>> arry;
std::string format_key; // 存放%后的格式化字符
std::string format_val; // 存放格式化字符后边 {} 中的子格式字符串
std::string string_row; // 存放原始的非格式化字符
bool sub_format_error = false;
int pos = 0;
while (pos < _pattern.size())
{
if (_pattern[pos] != "%")
{
string_row.append(1, _pattern[pos++]);
continue;
}
if (pos + 1 < _pattern.size() && _pattern[pos + 1] == '%')
{
string_row.append(1, '%');
pos += 2;
continue;
}
if (string_row.empty() == false)
{
arry.push_back(std::make_tuple(string_row, "", 0));
string_row.clear();
}
// 当前位置是%字符位置
pos += 1; // pos指向格式化字符位置
if (pos < _pattern.size() && isalpha(_pattern[pos]))
{
format_key = _pattern[pos]; // 保存格式化字符
}
else
{
std::cout << &_pattern[pos - 1] << "位置附近格式错误!\n";
return false;
}
pos += 1; // pos指向格式化字符的下一个位置,判断是否包含有子格式 %d{%Y-%m-%d}
if (pos < _pattern.size() && _pattern[pos] == '{')
{
sub_format_error = true;
pos += 1; // pos指向花括号下一个字符处
while (pos < _pattern.size())
{
if (_pattern[pos] == '}')
{
sub_format_error = false;
pos += 1; // 让pos指向下一个字符
break;
}
format_val.append(1, _pattern[pos++]);
}
}
arry.push_back(std::make_tuple(format_key, format_val, 1));
format_key.clear();
format_val.clear();
}
if (sub_format_error)
{
std::cout << "{}对应错误\n";
return false;
}
if (string_row.empty() == false)
arry.push_back(std::make_tuple(string_row, "", 0));
if (format_key.empty() == false)
arry.push_back(std::make_tuple(format_key, format_val, 1));
for(auto &it : arry){
if(std::get<2>(it) == 0){
FormatItem::ptr fi(new OtherFormatItem(std::get<0>(it)));
_items.push_back(fi);
}
else{
FormatItem::ptr fi = createItem(std::get<0>(it),std::get<1>(it));
if(fi.get() == nullptr){
std::cout<<"没有对应的格式化字符: %"<<std::get<0>(it)<<std::endl;
return false;
}
_items.push_back(fi);
}
}
return true;
}
private:
std::string _pattern;
std::vector<FormatItem::ptr> _items;
};
}
#endif
8.5 日志落地(LogSink)类设计(简单工厂模式)
日志落地类主要负责落地日志消息到目的地。
它主要包括以下内容:
- Formatter日志格式化器:主要是负责格式化日志消息,
- mutex互斥锁:保证多线程日志落地过程中的线程安全,避免出现交叉输出的情况。
这个类支持可扩展,其成员函数log设置为纯虚函数,当我们需要增加一个log输出目标,可以增加一个类继承自该类并重写log方法实现具体的落地日志逻辑。
目前实现了三个不同方向上的日志落地:
- 标准输出:StdoutSink
- 固定文件:FileSink
- 滚动文件:RollSink
滚动日志文件输出的必要性:
- 由于机器磁盘空间有限,我们不可能一直无限地向一个文件中增加数据
- 如果一个日志文件体积太大,一方面是不好打开,另一方面是即时打开了由于包含数据巨
大,也不利于查找我们需要的信息 - 所以实际开发中会对单个日志文件的大小也会做一些控制,即当大小超过某个大小时(如1GB),我们就重新创建一个新的日志文件来滚动写日志。对于那些过期的日志,大部分
企业内部都有专门的运维人员去定时清理过期的日志,或者设置系统定时任务,定时清理过
期日志。
日志文件的滚动思想:
日志文件滚动的条件有两个:文件大小 和 时间。我们可以选择:
- 日志文件在大于 1GB 的时候会更换新的文件
- 每天定点滚动一个日志文件
本项目基于文件大小的判断滚动生成新的文件:
#ifndef _M_SINK_H_
#define _M_SINK_H_
#include"util.hpp"
#include"message.hpp"
#include"formatter.hpp"
#include<memory>
#include<mutex>
namespace Mylogs{
class LogSink{
public:
using ptr = std::shared_ptr<LogSink>;
LogSink(){}
virtual ~LogSink(){}
virtual void log(const char *data,size_t len) = 0;
};
class stdoutSink : public LogSink{
public:
using ptr = std::shared_ptr<stdoutSink>;
stdoutSink() = default;
void log(const char *data,size_t len){
std::cout.write(data,len);
}
};
class FileSink : public LogSink{
public:
using ptr = std::shared_ptr<FileSink>;
FileSink(const std::string &filename):_filename(filename){
util::file::create_directory(util::file::path(filename));
_ofs.open(_filename,std::ios::binary | std::ios::app);
assert(_ofs.is_open());
}
const std::string &file() {return _filename;}
void log(const char *data,size_t len){
_ofs.write((const char*)data,len);
if(_ofs.open() == false){
std::cout<<"日志输出文件失败\n";
}
}
private:
std::string _filename;
std::ofstream _ofs;
};
class RollSink : public LogSink{
public:
using ptr = std::shared_ptr<RollSink>:
RollSink(const std::string &basename,size_t _max_fsize):
_basename(basename),_max_fsize(_max_fsize),_cur_fsize(0){
util::file::create_directory(util::file::path(basename));
}
void log(const char *data,size_t len){
initLogFile();
_ofs.write(data,len);
if(_ofs.good() == false){
std::cout<<"输出日志文件失败\n";
}
_cur_fsize = 0;
}
private:
void initLogFile(){
if(_ofs.is_open() == false || _cur_fsize >= _max_fsize){
_ofs.close();
std::string name = createFilename();
_ofs.open(name,std::ios::binary | std::ios::app);
assert(_ofs.is_open());
_cur_fsize = 0;
return;
}
return;
}
std::string createFilename(){
time_t t = time(NULL);
struct tm lt;
localtime_r(&t,<);
std::stringstream ss;
ss <<_basename;
ss << lt.tm_year + 1000;
ss << lt.tm_mon + 1;
ss << lt.tm_mday;
ss << lt.tm_hour;
ss << lt.tm_min;
ss << lt.tm_sec;
ss << ".log";
return ss.str();
}
private:
std::string _basename;
std::ofstream _ofs;
size_t _max_fsize;
size_t _cur_fsize;
};
class SinkFactory{
public:
template<typename SinkType,typename ...Args>
static LogSink::ptr create(Args &&...args){
return std::make_shared<SinkType>(std::forward<args>...);
}
};
}
#endif
8.6 日志器类(Logger)设计(建造者模式)
日志器主要是用来和前端交互,当我们需要使用日志系统打印log的时候,只需要创建Logger对象,调用该对象debug、info、warn、error、fatal等方法输出自己想打印的日志即可,支持解析可变参数列表和输出格式,即可以做到像使用printf函数一样打印日志。
当前日志系统支持同步日志 & 异步日志两种模式,两个不同的日志器唯一不同的地方在于他们在日志的落地方式上有所不同:
同步日志器:直接对日志消息进行输出。
异步日志器:将日志消息放入缓冲区,由异步线程进行输出。
因此日志器类在设计的时候先设计出一个Logger基类,在Logger基类的基础上,继承出SyncLogger同步日志器和AsyncLogger异步日志器。
且因为日志器模块是对前边多个模块的整合,想要创建一个日志器,需要设置日志器名称,设置日志输出等级,设置日志器类型,设置日志输出格式,设置落地方向,且落地方向有可能存在多个,整个日志器的创建过程较为复杂,为了保持良好的代码风格,编写出优雅的代码,因此日志器的创建这里采用了建造者模式来进行创建。
#ifndef _M_LOG_H_
#define _M_log_h_
#include "util.hpp"
#include "level.hpp"
#include "formatter.hpp"
#include "sink.hpp"
#include "looper.hpp"
#include <vector>
#include <list>
#include <atomic>
#include <unordered_map>
#include <cstdarg>
#include <type_traits>
namespace Mylogs
{
class SyncLogger;
class AsyncLogger;
class Logger
{
public:
enum class Type
{
LOGGER_SYNC = 0,
LOGGER_ASYNC
};
using ptr = std::shared_ptr<Logger>;
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; }
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);
}
void info(const char *file, size_t line, const char *fmt, ...)
{
if (shouldLog(LogLevel::value::INFO) == false)
return;
va_list al;
va_start(al, fmt);
log(LogLevel::value::INFO, file, line, fmt, al);
}
void warn(const char *file, size_t line, const char *fmt, ...)
{
if (shouldLog(LogLevel::value::WARN) == false)
return;
va_list al;
va_start(al, fmt);
log(LogLevel::value::WARN, file, line, fmt, al);
va_end(al);
}
void error(const char *file, size_t line, const char *fmt, ...)
{
if (shouldLog(LogLevel::value::ERROR) == false)
return;
va_list al;
va_start(al, fmt);
log(LogLevel::value::ERROR, file, line, fmt, al);
va_end(al);
}
void fatal(const char *file, size_t line, const char *fmt, ...)
{
if (shouldLog(LogLevel::value::FATAL) == false)
return;
va_list al;
va_start(al, fmt);
log(LogLevel::value::FATAL, file, line, fmt, al);
va_end(al);
}
public:
class Builder
{
public:
using ptr = std::shared_ptr<Builder>;
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; }
template <typename SinkType, typename... Args>
void buildSink(Args &&...args)
{
auto sink = SinkFactory::create<SinkType>(std::forward<Args>(args)...);
_sinks.push_back(sink);
}
virtual Logger::ptr build() = 0;
protected:
Logger::Type _logger_type;
std::string _logger_name;
LogLevel::value _level;
Formatter::ptr _formatter;
std::vector<LogSink::ptr> _sinks;
};
protected:
bool shouldLog(LogLevel::value level) { return level >= _level; }
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()));
}
virtual void logIt(const std::string &msg) = 0;
protected:
std::mutex _mutex;
std::string _name;
Formatter::ptr _formatter;
std::atomic<LogLevel::value> _level;
std::vector<LogSink::ptr> _sinks;
};
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) : Logger(name, formatter, sinks, level)
{
std::cout << LogLevel::toString(level) << "同步日志器" << name << "创建成功...\n";
}
private:
virtual 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());
}
}
};
class AsycLogger : public Logger
{
public:
using ptr = std::shared_ptr<AsycLogger>;
AsycLogger(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(&AsycLogger::backendLogIt, this, std::placeholders::_1)))
{
std::cout << LogLevel::toString(level) << "异步日志器:" << name << "创建成功...\n";
}
protected:
virtual 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());
}
}
protected:
AsyncLooper::ptr _looper;
};
class LocalLoggerBuilder : public Logger::Builder
{
public:
virtual Logger::ptr build()
{
if (_logger_name.empty())
{
std::cout << "日志器名称不能为空\n";
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;
}
};
class loggerManager
{
private:
std::mutex _mutex;
Logger::ptr _root_logger;
std::unordered_map<std::string, Logger::ptr> _loggers;
private:
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));
}
loggerManager(const loggerManager &) = delete;
loggerManager &operator=(const loggerManager &) = delete;
public:
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;
}
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;
}
};
class GlobalLoggerBuilder : public Logger::Builder
{
public:
virtual Logger::ptr 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 (_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);
}
loggerManager::getInstance().addLogger(_logger_name, lp);
return lp;
}
};
}
#endif
8.7 双缓冲区异步任务处理器(AsyncLooper)设计
设计思想:异步处理线程 + 数据池
使用者将需要完成的任务添加到任务池中,由异步线程来完成任务的实际执行操作。
任务池的设计思想:双缓冲区阻塞数据池
优势:避免了空间的频繁申请释放,且尽可能的减少了生产者与消费者之间锁冲突的概率,提高了任务处理效率。
在任务池的设计中,有很多备选方案,比如循环队列等等,但是不管是哪⼀种都会涉及到锁冲突的情况,因为在生产者与消费者模型中,任何两个角色之间都具有互斥关系,因此每一次的任务添加与取出都有可能涉及锁的冲突,而双缓冲区不同,双缓冲区是处理器将一个缓冲区中的任务全部处理完毕后,然后交换两个缓冲区,重新对新的缓冲区中的任务进行处理,虽然同时多线程写入也会冲突,但是冲突并不会像每次只处理一条的时候频繁(减少了生产者与消费者之间的锁冲突),且不涉及到空间的频繁申请释放所带来的消耗。
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>
#include <functional>
#include <cassert>
namespace Mylogs
{
#define BUFFER_DEFAULT_SIZE (1 * 1024 * 1024)
#define BUFFER_INCREMENT_SIZE (1 * 1024 * 1024)
#define BUFFER_THRESHOLD_SIZE (10 * 1024 * 1024)
class Buffer
{
public:
Buffer() : _reader_idx(0), _writer_idx(0), _v(BUFFER_DEFAULT_SIZE) {}
bool empty() { return _reader_idx == _writer_idx; }
size_t readAbleSize() { return _writer_idx - _reader_idx; }
size_t writeAbleSize() { return _v.size() - _writer_idx; }
void reset() { _reader_idx = _writer_idx = 0; }
void swap(Buffer &buf)
{
_v.swap(buf._v);
std::swap(_reader_idx,buf._reader_idx);
std::swap(_writer_idx,buf._writer_idx);
}
void push(const char *data,size_t len){
assert(len <= writeAbleSize());
ensureEnoughSpace(len);
std::copy(data,data+len,&_v[_writer_idx]);
_writer_idx += len;
}
const char *begin() {return &_v[_writer_idx];}
void pop(size_t len){
_reader_idx += len;
assert(_reader_idx <= _writer_idx);
}
protected:
void ensureEnoughSpace(size_t len)
{
if (len <= writeAbleSize())
return;
/*每次增大1M大小*/
size_t new_capacity;
if (_v.size() < BUFFER_THRESHOLD_SIZE)
{
new_capacity = _v.size() * 2 + len;
}
else
{
// 线性增长
new_capacity = _v.size() + BUFFER_INCREMENT_SIZE + len;
}
_v.resize(new_capacity);
}
private:
size_t _reader_idx;
size_t _writer_idx;
std::vector<char> _v;
};
}
#ifndef _M_LOOP_H_
#define _M_LOOP_H_
#include"util.hpp"
#include<vector>
#include<thread>
#include<mutex>
#include<atomic>
#include<condition_variable>
#include<functional>
#include"buffer.hpp"
namespace Mylogs{
class AsyncLooper{
public:
using Functor = std::function<void(Buffer &buffer)>;
using ptr = std::shared_ptr<AsyncLooper>;
AsyncLooper(const Functor &cb):_running(true),_looper_callback(cb),
_thread(std::thread(&AsyncLooper::worker_loop,this)){
}
~AsyncLooper() { stop();}
void stop(){
_running = false;
_pop_cond.notify_all();
_thread.join();
}
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();
}
private:
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;
}
private:
Functor _looper_callback;
private:
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;
};
}
#endif
8.8 异步日志器(AsyncLogger)设计
异步日志器类继承自日志器类,并在同步日志器类上拓展了异步消息处理器。当我们需要异步输出日志的时候,需要创建异步日志器和消息处理器,调用异步日志器的log、error、info、fatal等函数输出不同级别日志。
- log函数为重写Logger类的函数,主要实现将日志数据加入异步队列缓冲区中
- realLog函数主要由异步线程进行调用(是为异步消息处理器设置的回调函数),完成日志的实际落地工作。
class AsycLogger : public Logger
{
public:
using ptr = std::shared_ptr<AsycLogger>;
AsycLogger(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(&AsycLogger::backendLogIt, this, std::placeholders::_1)))
{
std::cout << LogLevel::toString(level) << "异步日志器:" << name << "创建成功...\n";
}
protected:
virtual 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());
}
}
protected:
AsyncLooper::ptr _looper;
};
8.9 单例日志器管理类设计(单例模式)
日志的输出,我们希望能够在任意位置都可以进行,但是当我们创建了一个日志器之后,就会受到日志器所在作用域的访问属性限制。
因此,为了突破访问区域的限制,我们创建一个日志器管理类,且这个类是一个单例类,这样的话,我们就可以在任意位置来通过管理器单例获取到指定的日志器来进行日志输出了。
基于单例日志器管理器的设计思想,我们对于日志器建造者类进行继承,继承出一个全局日志器建造者类,实现一个日志器在创建完毕后,直接将其添加到单例的日志器管理器中,以便于能够在任何位置通过日志器名称能够获取到指定的日志器进行日志输出。
class loggerManager
{
private:
std::mutex _mutex;
Logger::ptr _root_logger;
std::unordered_map<std::string, Logger::ptr> _loggers;
private:
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));
}
loggerManager(const loggerManager &) = delete;
loggerManager &operator=(const loggerManager &) = delete;
public:
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;
}
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;
}
};
class GlobalLoggerBuilder : public Logger::Builder
{
public:
virtual Logger::ptr 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 (_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);
}
loggerManager::getInstance().addLogger(_logger_name, lp);
return lp;
}
};
8.10 日志宏&全局接口设计(代理模式)
提供全局的日志器获取接口。
使用代理模式通过全局函数或宏函数来代理Logger类的log、debug、info、warn、error、fatal等接口,以便于控制源码文件名称和行号的输出控制,简化用户操作。
当仅需标准输出日志的时候可以通过主日志器来打印日志。且操作时只需要通过宏函数直接进行输出即可。
#ifndef _M_MY_H_
#define _M_MY_H
#include"logger.hpp"
namespace Mylogs{
//提供获取指定日志器的全局接口(避免用户自己操作单例对象)
Logger::ptr getLogger(const std::string &name){
return loggerManager::getInstance().getLogger(name);
}
Logger::ptr rootLogger(){
return loggerManager::getInstance().rootLogger();
}
//使用宏函数对日志器的接口进行代理(代理模式)
#define debug(fmt,...) debug(__FILE__,__LINE__,fmt,##__VA_ARGS__)
#define info(fmt,...) info(__FILE__,__LINE__,fmt,##__VA_ARGS__)
#define warn(fmt,...) warn(__FILE__,__LINE__,fmt,##__VA_ARGS__)
#define error(fmt,...) error(__FILE__,__LINE__,fmt,##__VA_ARGS__)
#define fatal(fmt,...) fatal(__FILE__,__LINE__,fmt,##__VA_ARGS__)
//提供宏函数,直接通过默认日志器进行日志的标准输出打印(不用获取日志器了)
#define LOG_DEBUG(logger,fmt,...) (Logger)->debug(fmt,##__VA_ARGS__)
#define LOG_INFO(logger,fmt,...) (Logger)->info(fmt,##__VA_ARGS__)
#define LOG_WARN(logger,fmt,...) (Logger)->warn(fmt,##__VA_ARGS__)
#define LOG_ERROR(logger,fmt,...) (Logger)->error(fmt,##__VA_ARGS__)
#define LOG_FATAL(logger,fmt,...) (Logger)->fatal(fmt,##__VA_ARGS__)
#define LOGD(fmt,...) LOG_DEBUG(Mylogs::rootLogger(),fmt,##__VA_ARGS__)
#define LOGI(fmt,...) LOG_INFO(Mylogs::rootLogger(),fmt,##__VA_ARGS__)
#define LOGW(fmt,...) LOG_WARN(Mylogs::rootLogger(),fmt,##__VA_ARGS__)
#define LOGE(fmt,...) LOG_ERROR(Mylogs::rootLogger(),fmt,##__VA_ARGS__)
#define LOGF(fmt,...) LOG_FATAL(Mylogs::rootLogger(),fmt,##__VA_ARGS__)
}
#endif
9. 功能用例
#include "Mylog.h"
void loggerTest(const std::string &logger_name) {
bitlog::Logger::ptr lp = bitlog::getLogger(logger_name);
assert(lp.get());
LOGF("------------example--------------------");
lp->debug("%s", "logger->debug");
lp->info("%s", "logger->info");
lp->warn("%s", "logger->warn");
lp->error("%s", "logger->error");
lp->fatal("%s", "logger->fatal");
LOG_DEBUG(lp, "%s", "LOG_DEBUG");
LOG_INFO(lp, "%s", "LOG_INFO");
LOG_WARN(lp, "%s", "LOG_WARN");
LOG_ERROR(lp, "%s", "LOG_ERROR");
LOG_FATAL(lp, "%s", "LOG_FATAL");
LOGF("---------------------------------------");
std::string log_msg = "hello bitejiuyeke-";
size_t fsize = 0;
size_t count = 0;
while(count < 1000000) {
std::string msg = log_msg + std::to_string(count++);
lp->error("%s", msg.c_str());
}
}
int main(int argc, char *argv[])
{
//实例化全局⽇志器建造者
bitlog::GlobalLoggerBuilder::ptr lbp(new bitlog::GlobalLoggerBuilder);
lbp->buildLoggerName("stdout_and_file_logger"); //设置⽇志器名称
lbp->buildFormatter("[%d][%c][%f:%l][%p] %m%n"); //设置⽇志输出格式
lbp->buildLoggerLevel(bitlog::LogLevel::value::DEBUG); //设置⽇志限制输出等级
lbp->buildSink<bitlog::StdoutSink>(); //创建⼀个标准输出的落地⽅向
lbp->buildSink<bitlog::FileSink>("./logs/sync.log"); //创建⼀个⽂件落地⽅向
lbp->buildSink<bitlog::RollSink>("./logs/roll-", 10 * 1024 * 1024); //创建滚
动⽇志落地⽅向
lbp->buildLoggerType(Mylog::Logger::Type::LOGGER_SYNC); //设置⽇志器类型为同步
⽇志
lbp->build(); //建造⽇志器
loggerTest("stdout_and_file_logger");
return 0;
}
10. 功能测试:
测试一个日志器中包含有所有的落地方向,观察是否每个方向都正常落地,分别测试同步方式和异步方式落地后数据是否正常。
#include "Mylog.h"
#include "bench.h"
#include <unistd.h>
void loggerTest(const std::string &logger_name) {
bitlog::Logger::ptr lp = Mylog::getLogger(logger_name);
assert(lp.get());
LOGF("------------example--------------------");
lp->debug("%s", "logger->debug");
lp->info("%s", "logger->info");
lp->warn("%s", "logger->warn");
lp->error("%s", "logger->error");
lp->fatal("%s", "logger->fatal");
LOG_DEBUG(lp, "%s", "LOG_DEBUG");
LOG_INFO(lp, "%s", "LOG_INFO");
LOG_WARN(lp, "%s", "LOG_WARN");
LOG_ERROR(lp, "%s", "LOG_ERROR");
LOG_FATAL(lp, "%s", "LOG_FATAL");
LOGF("---------------------------------------");
std::string log_msg = "hello bitejiuyeke-";
size_t fsize = 0;
size_t count = 0;
while(count < 1000000) {
std::string msg = log_msg + std::to_string(count++);
lp->error("%s", msg.c_str());
}
}
void functional_test() {
bitlog::GlobalLoggerBuilder::ptr lbp(new Mylog::GlobalLoggerBuilder);
lbp->buildLoggerName("stdout_and_file_logger");
lbp->buildFormatter("[%d][%c][%f:%l][%p] %m%n");
lbp->buildLoggerLevel(Mylog::LogLevel::value::DEBUG);
lbp->buildSink<Mylog::StdoutSink>();
lbp->buildSink<Mylog::FileSink>("./logs/sync.log");
lbp->buildSink<Mylog::RollSink>("./logs/roll-", 10 * 1024 * 1024);
lbp->buildLoggerType(Mylog::Logger::Type::LOGGER_ASYNC);
lbp->build();
loggerTest("stdout_and_file_logger");
}
int main(int argc, char *argv[])
{
functional_test();
return 0;
}
11. 性能测试
下面对日志系统做一个性能测试,测试一下平均每秒能打印多少条日志消息到文件。
主要的测试方法是:每秒能打印日志数 = 打印日志条数 / 总的打印日志消耗时间
主要测试要素:同步/异步 & 单线程/多线程
- 100w+条指定长度的日志输出所耗时间
- 每秒可以输出多少条日志
- 每秒可以输出多少MB日志
测试环境:
- CPU:AMD Ryzen 7 5800H with Radeon Graphics 3.20 GHz
- RAM:16G DDR4 3200
- ROM:512G-SSD
- OS:ubuntu-22.04TLS虚拟机(2CPU核心/4G内存)
#ifndef __M_BENCH_H__
#define __M_BENCH_H__
#include "Mylog.h"
#include <chrono>
namespace Mylog {
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";
std::cout << " 平均:" << (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;
}
}
#endif
#include "bitlog.h"
#include "bench.h"
#include <unistd.h>
void sync_bench_thread_log(size_t thread_count, size_t msg_count, size_t msglen)
{
static int num = 1;
std::string logger_name = "sync_bench_logger" + std::to_string(num++);
LOGI("************************************************");
LOGI("同步⽇志测试: %d threads, %d messages", thread_count, msg_count);
bitlog::GlobalLoggerBuilder::ptr lbp(new bitlog::GlobalLoggerBuilder);
lbp->buildLoggerName(logger_name);
lbp->buildFormatter("%m%n");
lbp->buildSink<bitlog::FileSink>("./logs/sync.log");
lbp->buildLoggerType(bitlog::Logger::Type::LOGGER_SYNC);
lbp->build();
bitlog::bench(logger_name, thread_count, msglen, msg_count);
LOGI("************************************************");
}
void async_bench_thread_log(size_t thread_count, size_t msg_count, size_t msglen)
{
static int num = 1;
std::string logger_name = "async_bench_logger" + std::to_string(num++);
LOGI("************************************************");
LOGI("异步⽇志测试: %d threads, %d messages", thread_count, msg_count);
bitlog::GlobalLoggerBuilder::ptr lbp(new bitlog::GlobalLoggerBuilder);
lbp->buildLoggerName(logger_name);
lbp->buildFormatter("%m");
lbp->buildSink<bitlog::FileSink>("./logs/async.log");
lbp->buildLoggerType(bitlog::Logger::Type::LOGGER_ASYNC);
lbp->build();
bitlog::bench(logger_name, thread_count, msglen, msg_count);
LOGI("************************************************");
}
void bench_test() {
// 同步写⽇志
sync_bench_thread_log(1, 1000000, 100);
sync_bench_thread_log(5, 1000000, 100);
/*异步⽇志输出,为了避免因为等待落地影响时间所以⽇志数量降低为⼩于缓冲区⼤⼩进⾏测试*/
async_bench_thread_log(1, 100000, 100);
async_bench_thread_log(5, 100000, 100);
}
int main(int argc, char *argv[])
{
bench_test();
return 0;
}
1 dev@bite:~/logger-v2$ ./logger
2 当前⽇志器:root 未检测到⽇志格式,默认设置为[
%d{%H:%M:%S}%T%t%T[%p]%T[%c]%T%f:%l%T%m%n ]!
3 当前⽇志器:root 未检测到落地⽅向,默认设置为标准输出!
4 DEBUG 同步⽇志器: root创建成功...
5 [09:48:34][140319692223424][INFO][root][logger.cc:62]
**************************************
6 [09:48:34][140319692223424][INFO][root][logger.cc:63] 异步⽇志测试: 1 threads,
10000000 messages
7 DEBUG异步⽇志器: async_bench_logger1创建成功...
8 输⼊线程数量: 1
9 输出⽇志数量: 10000000
10 输出⽇志⼤⼩: 976562KB
11 线程0耗时: 22.8947s 平均:436782/s
12 总消耗时间: 22.8947
13 平均每秒输出: 436782条⽇志
14 平均每秒输出: 41MB
15 [09:48:57][140319692223424][INFO][root][logger.cc:72]
**************************************
16 [09:48:57][140319692223424][INFO][root][logger.cc:62]
**************************************
17 [09:48:57][140319692223424][INFO][root][logger.cc:63] 异步⽇志测试: 5 threads,
10000000 messages
18 DEBUG异步⽇志器: async_bench_logger2创建成功...
19 输⼊线程数量: 5
20 输出⽇志数量: 10000000
21 输出⽇志⼤⼩: 976562KB
22 线程3耗时: 7.3118s 平均:273530/s
23 线程1耗时: 7.46903s 平均:267772/s
24 线程0耗时: 7.48037s 平均:267366/s
25 线程2耗时: 7.59597s 平均:263297/s
26 线程4耗时: 7.60846s 平均:262865/s
27 总消耗时间: 7.60846
28 平均每秒输出: 1314325条⽇志
29 平均每秒输出: 125MB
30 [09:49:05][140319692223424][INFO][root][logger.cc:72]
**************************************
31 [09:49:05][140319692223424][INFO][root][logger.cc:46]
**************************************
32 [09:49:05][140319692223424][INFO][root][logger.cc:47] 同步⽇志测试: 1 threads,
10000000 messages
33 DEBUG 同步⽇志器: sync_bench_logger1创建成功...
34 输⼊线程数量: 1
35 输出⽇志数量: 10000000
36 输出⽇志⼤⼩: 976562KB
37 线程0耗时: 8.55658s 平均:1168691/s
38 总消耗时间: 8.55658
39 平均每秒输出: 1168691条⽇志
40 平均每秒输出: 111MB
41 [09:49:14][140319692223424][INFO][root][logger.cc:56]
**************************************
42 [09:49:14][140319692223424][INFO][root][logger.cc:46]
**************************************
43 [09:49:14][140319692223424][INFO][root][logger.cc:47] 同步⽇志测试: 5 threads,
10000000 messages
44 DEBUG 同步⽇志器: sync_bench_logger2创建成功...
45 输⼊线程数量: 5
46 输出⽇志数量: 10000000
47 输出⽇志⼤⼩: 976562KB
48 线程1耗时: 9.42852s 平均:212122/s
49 线程3耗时: 9.56269s 平均:209146/s
50 线程4耗时: 9.62333s 平均:207828/s
51 线程2耗时: 9.68728s 平均:206456/s
52 线程0耗时: 9.71674s 平均:205830/s
53 总消耗时间: 9.71674
54 平均每秒输出: 1029151条⽇志
55 平均每秒输出: 98MB
56 [09:49:23][140319692223424][INFO][root][logger.cc:56]
**************************************
能够通过上边的测试看出来,一些情况:
在单线程情况下,异步效率看起来还没有同步高,这个我们得了解,现在的IO操作在用户态都会有缓冲区进行缓冲区,因此我们当前测试用例看起来的同步其实大多时候也是在操作内存,只有在缓冲区满了才会涉及到阻塞写磁盘操作,而异步单线程效率看起来低,也有⼀个很重要的原因就是单线程同步操作中不存在锁冲突,而单线程异步日志操作存在大量的锁冲突,因此性能也会有⼀定的降低。
但是,我们也要看到限制同步日志效率的最大原因是磁盘性能,打日志的线程多少并无明显区别,线程多了反而会降低,因为增加了磁盘的读写争抢,而对于异步日志的限制,并非磁盘的性能,而是cpu的处理性能,打日志并不会因为落地而阻塞,因此在多线程打日志的情况下性能有了显著的提高。
12. 扩展
丰富sink类型:
- 支持按小时按天滚动文件
- 支持将log通过网络传输落地到日志服务器(tcp/udp)
- 支持在控制台通过日志等级渲染不同颜色输出方便定位
- 支持落地日志到数据库
- 支持配置服务器地址,将日志落地到远程服务器
实现日志服务器负责存储日志并提供检索、分析、展示等功能
13.相关代码文件
util.hpp:
/*
通⽤功能类,与业务⽆关的功能实现
1. 获取系统时间
2. 获取⽂件⼤⼩
3. 创建⽬录
4. 获取⽂件所在⽬录
*/
#ifndef _M_UTIL_H_
#define _M_UTIL_M_
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <ctime>
#include <cassert>
#include <sys/stat.h>
namespace Mylogs
{
namespace util
{
class Date
{
public:
static size_t now() { return (size_t)time(nullptr); }
};
class file
{
public:
static bool exists(const std::string &name)
{
struct stat st;
return stat(name.c_str(), &st) == 0;
}
static std::string path(const std::string &name)
{
if (name.empty())
return ".";
size_t pos = name.find_last_of("/\\");
if (pos == std::string::npos)
return ".";
return name.substr(0, pos + 1);
}
static void create_directory(const std::string &path)
{
if (path.empty())
return;
if (exists(path))
return;
size_t pos, idx = 0;
while (idx < path.size())
{
pos = path.find_first_of("/\\", idx);
if (pos == std::string::npos)
{
mkdir(path.c_str(), 0777);
return;
}
if (pos == idx)
{
idx = pos + 1;
continue;
}
std::string subdir = path.substr(0, pos);
if (subdir == "." || subdir == "..")
{
idx = pos + 1;
continue;
}
if (exists(subdir))
{
idx = pos + 1;
continue;
}
mkdir(subdir.c_str(), 0777);
idx = pos + 1;
}
}
};
}
}
#endif
message.hpp:
#ifndef _M_MSG_H_
#define _M_MSG_H_
//#include"util.hpp"
#include"level.hpp"
#include<thread>
#include<memory>
namespace Mylogs{
struct LogMsg
{
using ptr = std::shared_ptr<LogMsg>;
size_t _line;
size_t _ctime;
std::thread::id _tid;
std::string _name;
std::string _file;
std::string _payload;
LogLevel::value _level;
LogMsg(const std::string &name,std::string file,size_t line,std::string &&payload,
LogLevel::value level):_name(name),_file(file),_line(line),_level(level),_payload(std::move(payload)),
_ctime(util::Date::now()),_tid(std::this_thread::get_id()){}
};
}
#endif
formatter.hpp:
#ifndef _M_FMT_H
#define _M_FMT_H
#include "level.hpp"
#include "message.hpp"
#include <memory>
#include <vector>
#include <tuple>
namespace Mylogs
{
class FormatItem
{
public:
using ptr = std::shared_ptr<FormatItem>;
virtual ~FormatItem() {}
virtual void format(std::ostream &os, const LogMsg &msg) = 0;
};
class MsgFormatItem : public FormatItem
{
public:
MsgFormatItem(const std::string &str = "") {}
virtual void format(std::ostream &os, const LogMsg &msg)
{
os << msg._payload;
}
};
class LevelFormatItem : public FormatItem
{
public:
LevelFormatItem(const std::string &str = "") {}
virtual void format(std::ostream &os, const LogMsg &msg)
{
os << LogLevel::toString(msg._level);
}
};
class NameFormatItem : public FormatItem
{
public:
NameFormatItem(const std::string &str = "") {}
virtual void format(std::ostream &os, const LogMsg &msg)
{
os << msg._name;
}
};
class ThreadFormatItem : public FormatItem
{
public:
ThreadFormatItem(const std::string &str = "") {}
virtual void format(std::ostream &os, const LogMsg &msg)
{
os << msg._tid;
}
};
class TimeFormatItem : public FormatItem
{
private:
std::string _format;
public:
TimeFormatItem(const std::string &format = "%H:%M:%S") : _format(format)
{
if (format.empty()){
_format = "%H:%M:%S";
}
}
virtual void format(std::ostream &os, const LogMsg &msg)
{
time_t t = msg._ctime;
struct tm lt;
localtime_r(&t, <);
char tmp[128];
strftime(tmp, 127, _format.c_str(), <);
os << tmp;
}
};
class CFileFormatItem : public FormatItem
{
public:
CFileFormatItem(const std::string &str = "") {}
virtual void format(std::ostream &os, const LogMsg &msg)
{
os << msg._file;
}
};
class CLineFormatItem : public FormatItem
{
public:
CLineFormatItem(const std::string &str = "") {}
virtual void format(std::ostream &os, const LogMsg &msg)
{
os << msg._line;
}
};
class TabFormatItem : public FormatItem
{
public:
TabFormatItem(const std::string &str = "") {}
virtual void format(std::ostream &os, const LogMsg &msg)
{
os << "\t";
}
};
class NLineFormatItem : public FormatItem
{
public:
NLineFormatItem(const std::string &str = "") {}
virtual void format(std::ostream &os, const LogMsg &msg)
{
os << "\n";
}
};
class OtherFormatItem : public FormatItem
{
private:
std::string _str;
public:
OtherFormatItem(const std::string &str = ""):_str(str) {}
virtual void format(std::ostream &os, const LogMsg &msg)
{
os << _str;
}
};
class Formatter
{
public:
using ptr = std::shared_ptr<Formatter>;
/*
%d 日期
%T 缩进
%t 线程id
%p 日志级别
%c 日志器名称
%f 文件名
%l 行号
%m 日志消息
%n 换行
*/
// 时间{年-月-日 时:分:秒}缩进 线程ID 缩进 [日志级别] 缩进 [日志名称] 缩进 文件名:行号 缩进 消息换行
Formatter(const std::string &pattern = "[%d{%H:%M:%S}][%t][%p][%c][%f:%l] %m%n") : _pattern(pattern)
{
assert(parsePattern());
}
const std::string pattern()
{
return _pattern;
}
std::string format(const LogMsg &msg)
{
std::stringstream ss;
for (auto &it : _items)
{
it->format(ss, msg);
}
return ss.str();
}
std::ostream &format(std::ostream& os, const LogMsg &msg)
{
for (auto &it : _items)
{
it->format(os, msg);
}
return os;
}
FormatItem::ptr createItem(const std::string &fc, const std::string &subfmt)
{
if (fc == "m")
return FormatItem::ptr(new MsgFormatItem(subfmt));
if (fc == "p")
return FormatItem::ptr(new LevelFormatItem(subfmt));
if (fc == "c")
return FormatItem::ptr(new NameFormatItem(subfmt));
if (fc == "t")
return FormatItem::ptr(new ThreadFormatItem(subfmt));
if (fc == "n")
return FormatItem::ptr(new NLineFormatItem(subfmt));
if (fc == "d")
return FormatItem::ptr(new TimeFormatItem(subfmt));
if (fc == "f")
return FormatItem::ptr(new CFileFormatItem(subfmt));
if (fc == "l")
return FormatItem::ptr(new CLineFormatItem(subfmt));
if (fc == "T")
return FormatItem::ptr(new TabFormatItem(subfmt));
return FormatItem::ptr();
}
// pattern解析
bool parsePattern()
{
// 每个要素分为三部分:
// 格式化字符 : %d %T %p...
// 对应的输出子格式 : {%H:%M:%S}
// 对应数据的类型 : 0-表示原始字符串,也就是非格式化字符,1-表示格式化数据类型
// 默认格式 "%d{%H:%M:%S}%T%t%T[%p]%T[%c]%T%f:%l%T%m%n"
std::vector<std::tuple<std::string, std::string, int>> arry;
std::string format_key; // 存放%后的格式化字符
std::string format_val; // 存放格式化字符后边 {} 中的子格式字符串
std::string string_row; // 存放原始的非格式化字符
bool sub_format_error = false;
int pos = 0;
while (pos < _pattern.size())
{
if (_pattern[pos] != '%')
{
string_row.append(1, _pattern[pos++]);
continue;
}
if (pos + 1 < _pattern.size() && _pattern[pos + 1] == '%')
{
string_row.append(1, '%');
pos += 2;
continue;
}
if (string_row.empty() == false)
{
arry.push_back(std::make_tuple(string_row, "", 0));
string_row.clear();
}
// 当前位置是%字符位置
pos += 1; // pos指向格式化字符位置
if (pos < _pattern.size() && isalpha(_pattern[pos]))
{
format_key = _pattern[pos]; // 保存格式化字符
}
else
{
std::cout << &_pattern[pos - 1] << "位置附近格式错误!\n";
return false;
}
pos += 1; // pos指向格式化字符的下一个位置,判断是否包含有子格式 %d{%Y-%m-%d}
if (pos < _pattern.size() && _pattern[pos] == '{')
{
sub_format_error = true;
pos += 1; // pos指向花括号下一个字符处
while (pos < _pattern.size())
{
if (_pattern[pos] == '}')
{
sub_format_error = false;
pos += 1; // 让pos指向下一个字符
break;
}
format_val.append(1, _pattern[pos++]);
}
}
arry.push_back(std::make_tuple(format_key, format_val, 1));
format_key.clear();
format_val.clear();
}
if (sub_format_error)
{
std::cout << "{}对应错误\n";
return false;
}
if (string_row.empty() == false)
arry.push_back(std::make_tuple(string_row, "", 0));
if (format_key.empty() == false)
arry.push_back(std::make_tuple(format_key, format_val, 1));
for(auto &it : arry){
if(std::get<2>(it) == 0){
FormatItem::ptr fi(new OtherFormatItem(std::get<0>(it)));
_items.push_back(fi);
}
else{
FormatItem::ptr fi = createItem(std::get<0>(it),std::get<1>(it));
if(fi.get() == nullptr){
std::cout<<"没有对应的格式化字符: %"<<std::get<0>(it)<<std::endl;
return false;
}
_items.push_back(fi);
}
}
return true;
}
private:
std::string _pattern;
std::vector<FormatItem::ptr> _items;
};
}
#endif
level.hpp:
#ifndef _M_LEVEL_H_
#define _M_LEVEL_H_
namespace Mylogs
{
class LogLevel
{
public:
enum class value
{
UNKNOW = 0,
DEBUG,
INFO,
WARN,
ERROR,
FATAL,
OFF
};
static const char *toString(LogLevel::value level)
{
switch (level)
{
#define TOSTRING(name) #name
case LogLevel::value::DEBUG:
TOSTRING(DEBUG);
case LogLevel::value::INFO:
return TOSTRING(INFO);
case LogLevel::value::WARN:
return TOSTRING(WARN);
case LogLevel::value::ERROR:
return TOSTRING(ERROR);
case LogLevel::value::FATAL:
return TOSTRING(FATAL);
default:
return TOSTRING(UNKNOW);
#undef TOSTRING
}
}
};
}
#endif
sink.hpp:
#ifndef _M_SINK_H_
#define _M_SINK_H_
#include"message.hpp"
#include"formatter.hpp"
#include<memory>
#include<mutex>
namespace Mylogs{
class LogSink{
public:
using ptr = std::shared_ptr<LogSink>;
LogSink(){}
virtual ~LogSink(){}
virtual void log(const char *data,size_t len) = 0;
};
class StdoutSink : public LogSink{
public:
using ptr = std::shared_ptr<StdoutSink>;
StdoutSink() = default;
void log(const char *data,size_t len){
std::cout.write(data,len);
}
};
class FileSink : public LogSink{
public:
using ptr = std::shared_ptr<FileSink>;
FileSink(const std::string &filename):_filename(filename){
util::file::create_directory(util::file::path(filename));
_ofs.open(_filename,std::ios::binary | std::ios::app);
assert(_ofs.is_open());
}
const std::string &file() {return _filename;}
void log(const char *data,size_t len){
_ofs.write((const char*)data,len);
if(_ofs.is_open() == false){
std::cout<<"日志输出文件失败\n";
}
}
private:
std::string _filename;
std::ofstream _ofs;
};
class RollSink : public LogSink{
public:
using ptr = std::shared_ptr<RollSink>;
RollSink(const std::string &basename,size_t _max_fsize):
_basename(basename),_max_fsize(_max_fsize),_cur_fsize(0){
util::file::create_directory(util::file::path(basename));
}
void log(const char *data,size_t len){
initLogFile();
_ofs.write(data,len);
if(_ofs.good() == false){
std::cout<<"输出日志文件失败\n";
}
_cur_fsize = 0;
}
private:
void initLogFile(){
if(_ofs.is_open() == false || _cur_fsize >= _max_fsize){
_ofs.close();
std::string name = createFilename();
_ofs.open(name,std::ios::binary | std::ios::app);
assert(_ofs.is_open());
_cur_fsize = 0;
return;
}
return;
}
std::string createFilename(){
time_t t = time(NULL);
struct tm lt;
localtime_r(&t,<);
std::stringstream ss;
ss <<_basename;
ss << lt.tm_year + 1000;
ss << lt.tm_mon + 1;
ss << lt.tm_mday;
ss << lt.tm_hour;
ss << lt.tm_min;
ss << lt.tm_sec;
ss << ".log";
return ss.str();
}
private:
std::string _basename;
std::ofstream _ofs;
size_t _max_fsize;
size_t _cur_fsize;
};
class SinkFactory{
public:
template<typename SinkType,typename ...Args>
static LogSink::ptr create(Args&& ...args){
return std::make_shared<SinkType>(std::forward<Args>(args)...);
}
};
}
#endif
loggger.hpp:
#ifndef _M_LOG_H_
#define _M_log_h_
#include "level.hpp"
#include "formatter.hpp"
#include "sink.hpp"
#include "looper.hpp"
//#include "buffer.hpp"
#include <vector>
#include <list>
#include <atomic>
#include <unordered_map>
#include <cstdarg>
#include <type_traits>
namespace Mylogs
{
class SyncLogger;
class AsyncLogger;
class Logger
{
public:
enum class Type
{
LOGGER_SYNC = 0,
LOGGER_ASYNC
};
using ptr = std::shared_ptr<Logger>;
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; }
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);
}
void info(const char *file, size_t line, const char *fmt, ...)
{
if (shouldLog(LogLevel::value::INFO) == false)
return;
va_list al;
va_start(al, fmt);
log(LogLevel::value::INFO, file, line, fmt, al);
}
void warn(const char *file, size_t line, const char *fmt, ...)
{
if (shouldLog(LogLevel::value::WARN) == false)
return;
va_list al;
va_start(al, fmt);
log(LogLevel::value::WARN, file, line, fmt, al);
va_end(al);
}
void error(const char *file, size_t line, const char *fmt, ...)
{
if (shouldLog(LogLevel::value::ERROR) == false)
return;
va_list al;
va_start(al, fmt);
log(LogLevel::value::ERROR, file, line, fmt, al);
va_end(al);
}
void fatal(const char *file, size_t line, const char *fmt, ...)
{
if (shouldLog(LogLevel::value::FATAL) == false)
return;
va_list al;
va_start(al, fmt);
log(LogLevel::value::FATAL, file, line, fmt, al);
va_end(al);
}
public:
class Builder
{
public:
using ptr = std::shared_ptr<Builder>;
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; }
template <typename SinkType, typename... Args>
void buildSink(Args &&...args)
{
auto sink = SinkFactory::create<SinkType>(std::forward<Args>(args)...);
_sinks.push_back(sink);
}
virtual Logger::ptr build() = 0;
protected:
Logger::Type _logger_type;
std::string _logger_name;
LogLevel::value _level;
Formatter::ptr _formatter;
std::vector<LogSink::ptr> _sinks;
};
protected:
bool shouldLog(LogLevel::value level) { return level >= _level; }
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()));
}
virtual void logIt(const std::string &msg) = 0;
protected:
std::mutex _mutex;
std::string _name;
Formatter::ptr _formatter;
std::atomic<LogLevel::value> _level;
std::vector<LogSink::ptr> _sinks;
};
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) : Logger(name, formatter, sinks, level)
{
std::cout << LogLevel::toString(level) << "同步日志器" << name << "创建成功...\n";
}
private:
virtual 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());
}
}
};
class AsyncLogger : public Logger
{
public:
using ptr = std::shared_ptr<AsyncLogger>;
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";
}
protected:
virtual 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());
}
}
protected:
AsyncLooper::ptr _looper;
};
class LocalLoggerBuilder : public Logger::Builder
{
public:
virtual Logger::ptr build()
{
if (_logger_name.empty())
{
std::cout << "日志器名称不能为空\n";
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;
}
};
class loggerManager
{
private:
std::mutex _mutex;
Logger::ptr _root_logger;
std::unordered_map<std::string, Logger::ptr> _loggers;
private:
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));
}
loggerManager(const loggerManager &) = delete;
loggerManager &operator=(const loggerManager &) = delete;
public:
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;
}
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;
}
};
class GlobalLoggerBuilder : public Logger::Builder
{
public:
virtual Logger::ptr 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 (_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);
}
loggerManager::getInstance().addLogger(_logger_name, lp);
return lp;
}
};
}
#endif
looper.hpp:
#ifndef _M_LOOP_H_
#define _M_LOOP_H_
//#include"util.hpp"
#include<vector>
#include<thread>
#include<mutex>
#include<atomic>
#include<condition_variable>
#include<functional>
#include"buffer.hpp"
namespace Mylogs{
class AsyncLooper{
public:
using Functor = std::function<void(Buffer &buffer)>;
using ptr = std::shared_ptr<AsyncLooper>;
AsyncLooper(const Functor &cb):_running(true),_looper_callback(cb),
_thread(std::thread(&AsyncLooper::worker_loop,this)){
}
~AsyncLooper() { stop();}
void stop(){
_running = false;
_pop_cond.notify_all();
_thread.join();
}
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();
}
private:
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;
}
private:
Functor _looper_callback;
private:
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;
};
}
#endif
mylogs.hpp:
#ifndef _M_MY_H_
#define _M_MY_H
#include"logger.hpp"
namespace Mylogs{
//提供获取指定日志器的全局接口(避免用户自己操作单例对象)
Logger::ptr getLogger(const std::string &name){
return loggerManager::getInstance().getLogger(name);
}
Logger::ptr rootLogger(){
return loggerManager::getInstance().rootLogger();
}
//使用宏函数对日志器的接口进行代理(代理模式)
#define debug(fmt,...) debug(__FILE__,__LINE__,fmt,##__VA_ARGS__)
#define info(fmt,...) info(__FILE__,__LINE__,fmt,##__VA_ARGS__)
#define warn(fmt,...) warn(__FILE__,__LINE__,fmt,##__VA_ARGS__)
#define error(fmt,...) error(__FILE__,__LINE__,fmt,##__VA_ARGS__)
#define fatal(fmt,...) fatal(__FILE__,__LINE__,fmt,##__VA_ARGS__)
//提供宏函数,直接通过默认日志器进行日志的标准输出打印(不用获取日志器了)
#define LOG_DEBUG(logger,fmt,...) (Logger)->debug(fmt,##__VA_ARGS__)
#define LOG_INFO(logger,fmt,...) (Logger)->info(fmt,##__VA_ARGS__)
#define LOG_WARN(logger,fmt,...) (Logger)->warn(fmt,##__VA_ARGS__)
#define LOG_ERROR(logger,fmt,...) (Logger)->error(fmt,##__VA_ARGS__)
#define LOG_FATAL(logger,fmt,...) (Logger)->fatal(fmt,##__VA_ARGS__)
#define LOGD(fmt,...) LOG_DEBUG(Mylogs::rootLogger(),fmt,##__VA_ARGS__)
#define LOGI(fmt,...) LOG_INFO(Mylogs::rootLogger(),fmt,##__VA_ARGS__)
#define LOGW(fmt,...) LOG_WARN(Mylogs::rootLogger(),fmt,##__VA_ARGS__)
#define LOGE(fmt,...) LOG_ERROR(Mylogs::rootLogger(),fmt,##__VA_ARGS__)
#define LOGF(fmt,...) LOG_FATAL(Mylogs::rootLogger(),fmt,##__VA_ARGS__)
}
#endif
buffer.hpp:
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>
#include <functional>
#include <cassert>
namespace Mylogs
{
#define BUFFER_DEFAULT_SIZE (1 * 1024 * 1024)
#define BUFFER_INCREMENT_SIZE (1 * 1024 * 1024)
#define BUFFER_THRESHOLD_SIZE (10 * 1024 * 1024)
class Buffer
{
public:
Buffer() : _reader_idx(0), _writer_idx(0), _v(BUFFER_DEFAULT_SIZE) {}
bool empty() { return _reader_idx == _writer_idx; }
size_t readAbleSize() { return _writer_idx - _reader_idx; }
size_t writeAbleSize() { return _v.size() - _writer_idx; }
void reset() { _reader_idx = _writer_idx = 0; }
void swap(Buffer &buf)
{
_v.swap(buf._v);
std::swap(_reader_idx,buf._reader_idx);
std::swap(_writer_idx,buf._writer_idx);
}
void push(const char *data,size_t len){
assert(len <= writeAbleSize());
ensureEnoughSpace(len);
std::copy(data,data+len,&_v[_writer_idx]);
_writer_idx += len;
}
const char *begin() {return &_v[_writer_idx];}
void pop(size_t len){
_reader_idx += len;
assert(_reader_idx <= _writer_idx);
}
protected:
void ensureEnoughSpace(size_t len)
{
if (len <= writeAbleSize())
return;
/*每次增大1M大小*/
size_t new_capacity;
if (_v.size() < BUFFER_THRESHOLD_SIZE)
{
new_capacity = _v.size() * 2 + len;
}
else
{
// 线性增长
new_capacity = _v.size() + BUFFER_INCREMENT_SIZE + len;
}
_v.resize(new_capacity);
}
private:
size_t _reader_idx;
size_t _writer_idx;
std::vector<char> _v;
};
}
更多推荐
所有评论(0)