同步异步日志系统
一、日志器管理模块(单例模式)
⽇志的输出,我们希望能够在任意位置都可以进⾏,但是当我们创建了⼀个⽇志器之后,就会受到⽇志器所在作⽤域的访问区域限制。 因此,为了突破访问区域的限制,我们创建⼀个⽇志器管理类,且这个类是⼀个单例类,这样的话, 我们就可以在任意位置来通过管理器单例获取到指定的⽇志器来进⾏⽇志输出了。
日志器管理器:
作用1:对所有创建的日志器进行管理
特性:将管理器设计为单例
作用2:可以在程序的任意位置,获取相同的单例对象,获取其中的日志器进行日志输出
拓展:单例管理器创建的时候,默认先创建一个日志器(用于进行标准输出的打印)
目的:让用户在不创建任何日志器的情况下,也能进行标准输出的打印,方便用户使用
设计:
管理的成员:
1.默认日志器
2.所管理的日志器数组
3.互斥锁
提供的接口:
1添加日志器管理
2.判断是否管理了指定名称的日志器
3.获取指定名称的日志器
4.获取默认日志器
1.1 对日志器管理器进行设计
// 日志器管理模块
class LoggerManager
{
public:
// 1添加日志器管理
void addLogger(Logger::ptr &logger);
// 2.判断是否管理了指定名称的日志器
bool hasLogger(const std::string &name);
// 3.获取指定名称的日志器
Logger::ptr getLogger(const std::string &name);
// 4.获取默认日志器
Logger::ptr rootLogger();
// 5. 获取单例句柄
static LoggerManager &getInstance();
private:
// 构造函数私有化
LoggerManager() {}
private:
// 1.默认日志器
Logger::ptr _root_logger;
// 2.所管理的日志器数组
std::vector<Logger::ptr> _loggers;
// 3.互斥锁
std::mutex _mutex;
};
1.2 实现日志器管理类的各个功能
// 日志器管理模块
class LoggerManager
{
public:
// 1添加日志器管理
void addLogger(Logger::ptr &logger)
{
// 如果已经有了日志器,就不需要再添加
if (hasLogger(logger->name()))
return;
std::unique_lock<std::mutex> lock(_mutex); // 添加日志器之前加锁
_loggers.insert(std::make_pair(logger->name(), logger));
}
// 2.判断是否管理了指定名称的日志器
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;
}
// 3.获取指定名称的日志器
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; // 日志器所对应的值
}
// 4.获取默认日志器
Logger::ptr rootLogger()
{
return _root_logger;
}
// 5. 获取单例句柄
static LoggerManager &getInstance()
{
// 在c++11之后,针对静态局部变量,编译器在编译的层面上实现了线程安全
// 当静态局部变量在没有构造完成之前,其他的线程进入就会阻塞
static LoggerManager eton;
return eton;
}
private:
// 构造函数私有化
LoggerManager()
{
// 构造一个日志器建造者
std::unique_ptr<logslearn::LoggerBuilder> builder(new logslearn::LocalLoggerBuilder());
builder->buildLoggerName("root");
_root_logger = builder->build(); // 建造者构建对象,没有建造的就用默认对象
// 把默认构造的日志器管理起来
_loggers.insert(std::make_pair("root", _root_logger));
}
private:
// 1.默认日志器
Logger::ptr _root_logger;
// 2.所管理的日志器
std::unordered_map<std::string, Logger::ptr> _loggers;
// 3.互斥锁
std::mutex _mutex;
};
1.3. 设计一个全局的日志器建造者
在局部的日志器建造者上增加一个功能:将日志器添加到单例对象中
//设计一个全局的日志器建造者-在局部的日志器建造者上增加一个功能:将日志器添加到单例对象中
class GlobalLoggerBuilder : public LoggerBuilder
{
public:
Logger::ptr build() override
{
// 必须要有日志器名称
assert(_logger_name.empty() == false);
// 必须要有formatter//必须要有格式化器,没有就要创建
if (_formatter.get() == nullptr)
{
_formatter = std::make_shared<Formatter>();
}
// 如果没有落地方式就给它添加一个标准输出的默认落地方式
if (_sliks.empty())
{
buildSink<StdoutSink>();
}
//默认日志器
Logger::ptr logger;
// 如果类型为LOGGER_ASYNC,那么日志器为异步日志器
if (logger_type == LoggerType::LOGGER_ASYNC)
{
// 返回异步日志器对象
logger=std::make_shared<AsyncLogger>(_logger_name, _limit_level, _formatter, _sliks, _looper_type);
}else{
// 返回同步日志器的对象
logger=std::make_shared<SyncLogger>(_logger_name, _limit_level, _formatter, _sliks); // r日志器名字,等级,格式化,落地方式
}
//把日志器添加到日志器管理器中
LoggerManager::getInstance().addLogger(logger);
// 返回同步日志器的对象
return logger;
}
};
1.4 测试日志器管理器的接口和全局建造者类
在main函数里创建日志器,在普通函数里写日志。
// 测试代码
#include "util.hpp"
#include "level.hpp"
#include "message.hpp"
#include "format.hpp"
#include "sink.hpp"
#include "logger.hpp"
#include <unistd.h>
#include "buffer.hpp"
#include "looper.hpp"
#include <fstream>
void test_log(){
//日志器管理器
logslearn::Logger::ptr logger=logslearn::LoggerManager::getInstance().getLogger("async_logger");
//测试日志打印
logger->debug(__FILE__, __LINE__, "%s", "测试日志");
logger->info(__FILE__, __LINE__, "%s", "测试日志");
logger->warn(__FILE__, __LINE__, "%s", "测试日志");
logger->error(__FILE__, __LINE__, "%s", "测试日志");
logger->fatal(__FILE__, __LINE__, "%s", "测试日志");
size_t count = 0;
while (count < 100000)
{
logger->fatal(__FILE__, __LINE__, "测试日志-%d", count++);
}
}
int main()
{
//测试日志器管理模块
//先要构造一个建造者出来
//全局建造者构造日志器
std::unique_ptr<logslearn::LoggerBuilder> builder(new logslearn::GlobalLoggerBuilder());
//建造者构建零部件
builder->buildLoggerName("async_logger");
builder->buildLoggerLevel(logslearn::loglevel::value::WARN);
builder->buildLoggerFormatter("[%d{%H:%M:%S}][%t][%c][%f:%l][%p]%T%m%n");
builder->buildLoggerType(logslearn::LoggerType::LOGGER_ASYNC);
builder->buildEnabeUnSafeAsync();//切换模式
builder->buildSink<logslearn::StdoutSink>(); // 标准输出落地
builder->buildSink<logslearn::FileSink>("./logfile/async.log"); // 文件落地方式
builder->build();
// builder->buildSink<logslearn::RoolBySizeSink>("./logfile/roll-", 1024 * 1024); // 滚动文件落地方式
test_log();
return 0;
}
二、宏函数和全局接口设计
提供全局接口&宏函数, 对日志系统接口,进行使用便捷性优化
思想:
1.提供获取指定日志器的全局接口(避免用户自己操作单例对象)
2.使用宏函数对日志器的接口进行代理(代理模式)
3.提供宏函数,直接通过默认日志器进行日志的标准输出打印(不要获取日志器了)
2.1 新建一个.h,文件,文件里面放我们写的.hpp(各个模块文件)
方便外界使用者进行调用
#ifndef __M_LOGSLEARN_H__
#define __M_LOGSLEARN_H__
#include "util.hpp"
#include "level.hpp"
#include "message.hpp"
#include "format.hpp"
#include "sink.hpp"
#include "logger.hpp"
#include <unistd.h>
#include "buffer.hpp"
#include "looper.hpp"
#include <fstream>
namespace logslearn
{
// 1.提供获取指定日志器的全局接口(避免用户自己操作单例对象)
Logger::ptr getLogger(const std::string &name) // 指定日志器
{
return logslearn::LoggerManager::getInstance().getLogger(name);
}
Logger::ptr rootLogger() // 默认日志器
{
return logslearn::LoggerManager::getInstance().rootLogger();
}
// 2.使用宏函数对日志器的接口进行代理(代理模式)
#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__)
// 3.提供宏函数,直接通过默认日志器进行日志的标准输出打印(不要获取日志器了)
// 方法1
// #define DEBUG(logger,fmt,...) logger->denug(fmt,##__VA_ARGS__)
// #define DLOG(fmt,...) DEBUG(rootLogger(),fmt,##__VA_ARGS__) //变成_root_logger->debug(fmt,...)
// 方法2
#define DEBUG(fmt, ...) logslearn::rootLogger()->debug(fmt, ##__VA_ARGS__)
#define INFO(fmt, ...) logslearn::rootLogger()->info(fmt, ##__VA_ARGS__)
#define WARN(fmt, ...) logslearn::rootLogger()->warn(fmt, ##__VA_ARGS__)
#define ERROR(fmt, ...) logslearn::rootLogger()->error(fmt, ##__VA_ARGS__)
#define FATAL(fmt, ...) logslearn::rootLogger()->fatal(fmt, ##__VA_ARGS__)
}
#endif
通过两套宏函数,简化了用户对日志的输出工作
2.2 对宏函数与全局接口进行功能测试
测试一:测试第一组宏是否正确
输出打印的结果符合我们的预期
测试二:对宏函数与全局接口进行测试
可以自定义格式按要求进行输出,需要自己去创建日志器(这里是吧日志打印到文件和屏幕两种落地方法)
// 测试代码
#include "logslearn.h"
void test_log(){
// //日志器管理器
logslearn::Logger::ptr logger=logslearn::LoggerManager::getInstance().getLogger("async_logger");
//测试日志打印
logger->debug( "%s", "测试日志");
logger->info( "%s", "测试日志");
logger->warn( "%s", "测试日志");
logger->error( "%s", "测试日志");
logger->fatal("%s", "测试日志");
size_t count = 0;
while (count < 100000)
{
logger->fatal( "测试日志-%d", count++);
}
}
int main()
{
//全局建造者构造日志器
std::unique_ptr<logslearn::LoggerBuilder> builder(new logslearn::GlobalLoggerBuilder());
//建造者构建零部件
builder->buildLoggerName("async_logger");
builder->buildLoggerLevel(logslearn::loglevel::value::WARN);
builder->buildLoggerFormatter("[%d{%H:%M:%S}][%t][%c][%f:%l][%p]%T%m%n");
builder->buildLoggerType(logslearn::LoggerType::LOGGER_ASYNC);
builder->buildEnabeUnSafeAsync();//切换模式
builder->buildSink<logslearn::StdoutSink>(); // 标准输出落地
builder->buildSink<logslearn::FileSink>("./logfile/async.log"); // 文件落地方式
builder->build();
// builder->buildSink<logslearn::RoolBySizeSink>("./logfile/roll-", 1024 * 1024); // 滚动文件落地方式
test_log();
return 0;
}
日志器是以默认的格式向屏幕输出
// 测试代码
#include "logslearn.h"
void test_log(){
//测试日志打印
DEBUG("%s", "测试日志");//注意,宏替换过后命名空间就没了
INFO("%s", "测试日志");
WARN("%s", "测试日志");
ERROR("%s", "测试日志");
FATAL("%s", "测试日志");
size_t count = 0;
while (count < 100000)
{
FATAL("测试日志-%d", count++);
}
}
int main()
{
test_log();
return 0;
}
经过10来天的奋战,终于吧同步异步日志系统写完了。