文章目录
专栏导读
🌸作者简介:花想云,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人...致力于 C/C++、Linux 学习。
🌸专栏简介:本文收录于 C++项目------基于多设计模式下的同步与异步日志系统
日志器主要是用来与前端交互,当我们需要使用日志系统打印日志消息时,只需要创建Logger
对象,调用该对象的debug
、info
、warn
、error
、fatal
等方法输出自己想要打印的日志消息即可。支持解析可变参数列表和输出格式,就可以做到像printf
函数一样打印日志。
因为日志器模块是对前边所有模块的一个整合,所以Logger
类管理的成员有:
日志器名称
(日志器的唯一标识);格式化模块对象
(Formatter);落地模块对象数组
(一个日志器可能会向多个位置进行日志输出);默认的输出限制等级
(控制达到指定等级的日志才可以输出);互斥锁
(保证日志输出过程是线程安全的,不会出现交叉日志);
Logger
类提供的操作有:
debug
等级日志的输出操作;info
等级日志的输出操作;warn
等级日志的输出操作;error
等级日志的输出操作;fatal
等级日志的输出操作;
当前日志系统支持同步日志和异步日志两种方式,两个不同的日志器唯一的区别是它们在日志落地方式上有所不同:
同步日志器
:直接对日志消息进行输出;异步日志器
:将日志消息放入缓冲区,由异步线程
进行输出。
因此日志器在设计的时候先设计一个Logger基类
,在Logger基类
的基础上继承出SyncLogger同步日志器
和AsyncLogger异步日志器
。
Logger类设计
debug
、info
等接口在设计时,需要传递参数有文件名、行号、参数包。至于为什么要传递文件名与行号,因为要避免获取文件名和行号时是在本函数内部;- 将参数包进行内容提取后保存在字符串中,交由
serialize
进行处理; serialize
函数的功能是,将字符串中的内容进行日志消息格式化
,并进行落地操作
;
cpp
class Logger
{
public:
using ptr = std::shared_ptr<Logger>;
Logger(const std::string &logger_name,
LogLevel::value level,
Formatter::ptr &formatter,
std::vector<LogSink::ptr> &sinks) :
_logger_name(logger_name),
_limit_level(level),
_formatter(formatter),
_sinks(sinks.begin(), sinks.end())
{}
// 获取日志器名称
const std::string& name(){ return _logger_name; }
void debug(const std::string &file, size_t line, const std::string &fmt, ...)
{
// 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
// 判断当前的日志是否达到了输出等级
if (LogLevel::value::DEBUG < _limit_level)
{
return;
}
// 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
va_list ap;
va_start(ap, fmt);
char *res;
int ret = vasprintf(&res, fmt.c_str(), ap);
if (ret == 1)
{
std::cout << "vasprintf failed\n";
return;
}
va_end(ap);
serialize(LogLevel::value::DEBUG, file, line, res);
free(res);
}
void info(const std::string &file, size_t line, const std::string &fmt, ...)
{
// 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
if (LogLevel::value::INFO < _limit_level)
{
return;
}
// 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
va_list ap;
va_start(ap, fmt);
char *res;
int ret = vasprintf(&res, fmt.c_str(), ap);
if (ret == 1)
{
std::cout << "vasprintf failed\n";
return;
}
va_end(ap);
serialize(LogLevel::value::INFO, file, line, res);
free(res);
}
void warn(const std::string &file, size_t line, const std::string &fmt, ...)
{
// 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
if (LogLevel::value::WARN < _limit_level)
{
return;
}
// 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
va_list ap;
va_start(ap, fmt);
char *res;
int ret = vasprintf(&res, fmt.c_str(), ap);
if (ret == 1)
{
std::cout << "vasprintf failed\n";
return;
}
va_end(ap);
serialize(LogLevel::value::WARN, file, line, res);
free(res);
}
void error(const std::string &file, size_t line, const std::string &fmt, ...)
{
// 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
if (LogLevel::value::ERROR < _limit_level)
{
return;
}
// 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
va_list ap;
va_start(ap, fmt);
char *res;
int ret = vasprintf(&res, fmt.c_str(), ap);
if (ret == 1)
{
std::cout << "vasprintf failed\n";
return;
}
va_end(ap);
serialize(LogLevel::value::ERROR, file, line, res);
free(res);
}
void fatal(const std::string &file, size_t line, const std::string &fmt, ...)
{
// 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
if (LogLevel::value::FATAL < _limit_level)
{
return;
}
// 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
va_list ap;
va_start(ap, fmt);
char *res;
int ret = vasprintf(&res, fmt.c_str(), ap);
if (ret == 1)
{
std::cout << "vasprintf failed\n";
return;
}
va_end(ap);
serialize(LogLevel::value::FATAL, file, line, res);
free(res);
}
protected:
void serialize(LogLevel::value level, const std::string &file, size_t line, char *str)
{
// 构造LogMsg对象
LogMsg msg(level, line, file, _logger_name, str);
// 通过格式化工具对LogMsg进行格式化, 得到格式化后的日志字符串
std::stringstream ss;
_formatter->format(ss, msg);
// 对日志进行落地
log(ss.str().c_str(), ss.str().size());
}
virtual void log(const char *data, size_t len) = 0;
protected:
std::mutex _mutex;
std::string _logger_name; // 日志器名称
std::atomic<LogLevel::value> _limit_level; // 限制输出等级
Formatter::ptr _formatter;
std::vector<LogSink::ptr> _sinks; // 落地方向数组
};
同步日志器类设计
同步日志器设计较为简单,设计思想是:
- 遍历日志落地数组,以数组中的各种落地方式进行落地操作;
cpp
class SyncLogger : public Logger
{
public:
SyncLogger(const std::string &logger_name,
LogLevel::value level,
LOG::Formatter::ptr &formatter,
std::vector<LogSink::ptr> &sinks)
: Logger(logger_name, level, formatter, sinks)
{
}
protected:
void log(const char *data, size_t len)
{
std::unique_lock<std::mutex> lock(_mutex);
if (_sinks.empty())
return;
for (auto &sink : _sinks)
{
sink->log(data, len);
}
}
};
同步日志器测试
cpp
int main()
{
LOG::LogMsg msg(LOG::LogLevel::value::INFO, 53, "main.cc", "root", "格式化功能测试...");
LOG::Formatter fmt;
std::string str = fmt.format(msg);
LOG::LogSink::ptr time_lsp = LOG::SinkFactory::create<RollByTimeSink>("./logfile/roll-", TimeGap::GAP_SECOND);
time_t old = LOG::util::Date::getTime();
while(LOG::util::Date::getTime() < old + 5)
{
time_lsp->log(str.c_str(), str.size());
sleep(1);
}
std::string logger_name = "sync_logger";
LOG::LogLevel::value limit = LOG::LogLevel::value::WARN;
LOG::Formatter::ptr fmt(new LOG::Formatter("[%d{%H:%M:%S}][%c][%f:%l][%p]%T%m%n"));
LOG::LogSink::ptr stdout_lsp = LOG::SinkFactory::create<LOG::StdOutSink>();
LOG::LogSink::ptr file_lsp = LOG::SinkFactory::create<LOG::FileSink>("./logfile/test.log");
LOG::LogSink::ptr roll_lsp = LOG::SinkFactory::create<LOG::RollBySizeSink>("./logfile/test.log", 1024*1024);
std::vector<LOG::LogSink> sinks = {stdout_lsp, file_lsp, roll_lsp};
LOG::Logger::ptr logger(new LOG::SyncLogger(logger_name, limit, fmt, sinks));
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 cursize = 0, count = 0;
while(cursize < 1024*1024*10)
{
logger->fatal(__FILE__, __LINE__, "测试日志-%d", count++);
cursize+=20;
}
return 0;
}
日志器建造者模式设计
观察上一小节中的日志器测试代码,在构建一个同步日志器时,需要先设置很多的零部件
。这对于用户来说未免有些繁琐
。
我们需要使用建造者模式
来建造日志器,而不要让用户直接去构造日志器,以简化用户的使用复杂度
。
设计思想:
抽象一个日志器建造者类
:- 设置日志器类型;
- 将不同类型(同步&异步)日志器的创建放到同一个日志器建造者类中完成。
派生出具体的建造者类
----局部日志器建造者 & 全局日志器建造者类(后面添加了全局单例管理器之后,将日志器添加全局管理)。
抽象日志器建造者类
- 建造者类中包含成员:
logger_type
日志器类型;logger_name
日志器名称;limit_level
日志输出限制等级;formatter
格式化对象;sinks
日志落地数组;
- 还有构建各个零件的函数;
cpp
enum class LoggerType
{
LOGGER_SYNC,
LOGGER_ASYNC
};
// 1.抽象一个日志器建造者类(完成日志器所需零部件的构建 & 日志器的构建)
class LoggerBuilder
{
public:
LoggerBuilder() : _logger_type(LoggerType::LOGGER_SYNC),
_limit_level(LogLevel::value::DEBUG)
{}
void buildLoggerType(LoggerType type) { _logger_type = type; }
void buildLoggerName(const std::string &name) { _logger_name = name; }
void buildLoggerLevel(LogLevel::value level) { _limit_level = level; }
void buildFormatter(const std::string &pattern)
{
_formatter = std::make_shared<Formatter>(pattern);
}
template <typename SinkType, typename... Args>
void buildSink(Args &&...args)
{
LogSink::ptr psink = SinkFactory::create<SinkType>(std::forward<Args>(args)...);
_sinks.push_back(psink);
}
virtual Logger::ptr build() = 0;
protected:
LoggerType _logger_type;
std::string _logger_name;
std::atomic<LogLevel::value> _limit_level;
Formatter::ptr _formatter;
std::vector<LogSink::ptr> _sinks;
};
派生局部日志器建造者
cpp
/*2.派生出具体的建造者类---局部日志器的建造者 & 全局日志器的建造者*/
class LocalLoggerBuilder : public LoggerBuilder
{
public:
Logger::ptr build() override
{
assert(_logger_name.empty() == false);
if (_formatter.get() == nullptr)
{
_formatter = std::make_shared<Formatter>();
}
if (_sinks.empty())
{
buildSink<StdOutSink>();
}
if (_logger_type == LoggerType::LOGGER_ASYNC)
{
// 后面实现异步日志器后再完善...
}
return std::make_shared<SyncLogger>(_logger_name, _limit_level, _formatter, _sinks);
}
};
日志器建造者类测试
cpp
int main()
{
std::unique_ptr<LOG::LoggerBuilder> builder(new LOG::GlobalLoggerBuilder());
builder->buildLoggerName("sync_logger");
builder->buildLoggerLevel(LOG::LogLevel::value::WARN);
builder->buildFormatter("[%c][%f:%l]%m%n");
builder->buildLoggerType(LOG::LoggerType::LOGGER_SYNC);
builder->buildEnableUnSafeAsync();
builder->buildSink<LOG::FileSink>("./logfile/async.log");
builder->buildSink<LOG::StdOutSink>();
LOG::Logger::ptr = builder->build();
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 cursize = 0, count = 0;
while(cursize < 1024*1024*10)
{
logger->fatal(__FILE__, __LINE__, "测试日志-%d", count++);
cursize+=20;
}
return 0;
}
同步日志器类与日志器建造者类整理
cpp
#ifndef __M_LOGGER_H__
#define __M_LOGGER_H__
#include "util.hpp"
#include "level.hpp"
#include "format.hpp"
#include "sink.hpp"
#include "looper.hpp"
#include <cstdarg>
#include <atomic>
#include <thread>
#include <mutex>
#include <unordered_map>
namespace LOG
{
class Logger
{
public:
using ptr = std::shared_ptr<Logger>;
Logger(const std::string &logger_name,
LogLevel::value level,
Formatter::ptr &formatter,
std::vector<LogSink::ptr> &sinks) :
_logger_name(logger_name),
_limit_level(level),
_formatter(formatter),
_sinks(sinks.begin(), sinks.end())
{
}
const std::string& name(){ return _logger_name; }
void debug(const std::string &file, size_t line, const std::string &fmt, ...)
{
// 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
// 判断当前的日志是否达到了输出等级
if (LogLevel::value::DEBUG < _limit_level)
{
return;
}
// 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
va_list ap;
va_start(ap, fmt);
char *res;
int ret = vasprintf(&res, fmt.c_str(), ap);
if (ret == 1)
{
std::cout << "vasprintf failed\n";
return;
}
va_end(ap);
serialize(LogLevel::value::DEBUG, file, line, res);
free(res);
}
void info(const std::string &file, size_t line, const std::string &fmt, ...)
{
// 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
if (LogLevel::value::INFO < _limit_level)
{
return;
}
// 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
va_list ap;
va_start(ap, fmt);
char *res;
int ret = vasprintf(&res, fmt.c_str(), ap);
if (ret == 1)
{
std::cout << "vasprintf failed\n";
return;
}
va_end(ap);
serialize(LogLevel::value::INFO, file, line, res);
free(res);
}
void warn(const std::string &file, size_t line, const std::string &fmt, ...)
{
// 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
if (LogLevel::value::WARN < _limit_level)
{
return;
}
// 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
va_list ap;
va_start(ap, fmt);
char *res;
int ret = vasprintf(&res, fmt.c_str(), ap);
if (ret == 1)
{
std::cout << "vasprintf failed\n";
return;
}
va_end(ap);
serialize(LogLevel::value::WARN, file, line, res);
free(res);
}
void error(const std::string &file, size_t line, const std::string &fmt, ...)
{
// 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
if (LogLevel::value::ERROR < _limit_level)
{
return;
}
// 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
va_list ap;
va_start(ap, fmt);
char *res;
int ret = vasprintf(&res, fmt.c_str(), ap);
if (ret == 1)
{
std::cout << "vasprintf failed\n";
return;
}
va_end(ap);
serialize(LogLevel::value::ERROR, file, line, res);
free(res);
}
void fatal(const std::string &file, size_t line, const std::string &fmt, ...)
{
// 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
if (LogLevel::value::FATAL < _limit_level)
{
return;
}
// 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
va_list ap;
va_start(ap, fmt);
char *res;
int ret = vasprintf(&res, fmt.c_str(), ap);
if (ret == 1)
{
std::cout << "vasprintf failed\n";
return;
}
va_end(ap);
serialize(LogLevel::value::FATAL, file, line, res);
free(res);
}
protected:
void serialize(LogLevel::value level, const std::string &file, size_t line, char *str)
{
// 构造LogMsg对象
LogMsg msg(level, line, file, _logger_name, str);
// 通过格式化工具对LogMsg进行格式化, 得到格式化后的日志字符串
std::stringstream ss;
_formatter->format(ss, msg);
// 对日志进行落地
log(ss.str().c_str(), ss.str().size());
}
virtual void log(const char *data, size_t len) = 0;
protected:
std::mutex _mutex;
std::string _logger_name; // 日志器名称
std::atomic<LogLevel::value> _limit_level; // 限制输出等级
Formatter::ptr _formatter;
std::vector<LogSink::ptr> _sinks;
};
class SyncLogger : public Logger
{
public:
SyncLogger(const std::string &logger_name,
LogLevel::value level,
LOG::Formatter::ptr &formatter,
std::vector<LogSink::ptr> &sinks)
: Logger(logger_name, level, formatter, sinks)
{
}
protected:
void log(const char *data, size_t len)
{
std::unique_lock<std::mutex> lock(_mutex);
if (_sinks.empty())
return;
for (auto &sink : _sinks)
{
sink->log(data, len);
}
}
};
// 1.抽象一个日志器建造者类(完成日志器所需零部件的构建 & 日志器的构建)
// 1.设置日志器类型
// 2.将不同类型的日志器的创建放到同一个日志器建造者类中完成
enum class LoggerType
{
LOGGER_SYNC,
LOGGER_ASYNC
};
class LoggerBuilder
{
public:
LoggerBuilder() : _logger_type(LoggerType::LOGGER_SYNC),
_limit_level(LogLevel::value::DEBUG)
{}
void buildLoggerType(LoggerType type) { _logger_type = type; }
void buildEnableUnSafeAsync() { _looper_type = AsyncType::ASYNC_UNSAFE; }
void buildLoggerName(const std::string &name) { _logger_name = name; }
void buildLoggerLevel(LogLevel::value level) { _limit_level = level; }
void buildFormatter(const std::string &pattern)
{
_formatter = std::make_shared<Formatter>(pattern);
}
template <typename SinkType, typename... Args>
void buildSink(Args &&...args)
{
LogSink::ptr psink = SinkFactory::create<SinkType>(std::forward<Args>(args)...);
_sinks.push_back(psink);
}
virtual Logger::ptr build() = 0;
protected:
LoggerType _logger_type;
std::string _logger_name;
std::atomic<LogLevel::value> _limit_level;
Formatter::ptr _formatter;
std::vector<LogSink::ptr> _sinks;
};
/*2.派生出具体的建造者类---局部日志器的建造者 & 全局日志器的建造者*/
class LocalLoggerBuilder : public LoggerBuilder
{
public:
Logger::ptr build() override
{
assert(_logger_name.empty() == false);
if (_formatter.get() == nullptr)
{
_formatter = std::make_shared<Formatter>();
}
if (_sinks.empty())
{
buildSink<StdOutSink>();
}
if (_logger_type == LoggerType::LOGGER_ASYNC)
{}
return std::make_shared<SyncLogger>(_logger_name, _limit_level, _formatter, _sinks);
}
};
}
#endif