项目日志:

为什么实现日志?

  1. 通过日志可以避免直接在屏幕打印错误信息,当信息过多时无法精准定位
  2. 我们可以将日志的信息进行类型划分,正常的消息信息写入INFO,错误的运行问题写入ERROR
  3. 由此帮助我们在程序运行错误、项目代码优化调整时提供参照依据
  4. 帮助我们保存运行记录

如何实现?

在Rpc框架的调用过程中,我们需要引入一个消息队列(中间件)进行异步传输日志内容,因为写入日志的过程为磁盘 I/O ,避免拖慢Rpc执行的效率,我们直接将执行中的信息写入消息队列,再由消息队列将信息写入日志文件

需要注意:

多工作线程可能同时写入消息队列,需要保障线程安全!

消息队列为空时,消息队列写出至日志文件的线程需要等待唤醒

#pragma once
#include"lockqueue.h"
#include<string>

enum Loglevel
{
    INFO,//普通消息
    ERROR,//错误信息
};

//定义宏,直接调用具体的实现日志记录,不需要用户自行调用函数接口
#define LOG_INFO(logmsgformat, ...) \
    do \
    {  \
        Logger &logger = Logger::GetInstance(); \
        logger.SetLogLevel(INFO); \
        char c[1024] = {0}; \
        snprintf(c, 1024, logmsgformat, ##__VA_ARGS__); \
        logger.Log(c); \
    } while(0)

#define LOG_ERROR(logmsgformat, ... ) \
    do \
    {  \
        Logger &logger=Logger::GetInstance();\
        logger.SetLogLevel(ERROR);\
        char c[1024]={0};\
        snprintf(c,1024,logmsgformat,##__VA_ARGS__);\
        logger.Log(c);\
    } while(0)
// 定义宏 LOG_INFO("xxx %d %s", 20, "xxxx")

class Logger{
public:
    //获取单例对象
    static Logger& GetInstance();
    //设置日志级别
    void SetLogLevel(Loglevel level);
    //写日志
    void Log(std::string msg);
private:
    int m_loglevel;//日志级别
    LockQueue<std::string>m_locQue;//日志队列缓冲区

    Logger();//单例模式只初始化一次
    Logger(const Logger&)=delete;
    Logger(const Logger&&)=delete;
};

代码解析:

在定义了logger类的完整功能(初始化、获取实例、写入日志、写入日志级别)

定义了宏LOG_INFO \ LOG_ERROR,帮助用户直接调用宏,便可以根据参数将信息写入日志:

LOG_ERROR(" request parse error! content is: %s",args_str.c_str());

直接通过宏定义方法,将(获取实例、写入消息类型、构建字符串、写入日志)的方法封装成集合,通过“logmsgformat, ...”获取输入的字符串、可变参,并加入当前时刻信息构建日志内容。

#include "logger.h"
#include"time.h"
#include<iostream>
Logger::Logger()
{
    //启动线程:写入日志文件
    std::thread writeLogTask([&](){
        for(;;)
        {
            time_t now=time(nullptr);
            tm* nowtm=localtime(&now);
            char file_name[128];
            sprintf(file_name,"%d-%d-%d-log.txt",nowtm->tm_year+1900,nowtm->tm_mon+1,nowtm->tm_mday);

            FILE*pf=fopen(file_name,"a+");
            if(pf==nullptr)
            {
                //文件打开失败
                std::cout<<"logger file:"<<file_name<<"open error!"<<std::endl;
                exit(EXIT_FAILURE);
            }
            std::string msg=m_locQue.Pop();
            char time_buf[128]={0};
            //为每一条日志记录加入具体时间:
            snprintf(time_buf,128,"%d:%d:%d->[%s]  :",nowtm->tm_hour,nowtm->tm_min,nowtm->tm_sec,
                                                    (m_loglevel==INFO?"INFO":"ERROR"));
            msg.insert(0,time_buf);
            msg.append("\n");//尾部添加换行
            fputs(msg.c_str(),pf);
            fclose(pf);
        }
    });
    // 设置分离线程,守护线程
    writeLogTask.detach();
}
// 设置日志级别
void Logger::SetLogLevel(Loglevel level)
{
    m_loglevel=level;
}
// 写日志
void Logger::Log(std::string msg)
{
    m_locQue.Push(msg);//将信息写入缓冲区lockqueue
}

// 获取单例对象
Logger &Logger::GetInstance()
{
    static Logger logger;
    return logger;
}

消息队列:

消息队列由于使用了模板类型,无法分为.h \.cc 文件,在该类中主要实现写入消息队列、写出消息队列并写入日志的两个功能

#pragma once
#include<queue>
#include<thread>
#include<mutex>//互斥锁
#include<condition_variable>//条件变量


//异步写入日志
template<typename T>
class LockQueue
{
    public:
    void Push(const T& data)
    {
        std::lock_guard<std::mutex>lock(m_mutex);//lock_guard上锁,生命周期在该大括号内,出了括号自动析构该对象,解锁
        m_queue.push(data);
        //写入数据后,唤醒写出线程,将数据写入日志文件
        m_condvariable.notify_one();
    }
    T Pop()
    {
        std::unique_lock<std::mutex>lock(m_mutex);
        while(m_queue.empty())
        {
            //消息队列空,线程进入wait 状态
            m_condvariable.wait(lock);
        }
        T data=m_queue.front();
        m_queue.pop();
        return data;
    }
    private:
    std::queue<T> m_queue;
    std::mutex m_mutex;
    std::condition_variable m_condvariable;
};

代码解析:

成员数据:队列、锁、进程同步原语

Push:

  1. 在多线程写入消息队列的时候,我们为确保线程安全、同一时刻只允许一个线程写入消息队列,我们在Push的开始前上锁,以mutex为参数创建lock_guard对象,该对象构造函数自动上锁,该锁在}后出生命周期后,lock_guard对象生命周期结束,自动调用析构函数解锁;
  2. 继而我们将信息内容写入消息队列,信息类型由具体传入参数决定
  3. 在当前消息队列内不为空的时刻,我们可以唤醒另一等待线程(若无阻塞线程,该语句无效),将消息队列问题写入日志文件

Pop:

  1. 保障同一时刻仅有一个线程写入日志文件,上锁(同上)
  2. 判断队列是否为空,空则进入阻塞状态等待队列内由写入时被唤醒,在阻塞期间,其他线程由于Pop开始时的锁未解开,无法进入Pop方法,则同一时刻只有洗个线程被阻塞并等待唤醒
  3. 队列非空,则获取对头信息并出队,将信息写入日志文件
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值