当前位置: 首页 > news >正文

C++项目 —— 基于多设计模式下的同步异步日志系统(2)(工厂模式)

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,&lt);

            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,&lt);

            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_secint0-61 (通常 0-59)允许闰秒
tm_minint分钟0-59
tm_hourint小时(24小时制)0-23
tm_mdayint月中的第几天(Day of month)1-31
tm_monint月份(从0开始)0-11 (0=1月)使用时需 +1
tm_yearint年份(从1900开始)0+=1900年使用时需 +1900
tm_wdayint星期几(从0开始,0=周日)0-6 (0=周日)
tm_ydayint年中的第几天(从0开始)0-365
tm_isdstint夏令时标志:
正数=启用
0=禁用
负数=信息不可用
-1, 0, 1

关键注意事项

  1. 特殊计数规则

    • 月份tm_mon 从 0 开始(0=1月,11=12月),显示时需要 +1
    • 年份tm_year 是 1900 年起的偏移量,真实年份 = tm_year + 1900
    • 星期tm_wday 中 0 表示周日
  2. 夏令时处理

    if (timeinfo.tm_isdst > 0) {
        std::cout << "夏令时生效";
    }
    
  3. 有效范围扩展

    • 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

常见问题

  1. 为什么年份从1900开始?
    历史原因,早期系统用2位数存储年份,1900为基准年。

  2. 如何获取时区信息?
    通过 tm_gmtofftm_zone(非标准扩展,需检查平台支持):

    #ifdef __linux__
    std::cout << "UTC偏移: " << timeinfo.tm_gmtoff / 3600 << "小时";
    #endif
    
  3. 线程安全注意

    • 优先使用 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] ← 注意+1
    int tm_year;   // 年-1900  ← 注意+1900
    int tm_wday;   // 周几 [0,6] (0=周日)
    int tm_yday;   // 年内天数 [0,365]
    int tm_isdst;  // 夏令时标志
};

掌握这些成员的含义和特性,可以高效处理各种时间操作需求。

http://www.dtcms.com/a/130577.html

相关文章:

  • CAD 像素点显示图片——CAD二次开发 OpenCV实现
  • c语言 open函数
  • 「OC」小白书读书笔记——Block的相关知识(下)
  • 实现vlan间的通信
  • 解决单设备号双目摄像头调用难题:经验分享与总结
  • 融媒体中心智能语音识别系统设计与实现
  • 第2个小脚本:批量读取所有英文txt文章内容提取高频的单词
  • Matlab学习笔记五十:循环语句和条件语句的用法
  • 【微服务架构】SpringSecurity核心源码剖析+jwt+OAuth(七):SpringSecurity中的权限管理
  • 【HD-RK3576-PI】系统更新与恢复
  • Spring MVC 是如何将 @RequestMapping 注解映射到对应的 Handler 方法?
  • 【大英赛】大英赛准备笔记
  • MCP基础学习计划详细总结
  • Vue3项目中的前缀和
  • C++ ------ 智能指针
  • 2025年常见渗透测试面试题-webshell免杀思路(题目+回答)
  • 抓包神器,自研EtherCAT抓包工具
  • Next.js/Nuxt.js 服务端渲染优化
  • 1.1 初识AI
  • C语言进阶之字符函数和字符串函数
  • AcWing 5972. 科学记数法
  • 【游戏安全】强制交互类风险
  • Magnet 库的技术架构与核心机制解析
  • Docker部署SpringBoot项目(完整版)
  • 重载“<<”操作符
  • 基于多通道降压稳压器的机器人关节供电系统设计
  • 人工智能day03
  • 设计模式总章
  • UE5 添加随机弹道
  • 【linux知识】web服务环境搭建(一):用户以及开发环境初始化