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

文章目录

  • 专栏导读
  • 抽象基类
  • StdoutSink类设计
  • FileSink类设计
  • RollBySizeSink类设计
  • 日志落地工厂类设计
  • 日志落地类整理

专栏导读

作者简介:花想云 ,在读本科生一枚,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; // 已经写入的文件大小
};

日志落地工厂类设计

  • 为了避免用户将来实现自己的落地方向时需要修改源代码,这违背了开闭原则,所以我们采用工厂类的设计;
  • 由于不同的落地方向如StdoutSink、FileSink、RollBySizeSink,它们各自的构造函数所需参数并不相同,无法统一的管理,所以我们采用参数包的方式来解决。
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 
#include 
#include 
#include 

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

在这里插入图片描述

你可能感兴趣的:(c++,设计模式,日志系统,c++项目)