文章目录
日志落地模块
设计
功能是,将格式化完成后的日志消息字符串,输出到指定的位置
支持将日志落地到不同的位置
- 标准输出
- 指定文件
- 滚动文件
滚动文件按照时间或者大小进行滚动切换,可以按照天数对日志信息进行管理
我们这里实现按照大小进行滚动文件的设计
同时也是支持落地方向的扩展,可以写入到云服务器或者数据库中
用户可以自己编写一个新的日志落地模块进行实现,因此需要设计一个简单工厂模式进行管理
实现思想是这样的
- 抽象出落地模块的基类
- 派生出不同落地方向的子类
- 使用工厂模式进行创建和表示的分离,便于对象的扩展
实现
cpp
/*
日志落地模块的实现
1. 抽象落地基类
2. 派生子类
3. 使用工厂模式进行创建与表示的分离
*/
#pragma once
#include "util.hpp"
#include <memory>
#include <fstream>
#include <cassert>
#include <sstream>
namespace Xulog
{
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:
// 日志写入到标准输出
void log(const char *data, size_t len)
{
std::cout.write(data, len);
}
};
// 指定文件
class FileSink : public LogSink
{
public:
// 传入文件名时,构造并打开文件,将操作句柄管理起来
FileSink(const std::string &pathname)
: _pathname(pathname)
{
Util::File::createDirectory(Util::File::path(_pathname)); // 创建目录
_ofs.open(_pathname, std::ios::binary | std::ios::app); // 打开文件
assert(_ofs.is_open());
}
void log(const char *data, size_t len)
{
_ofs.write(data, len);
assert(_ofs.good());
}
private:
std::string _pathname;
std::ofstream _ofs;
};
// 滚动文件(大小)
class RollSinkBySize : public LogSink
{
public:
RollSinkBySize(const std::string &basename, size_t max_size)
: _basename(basename), _max_fsize(max_size), _current_fsize(0), _cnt(0)
{
std::string pathname = creatNewFIle();
Util::File::createDirectory(Util::File::path(pathname)); // 创建目录
_ofs.open(pathname, std::ios::binary | std::ios::app);
assert(_ofs.is_open());
}
void log(const char *data, size_t len)
{
if (_current_fsize >= _max_fsize)
{
std::string pathname = creatNewFIle();
_ofs.close(); // 关闭原来已经打开的文件
_ofs.open(pathname, std::ios::binary | std::ios::app);
assert(_ofs.is_open());
_current_fsize = 0;
// _cnt = 0;
}
_ofs.write(data, len);
assert(_ofs.good());
_current_fsize += len;
}
private:
std::string creatNewFIle() // 大小判断,超过则创建新文件
{
// 获取系统时间,构造文件扩展名
time_t t = 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 << "-" << _cnt++ << ".log";
return filename.str();
}
private:
std::string _basename; // 基础文件名 (+扩展文件名-时间|计数器)
std::ofstream _ofs;
size_t _max_fsize; // 大小上限
size_t _current_fsize; // 当前大小
size_t _cnt; // 日志数量
};
// 简单工厂模式
class SinkFactory
{
public:
template <typename SinkType, typename... Args>
static LogSink::ptr create(Args &&...args)
{
return std::make_shared<SinkType>(std::forward<Args>(args)...);
}
};
}
扩展实现
cpp
// 扩展测试: 滚动文件(时间)
// 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; // 间隔大小
};
测试
cpp
Xulog::LogMsg msg(Xulog::LogLevel::value::ERROR, 124, "main.cc", "root", "格式化功能测试");
Xulog::Formatter fmt1;
std::string str1 = fmt1.Format(msg);
// 测试原生日志落地模块
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_lsp->log(str1.c_str(), str1.size());
file_lsp->log(str1.c_str(), str1.size());
size_t size = 0;
size_t cnt = 0;
while (size < 1024 * 1024 * 100) // 100 个
{
std::string tmp = std::to_string(cnt++);
tmp += str1;
roll_lsp->log(tmp.c_str(), tmp.size());
size += tmp.size();
}
time_t t = Xulog::Util::Date::getTime();
while (Xulog::Util::Date::getTime() < t + 3)
{
time_lsp->log(str1.c_str(), str1.size());
}