什么是日志
日志简单来说就是记录系统、应用程序、设备或服务在运行过程中发生的事件、状态变化、用户操作、错误信息等详细信息的文件或数据流。
你可以把它想象成一部详细的运行日记或飞行数据记录仪(黑匣子)。
如何使用 C++11 实现一个高效异步日志系统。文章涵盖了日志系统的基础概念、同步与异步日志的区别、核心设计模式(单例、工厂、建造者、代理)的应用、双缓冲区异步任务处理器的设计原理以及具体的代码实现。通过对比测试,展示了异步日志在多线程环境下的性能优势,并提供了完整的模块划分和代码文件参考。

日志简单来说就是记录系统、应用程序、设备或服务在运行过程中发生的事件、状态变化、用户操作、错误信息等详细信息的文件或数据流。
你可以把它想象成一部详细的运行日记或飞行数据记录仪(黑匣子)。
目的:
作用:
总结来说:
日志就是系统和应用的'黑匣子'和'日记本'。它忠实、详细地记录下运行过程中的点点滴滴,是工程师进行故障诊断、性能优化、安全防护、行为分析和合规审计的基石。
本项目主要实现一个日志系统,其主要支持以下功能:
本项目不依赖其他任何第三方库,只需要安装好 CentOS/Ubuntu + vscode/vim 环境即可开发。
日志系统的技术实现主要包括三种类型:
同步日志是指当输出日志时,必须等待日志输出语句执行完毕后,才能执行后面的业务逻辑语句,日志输出语句与程序的业务逻辑语句将在同一个线程运行。每次调用一次打印日志 API 就对应一次系统调用 write 写日志文件。
在高并发场景下,随着日志数量不断增加,同步日志系统容易产生系统瓶颈:
异步日志是指在进行日志输出时,日志输出语句与业务逻辑语句并不是在同一个线程中运行,而是有专门的线程用于进行日志的输出操作。业务线程只需要将日志放到一个内存缓冲区中不用等待即可继续执行后续业务逻辑(作为日志的生产者),而日志的落地操作交给单独的日志线程去完成(作为日志的消费者),这是一个典型的生产 - 消费模型。
这样做的好处是即使日志没有真的地完成输出也不会影响程序的主业务,可以提高程序的性能:
在初学 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;
}
设计模式是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。
六大原则:
单一职责原则(Single Responsibility Principle);
开闭原则(Open Closed Principle);
里氏替换原则(Liskov Substitution Principle);
依赖倒置原则(Dependence Inversion Principle)
迪米特法则(Law of Demeter),又叫'最少知道法则';
接口隔离原则(Interface Segregation Principle);
从整体上来理解六大设计原则,可以简要的概括为一句话,用抽象构建框架,用实现扩展细节,具体到每一条设计原则,则对应一条注意事项:
一个类只能创建一个对象,即单例模式,该设计模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
单例模式有两种实现模式:饿汉模式和懒汉模式
饿汉模式:程序启动时就会创建一个唯一的实例对象。因为单例对象已经确定,所以比较适用于多线程环境中,多线程获取单例对象不需要加锁,可以有效的避免资源竞争,提高性能。
懒汉模式:第一次使用要使用单例对象的时候创建实例对象。如果单例对象构造特别耗时或者耗费资源 (加载插件、加载网络资源等),可以选择懒汉模式,在第一次使用的时候才创建对象。
工厂模式是一种创建型设计模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们创建对象时不会对上层暴露创建逻辑,而是通过使用一个共同结构来指向新创建的对象,以此实现创建 - 使用的分离。
工厂模式可以分为:
//简单工厂模式:通过参数控制可以生产任何产品
// 优点:简单粗暴,直观易懂。使用一个工厂生产同一等级结构下的任意产品
// 缺点:
// 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>();
}
};
这个模式的结构和管理产品对象的方式十分简单,但是它的扩展性非常差,当我们需要新增产品的时候,就需要去修改工厂类新增一个类型的产品创建逻辑,违背了开闭原则。
//工厂方法:定义一个创建对象的接口,但是由子类来决定创建哪种对象,使用多个工厂分别生产指定的固定产品
// 优点:
// 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());
auto fruit = factory->create();
fruit->show();
factory.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(const 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<Animal> getAnimal(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;
}
抽象工厂模式适用于生产多个工厂系列产品衍生的设计模式,增加新的产品等级结构复杂,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,违背了'开闭原则'。
建造者模式是一种创建型设计模式,使用多个简单的对象一步一步构建成一个复杂的对象,能够将一个复杂的对象的构建与它的表示分离,提供一种创建对象的最佳方式。主要用于解决对象的构建过于复杂的问题。
建造者模式主要基于四个核心类实现:
#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 = new 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;
}
本项目实现的是一个多日志器日志系统,主要实现的功能是让程序员能够轻松的将程序运行日志信息落地到指定的位置,且支持同步与异步两种方式的日志落地方式。
项目的框架设计将项目分为以下几个模块来实现。
日志等级模块:对输出日志的等级进行划分,以便于控制日志的输出,并提供等级枚举转字符串功能。
日志消息模块:中间存储日志输出所需的各项要素信息
日志消息格式化模块:设置日志输出格式,并提供对日志消息进行格式化功能。
日志消息落地模块:决定了日志的落地方向,可以是标准输出,也可以是日志文件,也可以滚动文件输出....
日志器模块:
日志器管理模块:
异步线程模块:
(此处省略图片链接)
提前完成一些零碎的功能接口,以便于项目中会用到。
/* 通用功能类,与业务无关的功能实现 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
日志等级总共分为 7 个等级,分别为:
#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) {
case LogLevel::value::DEBUG: return "DEBUG";
case LogLevel::value::INFO: return "INFO";
case LogLevel::value::WARN: return "WARN";
case LogLevel::value::ERROR: return "ERROR";
case LogLevel::value::FATAL: return "FATAL";
default: return "UNKNOW";
}
}
};
}
#endif
日志消息类主要是封装一条完整的日志消息所需的内容,其中包括日志等级、对应的 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::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)类主要负责格式化日志消息。其主要包含以下内容
pattern 成员:保存日志输出的格式字符串。
示例:"[%d{%H:%M:%S}] %m%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::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
日志落地类主要负责落地日志消息到目的地。
它主要包括以下内容:
这个类支持可扩展,其成员函数 log 设置为纯虚函数,当我们需要增加一个 log 输出目标,可以增加一个类继承自该类并重写 log 方法实现具体的落地日志逻辑。
目前实现了三个不同方向上的日志落地:
滚动日志文件输出的必要性:
日志文件的滚动思想:
日志文件滚动的条件有两个:文件大小 和 时间。我们可以选择:
本项目基于文件大小的判断滚动生成新的文件:
#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()){ 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
日志器主要是用来和前端交互,当我们需要使用日志系统打印 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);
va_end(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
设计思想:异步处理线程 + 数据池
使用者将需要完成的任务添加到任务池中,由异步线程来完成任务的实际执行操作。
任务池的设计思想:双缓冲区阻塞数据池
优势:避免了空间的频繁申请释放,且尽可能的减少了生产者与消费者之间锁冲突的概率,提高了任务处理效率。
在任务池的设计中,有很多备选方案,比如循环队列等等,但是不管是哪一种都会涉及到锁冲突的情况,因为在生产者与消费者模型中,任何两个角色之间都具有互斥关系,因此每一次的任务添加与取出都有可能涉及锁的冲突,而双缓冲区不同,双缓冲区是处理器将一个缓冲区中的任务全部处理完毕后,然后交换两个缓冲区,重新对新的缓冲区中的任务进行处理,虽然同时多线程写入也会冲突,但是冲突并不会像每次只处理一条的时候频繁(减少了生产者与消费者之间的锁冲突),且不涉及到空间的频繁申请释放所带来的消耗。
#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
异步日志器类继承自日志器类,并在同步日志器类上拓展了异步消息处理器。当我们需要异步输出日志的时候,需要创建异步日志器和消息处理器,调用异步日志器的 log、error、info、fatal 等函数输出不同级别日志。
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 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;
}
};
提供全局的日志器获取接口。
使用代理模式通过全局函数或宏函数来代理 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
#include "Mylog.h"
void loggerTest(const std::string &logger_name) {
Mylogs::Logger::ptr lp = Mylogs::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 test-";
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[]) {
//实例化全局日志器建造者
Mylogs::GlobalLoggerBuilder::ptr lbp(new Mylogs::GlobalLoggerBuilder);
lbp->buildLoggerName("stdout_and_file_logger");
//设置日志器名称
lbp->buildFormatter("[%d][%c][%f:%l][%p] %m%n");
//设置日志输出格式
lbp->buildLoggerLevel(Mylogs::LogLevel::value::DEBUG);
//设置日志限制输出等级
lbp->buildSink<Mylogs::StdoutSink>();
//创建一个标准输出的落地方向
lbp->buildSink<Mylogs::FileSink>("./logs/sync.log");
//创建一个文件落地方向
lbp->buildSink<Mylogs::RollSink>("./logs/roll-", 10 * 1024 * 1024);
//创建滚动日志落地方向
lbp->buildLoggerType(Mylogs::Logger::Type::LOGGER_SYNC);
//设置日志器类型为同步日志
lbp->build();
//建造日志器
loggerTest("stdout_and_file_logger");
return 0;
}
测试一个日志器中包含有所有的落地方向,观察是否每个方向都正常落地,分别测试同步方式和异步方式落地后数据是否正常。
#include "Mylog.h"
#include "bench.h"
#include <unistd.h>
void loggerTest(const std::string &logger_name) {
Mylogs::Logger::ptr lp = Mylogs::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 test-";
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() {
Mylogs::GlobalLoggerBuilder::ptr lbp(new Mylogs::GlobalLoggerBuilder);
lbp->buildLoggerName("stdout_and_file_logger");
lbp->buildFormatter("[%d][%c][%f:%l][%p] %m%n");
lbp->buildLoggerLevel(Mylogs::LogLevel::value::DEBUG);
lbp->buildSink<Mylogs::StdoutSink>();
lbp->buildSink<Mylogs::FileSink>("./logs/sync.log");
lbp->buildSink<Mylogs::RollSink>("./logs/roll-", 10 * 1024 * 1024);
lbp->buildLoggerType(Mylogs::Logger::Type::LOGGER_ASYNC);
lbp->build();
loggerTest("stdout_and_file_logger");
}
int main(int argc, char *argv[]) {
functional_test();
return 0;
}
下面对日志系统做一个性能测试,测试一下平均每秒能打印多少条日志消息到文件。 主要的测试方法是:每秒能打印日志数 = 打印日志条数 / 总的打印日志消耗时间 主要测试要素:同步/异步 & 单线程/多线程
测试环境:
#ifndef __M_BENCH_H__
#define __M_BENCH_H__
#include "Mylog.h"
#include <chrono>
namespace Mylogs {
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);
Mylogs::GlobalLoggerBuilder::ptr lbp(new Mylogs::GlobalLoggerBuilder);
lbp->buildLoggerName(logger_name);
lbp->buildFormatter("%m%n");
lbp->buildSink<Mylogs::FileSink>("./logs/sync.log");
lbp->buildLoggerType(Mylogs::Logger::Type::LOGGER_SYNC);
lbp->build();
Mylogs::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);
Mylogs::GlobalLoggerBuilder::ptr lbp(new Mylogs::GlobalLoggerBuilder);
lbp->buildLoggerName(logger_name);
lbp->buildFormatter("%m");
lbp->buildSink<Mylogs::FileSink>("./logs/async.log");
lbp->buildLoggerType(Mylogs::Logger::Type::LOGGER_ASYNC);
lbp->build();
Mylogs::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;
}
测试结果分析:
能够通过上边的测试看出来,一些情况:
在单线程情况下,异步效率看起来还没有同步高,这个我们得了解,现在的 IO 操作在用户态都会有缓冲区进行缓冲区,因此我们当前测试用例看起来的同步其实大多时候也是在操作内存,只有在缓冲区满了才会涉及到阻塞写磁盘操作,而异步单线程效率看起来低,也有一个很重要的原因就是单线程同步操作中不存在锁冲突,而单线程异步日志操作存在大量的锁冲突,因此性能也会有有一定的降低。
但是,我们也要看到限制同步日志效率的最大原因是磁盘性能,打日志的线程多少并无明显区别,线程多了反而会降低,因为增加了磁盘的读写争抢,而对于异步日志的限制,并非磁盘的性能,而是 cpu 的处理性能,打日志并不会因为落地而阻塞,因此在多线程打日志的情况下性能有了显著的提高。
丰富 sink 类型:
实现日志服务器负责存储日志并提供检索、分析、展示等功能
/* 通用功能类,与业务无关的功能实现 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
#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
#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
#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) {
case LogLevel::value::DEBUG: return "DEBUG";
case LogLevel::value::INFO: return "INFO";
case LogLevel::value::WARN: return "WARN";
case LogLevel::value::ERROR: return "ERROR";
case LogLevel::value::FATAL: return "FATAL";
default: return "UNKNOW";
}
}
};
}
#endif
#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()){ 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
#ifndef _M_LOG_H_
#define _M_log_h_
#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);
va_end(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
#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
#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
#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;
};
}

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online