实现简单日志管理类 + 单例模式分析优化

发布时间 2024-01-08 16:28:41作者: 蔡头一枚
#pragma once

#include <stdio.h>
#include <iostream>
#include <string>
#include <vector>
#include <list>
#include <stdarg.h>
#include <pthread.h>
#include <unistd.h>
#include <mutex>
#include <thread>
#include <functional>
#include <condition_variable>

using namespace std;

// 打印宏
#define _DEBUG(...) LoggerMng::ptrInstance().get()->writeLog(LoggerMng::LEVEL_DEBUG, __VA_ARGS__)
#define _INFO(...)  LoggerMng::ptrInstance().get()->writeLog(LoggerMng::LEVEL_INFO, __VA_ARGS__)
#define _WARN(...)  LoggerMng::ptrInstance().get()->writeLog(LoggerMng::LEVEL_WARN, __VA_ARGS__)
#define _ERROR(...) LoggerMng::ptrInstance().get()->writeLog(LoggerMng::LEVEL_ERROR, __VA_ARGS__)

#define LOG_FILE_PATH       "./log"
#define INIT_GARBAGE          1

/**
 * @brief The LoggerMng class               日志管理类
 */
class LoggerMng
{
public:
    ~LoggerMng();

    // 日志等级
    enum LOG_LEVEL
    {
        LEVEL_DEBUG = 0,
        LEVEL_INFO,
        LEVEL_WARN,
        LEVEL_ERROR
    };

    // 改进1: 利用其他类的析构来回收单例对象
    static LoggerMng *getInstance();
    class GarbageCollector
    {
    public:
        ~GarbageCollector()
        {
            std::cout << __PRETTY_FUNCTION__ << std::endl;
            if ( m_pInstance )
            {
                delete m_pInstance;
                m_pInstance = nullptr;
            }
        }
    };
    static GarbageCollector gc;


    /**
     * @brief Instance
     * @return
     */
    //改进2: 使用静态局部变量(注意:要使用引用,不然外面调用会引起新的一次构建)
    static LoggerMng &Instance();

    // 改进3: 使用智能指针自动释放
    static unique_ptr<LoggerMng> &ptrInstance();

    /**
     * @brief init                          初始化日志类
     * @return
     */
    bool init(const string &filePath);

    /**
     * @brief writeLog                      写文件
     */
    void writeLog(const LOG_LEVEL &level, const char* format, ...);

private:
    LoggerMng();
    LoggerMng(LoggerMng &l) = delete;
    LoggerMng &operator=(LoggerMng &l) = delete;

    inline string level2Str(const LOG_LEVEL &level);

    static LoggerMng *m_pInstance;
    static unique_ptr<LoggerMng> m_ptrInstance;

    /**
     * @brief threadWriteLog                写日志线程
     */
    void threadWriteLog();

    bool m_isExit = false;                      //  线程是否结束
    int m_today = 1993;                         // 日期比较
    FILE *m_pFile = nullptr;                    // 文件句柄
    std::mutex m_mutex;                         // 互斥锁
    unique_ptr<std::thread> m_threadPtr;        // 线程指针
    std::condition_variable m_conVar;           // 条件变量
    std::list<std::string> m_strList;           // 字符队列, 存放打印字符
};
#include "LoggerMng.h"

LoggerMng *LoggerMng::m_pInstance = nullptr;
#if (INIT_GARBAGE == 1)
LoggerMng::GarbageCollector gc;
#endif

unique_ptr<LoggerMng> LoggerMng::m_ptrInstance(nullptr);


LoggerMng &LoggerMng::Instance()
{
    static LoggerMng instance;                          // 静态局部变量, 要使用引用返回值, 否则调用一次就构建一次,浪费资源
    return instance;
}

LoggerMng *LoggerMng::getInstance()
{
    if ( m_pInstance == nullptr )                       // 静态成员使用指针的话,程序退出时无法指针类的析构函数
    {
        m_pInstance = new LoggerMng();
    }
    return m_pInstance;
}

unique_ptr<LoggerMng> &LoggerMng::ptrInstance()
{
    if ( m_ptrInstance == nullptr )
    {
        m_ptrInstance.reset(new LoggerMng());
    }
    return m_ptrInstance;
}

bool LoggerMng::init(const string &filePath)
{
    if ( filePath.empty() )
    {
        return false;
    }

    // 开启写日志线程
    m_threadPtr.reset(new std::thread(std::bind(&LoggerMng::threadWriteLog, this)));

    // 创建日志文件
    time_t t = time(NULL);
    struct tm* sys_tm = localtime(&t);
    struct tm my_tm = *sys_tm;

    char buf[256] = {0};
    snprintf(buf, 255, "%d_%02d_%02d.log",my_tm.tm_year+1900, my_tm.tm_mon+1, my_tm.tm_mday);
    string fileName = buf;

    m_today = my_tm.tm_mday;

    m_pFile = fopen(fileName.c_str(), "a");
    return (m_pFile != nullptr);
}

void LoggerMng::writeLog(const LoggerMng::LOG_LEVEL &level, const char *format, ...)
{
    string levelStr = level2Str(level);

    struct timeval now = {0,0};
    gettimeofday(&now, NULL);
    time_t t = now.tv_sec;
    struct tm* sys_tm = localtime(&t);
    struct tm my_tm = *sys_tm;

    // 时间不是同一天时,先关闭之前的文件,重新打开新的文件
    if ( m_today != my_tm.tm_mday )
    {
        m_today = my_tm.tm_mday;

        // 关闭之前的文件
        fflush(m_pFile);
        fclose(m_pFile);

        char newFileName[256] = {0};
        snprintf(newFileName, 256, "%d_%02d_%02d.log",my_tm.tm_year+1900, my_tm.tm_mon+1, my_tm.tm_mday);

        m_pFile = fopen(newFileName, "a");
        if ( m_pFile == nullptr ){
            return ;
        }
    }

    char msg[256] = {0};
    va_list valist;
    va_start(valist, format);
    vsnprintf(msg, 256, format, valist);            // 取可变参数保存到缓冲区
    va_end(valist);

    char content[512] = { 0 };
    snprintf(content, 512, "[%04d-%02d-%02d %02d:%02d:%02d.%ld][%s:%d]%s\n",
             sys_tm->tm_year + 1900, sys_tm->tm_mon + 1, sys_tm->tm_mday, sys_tm->tm_hour, sys_tm->tm_min, sys_tm->tm_sec, now.tv_usec/1000, __FILE__, __LINE__, msg);

    {
        // {}作用域结束后释放锁
        std::unique_lock<std::mutex> uniqueLock(m_mutex);
        m_strList.emplace_back(string(content));            // 从末尾添加数据 - emplace_back效率比push_back高
    }

    m_conVar.notify_one();
}

void LoggerMng::threadWriteLog()
{
    while( !m_isExit )
    {
        std::unique_lock<std::mutex> uniqueLck(m_mutex);
        // 避免虚假唤醒
        while (m_strList.empty())
        {
            if (m_isExit){
                return;
            }
            m_conVar.wait(uniqueLck);
        }

        string str = m_strList.front();
        std::cout << "write str: " << str << endl;
        fputs(str.c_str(), m_pFile);
        fflush(m_pFile);
        m_strList.pop_front();
    }
}

LoggerMng::LoggerMng()
{
}

string LoggerMng::level2Str(const LoggerMng::LOG_LEVEL &level)
{
    if ( level == LEVEL_DEBUG ) return string("Debug");
    if ( level == LEVEL_INFO ) return string("Info");
    if ( level == LEVEL_WARN ) return string("Warn");
    if ( level == LEVEL_ERROR ) return string("Error");
    return string("Debug");
}

LoggerMng::~LoggerMng()
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
    m_isExit = true;

    m_conVar.notify_one();

    m_threadPtr->join();

    if ( m_pFile )
    {
        fclose(m_pFile);
        m_pFile = nullptr;
    }
}
#include "LoggerMng.h"

int main()
{
    LoggerMng::ptrInstance().get()->init(LOG_FILE_PATH);
    for ( int i=0; i< 10; i++ )
    {
        string str = string("AD") + std::to_string(i) ;
        _DEBUG("你好, 教练%s, 我是成员: %d", str.c_str(), i);
    }

    string str = "AD";
    _ERROR("ERROR");
    _INFO("str: %s", str.c_str());
    _WARN("num: %d - str: %s", 8, str.c_str());
}

运行结果:

 

 

参考:  (五)如何编写高性能日志 (qq.com)

linux c/c++ 后台开发常用组件之:c++日志模块_后台中的哪些模块用c++开发的话比较合适-CSDN博客