推广下载app拿佣金seo外包如何
C++项目 —— 基于多设计模式下的同步&异步日志系统(2)(工厂模式)
- 基类实现
- 滚动文件
- 文件名问题
- 工厂模式
- 一些扩展点
- `struct tm` 成员列表
- 关键注意事项
- 使用示例
- 1. 获取当前时间并打印
- 2. 与 `strftime` 配合使用
- 3. 构造自定义时间
- 常见问题
- 可视化记忆表
我们在之前把日志消息的主体已经组织好了,而且我们创建的适当的类控制日志格式,并且按照我们的格式组织日志消息。如果还没有看过的小伙伴可以点击这里:
https://blog.csdn.net/qq_67693066/article/details/147162921?sharetype=blogdetail&sharerId=147162921&sharerefer=PC&sharesource=qq_67693066&spm=1011.2480.3001.8118
我们这次的任务是要完成日志器中的一个小功能实现:日志输出的方向,我们日志输出的方向有:控制台,固定文件,滚动文件。这个模块的实现还会涉及到工厂模式的使用。
基类实现
首先设计思想还是比较清晰的,设计一个基类,派生出三个不同的方向,我们先实现两个方向比较简单的:
namespace logs
{//基类实现class BaseSink{public:using ptr = std::shared_ptr<BaseSink>; BaseSink(){}virtual ~BaseSink() {}virtual void log(const char *data, size_t len) = 0; //要继承实现的接口};class StdoutSink : public BaseSink{public://将日志消息写到标准输出void log(const char *data, size_t len){std::cout.write(data, len);}};class FixFileSink : public BaseSink {public:FixFileSink(const std::string& pathname):_pathname(pathname){//1.创建文件所在路径logs::utils::File::createDiretory(logs::utils::File::path(_pathname));//2.创建文件并打开_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; //流式文件操作};
}
我们也可以顺便测试一下:
#include"utils.hpp"
#include"level.hpp"
#include"message.hpp"
#include"fometter.hpp"
#include "sink.hpp"int main()
{// std::cout << logs::utils::File::path("./abc/def");// logs::utils::File::createDiretory("./abc/def");//std::cout <<logs::Loglevel::toString(logs::Loglevel::value::DEBUG);logs::logMsg msg(logs::Loglevel::value::DEBUG,"main.cc",53,"root","格式化功能测试....");logs::Formetter fmt("abc[%d{%H:%M:%S}][%c]%T%m%n");std::string str = fmt.format(msg);//标准输入测试size_t len = str.size();logs:: StdoutSink st;st.log(str.c_str(),len);//文件测试logs::FixFileSink fx("../test/test.txt");fx.log(str.c_str(),len);
}
滚动文件
滚动文件意思是,如果这个文件日志已经被写满了,会自动换到一个新的文件写日志,写满了的话又继续换。
文件名问题
既然会不停的创建文件,我们就不得不考虑一个问题,文件名。文件名是不能重复的,那么有没有一种方式,能保证我们的文件名是绝对不会重复的呢?有的,时间戳,时间只要在流逝,时间戳就会变。我们可以让时间戳作为我们文件名的一部分,这样就可以保证文件绝对不会重复了。
class RollFileSink : public BaseSink{public:private:std::string createNewFile(){//1.获取当前时间戳time_t time = logs::utils::Date::get_time();struct tm lt;localtime_r(&time,<);std::stringstream filename;filename << _basename;filename << lt.tm_year + 1900;filename << lt.tm_mon + 1;filename << lt.tm_mday;filename << lt.tm_hour;filename << lt.tm_min;filename << lt.tm_sec;filename << "-";filename << _name_count++;filename << ".log";return filename.str();}// 基础文件名 + 扩展文件名(以时间生成)组成一个实际的当前输出文件名size_t _name_count;std::string _basename; //基础文件名std::ofstream _ofs; //流式文件size_t _max_size; //最大文件大小size_t _cur_size: //当前文件大小};
class RollFileSink : public BaseSink{public:RollFileSink(const std::string& basename,size_t max_size):_basename(basename),_name_count(0),_max_size(max_size),_cur_size(0){std::string pathname = createNewFile();//1、创建文件的所在路径logs::utils::File::createDiretory(logs::utils::File::path(pathname));// 2.创建并打开日志文件_ofs.open(pathname, std::ios::binary | std::ios::app);assert(_ofs.is_open());}// 将日志消息写到固定文件中void log(const char *data, size_t len){if(_cur_size > _max_size){_ofs.close();std::string pathname = createNewFile();//1、创建文件的所在路径logs::utils::File::createDiretory(logs::utils::File::path(pathname));// 2.创建并打开日志文件_ofs.open(pathname, std::ios::binary | std::ios::app);assert(_ofs.is_open());}_ofs.write(data, len);assert(_ofs.good());_cur_size += len;}private:std::string createNewFile(){//1.获取当前时间戳time_t time = logs::utils::Date::get_time();struct tm lt;localtime_r(&time,<);std::stringstream filename;filename << _basename;filename << lt.tm_year + 1900;filename << lt.tm_mon + 1;filename << lt.tm_mday;filename << lt.tm_hour;filename << lt.tm_min;filename << lt.tm_sec;filename << "-";filename << _name_count++;filename << ".log";return filename.str();}// 基础文件名 + 扩展文件名(以时间生成)组成一个实际的当前输出文件名size_t _name_count;std::string _basename; //基础文件名std::ofstream _ofs; //流式文件size_t _max_size; //最大文件大小size_t _cur_size; //当前文件大小};
我们可以测试一下:
logs::RollFileSink roll("../test/mytest",1024);size_t cur = 0;while(cur < 1024 * 2){roll.log(str.c_str(),len);cur+=len;}
工厂模式
代码定义了一个名为 SinkFactory 的工厂类,用于创建日志输出器(Sink)的智能指针实例。它使用了现代 C++ 的模板和可变参数特性,是一个非常灵活通用的工厂实现:
class SinkFactory{public:template<typename SinkType,typename... Args>static BaseSink::ptr create(Args && ...args){return std::make_shared<SinkType>(std::forward<Args>(args)...);}};
auto st1 = logs::SinkFactory::create<logs::StdoutSink>();st1->log(str.c_str(),len);
这样我们不用用户直接接触接口,而是通过工厂,这样更具灵活性。
一些扩展点
struct tm
是 C/C++ 标准库中用于表示日历时间的结构体,定义在 <ctime>
头文件中。以下是其成员变量及详细说明:
struct tm
成员列表
成员 | 类型 | 说明 | 取值范围 | 注意事项 |
---|---|---|---|---|
tm_sec | int | 秒 | 0-61 (通常 0-59) | 允许闰秒 |
tm_min | int | 分钟 | 0-59 | |
tm_hour | int | 小时(24小时制) | 0-23 | |
tm_mday | int | 月中的第几天(Day of month) | 1-31 | |
tm_mon | int | 月份(从0开始) | 0-11 (0=1月) | 使用时需 +1 |
tm_year | int | 年份(从1900开始) | 0+=1900年 | 使用时需 +1900 |
tm_wday | int | 星期几(从0开始,0=周日) | 0-6 (0=周日) | |
tm_yday | int | 年中的第几天(从0开始) | 0-365 | |
tm_isdst | int | 夏令时标志: 正数=启用 0=禁用 负数=信息不可用 | -1, 0, 1 |
关键注意事项
-
特殊计数规则:
- 月份:
tm_mon
从 0 开始(0=1月,11=12月),显示时需要+1
- 年份:
tm_year
是 1900 年起的偏移量,真实年份 =tm_year + 1900
- 星期:
tm_wday
中 0 表示周日
- 月份:
-
夏令时处理:
if (timeinfo.tm_isdst > 0) {std::cout << "夏令时生效"; }
-
有效范围扩展:
tm_sec
允许 60-61 以兼容闰秒- 其他字段超出范围时,
mktime()
会自动标准化
使用示例
1. 获取当前时间并打印
#include <ctime>
#include <iostream>int main() {time_t now = time(nullptr);struct tm timeinfo;localtime_r(&now, &timeinfo); // 线程安全版本std::cout << "当前时间: " << 1900 + timeinfo.tm_year << "-" << 1 + timeinfo.tm_mon << "-"<< timeinfo.tm_mday << " "<< timeinfo.tm_hour << ":"<< timeinfo.tm_min << ":"<< timeinfo.tm_sec;
}
2. 与 strftime
配合使用
char buf[64];
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &timeinfo);
std::cout << "格式化时间: " << buf;
3. 构造自定义时间
struct tm custom_time = {0};
custom_time.tm_year = 2023 - 1900; // 2023年
custom_time.tm_mon = 6 - 1; // 6月
custom_time.tm_mday = 15; // 15日
time_t t = mktime(&custom_time); // 转换为time_t
常见问题
-
为什么年份从1900开始?
历史原因,早期系统用2位数存储年份,1900为基准年。 -
如何获取时区信息?
通过tm_gmtoff
和tm_zone
(非标准扩展,需检查平台支持):#ifdef __linux__ std::cout << "UTC偏移: " << timeinfo.tm_gmtoff / 3600 << "小时"; #endif
-
线程安全注意
- 优先使用
localtime_r()
(POSIX)或localtime_s()
(Windows) - 避免使用非线程安全的
localtime()
- 优先使用
可视化记忆表
struct tm {int tm_sec; // 秒 [0,61]int tm_min; // 分 [0,59]int tm_hour; // 时 [0,23]int tm_mday; // 日 [1,31]int tm_mon; // 月 [0,11] ← 注意+1int tm_year; // 年-1900 ← 注意+1900int tm_wday; // 周几 [0,6] (0=周日)int tm_yday; // 年内天数 [0,365]int tm_isdst; // 夏令时标志
};
掌握这些成员的含义和特性,可以高效处理各种时间操作需求。