C++项目实战——基于多设计模式下的同步异步日志系统-⑧-日志落地类设计

news2024/12/23 23:39:07

文章目录

  • 专栏导读
  • 抽象基类
  • StdoutSink类设计
  • FileSink类设计
  • RollBySizeSink类设计
  • 日志落地工厂类设计
  • 日志落地类整理
  • 日志落地拓展测试
    • RollByTimeSink类设计
    • 测试代码
    • 测试完整代码

专栏导读

🌸作者简介:花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。

🌸专栏简介:本文收录于 C++项目——基于多设计模式下的同步与异步日志系统

🌸相关专栏推荐:C语言初阶系列C语言进阶系列C++系列数据结构与算法Linux

在这里插入图片描述

日志落地类主要负责将日志消息输出到指定的位置。目前实现了三个日志落地方向:

  • 标准输出:StdoutSink;
  • 固定文件:FileSink;
  • 滚动文件(文件按照时间/大小进行滚动切换):RollSink;

同时,日志落地类还应提供可扩展落地方向的功能。用户可以自己编写一个新的落地模块,将日志进行其他方向的落地。

  • 实现思想:
    • 抽象出落地模块类
    • 不同落地方向从基类进行派生
    • 使用工厂模式进行创建与表示分离

抽象基类

  • 提供一个智能指针对象方便管理;
  • 将日志输函数log作与析构函数设置为虚函数
class LogSink
{
public:
    using ptr = std::shared_ptr<LogSink>;
    LogSink() {}
    virtual ~LogSink() {}
    virtual void log(const char *data, size_t len) = 0;
};

StdoutSink类设计

// 落地方向:标准输出
class StdOutSink : public LogSink
{
public:
    void log(const char *data, size_t len)
    {
        std::cout.write(data, len);
    }
};

FileSink类设计

类中包含两个成员:

  • pathname:文件名,用来指定日志消息输出到哪个文件;
  • ofs:文件输出类对象,进行输出操作;

在C++中,ofstream 是用于文件输出的类,它是 C++ 标准库中的一部分,通常与 ifstream(用于文件输入)一起使用。ofstream 类允许你创建、打开、写入和关闭文本文件。你可以使用它来将数据写入文件,例如文本、数字或二进制数据。

// 落地方向:指定文件
class FileSink : public LogSink
{
public:
    FileSink(const std::string &pathname)
        :_pathname(pathname)
    {
        // 1.创建日志文件所在目录
        util::File::createDirectory(util::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;
};

RollBySizeSink类设计

日志文件滚动的条件有两个:文件大小和时间。我们可以选择:

  • 日志文件在大于1GB的时候会更换新的文件;
  • 每天定点滚动一个日志文件。

本项目基于文件大小的判断滚动生成新的文件。

滚动文件输出的必要性

  • 由于磁盘空间有限,我们不可能一直无限的向一个文件中增加数据;
  • 如果一个日志文件的体积太大,一方面是不好打开,另一方面是即使打开了,由于包含数据巨大,也不利于查找我们需要的信息;
  • 所以实际开发中会对单个日志文件的大小也会做一些限制,即当大小超过了某个大小时(如1GB),我们就重新创建一个新的日志文件来滚动写日志。对于那些过期的文件,大部分企业内都有专门的运维人员去定时清理过期的日志文件,或者设置定时任务,定时清理过期日志。
// 落地方向:滚动文件
class RollBySizeSink : public LogSink
{
public:
    RollBySizeSink(const std::string &basename, size_t max_size)
        : _basename(basename),
          _max_fsize(max_size),
          _cur_fsize(0),
          _name_count(0)
    {
        std::string pathname = createNewFile();
        // 1.创建日志文件所在的目录
        util::File::createDirectory(util::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_fsize >= _max_fsize)
        {
            _ofs.close(); // 关闭原来已经打开的文件
            std::string pathname = createNewFile();
            _ofs.open(pathname, std::ios::binary | std::ios::app);
            assert(_ofs.is_open());
            _cur_fsize = 0;
        }

        _ofs.write(data, len);
        assert(_ofs.good());
        _cur_fsize += len;
    }

private:
	// 切换文件后,以时间格式创建新的文件名
    std::string createNewFile()
    {
        time_t t = util::Date::getTime();
        struct tm lt;
        localtime_r(&t, &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();
    }

private:
    size_t _name_count;
    std::string _basename;
    std::ofstream _ofs;
    size_t _max_fsize; // 日志文件最大大小
    size_t _cur_fsize; // 已经写入的文件大小
};

日志落地工厂类设计

  • 为了避免用户将来实现自己的落地方向时需要修改源代码,这违背了开闭原则,所以我们采用工厂类的设计;
  • 由于不同的落地方向如StdoutSinkFileSinkRollBySizeSink,它们各自的构造函数所需参数并不相同,无法统一的管理,所以我们采用参数包的方式来解决。
class SinkFactory
{
public:
    template <typename SinkType, typename... Args>
    static LogSink::ptr create(Args &&...args)
    {
        return std::make_shared<SinkType>(std::forward<Args>(args)...);
    }
};

日志落地类整理

#ifndef __M_SINK_H__
#define __M_SINK_H__

#include "util.hpp"
#include <memory>
#include <sstream>
#include <fstream>
#include <cassert>

namespace LOG
{
    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)
        {
            // 1.创建日志文件所在目录
            util::File::createDirectory(util::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;
    };

    // 落地方向:滚动文件
    class RollBySizeSink : public LogSink
    {
    public:
        RollBySizeSink(const std::string &basename, size_t max_size)
            : _basename(basename),
              _max_fsize(max_size),
              _cur_fsize(0),
              _name_count(0)
        {
            std::string pathname = createNewFile();
            // 1.创建日志文件所在的目录
            util::File::createDirectory(util::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_fsize >= _max_fsize)
            {
                _ofs.close(); // 关闭原来已经打开的文件
                std::string pathname = createNewFile();
                _ofs.open(pathname, std::ios::binary | std::ios::app);
                assert(_ofs.is_open());
                _cur_fsize = 0;
            }

            _ofs.write(data, len);
            assert(_ofs.good());
            _cur_fsize += len;
        }

    private:
        std::string createNewFile()
        {
            time_t t = util::Date::getTime();
            struct tm lt;
            localtime_r(&t, &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();
        }

    private:
        size_t _name_count;
        std::string _basename;
        std::ofstream _ofs;
        size_t _max_fsize; // 日志文件最大大小
        size_t _cur_fsize; // 已经写入的文件大小
    };
    class SinkFactory
    {
    public:
        template <typename SinkType, typename... Args>
        static LogSink::ptr create(Args &&...args)
        {
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }
    };
}
#endif

日志落地拓展测试

本小节主要内容为测试日志落地类是否支持扩展功能。我们新增一个基于时间的滚动文件类RollByTimeSink

RollByTimeSink类设计

#include <unistd.h>

enum class TimeGap
{
    GAP_SECOND,
    GAP_MINUTE,
    GAP_HOUR,
    GAP_DAY
};

class RollByTimeSink : public LOG::LogSink
{
public:
    // 构造时传入文件名,并打开文件,将操作句柄管理起来
    RollByTimeSink(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;
        }
        _cur_gap = _gap_size == 1? LOG::util::Date::getTime() : LOG::util::Date::getTime() % _gap_size; // 获取当前是第几个时间段
        std::string filename = createNewFile();
        LOG::util::File::createDirectory(LOG::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 cur = LOG::util::Date::getTime();
        if((cur % _gap_size) != _cur_gap)
        {
            _ofs.close();
            std::string filename = createNewFile();
            _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 = LOG::util::Date::getTime();
        struct tm lt;
        localtime_r(&t, &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 << ".log";
        return filename.str();
    }
private:
    std::string _basename;
    size_t _gap_size; // 时间段的大小
    int _cur_gap; // 当前是第几个时间段
    std::ofstream _ofs;
};

测试代码

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);
    }
}

测试结果
在这里插入图片描述

测试完整代码

test.cc

#include "LogSink.hpp"
#include "util.hpp"
#include <unistd.h>

enum class TimeGap
{
    GAP_SECOND,
    GAP_MINUTE,
    GAP_HOUR,
    GAP_DAY
};

class RollByTimeSink : public LOG::LogSink
{
public:
    // 构造时传入文件名,并打开文件,将操作句柄管理起来
    RollByTimeSink(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;
        }
        _cur_gap = _gap_size == 1? LOG::util::Date::getTime() : LOG::util::Date::getTime() % _gap_size; // 获取当前是第几个时间段
        std::string filename = createNewFile();
        LOG::util::File::createDirectory(LOG::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 cur = LOG::util::Date::getTime();
        if((cur % _gap_size) != _cur_gap)
        {
            _ofs.close();
            std::string filename = createNewFile();
            _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 = LOG::util::Date::getTime();
        struct tm lt;
        localtime_r(&t, &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 << ".log";
        return filename.str();
    }
private:
    std::string _basename;
    size_t _gap_size; // 时间段的大小
    int _cur_gap; // 当前是第几个时间段
    std::ofstream _ofs;
};

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);
    }
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1006592.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

React框架下如何集成H.265网页流媒体视频播放器EasyPlayer.js?

H5无插件流媒体播放器EasyPlayer属于一款高效、精炼、稳定且免费的流媒体播放器&#xff0c;可支持多种流媒体协议播放&#xff0c;可支持H.264与H.265编码格式&#xff0c;性能稳定、播放流畅&#xff0c;能支持WebSocket-FLV、HTTP-FLV&#xff0c;HLS&#xff08;m3u8&#…

Segment Anything Model(SAM)论文解读

一、引言 在这项工作中&#xff0c;作者的目标是建立一个图像分割的基础模型。也就是说&#xff0c;寻求开发一个提示模型&#xff0c;并使用一个能够实现强大泛化的任务在广泛的数据集上对其进行预训练。有了这个模型&#xff0c;使用即时工程解决新数据分布上的一系列下游分…

《C++ primer plus》精炼(OOP部分)——对象和类(3)

学习是人类的天性&#xff0c;持续学习是人类的本能。 文章目录 抽象数据类型&#xff08;ADT&#xff09;--------------------------第11章&#xff1a;使用类--------------------------运算符重载运算符重载限制 抽象数据类型&#xff08;ADT&#xff09; 类的思想很适合用…

狼的传说小游戏

欢迎来到程序小院 狼的传说 玩法&#xff1a; 鼠标左键选择能防御、战斧、风暴3%、滚石10%、藤曼5%、冰柱5%、飞跃10%、三叶草20%、钢叉15%&#xff0c;消灭所有敌人&#xff0c;不同关卡不同敌人&#xff0c;快去闯关消灭敌人吧^^。开始游戏https://www.ormcc.com/play/gameS…

Redis 事务 - 监控测试

Redis 基本事务操作 Redis事务本质&#xff1a;一组命令的集合&#xff01;一个事务中的所有命令都会被序列化&#xff0c;在事务执行过程的中&#xff0c;会按照顺序执行&#xff01; Redis事务是一组Redis命令的有序集合&#xff0c;这些命令在事务中按照顺序执行&#xff0…

昆明草海隧道湿地公园,荻花盛开,又一个网红打卡地

昆明&#xff0c;这座美丽的城市&#xff0c;总是带给人们无尽的惊喜。其中&#xff0c;草海隧道湿地公园更是备受游客青睐。而如今&#xff0c;这里已经成为了一处网红打卡地&#xff0c;吸引了无数游客前来观赏。 一、网红打卡地&#xff0c;草海隧道湿地公园草海隧道湿地公园…

【IEEE会议征稿】第三届电子信息工程与计算机技术国际学术会议(EIECT 2023)

第三届电子信息工程与计算机技术国际学术会议&#xff08;EIECT 2023&#xff09; 2023 3rd International Conference on Electronic Information Engineering and Computer Technology 随着科学技术的高速发展&#xff0c;计算机技术革新日新月异&#xff0c;其智能化、网络…

三分钟教会你快速使用SpringBoot整合第三方登录

前言 在我们生活中无时无刻都在使用第三方登录&#xff0c;如QQ登录、微信登录等&#xff0c;今天教你如何快速使用springboot整合第三方登录&#xff0c;下面教程以Gitee为例 1. 我们借助JustAuth组件来完成第三方登录 Justauth官网&#xff1a;https://www.justauth.cn/ …

数据在内存中的存储——练习3

题目&#xff1a; 3.1 #include <stdio.h> int main() {char a -128;printf("%u\n",a);return 0; }3.2 #include <stdio.h> int main() {char a 128;printf("%u\n",a);return 0; }思路分析&#xff1a; 首先二者极其相似%u是无符号格式进行…

Callable 和 FutureTask 带返回值线程使用和源码分析

Callable 和 FutureTask 可以创建带返回值的线程&#xff0c;那它是怎么实现的呢&#xff1f;笔者下面分析&#xff0c;先看看它是怎么使用的 1、Callable FutureTask使用 新建 Name类&#xff0c;实现 Callable 接口&#xff0c;返回 String 类型值 package com.wsjzzcbq.ja…

骨传导耳机危害有哪些?值得入手吗?

事实上&#xff0c;只要是正常使用&#xff0c;骨传导耳机并不会对身体造成伤害&#xff0c;并且在众多耳机种类中&#xff0c;骨传导耳机可以说是相对健康的一种耳机&#xff0c;这种耳机最独特的地方便是声波不经过外耳道和鼓膜&#xff0c; 而是直接将人体骨骼结构作为传声介…

时间序列预测系列之循环神经网络

文章目录 1.前言2.RNN基础组件1.RNN2.LSTM3.GRU4.FC-LSTM5.ConvLSTM6.CNN-LSTM 1.前言 循环神经网络&#xff08;Recurrent Neural Network&#xff0c;简称RNN&#xff09;是一类在处理序列数据和时间序列数据时非常有用的神经网络架构。RNN的主要特点是它们具有循环连接&…

QT网页 webengine / CEF

QT WebEngine 官方文档 WebEngine 架构&#xff1a; 能看到 WebEngine 有一个核心模块是基于 Chromium 构造的&#xff0c;通过使用 Chromium 的Blink渲染引擎和V8 JavaScript引擎来处理和渲染Web内容&#xff0c;并将这些底层技术封装为一系列高级的C类和接口&#xff0c;以…

JVM基础-Hotspot VM相关知识学习

这里写目录标题 jdkJVM虚拟机类类的生命周期类加载的时机类的双亲委派机制类的验证 java对象Mark WordKlass Pointer实例数据对齐数据 字符串常量池垃圾收集器1.Serial收集器&#xff08;串行收集器&#xff09;cms垃圾算法G1垃圾收集器与CMS收集器相比, G1收集器的优势:G1收集…

开利网络到访东家集团,沟通招商加盟数字化机制落地事项

近日&#xff0c;开利网络到访东家集团&#xff0c;就集团近日开展的奖金池激励制度的推进情况和市场反馈进行复盘与沟通。通过打破“层层中间商”&#xff0c;提供厂家直供价格的方式&#xff0c;东家集团推出了数字化激励机制&#xff0c;消费集团会员礼包即可在会员专区进行…

Layui快速入门之第五节 导航

目录 一&#xff1a;基本概念 导航依赖element模块 API 渲染 属性 事件 二&#xff1a;水平导航 常规用法&#xff1a; 三&#xff1a;垂直导航 四&#xff1a;侧边垂直导航 五&#xff1a;导航主题 六&#xff1a;加入徽章等元素 七&#xff1a;面包屑导航 ps&a…

大学经典题目:Java输出杨辉三角形

本节利用​ 过 Java 语 ​言中的流程控制语句&#xff0c;如条件语句、循环语句和跳转语句等知识输出一个指定行数的杨辉三角形。 杨辉三角形由数字进行排列&#xff0c;可以把它看作是一个数字表&#xff0c;其基本特性是两侧数值均为 1&#xff0c;其他位置的数值是其左上方数…

Kettle——大数据ETL工具

文章目录 ETL一、Kettle二、安装和运行Kettle三、Kettle使用四、Kettle核心概念可视化转换步骤跳 ETL ETL(Extract-Transform-Load&#xff0c;即数据抽取、转换、转载)&#xff0c;对于企业或行业应用来说&#xff0c;我们经常会遇到各种数据的处理&#xff0c;转换&#xff…

【操作系统】进程的概念、组成、特征

概念组成 程序&#xff1a;静态的放在磁盘&#xff08;外存&#xff09;里的可执行文件&#xff08;代码&#xff09; 作业&#xff1a;代码&#xff0b;数据&#xff0b;申请&#xff08;JCB&#xff09;&#xff08;外存&#xff09; 进程&#xff1a;程序的一次执行过程。 …

5. 自动求导

5.1 向量链式法则 ① 例子1是一个线性回归的例子&#xff0c;如下图所示。 5.2 自动求导 5.3 计算图 5.4 两种模型 ① b是之前计算的结果&#xff0c;是一个已知的值。 5.5 复杂度 5.6 自动求导 import torch x torch.arange(4.0) x 结果&#xff1a; ② 在外面计算y关于x的…