日志系统——日志落地模块设计

news2024/12/23 9:21:46

一,大致框架

首先我们需要明确模块的功能,将格式化后的日志信息字符串,输出到对应的位置。同时由于用户输出信息的方式是多样的,因此我们日志落地模块也支持拓展的功能,也就是用户自定义落地方式。

日志信息落地的方式大概可以分为下面三种

1.标准输出,指的是将信息直接进行cout打印,程序相当简单,但是不方便分析

2.指定文件, 将日志信息打印到目标文件中,方便后续查看进程运行状况,但也有问题例如单个文件过大,多天的信息堆积一处不方便查询观看。

3.滚动文件,滚动文件分为两种:①按照文件大小进行切换,例如一个文件存储1G的内容,内容超出1G,重新创建文件存储。②按照时间进行切换,例如以天为单位,每一天都开一个新文件存储信息(该方式我们会以用户自定义输出方法的方法实现)。

实现思路:

1.抽象出一个日志落地模块基类

2.不同的落地方式从基类创建派生类实现

3.使用简单工厂模式,将对落地方式进行一层封装,也就是将创建和实现分开。

二,代码实现

2.1 sink.hpp

#ifndef _M_SINK_H_
#define _M_SINK_H_

#include "util.hpp"
#include "format.hpp"
#include <iostream>
#include <fstream>
#include <memory>
#include <cassert>
#include <ctime>

namespace mjwlog
{
    // 基类
    class Sink
    {
    public:
        using ptr = std::shared_ptr<Sink>;
        Sink(){};
        virtual ~Sink(){};
        virtual void log(const char *msg, const size_t len) = 0;
    };

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

    // 文件输出落地方向
    class FileSink : public Sink
    {
    public:
        // 先获得存储位置
        FileSink(const std::string& filepath)
            : _filepath(filepath)
        {
            // 在构造的时候就将文件打开
            // 1.先判断目录是否存在,不存在创建
            util::FileDirectory::createDirectory(util::FileDirectory::getDirectory(_filepath));
            _ofs.open(_filepath, std::ios_base::binary | std::ios_base::app);
            assert(_ofs.is_open()); // 打开失败,直接退出报错
        }

        // 写入内容
        void log(const char *msg, const size_t len) override
        {
            _ofs.write(msg, len);
            if (!_ofs.good())
            {
                std::cout << "日志写入文件失败" << std::endl;
            }
        }

    private:
        std::string _filepath;
        std::ofstream _ofs;
    };

    // 滚动文件输出落地方向(按照大小)
    class RollFileSink : public Sink
    {
    public:
        // 先获得存储位置
        // basepath:"/a/b/log"(自行添加后缀方便辨认) ->"/a/b/log200208051123.log-_filecount"
        RollFileSink(const std::string& basepath,const size_t& max_size)
            : _basepath(basepath),
              _max_size(max_size),
              _cur_size(0),
              _filecount(1)
        {
            // 在构造的时候就将文件打开
            //检查目录"/a/b/log"是否存在
            util::FileDirectory::createDirectory(util::FileDirectory::getDirectory(_basepath));

            //打开文件
            creatfile();
        }

        // 写入内容
        void log(const char *msg, const size_t len) override
        {
            //判断文件是否已满,满了就重新开文件
            if(_cur_size>=_max_size)
            {
                creatfile();
                //注意创建新文件后,_cur_size需要置为0
                _cur_size=0;
            }

            _ofs.write(msg,len);

            if (!_ofs.good())
            {
                std::cout << "日志写入文件失败" << std::endl;
            }
            _cur_size+=len;
        }

    private:
        //辅助接口
        //1.添加后缀
        std::string AddSuffix()
        {
            std::string suffix = _basepath;
            // basepath:"/a/b/log"(自行添加后缀方便辨认) ->"/a/b/log200208051123.log-_filecount"
            time_t time = util::gettime::nowtime();
            // localtime_r 函数获取时间详细属性
            struct tm attr;
            localtime_r(&time, &attr);

            suffix += std::to_string(attr.tm_year+1900); // 年,默认从1900开始算,因此需要+1900
            suffix += std::to_string(attr.tm_mon+1);  // 月,0-11
            suffix += std::to_string(attr.tm_mday); // 日
            suffix += std::to_string(attr.tm_hour); // 时
            suffix += std::to_string(attr.tm_min);  // 分
            suffix += ".log";
            suffix+="-"+std::to_string(_filecount++);

            return suffix;
        }

        //2.重新开文件
        void creatfile()
        {
            //打开前需要将原文件关闭
            _ofs.close();
            // 1.先判断目录是否存在,不存在创建
            std::string filepath=AddSuffix();
            _ofs.open(filepath, std::ios_base::binary | std::ios_base::app);
            assert(_ofs.is_open()); // 打开失败,直接退出报错
        }

    private:
        std::string _basepath;
        std::ofstream _ofs;
        size_t _max_size; // 文件空间上限
        size_t _cur_size; // 文件当前空间大小
        int _filecount;
    };

    //简单工厂模式,将创建和实现方法分开
    class SinkFactory
    {
    public: 
        template<typename SinkDirection,typename ...Args>
        static Sink::ptr  LogSink(Args &&...args)
        {
            return std::make_shared<SinkDirection>(std::forward<Args>(args)...);
        }

    };



}

#endif

测试

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

int main()
{
    mjwlog::message msg(53,mjwlog::LogLevel::level::DEBUG,"test","root","日志格式化输出调试");
    mjwlog::Formatter ft;
    std::cout<<ft.format(msg)<<std::endl;

    mjwlog::Sink::ptr stdout_ptr=mjwlog::SinkFactory::LogSink<mjwlog::StdoutSink>();
    mjwlog::Sink::ptr file_ptr=mjwlog::SinkFactory::LogSink<mjwlog::FileSink>("./logfile/test.log");
    //以1m为分界线,进行滚动文件输出
    mjwlog::Sink::ptr rollfile_ptr=mjwlog::SinkFactory::LogSink<mjwlog::RollFileSink>("./logfile/test",1024*1024);

    stdout_ptr->log(ft.format(msg).c_str(),ft.format(msg).size());
    file_ptr->log(ft.format(msg).c_str(),ft.format(msg).size());

    size_t testsize=0;
    //我们写入10m的内容
    while(testsize<1024*1024*10)
    {
        rollfile_ptr->log(ft.format(msg).c_str(),ft.format(msg).size());
        testsize+=ft.format(msg).size();
    }




    //std::cout<<"abc\taaa"<<std::endl;
    /* std::cout<<mjwlog::LogLevel::LeveltoString(mjwlog::LogLevel::level::DEBUG)<<std::endl;
    std::cout<<mjwlog::LogLevel::LeveltoString(mjwlog::LogLevel::level::INFO)<<std::endl;
    std::cout<<mjwlog::LogLevel::LeveltoString(mjwlog::LogLevel::level::WARN)<<std::endl;
    std::cout<<mjwlog::LogLevel::LeveltoString(mjwlog::LogLevel::level::ERROR)<<std::endl;
    std::cout<<mjwlog::LogLevel::LeveltoString(mjwlog::LogLevel::level::FATAL)<<std::endl;
    std::cout<<mjwlog::LogLevel::LeveltoString(mjwlog::LogLevel::level::OFF)<<std::endl; */


    /* std::cout<<mjwlog::util::gettime::nowtime()<<std::endl;
    mjwlog::util::FileDirectory::createDirectory("./abc/abbbb/xxxx/vvvv/c.txt"); */
    return 0;
}

结果

2.2 自定义日志落地方式

 这里我们以写入滚动文件(按照时间)为例

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

// 滚动文件输出落地方向(按照时间)
enum class time_seg
{
    SECOND = 0,
    MINUTE,
    HOUR,
    DAY
};
//注意:当_seg以秒为单位时,getTimeSecond(_seg)返回1的话,任何数%1都为0
//这样就导致_create_time和_cur_time永远为0,这样就永远没办法创新文件
//因此当_seg以秒为单位时,_create_time和_cur_time不%上任何值
class TimeFileSink : public mjwlog::Sink
{
public:
    // 先获得存储位置
    // basepath:"/a/b/log"(自行添加后缀方便辨认) ->"/a/b/log200208051123.log-_filecount"
    TimeFileSink(const std::string &basepath, const time_seg &seg)
        : _basepath(basepath),
          _seg(seg),
          _create_time(0),
          _cur_time(0),
          _filecount(1)
    {
        // 在构造的时候就将文件打开
        // 检查目录"/a/b/log"是否存在
        mjwlog::util::FileDirectory::createDirectory(mjwlog::util::FileDirectory::getDirectory(_basepath));
        // 打开文件
        creatfile();

        //确定创建时的时间段,注意每次重新创建文件都要重置_create_time
        
        _create_time=_seg==time_seg::SECOND?mjwlog::util::gettime::nowtime():mjwlog::util::gettime::nowtime()%getTimeSecond(_seg);

    }

    // 写入内容
    void log(const char *msg, const size_t len) override
    {
        // 判断文件是否已满,满了就重新开文件
        _cur_time=_seg==time_seg::SECOND?mjwlog::util::gettime::nowtime():mjwlog::util::gettime::nowtime()%getTimeSecond(_seg);
        
        if (_cur_time != _create_time)
        {
            creatfile();
            // 注意创建新文件后,_cur_size需要置为0
            _create_time=_seg==time_seg::SECOND?mjwlog::util::gettime::nowtime():mjwlog::util::gettime::nowtime()%getTimeSecond(_seg);

        }

        _ofs.write(msg, len);

        if (!_ofs.good())
        {
            std::cout << "日志写入文件失败" << std::endl;
        }
    }

private:
    // 辅助接口
    // 1.添加后缀
    std::string AddSuffix()
    {
        std::string suffix = _basepath;
        // basepath:"/a/b/log"(自行添加后缀方便辨认) ->"/a/b/log200208051123.log-_filecount"
        time_t time = mjwlog::util::gettime::nowtime();
        // localtime_r 函数获取时间详细属性
        struct tm attr;
        localtime_r(&time, &attr);

        suffix += std::to_string(attr.tm_year + 1900); // 年,默认从1900开始算,因此需要+1900
        suffix += std::to_string(attr.tm_mon + 1);     // 月,0-11
        suffix += std::to_string(attr.tm_mday);        // 日
        suffix += std::to_string(attr.tm_hour);        // 时
        suffix += std::to_string(attr.tm_min);         // 分
        suffix += ".log";
        suffix += "-" + std::to_string(_filecount++);

        return suffix;
    }

    // 2.重新开文件
    void creatfile()
    {
        // 打开前需要将原文件关闭
        _ofs.close();
        // 1.先判断目录是否存在,不存在创建
        std::string filepath = AddSuffix();
        _ofs.open(filepath, std::ios_base::binary | std::ios_base::app);
        assert(_ofs.is_open()); // 打开失败,直接退出报错
    }

    // 3.获取时间对应的秒数,SECOND——1
    int getTimeSecond(time_seg seg)
    {
        switch (seg)
        {
        case time_seg::SECOND:
            return 1;
        case time_seg::HOUR:
            return 60;
        case time_seg::MINUTE:
            return 2400;
        case time_seg::DAY:
            return 2400 * 24;
        default:
            std::cout << "暂时不支持当前间隔时间" << std::endl;
            abort();
        }

    }

private:
    std::string _basepath;
    std::ofstream _ofs;
    // 时间间隔,由用户确定
    time_seg _seg;
    // 例如我们以1秒为间隔分文件存储
    // 创建文件时为第31秒,32秒存入内容时检测,发现两个时间段对不上
    // 此时就需要重新创建文件写入日志
    size_t _create_time; // 文件创建时的时间段
    size_t _cur_time;    // 现在的时间段
    int _filecount;
};

int main()
{
    mjwlog::message msg(53, mjwlog::LogLevel::level::DEBUG, "test", "root", "日志格式化输出调试");
    mjwlog::Formatter ft;
    std::cout << ft.format(msg) << std::endl;

    mjwlog::Sink::ptr timefile_ptr=mjwlog::SinkFactory::LogSink<TimeFileSink>("./logfile/test",time_seg::SECOND);

    time_t old=mjwlog::util::gettime::nowtime();
    while(mjwlog::util::gettime::nowtime()<old+5)
    {
        timefile_ptr->log(ft.format(msg).c_str(), ft.format(msg).size());
        usleep(1000);
    }

    return 0;
}

 结果

 写入滚动文件(按照时间)落地方向,主要用来证明用户可以根据自己需要自定义日志落地,完成证明后,我们将其并入到sink.hpp中。

 

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

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

相关文章

vite打包遇到的错误

1.js emit is not supported 2.将package.json中的bulid后面写成“vue-tsc --noEmit --skipLibCheck && vite build” 3.再次打包成功

设计模式——接口隔离原则

文章目录 基本介绍应用实例应传统方法的问题和使用接口隔离原则改进 基本介绍 客户端不应该依赖它不需要的接口&#xff0c;即一个类对另一个类的依赖应该建立在最小的接口上先看一张图: 类 A 通过接口 Interface1 依赖类 B&#xff0c;类 C 通过接口 Interface1 依赖类 D&…

四、性能监控工具nmon,severAgent

简单的性能监控工具 一、 性能监控概括二、 常用的性能监控工具1、nmon概况nmon有三种运行模式&#xff1a;nmon的使用 2、SeverAgent安装步骤集成jmeter注意点特点 一、 性能监控概括 性能测试工具&#xff1a; 用什么工具来做性能测试性能测试场景设计&#xff1a; 用什么方法…

VisualVM(All-in-One Java Troubleshooting Tool)多合-故障处理工具

VisualVM&#xff1a;多合-故障处理工具 VisualVM&#xff08;All-in-One Java Troubleshooting Tool&#xff09;是功能最强大的 运行监视 和 故障处理 程序之一&#xff0c;曾经在很长一段时间内是Oracle官方主力发展的虚拟机故障处理工具。Oracle曾在VisualVM的软件说明中写…

基于opencv的手势控制音量和ai换脸

基于opencv的手势控制音量和ai换脸 HandTrackingModule.py import cv2 import mediapipe as mp import timeclass handDetector():def __init__(self, mode False, maxHands 2, model_complexity 1, detectionCon 0.5, trackCon 0.5):self.mode modeself.maxHands max…

dockerfile编写LNMP

目录 1. 项目环境 2. 服务器环境 二、部署nginx&#xff08;容器IP为192.168.158.26&#xff09; 1、整个Dockerfile文件内容 ​编辑 2、配置nginx.conf文件 3、构建镜像 三、部署mysql 1、整个Docker文件内容 3、生成镜像 4、启动镜像容器 5、验证mysql 四、PHP部署 1…

初始web

华子目录 前后端与全栈BS架构网页开发原则前端三剑客初始htmlhtml的基本框架如何使用vscode创建网页网页基本框架html基本标签 前后端与全栈 前端:给用户看的内容 – 荧幕前&#xff08;负责显示&#xff09; 后端:在后台处理数据 – 荧幕后&#xff08;负责处理&#xff09; …

HTB-Gofer

HTB-Gofer 信息收集立足jhudson -> tbuckleyroot 信息收集 探索一阵子没有什么收获&#xff0c;去看看smb服务。 mail提到有一个proxy。 扫描子域找到proxy。 我们没有任何账号密码&#xff0c;试了常见的弱口令也没有任何效果。 要是我用POST会发生什么。 似乎不会发生什…

idea 新建servlet 访问提示404 WebServlet注解找不到包 报错

检查访问路径是否设置正确 如果设置为name “/testServlet”&#xff0c;则会404 WebServlet注解报错找不到包 检查是否引入了tomcat依赖包

游戏msvcr120.dll丢失怎样修复?msvcr120.dll丢失常见原因

在尝试运行某些游戏时&#xff0c;我遇到了“msvcr120.dll丢失”的错误提示。经过一番调查和尝试&#xff0c;我成功地解决了这个问题。msvcr120.dll是Visual C Redistributable Package的一部分&#xff0c;它包含了许多运行Windows应用程序所需的库和函数。当游戏或其他应用程…

5.8.webrtc事件处理基础知识

在之前的课程中呢&#xff0c;我向你介绍了大量web rtc线程相关内容&#xff0c;今天呢&#xff0c;我们来看一下线程事件处理的基本知识。首先&#xff0c;我们要清楚啊&#xff0c;不同的平台处理事件的API是不一样的&#xff0c;这就如同我们当时创建线程是类似的&#xff0…

【算法刷题之链表篇(2)】

目录 1.leetcode-23. 合并 K 个升序链表&#xff08;较难&#xff09;&#xff08;1&#xff09;题目描述&#xff08;2&#xff09;方法一&#xff1a;顺序合并&#xff08;3&#xff09;方法二&#xff1a;分治合并&#xff08;4&#xff09;方法三&#xff1a;使用优先队列合…

2023国赛数学建模E题思路模型代码 高教社杯

本次比赛我们将会全程更新思路模型及代码&#xff0c;大家查看文末名片获取 之前国赛相关的资料和助攻可以查看 2022数学建模国赛C题思路分析_2022国赛c题matlab_UST数模社_的博客-CSDN博客 2022国赛数学建模A题B题C题D题资料思路汇总 高教社杯_2022国赛c题matlab_UST数模社…

C# 设置、获取程序,产品版本号

右键&#xff0c;程序属性。打开“程序集信息” 选择需要设置的版本信息。下面的代码&#xff0c;获取不同的设置内容。 string 其他 Assembly.GetExecutingAssembly().FullName; string 程序集版本 Assembly.GetExecutingAssembly().G…

(杭电多校)2023“钉耙编程”中国大学生算法设计超级联赛(10)

1003 Many Topological Problems 每个节点序号和权值分开计算,两者的排列组合数相乘即为答案 对于序号的顺序,一共有n个位置,第一个位置可以放序号1,2,..n共n个点,第二个则可放置n-1个点,以此类推,排列组合数为n的阶乘 对于权值,从小到大放置,如果不考虑k的话,对于权值为x的数,…

企培版edusoho对接第三方云视频点播 最新版本代码披露 支持m3u8视频加密

edusoho企培系列版本更新日志&#xff1a;新增功能和优化历史 倍数播放功能、视频分类、支持m3u8视频加密 \plugins\AliVideoPlugin\DependencyInjection\Configuration.php <?php namespace AliVideoPlugin\DependencyInjection; use Symfony\Component\Config\Definiti…

7-9 说反话-加强版

分数 20 全屏浏览题目 切换布局 作者 陈越 单位 浙江大学 给定一句英语&#xff0c;要求你编写程序&#xff0c;将句中所有单词的顺序颠倒输出。 输入格式&#xff1a; 测试输入包含一个测试用例&#xff0c;在一行内给出总长度不超过500 000的字符串。字符串由若干单词和…

Docker创建 LNMP 服务+Wordpress 网站平台

Docker创建 LNMP 服务Wordpress 网站平台 一.环境及准备工作 1.项目环境 公司在实际的生产环境中&#xff0c;需要使用 Docker 技术在一台主机上创建 LNMP 服务并运行 Wordpress 网站平台。然后对此服务进行相关的性能调优和管理工作。 容器 系统 IP地址 软件 nginx centos…

Log4j反序列化命令执行漏洞(CVE-2017-5645)Apache Log4j2 lookup JNDI 注入漏洞(CVE-2021-44228)

一.Log4j反序列化命令执行漏洞(CVE-2017-5645&#xff09; Apache Log4j是一个用于Java的日志记录库&#xff0c;其支持启动远程日志服务器。Apache Log4j 2.8.2之前的2.x版本中存在安全漏洞。攻击者可利用该漏洞执行任意代码 环境&#xff1a;vulhub 工具下载地址&#xff1…

Docker容器:docker基础及网络

Docker容器&#xff1a;docker基础及安装 一.docker容器概述 1.什么是容器 &#xff08;1&#xff09;Docker是在Linux容器里运行应用的开源工具&#xff0c;是一种轻量级的“虚拟机”。 &#xff08;2&#xff09;是一个开源的应用容器引擎&#xff0c;基于go语言开发并遵…