项目日志——日志落地模块的设计、实现、测试

文章目录

日志落地模块

设计

功能是,将格式化完成后的日志消息字符串,输出到指定的位置

支持将日志落地到不同的位置

  • 标准输出
  • 指定文件
  • 滚动文件

滚动文件按照时间或者大小进行滚动切换,可以按照天数对日志信息进行管理

我们这里实现按照大小进行滚动文件的设计

同时也是支持落地方向的扩展,可以写入到云服务器或者数据库中

用户可以自己编写一个新的日志落地模块进行实现,因此需要设计一个简单工厂模式进行管理

实现思想是这样的

  1. 抽象出落地模块的基类
  2. 派生出不同落地方向的子类
  3. 使用工厂模式进行创建和表示的分离,便于对象的扩展

实现

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, &lt);
            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, &lt);
        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());
    }
相关推荐
SunkingYang9 个月前
python如何通过自身日志系统读写日志文件
python·logging·日志文件·日志系统·读写日志文件·如何写日志
花想云1 年前
C++项目实战——基于多设计模式下的同步&异步日志系统-⑪-日志器管理类与全局建造者类设计(单例模式)
c++·单例模式·设计模式·日志系统·c++项目
花想云1 年前
C++项目实战——基于多设计模式下的同步&异步日志系统-⑫-日志宏&全局接口设计(代理模式)
c++·设计模式·代理模式·日志系统·c++项目
花想云1 年前
C++项目实战——基于多设计模式下的同步&异步日志系统-⑦-日志输出格式化类设计
c++·设计模式·日志系统·c++项目
花想云1 年前
C++项目实战——基于多设计模式下的同步&异步日志系统-⑥-日志等级类与日志消息类设计
c++·设计模式·日志系统·c++项目
花想云1 年前
C++项目实战——基于多设计模式下的同步&异步日志系统-④-日志系统框架设计
c++·设计模式·日志系统·c++项目实战
m晴朗1 年前
Log4Qt日志框架(1)- 引入到QT中
qt·log4qt·日志系统