多功能的日志系统,支持多线程并发写日志,同步和异步输出方式,不同的输出位置(标准输出,指定文件和滚动文件),输出多级别日志.
按模块分为:等级,信息,格式化,信息落地,日志器,日志器管理,异步线程7个模块.
日志系统的作用,日志系统可以帮助定位bug,分析问题.比如上线产品不能通过调试处理bug时,对于分布式,多线程,多进程代码,很难通过调试定位bug时,帮助首次接触代码的信任理解代码运行流程,这些场景日志系统都能起到很大作用.
1.单一原则:类的职责要单一,只做一件事.
2.里氏替换原则:不要破坏继承关系,父类出现的地方,子类就可以出现.
3.依赖倒置原则: 高层模块不直接依赖低层模块,两者要依赖其抽象.
4.迪米特法则:尽量减少对象之间交互.降低耦合度
5.接口隔离原则,对象之间依赖关系建立在最小接口上,不暴漏不必要的接口.
6.开闭原则,关闭修改,开放扩展.
即一个类只能创建一个全局对象,被整个系统共享;单例模式细分为懒汉模式和饿汉模式,懒汉模式使用时才常见对象,适用于单例对象加载耗时,耗资源的场景.饿汉模式程序启动即创建对象.c++11之前有避免被多线程竞争的优点.
使用场景
需要根据不同条件创建不同对象时,使用工厂类可以实现.
当对象创建过于复杂,需要封装创建过程时用工厂类封装,隐藏创建过程,暴漏接口,时代码更加简洁;
需要动态切换对象创建方式,使用工厂模式通过切换工厂类创建不同对象,实现灵活的切换对象的创建方式.
需要集中管理对象的创建,使用工厂类集中管理对象的创建,修改时,只需要修改工厂类即可,方便维护.
简单工厂
集中管理所有对象的创建,通过条件判断创建不同的对象.缺点是没有遵循开闭原则,扩展时需要修改工厂类代码.
class Fruit
{
public:
virtual void Show() = 0;
};
class Apple: public Fruit
{
public:
void Show()override
{
std::cout << "Apple" << std::endl;
}
};
class Banana : public Fruit
{
public:
void Show()override
{
std::cout << "Banana" << std::endl;
}
};
class FruitFactory
{
public:
static std::shared_ptr<Fruit> Create(const std::string name)
{
if (name == "Banana")
{
return std::make_shared<Banana>();
}
else
{
return std::make_shared<Apple>();
}
}
};
方法模式
创建基类工厂,不同子类工厂创建不同对象,遵循了开闭原则,只需增加子类工厂,就能扩展对象的创建.
class Fruit
{
public:
virtual void Show() = 0;
};
class Apple: public Fruit
{
public:
void Show()override
{
std::cout << "Apple" << std::endl;
}
};
class Banana : public Fruit
{
public:
void Show()override
{
std::cout << "Banana" << std::endl;
}
};
//工厂基类
class Factory
{
public:
virtual std::shared_ptr<Fruit> Create() = 0;
};
class AppleFactory: public Factory
{
public:
std::shared_ptr<Fruit> Create()override
{
return std::make_shared<Apple>();
}
};
class BananaFactory:public Factory
{
public:
std::shared_ptr<Fruit> Create()override
{
return std::make_shared<Banana>();
}
};
抽象工厂
创建基类工厂,基类工厂定义了不同产品族的创建方法,产品族是一组有依赖关系的类,当增加新的产品族时,只需要在基类工厂添加抽象方法,在增加子类工厂即可.最后用超级工厂通过条件判断创建不同的工厂对象,用工厂对象创建产品族.
//抽象工厂模式
//创建动物类家族
class Animal
{
public:
virtual void name() = 0;
};
class Dog : public Animal
{
public:
void name()override
{
std::cout << "dog" << std::endl;
}
};
class Monkey : public Animal
{
public:
void name()override
{
std::cout << "monkey" << std::endl;
}
};
//超级工厂
class Factory
{
public:
virtual std::shared_ptr<Fruit> FruitCreate(const std::string name) = 0;
virtual std::shared_ptr<Animal> AnimalCreate(const std::string name) = 0;
};
//创建水果类 / 动物类简单工厂
class FruitFactory: public Factory
{
public:
virtual std::shared_ptr<Fruit> FruitCreate(const std::string name)
{
if (name == "Banana")
return std::make_shared<Banana>();
else
return std::make_shared<Apple>();
}
virtual std::shared_ptr<Animal> AnimalCreate(const std::string name)
{
return std::shared_ptr<Animal>();
}
};
class AnimalFactory : public Factory
{
public:
std::shared_ptr<Fruit> FruitCreate(const std::string name)override
{
return std::shared_ptr<Fruit>();
}
std::shared_ptr<Animal> AnimalCreate(const std::string name)override
{
if (name == "Dog")
return std::make_shared<Dog>();
else
return std::make_shared<Monkey>();
}
};
//创建简单模式工厂构建类
class FactoryBuild
{
public:
static std::shared_ptr<Factory> FactoryCreate(const std::string name)
{
if (name == "Animal")
return std::make_shared<AnimalFactory>();
else
return std::make_shared<FruitFactory>();
}
};
建造者模式分步骤创建复杂对象的解决方案,隐藏了对象的创建细节。对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。建造者模式由指导者,建造者,具体建造者组成:
建造者模式常用于创建复杂对象,可以在构建过程中灵活地修改、替换或增加创建步骤,而不需要修改已有的代码。
//抽象类
class Computer
{
public:
void SetMainBoard(const std::string board) { _board = board; }
void SetDisplay(const std::string display) { _display = display; }
void Show()
{
std::string computer = "computer:\n";
computer += "\tboard" + _board + "\n";
computer += "\tdisplay" + _display + "\n";
computer += "\tos" + _os + "\n";
std::cout << computer << std::endl;
}
virtual void SetOs() = 0;
protected:
std::string _board;
std::string _display;
std::string _os;
};
//具体类
class Linux : public Computer
{
public:
void SetOs()
{
_os = "centos 7";
}
};
//抽象创建类
class Build
{
public:
virtual void BuildSetMainBoard(const std::string board) = 0;
virtual void BuildSetDisplay(const std::string display) = 0;
virtual void BuildSetOs() = 0;
virtual std::shared_ptr<Computer> BuildRet() = 0;
};
//具体创建类
class LinuxBuild : public Build
{
public:
LinuxBuild() :_computer(new Linux) {}
void BuildSetMainBoard(const std::string board)
{
_computer->SetMainBoard(board);
}
void BuildSetDisplay(const std::string display)
{
_computer->SetDisplay(display);
}
void BuildSetOs()
{
_computer->SetOs();
}
std::shared_ptr<Computer> BuildRet()
{
return _computer;
}
private:
std::shared_ptr<Computer> _computer;
};
//指挥类
class Director
{
public:
Director(Build* build) :_build(build) {}
void Construct(const std::string board, const std::string display)
{
_build->BuildSetDisplay(display);
_build->BuildSetMainBoard(board);
_build->BuildSetOs();
}
private:
std::shared_ptr<Build> _build;
};
代理模式可以通过引入代理对象,为目标对象提供额外的功能或控制访问。它能够在不改变目标对象的情况下,灵活地扩展和增强对象的行为。
//代理模式
//抽象类
class Abstract
{
public:
virtual void headler()=0;
};
//真实对象
class Real: public Abstract
{
public:
void headler()override
{
std::cout << "方法处理" << std::endl;
}
};
//代理对象
class Agent : public Abstract
{
public:
void headler()override
{
std::cout << "功能加强" << std::endl;
std::cout << "对象隐藏" << std::endl;
std::cout << "访问控制" << std::endl;
_real.headler();
}
private:
Real _real;
};
作用:对日志信息做等级划分,以便于控制日志输出,提供等级枚举转字符串功能.
#pragma once
/*
日志等级模块
1.定义日志等级
2.提供日志等级枚举转字符串功能
*/
namespace logspace
{
class LogLevel
{
public:
//日志等级
enum class Level
{
/*未知,调试,提示,警告,错误,致命,关闭*/
UNKNOW=0,
DEBUG,
INFO,
ERROR,
WARN,
FATAL,
OFF,
};
//等级转字符串
static const char* ToString(LogLevel::Level val)
{
if(val==Level::DEBUG)return"DEBUG";
else if(val==Level::ERROR)return"ERROR";
else if(val==Level::FATAL)return"FATAL";
else if(val==Level::INFO)return "INFO";
else if(val==Level::OFF)return "OFF";
return "UNKNOW";
}
};
}
作用:封装以及格式化存储日志信息
#pragma once
/*
日志信息模块
格式存储日志信息
*/
#include
#include
#include"util.hpp"
#include"level.hpp"
namespace logspace
{
//消息模块
struct Message
{
//时间
time_t _time;
//日志器名称
std::string _logname;
//文件名
std::string _filename;
//行号
int _linenum;
//线程ID
std::thread::id _thid;
//等级
LogLevel::Level _level;
//消息主体
std::string _payload;
//构造
Message(std::string logname, std::string filename,
size_t linenum, LogLevel::Level level, std::string message)
:_time(util::Util::GetTime()),
_logname(logname),
_filename(filename),
_linenum(linenum),
_thid(std::this_thread::get_id()),
_level(level),
_payload(message)
{}
};
}
格式化信息就依次从消息体中取出需要的信息进行链接,这就需要将不同的信息字段分开实现.以便组织,考虑使用多态实现,这样也方便扩展格式化新的信息字段.
实现思想:使用多态实现不同信息字段的格式化,使用格式化字符与格式化子类形成映射关系,利用格式化字符串组织格式化子类对象到容器中,遍历容器中的对象,将信息格式化并连接.
#pragma once
/*
格式化模块
功能:
提供格式化消息灵活序列化方式,可控制输出字段顺序和字段内容
实现思想:
1.%d{%H:%M:%s}(时间),%c(日志器名字),%t(线程id),%T(制表符),%m(有效载荷),%n(回车),[](其他字符)等单个
项目的序列化类
2.通过诸如[%d{%H:%M:%s}][%c][%t]%T%m%n样式的格式字符串,将不同的单个项目的序列化类按格式化字符顺序组织在数组中,
遍历数组将格式化消息组织成一个序列化字符串.
*/
#include "message.hpp"
#include"level.hpp"
#include
#include
#include
#include
#include
namespace logspace
{
//基类
class FormatItem
{
public:
using ptr = std::shared_ptr<FormatItem>;
virtual void Format(std::ostream& out, const Message& mes) = 0;
};
//时间
class TimeFormatItem :public FormatItem
{
public:
TimeFormatItem(const std::string str = "%H:%M:%S")
:_str(str)
{}
void Format(std::ostream& out, const Message& mes)
{
//将时间格式化到tm结构中
time_t time = mes._time;
struct tm ptrtm;
localtime_r( &time,&ptrtm);
//将tm结构序列化为字符串
char buffer[4096];
size_t n = strftime(buffer, 4096, _str.c_str(), &ptrtm);
if (n == 0)std::cout << "format char number out of range" << std::endl;
//将格式化字符串添加到标准输出
out << buffer;
}
public:
std::string _str;
};
//日志器名字
class LogFormatItem :public FormatItem
{
public:
void Format(std::ostream& out, const Message& mes)
{
out << mes._logname;
}
};
//文件名
class FileFormatItem :public FormatItem
{
public:
void Format(std::ostream& out, const Message& mes)
{
out << mes._filename;
}
};
//行号
class LineFormatItem :public FormatItem
{
public:
void Format(std::ostream& out, const Message& mes)
{
out << std::to_string(mes._linenum);
}
};
//线程id
class ThidFormatItem :public FormatItem
{
public:
void Format(std::ostream& out, const Message& mes)
{
out << mes._thid;
}
};
//等级
class LevelFormatItem :public FormatItem
{
public:
void Format(std::ostream& out, const Message& mes)
{
out << LogLevel::ToString(mes._level);
}
};
//有效载荷
class PayloadFormatItem :public FormatItem
{
public:
void Format(std::ostream& out, const Message& mes)
{
out << mes._payload;
}
};
//换行
class NewlineFormatItem :public FormatItem
{
public:
void Format(std::ostream& out, const Message& mes)
{
(void)mes;
out << '\n';
}
};
//制表符
class TabFormatItem :public FormatItem
{
public:
void Format(std::ostream& out, const Message& mes)
{
(void)mes;
out << '\t';
}
};
//其他
class OtherFormatItem :public FormatItem
{
public:
OtherFormatItem(const std::string str = "") :_str(str) {}
void Format(std::ostream& out, const Message& mes)
{
(void)mes;
out << _str;
}
public:
std::string _str;
};
//格式化类
class Formatter
{
public:
using ptr = std::shared_ptr<Formatter>;
public:
//模式字符串
Formatter(const std::string pattern = "[%d{%H:%M:%S}][%c][%p][%f][%l][%t]%t%m%n")
:_pattern(pattern)
{
assert(ParsePattern());
}
//根据item中的格式化子类,序列化为字符串
//序列化->字符串
std::string Format(const Message& mes)
{
std::stringstream ss;
for (auto e : _item)
{
e->Format(ss, mes);
}
return ss.str();
}
//序列化->流
std::ostream& Format(std::ostream& out, const Message& mes)
{
for (auto e : _item)
{
e->Format(out, mes);
}
return out;
}
//解析模式字符串,
bool ParsePattern()
{
/*[% d{ % H: % M : % S }] [% c] [% p] [% f] [% l] [% t] % t% m% n*/
//将pattern中的格式信息解析为对应的格式子类指针,存储在item中.
std::vector<std::pair<std::string, std::string>> key_value;
std::string key, value;
size_t pos = 0;
while (pos < _pattern.size())
{
//处理非格式化字符
if (_pattern[pos] != '%')
{
value += _pattern[pos++];
continue;
}
if (_pattern[pos] == '%' && pos + 1 < _pattern.size() && _pattern[pos + 1] == '%')
{
value += _pattern[pos];
pos += 2;
continue;
}
if (value.empty() == false)
{
key_value.push_back(std::make_pair("", value));
value.clear();
}
//处理%后边的字符
++pos;
if (pos < _pattern.size() && isalpha(_pattern[pos]))
{
key += _pattern[pos];
}
else
{
std::cout << "%之后没有对应的格式化字符" << std::endl;
return false;
}
++pos;
if (pos < _pattern.size() && _pattern[pos] == '{')
{
++pos;
while (_pattern[pos] != '}' && pos < _pattern.size())
{
value += _pattern[pos++];
}
if (_pattern[pos] != '}')
{
std::cout << "{}不匹配" << std::endl;
return false;
}
++pos;
}
key_value.push_back(std::make_pair(key, value));
key.clear();
value.clear();
}
for (auto e : key_value)
{
FormatItem::ptr pf = CreateInstance(e.first, e.second);
_item.push_back(pf);
}
return true;
}
private:
//根据模式信息创建格式化子类对象指针
FormatItem::ptr CreateInstance(const std::string& key, const std::string& value)
{
if (key == "d")return std::make_shared<TimeFormatItem>(value);
else if (key == "c")return std::make_shared<LogFormatItem>();
else if (key == "p")return std::make_shared<LevelFormatItem>();
else if (key == "f")return std::make_shared<FileFormatItem>();
else if (key == "l")return std::make_shared<LineFormatItem>();
else if (key == "t")return std::make_shared<ThidFormatItem>();
else if (key == "m")return std::make_shared<PayloadFormatItem>();
else if (key == "T")return std::make_shared<TabFormatItem>();
else if (key == "n")return std::make_shared<NewlineFormatItem>();
else return std::make_shared<OtherFormatItem>(value);
}
private:
//模式
const std::string _pattern;
//格式化子类指针数组
std::vector<FormatItem::ptr> _item;
};
}
使用多态实现不同落地方式的设计思想:因为需要实现多种落地方式,他们功能一样,只是实现方法不同,所以选择使用C++中多态的特性实现不同落地方式,这样实现也方便后续扩展新的落地方式.
**使用简单工厂模式管理对象的设计思想:**使用不同落地方式,使用时需要创建不同的子类对象,如果后期子类对象的创建方式发生变化,需要修改所有创建该对象的代码,非常不方便,使用简单工厂模式集中管理对象的创建,如果发生变化只需要修改工厂类即可.
由于需要根据类型创建不同的子类对象,而且创建不同的子类对象,需要传递的初始化参数也不同,所以选择使用了模板和可变参数模板来适应不同的传参方式.
#pragma once
/*
日志落地模块
功能:提供多种落地模式,可根据类型灵活选择
实现思想:提过一个落地抽象类,重写多个子类,实现不同落地模式.
通过向工厂类传递不同的类型,建造构建不同的子类对象.
*/
#include
#include
#include
#include
#include
#include
#include"util.hpp"
namespace logspace
{
//基类
class LogSink
{
public:
using ptr = std::shared_ptr<LogSink>;
virtual void Sink(const std::string& log, size_t len) = 0;
virtual ~LogSink() {};
};
//标准输出落地
class StdoutSink :public LogSink
{
public:
void Sink(const std::string& log, size_t len)override
{
std::cout.write(log.c_str(), len);
}
~StdoutSink()override {}
};
//指定文件落地
class FileSink :public LogSink
{
public:
FileSink(const std::string& filename)
:_filename(filename)
{
//获取路径
std::string path = util::File::GetPath(_filename);
//创建路径
util::File::CreateDirctory(path);
//打开文件
_of.open(_filename, std::ios::binary);
if (_of.is_open() == false)
{
std::cout << _filename << " open fail" << std::endl;
}
}
void Sink(const std::string& log, size_t len)override
{
//写入文件
_of.write(log.c_str(), len);
if (_of.good() == false)
{
std::cout << "write fail" << std::endl;
}
}
~FileSink()override
{
//关闭文件
_of.close();
}
private:
std::string _filename;
std::ofstream _of;
};
//滚动文件落地
class RollSink :public LogSink
{
public:
RollSink(const std::string& basename, size_t maxsize)
:_basename(basename), _maxsize(maxsize), _cursize(0), _count_num(0)
{
//获取路径
std::string path = util::File::GetPath(_basename);
//创建路径
util::File::CreateDirctory(path);
std::string filename = Createfile();
_of.open(filename, std::ios::binary);
if (_of.is_open() == false)
{
std::cout << filename << " open fail" << std::endl;
}
}
void Sink(const std::string& log, size_t len)override
{
if (_cursize >= _maxsize)
{
//关闭之前的文件
_of.close();
//获取新的文件名
std::string filename = Createfile();
//打开新文件
_of.open(filename, std::ios::binary);
if (_of.is_open() == false)
{
std::cout << filename << " open fail" << std::endl;
}
_cursize = 0;
}
//写入文件
_of.write(log.c_str(), len);
if (_of.good() == false)
{
std::cout << "write fail" << std::endl;
}
_cursize += len;
}
~RollSink()override
{
_of.close();
}
private:
std::string Createfile()
{
//获取时间
time_t t = time(nullptr);
struct tm tl;
localtime_r(&t,&tl);
std::stringstream ss;
ss << _basename;
ss << tl.tm_year;
ss << tl.tm_mon;
ss << tl.tm_mday;
ss << tl.tm_hour;
ss << tl.tm_min;
ss << tl.tm_sec;
ss << "-" + std::to_string(_count_num++);
ss << ".log";
return ss.str();
}
private:
std::string _basename;
std::ofstream _of;
size_t _maxsize;
size_t _cursize;
size_t _count_num;
};
//按时间滚动文件落地
class RollByTimeSink :public LogSink
{
public:
enum class TimeType
{
HOUR,
MINUTE,
SECOND,
DAY,
};
public:
RollByTimeSink(const std::string& basename, const TimeType& tt)
:_basename(basename), _cur_gap(0)
{
switch (tt)
{
case TimeType::DAY:
{
_gap_size = 3600 * 24;
break;
}
case TimeType::HOUR:
{
_gap_size = 3600;
break;
}
case TimeType::MINUTE:
{
_gap_size = 60;
break;
}
case TimeType::SECOND:
{
_gap_size = 1;
break;
}
default:
{
std::cout << "fail type" << std::endl;
break;
}
}
//获取路径
std::string path = util::File::GetPath(_basename);
//创建路径
util::File::CreateDirctory(path);
std::string filename = Createfile();
}
void Sink(const std::string& log, size_t len)override
{
//检查是否更新到下一个时间段,是则重新创建文件
time_t gap = _gap_size == 1 ? time(nullptr) : time(nullptr) % _gap_size;
if (_cur_gap != gap)
{
//关闭之前的文件
_of.close();
//获取新的文件名
std::string filename = Createfile();
//打开新文件
_of.open(filename, std::ios::binary);
if (_of.is_open() == false)
{
std::cout << filename << " open fail" << std::endl;
}
_cur_gap = gap;
}
//写入文件
_of.write(log.c_str(), len);
if (_of.good() == false)
{
std::cout << "write fail" << std::endl;
}
}
~RollByTimeSink()override
{
_of.close();
}
private:
std::string Createfile()
{
//获取时间
time_t t = time(nullptr);
struct tm tl;
localtime_r(&t, &tl);
std::stringstream ss;
ss << _basename;
ss << tl.tm_year;
ss << tl.tm_mon;
ss << tl.tm_mday;
ss << tl.tm_hour;
ss << tl.tm_min;
ss << tl.tm_sec;
ss << "-" + std::to_string(_count_num++);
ss << ".log";
return ss.str();
}
private:
std::string _basename;
std::ofstream _of;
time_t _gap_size;
time_t _cur_gap;
size_t _count_num=0;
};
template <typename SinkType, typename ...Args>
class SinkFactory
{
public:
static LogSink::ptr Create(Args&& ...args)
{
return std::make_shared<SinkType>(std::forward<Args>(args)...);
}
};
}
设计思想:考虑到日志器分为异步日志器和同步日志器两种,且日志器中的日志输出控制,功能相同,只需要实现不同的落地模式即可,所以使用多态实现,这样也方便扩展新的日志器.
异步日志器设计思想:程序在单线程的情况下,如果因为磁盘内存不足或者没有显示器资源就会造成使用日志器的程序被阻塞,异步输出模式,主线程激昂信息保存在内存中,由其他线程实施落地可避免这一情况.
#pragma once
/*
日志器模块
功能:统一管理日志的格式化,落地,日志等级,组织日志的输出.
实现思想:抽象一个基类,基类实现日志的格式化,等级控制,落地方式,子类实现落地模式是异步还是同步
同步是由日志器直接落地,异步日志是将日志暂存到内存缓冲区,创建另一个线程将内存缓冲区异步落地,避免
因落地文件未就绪或者其他原因导致主线程被阻塞.
*/
#include
#include
#include
#include
#include
#include
#include
#include"buffer.hpp"
#include"format.hpp"
#include"sink.hpp"
#include"asynch.hpp"
namespace logspace
{
//抽象日志器基类
class Logger
{
public:
using ptr = std::shared_ptr<Logger>;
Logger
(
std::string log_name,
Formatter::ptr pformat,
LogLevel::Level level,
std::vector<LogSink::ptr> sinks,
AsynchType work_pattern=AsynchType::SAFE_PATTERN
)
:_log_name(log_name),
_pformat(pformat),
_level(level),
_sinks(sinks.begin(),sinks.end()),
_work_pattern(work_pattern)
{}
//获取日志器名称
const std::string& GetLogName()
{
return _log_name;
}
//按等级序列化消息对象
//发布
void Debug(std::string file, size_t linenum, const char* format, ...)
{
//判断等级
if (_level < LogLevel::Level::DEBUG)return;
//提取不定参数
va_list ap;
va_start(ap, format);
char buffer[4096];
vsprintf(buffer, format, ap);
va_end(ap);
std::string str = Serialize(file, linenum, LogLevel::Level::DEBUG, buffer);
//落地
Sink(str);
}
//提示
void Info(std::string file, size_t linenum, const char* format, ...)
{
//判断等级
if (_level < LogLevel::Level::INFO)return;
//提取不定参数
va_list ap;
va_start(ap, format);
char buffer[4096];
vsprintf(buffer, format, ap);
va_end(ap);
std::string str = Serialize(file, linenum, LogLevel::Level::INFO, buffer);
//落地
Sink(str);
}
//警告
void Warning(std::string file, size_t linenum, const char* format, ...)
{
//判断等级
if (_level < LogLevel::Level::WARN)return;
//提取不定参数
va_list ap;
va_start(ap, format);
char buffer[4096];
vsprintf(buffer, format, ap);
va_end(ap);
std::string str = Serialize(file, linenum, LogLevel::Level::WARN, buffer);
//落地
Sink(str);
}
//错误
void Error(std::string file, size_t linenum, const char* format, ...)
{
//判断等级
if (_level < LogLevel::Level::ERROR)return;
//提取不定参数
va_list ap;
va_start(ap, format);
char buffer[4096];
vsprintf(buffer, format, ap);
va_end(ap);
std::string str = Serialize(file, linenum, LogLevel::Level::ERROR, buffer);
//落地
Sink(str);
}
//致命
void Fatal(std::string file, size_t linenum, const char* format, ...)
{
//判断等级
if (_level < LogLevel::Level::FATAL)return;
//提取不定参数
va_list ap;
va_start(ap, format);
char buffer[4096];
vsprintf(buffer, format, ap);
va_end(ap);
std::string str = Serialize(file, linenum, LogLevel::Level::FATAL, buffer);
//落地
Sink(str);
}
protected:
virtual void Sink(const std::string& str) = 0;
protected:
std::string Serialize(const std::string file,const size_t linenum,const LogLevel::Level,char* buffer)
{
//设置消息对象
Message mes(_log_name, file, linenum, LogLevel::Level::DEBUG, buffer);
//序列化字符串
std::string str = _pformat->Format(mes);
return str;
}
protected:
//名字
std::string _log_name;
//格式化对象
Formatter::ptr _pformat;
//等级对象
LogLevel::Level _level;
//落地对象数组
std::vector<LogSink::ptr> _sinks;
//锁
std::mutex _mutex;
//异步工作模式
AsynchType _work_pattern;
};
//同步输出子类
class SynchLogger :public Logger
{
public:
SynchLogger
(
std::string log_name,
Formatter::ptr pformat,
LogLevel::Level level,
std::vector<LogSink::ptr> sinks
)
:Logger(log_name,pformat,level,sinks)
{}
protected:
//落地
void Sink(const std::string& str)override
{
if (_sinks.empty())return;
std::unique_lock<std::mutex> lk(_mutex);
for (auto e : _sinks)
{
e->Sink(str, str.size());
}
}
};
//异步输出子类
class AsynchLogger :public Logger
{
public:
AsynchLogger
(
std::string log_name,
Formatter::ptr pformat,
LogLevel::Level level,
std::vector<LogSink::ptr> sinks,
AsynchType work_pattern=AsynchType::SAFE_PATTERN
)
:Logger(log_name, pformat, level, sinks,work_pattern),
_pasynch(std::make_shared<AsynchLoop>(std::bind(&AsynchLogger::handler, this, std::placeholders::_1),_work_pattern))
{}
private:
//将日志信息输出到异步工作器管理的生产缓冲区中暂存
void Sink(const std::string& str)override
{
_pasynch->Push(str.c_str(), str.size());
}
//将缓冲群殴
void handler(Buffer buffer)
{
if (_sinks.empty())return;
for (auto e : _sinks)
{
e->Sink(buffer.Begin(), buffer.ReadAbleSize());
}
}
private:
AsynchLoop::ptr _pasynch;
};
}
缓冲区的设计思想:为了避免了重复申请和释放空间资源,缓冲区类采用固定大小的字符数组作为缓冲区,使用两个指针来控制缓冲区可用区间.
异步线程模块设计思想:异步线程模块主要完成异步从缓冲区提取数据进行落地输出的操作,为了避免重复的拷贝造作,管理两个缓冲区,一个生产,一个消费,当生产缓冲区有数据时就将交换生产和消费缓冲区的数组以及可用区间指针.
生产缓冲区是主线和异步线程的临界资源,需要维护他们的同步互斥关系,俩个线程不能同时访问缓冲区,当缓冲区满的时候,主线程主动释放锁,阻塞等待,当缓冲区没有信息时,异步线程就主动释放锁,阻塞等待.
#pragma once
/*
异步模块
功能:控制生产/消费缓冲区,创建异步线程将消费缓冲区日志落地.
实现思想:管理生产和消费两个缓冲区,主线程将消息添加到生产缓冲区,子线程将
指向消费缓冲区的有效信息头尾的指针与生产缓冲区交换,然后将日志信息落地
*/
#include"build.hpp"
#include"buffer.hpp"
#include
#include
#include
#include
#include
#include
namespace logspace
{
enum class AsynchType
{
SAFE_PATTERN,
UNSAFE_PATTERN,
};
class AsynchLoop
{
public:
using ptr = std::shared_ptr<AsynchLoop>;
using FUNTYPE = std::function<void(Buffer)>;
public:
AsynchLoop(FUNTYPE handler, AsynchType work_pattern)
:_handler(handler),
_stop(false),
_thread(std::thread(&AsynchLoop::Routine, this)),
_work_pattern(work_pattern)
{}
//生产数据
void Push(const char* data, size_t len)
{
//判断是否达到生产数据的条件,没有就阻塞
std::unique_lock<std::mutex> lock(_mutex);
//如果不是安全模式,就需要等待消费者消费完数据才能继续生产数据,这段时间就需要阻塞
if (_work_pattern == AsynchType::SAFE_PATTERN)
{
_pro_con.wait(lock, [&]()->bool {return _pro_buffer.WriteAbleSize() >= len; });
}
//添加数据
_pro_buffer.push(data, len);
//唤醒所有工作线程
_cons_con.notify_all();
}
~AsynchLoop()
{
Stop();
}
private:
//工作线程退出条件
void Stop()
{
_stop = true;
//唤醒工作线程
_cons_con.notify_all();
//线程等待
_thread.join();
}
//线程例程
void Routine()
{
//循环操作缓冲区
while (true)
{
{
//判断生产缓冲区中是否有数据,有则交换,无则等待
std::unique_lock<std::mutex>lock(_mutex);
_cons_con.wait(lock, [&]()->bool {return _stop || !_pro_buffer.Empty(); });
//当_stop为真,缓冲区为空的时候才退出,否则继续
if (_stop && _pro_buffer.Empty())
{
break;
}
//交换缓冲区
_cons_buffer.Swap(_pro_buffer);
}
//如果不是安全模式就不是阻塞,生产条件一直成立,没有阻塞即不用唤醒
if (_work_pattern == AsynchType::SAFE_PATTERN)
{
//唤醒生产者
_pro_con.notify_all();
}
//处理缓冲区数据
_handler(_cons_buffer);
//初始化消费缓冲区
_cons_buffer.reset();
}
}
private:
//异步工作模式
AsynchType _work_pattern;
//数据处理回调函数
FUNTYPE _handler;
//生产者条件变量
std::condition_variable _pro_con;
//消费者条件变量
std::condition_variable _cons_con;
//生产缓冲区
Buffer _pro_buffer;
//消费缓冲区
Buffer _cons_buffer;
//锁
std::mutex _mutex;
//线程对象
std::thread _thread;
//线程控制变量
std::atomic<bool> _stop;
};
}
考虑到日志器的构建需要先构建日志等级对象,信息对象,格式化对象,落地方式对象,比较复杂,所以考虑使用建造者模式构建日志器对象.
#pragma once
/*
建造模块
功能:提供不同异步和同步日志器以及全局日志器的构建方法
实现思想:使用建造者模式,基类提供日志器类型,日志器名字,日志等级,格式化器,落地方式,异步工作模式构造方法
子类提供日志器对象构造.
全局对象构建模块
功能:构建全局对象,可管理多个全局对象,可添加,根据日志器名字获取日志器全局对象,提供默认日志器全局对象
实现思想:使用单例懒汉模式,使用时创建全局对象,对象中管理默认日志器对象,和日志器数组,提供增加日志器,和获取日志器接口.
通过单例类扩展出全局日志器构建模块.
*/
#include
#include
#include"logger.hpp"
#include"level.hpp"
/*
1.抽象构造者对象
2.构建构造者
*/
namespace logspace
{
enum class LogType
{
LOG_SYNCHRONIZATION,
LOG_ASYNCHRONIZATION,
};
class BuildLogger
{
public:
BuildLogger()
:_log_type(LogType::LOG_SYNCHRONIZATION)
, _level(LogLevel::Level::DEBUG)
, _work_pattern(AsynchType::SAFE_PATTERN)
{}
//异步日志器工作模式,启动非安全模式
void BuildEnableUnsafePattern()
{
_work_pattern = AsynchType::UNSAFE_PATTERN;
}
//构造日志器类型
void BuildLogType(LogType log_type)
{
_log_type = log_type;
}
//日志器名字
void BuildLogName(std::string log_name)
{
_log_name = log_name;
}
//日志输出等级
void BuildLogLeve(LogLevel::Level level)
{
_level = level;
}
//日志格式化对象
void BuildLogformat(std::string pattern)
{
_format = std::make_shared<Formatter>(pattern);
}
//日志落地对象
template<class SinkType, class ...Args>
void BuildLogSink(Args&& ...args)
{
LogSink::ptr s = SinkFactory<SinkType,Args...>::Create(std::forward<Args>(args)...);
_sink.push_back(s);
}
//日志器对象构造
virtual Logger::ptr Build() = 0;
protected:
AsynchType _work_pattern;
LogType _log_type;
std::string _log_name;
LogLevel::Level _level;
Formatter::ptr _format;
std::vector<LogSink::ptr> _sink;
};
class BuildRealLogger :public BuildLogger
{
public:
Logger::ptr Build()override
{
assert(!_log_name.empty());
if (_format.get() == nullptr)
{
_format = std::make_shared<Formatter>();
}
if (_sink.empty() == true)
{
LogSink::ptr psink = SinkFactory<StdoutSink>::Create();
_sink.push_back(psink);
}
if (_log_type == LogType::LOG_ASYNCHRONIZATION)
{
return std::make_shared<AsynchLogger>(_log_name, _format,_level, _sink,_work_pattern);
}
return std::make_shared<SynchLogger>(_log_name, _format, _level,_sink);
}
};
/*
日志器管理类(单例)
1.方便使用全局对象
2.管理默认日志对象
3.管理日志对象数组
4.管理对象数组锁
5.添加日志器对象接口
6.获取指定日志器接口
7.判断有无管理日志器接口
*/
class LogManage
{
public:
static LogManage& Create()
{
static LogManage obj;
return obj;
}
//添加日志器
void AddLogger(const Logger::ptr plog)
{
std::unique_lock<std::mutex> lock(_mutex);
_logs[plog->GetLogName()] = plog;
}
//获取指定日志器
Logger::ptr GetLogger(const std::string& log_name)
{
if (HasLogger(log_name))
{
return _logs[log_name];
}
return nullptr;
}
//判断有无日志器
bool HasLogger(const std::string& log_name)
{
return _logs.count(log_name);
}
Logger::ptr GetRootLog()
{
return _root_log;
}
private:
LogManage()
{
//构造默认日志器
//创建构造日志器子类指针
std::shared_ptr<BuildLogger> blog(new BuildRealLogger());
blog->BuildLogName("root");
//将构造好的日志器赋值给默认日志器
_root_log = blog->Build();
//将默认日志器添加到数组中
_logs[_root_log->GetLogName()] = _root_log;
}
LogManage(const LogManage& log) = delete;
private:
Logger::ptr _root_log;
std::unordered_map<std::string, Logger::ptr> _logs;
std::mutex _mutex;
};
//全局日志器建造
class GlobalBuildRealLogger :public BuildLogger
{
public:
Logger::ptr Build()override
{
assert(!_log_name.empty());
if (_format.get() == nullptr)
{
_format = std::make_shared<Formatter>();
}
if (_sink.empty() == true)
{
LogSink::ptr psink = SinkFactory<StdoutSink>::Create();
_sink.push_back(psink);
}
Logger::ptr plog;
if (_log_type == LogType::LOG_ASYNCHRONIZATION)
{
plog = std::make_shared<AsynchLogger>(_log_name, _format, _level,_sink,_work_pattern);
}
else
{
plog = std::make_shared<SynchLogger>(_log_name, _format, _level,_sink);
}
LogManage::Create().AddLogger(plog);
return plog;
}
};
}