项目日志——日志器模块的设计、同步日志器实现、测试

日志器模块

设计

日志器模块的主要功能就是将前面的所有模块进行整合,向外提供接口,完成不同等级日志的输出

需要管理的成员有

  • 格式化模块的对象
  • 落地模块的对象组
  • 当前默认的输出等级
  • 互斥锁
  • 日志器名称
  • 各个等级的日志输出操作

对于同步日志和异步日志,实现的方式是不同的,因此需要抽象出一个基类,再派生出两个子类

同步日志器实现

/*
    日志器模块的实现
        1. 抽象日志器基类
        2. 派生同步异步日志器子类
*/
#pragma once
// #ifndef _GUN_SOURCE
// #define _GNU_SOURCE
// #endif
#include "util.hpp"
#include "level.hpp"
#include "format.hpp"
#include "sink.hpp"
#include <atomic>
#include <mutex>
#include <cstdarg>

namespace Xulog
{
    class Logger
    {
    public:
        using ptr = std::shared_ptr<Logger>;
        Logger(const std::string &loggername, LogLevel::value level, Formatter::ptr &formatter, std::vector<LogSink::ptr> sinks)
            : _logger_name(loggername), _limit_level(level), _formatter(formatter), _sinks(sinks.begin(), sinks.end())
        {
        }
        // 构造日志消息,格式化,交给输出接口
        void debug(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 是否达到输出等级
            if (LogLevel::value::DEBUG < _limit_level)
                return;
            // 将不定参进行格式化,将其转为字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == -1)
            {
                std::cout << "vasprintf fail\n";
                return;
            }
            va_end(ap);
            serialize(LogLevel::value::DEBUG, file, line, res);
            free(res);
        }
        void info(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 是否达到输出等级
            if (LogLevel::value::INFO < _limit_level)
                return;
            // 将不定参进行格式化,将其转为字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == -1)
            {
                std::cout << "vasprintf fail\n";
                return;
            }
            va_end(ap);
            serialize(LogLevel::value::INFO, file, line, res);
            free(res);
        }
        void warn(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 是否达到输出等级
            if (LogLevel::value::WARN < _limit_level)
                return;
            // 将不定参进行格式化,将其转为字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == -1)
            {
                std::cout << "vasprintf fail\n";
                return;
            }
            va_end(ap);
            serialize(LogLevel::value::WARN, file, line, res);
            free(res);
        }
        void error(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 是否达到输出等级
            if (LogLevel::value::ERROR < _limit_level)
                return;
            // 将不定参进行格式化,将其转为字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == -1)
            {
                std::cout << "vasprintf fail\n";
                return;
            }
            va_end(ap);
            serialize(LogLevel::value::ERROR, file, line, res);
            free(res);
        }
        void fatal(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 是否达到输出等级
            if (LogLevel::value::FATAL < _limit_level)
                return;
            // 将不定参进行格式化,将其转为字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == -1)
            {
                std::cout << "vasprintf fail\n";
                return;
            }
            va_end(ap);
            serialize(LogLevel::value::FATAL, file, line, res);
            free(res);
        }

    protected:
        // 抽象接口完成实际的落地输出,同步异步的落地方式不同
        virtual void log(const char *data, size_t len) = 0;
        void serialize(LogLevel::value level, const std::string &file, size_t line, char *str)
        {
            // 日志内容格式化
            LogMsg msg(level, line, file, _logger_name, str);
            std::stringstream ss;
            _formatter->Format(ss, msg);
            // 日志落地
            log(ss.str().c_str(), ss.str().size());
        }

        std::mutex _mutex;
        std::string _logger_name;
        std::atomic<LogLevel::value> _limit_level;
        Formatter::ptr _formatter;
        std::vector<LogSink::ptr> _sinks;
    };
    // 同步日志器
    class SyncLogger : public Logger
    {
    public:
        SyncLogger(const std::string &loggername, LogLevel::value level, Formatter::ptr &formatter, std::vector<LogSink::ptr> sinks)
            : Logger(loggername, level, formatter, sinks)
        {
        }

    protected:
        // 直接通过落地模块句柄进行日志输出
        void log(const char *data, size_t len) override
        {
            std::unique_lock<std::mutex> lock(_mutex);
            if (_sinks.empty())
                return;
            else
                for (auto &sink : _sinks)
                {
                    sink->log(data, len);
                }
        }
    };
    // TODO 异步日志器
}

测试

#include "util.hpp"
#include "level.hpp"
#include "format.hpp"
#include "sink.hpp"
#include "logger.hpp"

// 扩展测试: 滚动文件(时间)
// 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;    // 间隔大小
};

int main()
{
    // 测试同步日志器
    std::string logger_name = "synclog";
    Xulog::LogLevel::value limit = Xulog::LogLevel::value::WARN;
    Xulog::Formatter::ptr fmt(new Xulog::Formatter());
    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::vector<Xulog::LogSink::ptr> sinks = {std_lsp, file_lsp, roll_lsp, time_lsp};
    Xulog::Logger::ptr logger(new Xulog::SyncLogger(logger_name, limit, fmt, sinks));
    std::string str = "测试同步日志器-";
    logger->debug(__FILE__, __LINE__, "%s", str.c_str());
    logger->error(__FILE__, __LINE__, "%s", str.c_str());
    logger->fatal(__FILE__, __LINE__, "%s", str.c_str());
    logger->info(__FILE__, __LINE__, "%s", str.c_str());
    logger->warn(__FILE__, __LINE__, "%s", str.c_str());

    size_t size = 0;
    int cnt = 1;
    while (size < 1024 * 1024 * 10) // 10 个
    {
        logger->fatal(__FILE__, __LINE__, "%s-%d", str.c_str(), cnt++);
        size += 20;
    }
    return 0;
}
  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

栖林_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值