日志器模块
设计
日志器模块的主要功能就是将前面的所有模块进行整合,向外提供接口,完成不同等级日志的输出
需要管理的成员有
- 格式化模块的对象
- 落地模块的对象组
- 当前默认的输出等级
- 互斥锁
- 日志器名称
- 各个等级的日志输出操作
对于同步日志和异步日志,实现的方式是不同的,因此需要抽象出一个基类,再派生出两个子类
同步日志器实现
/*
日志器模块的实现
1. 抽象日志器基类
2. 派生同步异步日志器子类
*/
#pragma once
// #ifndef _GUN_SOURCE
// #define _GNU_SOURCE
// #endif
#include "util.hpp"
#include "level.hpp"
#include "format.hpp"
#include "sink.hpp"
#include <atomic>
#include <mutex>
#include <cstdarg>
namespace Xulog
{
class Logger
{
public:
using ptr = std::shared_ptr<Logger>;
Logger(const std::string &loggername, LogLevel::value level, Formatter::ptr &formatter, std::vector<LogSink::ptr> sinks)
: _logger_name(loggername), _limit_level(level), _formatter(formatter), _sinks(sinks.begin(), sinks.end())
{
}
// 构造日志消息,格式化,交给输出接口
void debug(const std::string &file, size_t line, const std::string &fmt, ...)
{
// 是否达到输出等级
if (LogLevel::value::DEBUG < _limit_level)
return;
// 将不定参进行格式化,将其转为字符串
va_list ap;
va_start(ap, fmt);
char *res;
int ret = vasprintf(&res, fmt.c_str(), ap);
if (ret == -1)
{
std::cout << "vasprintf fail\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;
// 将不定参进行格式化,将其转为字符串
va_list ap;
va_start(ap, fmt);
char *res;
int ret = vasprintf(&res, fmt.c_str(), ap);
if (ret == -1)
{
std::cout << "vasprintf fail\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;
// 将不定参进行格式化,将其转为字符串
va_list ap;
va_start(ap, fmt);
char *res;
int ret = vasprintf(&res, fmt.c_str(), ap);
if (ret == -1)
{
std::cout << "vasprintf fail\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;
// 将不定参进行格式化,将其转为字符串
va_list ap;
va_start(ap, fmt);
char *res;
int ret = vasprintf(&res, fmt.c_str(), ap);
if (ret == -1)
{
std::cout << "vasprintf fail\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;
// 将不定参进行格式化,将其转为字符串
va_list ap;
va_start(ap, fmt);
char *res;
int ret = vasprintf(&res, fmt.c_str(), ap);
if (ret == -1)
{
std::cout << "vasprintf fail\n";
return;
}
va_end(ap);
serialize(LogLevel::value::FATAL, file, line, res);
free(res);
}
protected:
// 抽象接口完成实际的落地输出,同步异步的落地方式不同
virtual void log(const char *data, size_t len) = 0;
void serialize(LogLevel::value level, const std::string &file, size_t line, char *str)
{
// 日志内容格式化
LogMsg msg(level, line, file, _logger_name, str);
std::stringstream ss;
_formatter->Format(ss, msg);
// 日志落地
log(ss.str().c_str(), ss.str().size());
}
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 &loggername, LogLevel::value level, Formatter::ptr &formatter, std::vector<LogSink::ptr> sinks)
: Logger(loggername, level, formatter, sinks)
{
}
protected:
// 直接通过落地模块句柄进行日志输出
void log(const char *data, size_t len) override
{
std::unique_lock<std::mutex> lock(_mutex);
if (_sinks.empty())
return;
else
for (auto &sink : _sinks)
{
sink->log(data, len);
}
}
};
// TODO 异步日志器
}
测试
#include "util.hpp"
#include "level.hpp"
#include "format.hpp"
#include "sink.hpp"
#include "logger.hpp"
// 扩展测试: 滚动文件(时间)
// 1. 以时间段滚动
// 2. time(nullptr)%gap;
enum class TimeGap
{
GAP_SECOND,
GAP_MINUTE,
GAP_HOUR,
GAP_DAY
};
class RollSinkByTime : public Xulog::LogSink
{
public:
// 传入文件名时,构造并打开文件,将操作句柄管理起来
RollSinkByTime(const std::string &basename, TimeGap gap_type)
: _basename(basename)
{
switch (gap_type)
{
case TimeGap::GAP_SECOND:
_gap_size = 1;
break;
case TimeGap::GAP_MINUTE:
_gap_size = 60;
break;
case TimeGap::GAP_HOUR:
_gap_size = 3600;
break;
case TimeGap::GAP_DAY:
_gap_size = 3600 * 24;
break;
}
_current_gap = _gap_size == 1 ? Xulog::Util::Date::getTime() : (Xulog::Util::Date::getTime() % _gap_size);
std::string filename = createNewFile();
Xulog::Util::File::createDirectory(Xulog::Util::File::path(filename)); // 创建目录
_ofs.open(filename, std::ios::binary | std::ios::app);
assert(_ofs.is_open());
}
void log(const char *data, size_t len)
{
time_t current = Xulog::Util::Date::getTime();
if (current % _gap_size != _current_gap)
{
std::string filename = createNewFile();
_ofs.close();
_ofs.open(filename, std::ios::binary | std::ios::app);
assert(_ofs.is_open());
}
_ofs.write(data, len);
assert(_ofs.good());
}
private:
std::string createNewFile()
{
time_t t = Xulog::Util::Date::getTime();
struct tm lt;
localtime_r(&t, <);
std::stringstream filename;
filename << _basename << lt.tm_year + 1900 << lt.tm_mon + 1 << lt.tm_mday << lt.tm_hour << lt.tm_min << lt.tm_sec << ".log";
return filename.str();
}
private:
std::string _basename;
std::ofstream _ofs;
size_t _current_gap; // 当前时间段的个数
size_t _gap_size; // 间隔大小
};
int main()
{
// 测试同步日志器
std::string logger_name = "synclog";
Xulog::LogLevel::value limit = Xulog::LogLevel::value::WARN;
Xulog::Formatter::ptr fmt(new Xulog::Formatter());
Xulog::LogSink::ptr std_lsp = Xulog::SinkFactory::create<Xulog::StdoutSink>();
Xulog::LogSink::ptr file_lsp = Xulog::SinkFactory::create<Xulog::FileSink>("./log/test.log");
Xulog::LogSink::ptr roll_lsp = Xulog::SinkFactory::create<Xulog::RollSinkBySize>("./log/roll-", 1024 * 1024); // 每个文件1MB
Xulog::LogSink::ptr time_lsp = Xulog::SinkFactory::create<RollSinkByTime>("./log/roll-", TimeGap::GAP_SECOND); // 每个文件1s
std::vector<Xulog::LogSink::ptr> sinks = {std_lsp, file_lsp, roll_lsp, time_lsp};
Xulog::Logger::ptr logger(new Xulog::SyncLogger(logger_name, limit, fmt, sinks));
std::string str = "测试同步日志器-";
logger->debug(__FILE__, __LINE__, "%s", str.c_str());
logger->error(__FILE__, __LINE__, "%s", str.c_str());
logger->fatal(__FILE__, __LINE__, "%s", str.c_str());
logger->info(__FILE__, __LINE__, "%s", str.c_str());
logger->warn(__FILE__, __LINE__, "%s", str.c_str());
size_t size = 0;
int cnt = 1;
while (size < 1024 * 1024 * 10) // 10 个
{
logger->fatal(__FILE__, __LINE__, "%s-%d", str.c_str(), cnt++);
size += 20;
}
return 0;
}