日志模块的设计
本项目所实现的日志模块采用异步工作方式,多个线程往队列写日志,一个专门的写线程从队列中读取日志信息写入磁盘中的日志文件。
要点:线程安全,线程间通信,队列,生产者消费者模型
日志模块设计图如下:
源代码实现
队列的实现
模板类的声明和成员函数实现最好都是实现在同一个头文件,分文件编写可能造成链接错误,日志模块是生产者消费者模型。
// mprpc/src/include/lockqueue.h
#pragma once
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
// 模板类的声明和成员函数实现最好都是实现在同一个头文件
// 分文件编写可能造成链接错误
// 异步写日志使用的队列
// 生产者消费者模型
template<typename T>
class LockQueue
{
public:
// producer
void Push(const T &data)
{
std::lock_guard<std::mutex> lock(m_mutex);
m_queue.push(data);
m_cond.notify_one(); // 消费者只有一个写线程
}
// 一个写线程读日志queue,将其写入磁盘中的日志文件
T Pop()
{
std::unique_lock<std::mutex> lock(m_mutex);
while(m_queue.empty()) // 防止虚假唤醒
{
// 日志队列为空,进入wait状态
m_cond.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_cond;
};
日志模块Logger类实现
类的声明如下
// logger.h
#pragma once
#include <string>
#include "lockqueue.h"
class Logger;
// 提供用户使用日志模块的宏定义方法 ,LOG_INFO("xxx %d %s", 20, "abc")
#define LOG_INFO(fmt, ...) \
do \
{
\
Logger &logger = Logger::GetInstance(); \
logger.setLogLevel(INFO); \
char c[1024] = {
0}; \
sprintf(c, fmt, ##__VA_ARGS__); \
logger.Log(c); \
} while (0)
#define LOG_ERROR(fmt, ...) \
do \
{
\
Logger &logger = Logger::GetInstance(); \
logger.setLogLevel(ERROR); \
char c[1024] = {
0}; \
sprintf(c, fmt, ##__VA_ARGS__); \
logger.Log(c); \
} while (0)
// 定义日志级别
enum LogLevel
{
INFO,
ERROR,
};
// 单例类
class Logger
{
public:
static Logger &GetInstance();
void setLogLevel(LogLevel level);
void Log(std::string msg);
private:
int m_loglevel;
LockQueue<std::string> m_lckQue; // 日志缓冲队列
Logger();
// 拷贝构造和移动构造
Logger(const Logger &) = delete;
Logger(Logger &&) = delete;
};
类的实现如下
// logger.cc
#include "logger.h"
#include <time.h>
#include <iostream>
// 懒汉式
Logger& Logger::GetInstance()
{
static Logger logger;
return logger;
}
Logger::Logger()
{
// 启动专门写日志的线程
std::thread writeLogTask([&](){
for(;;)
{
// 获取当前日期
time_t now = time(nullptr);
tm *nowtm = localtime(&now);
// 组织文件名
char file_name[128] = {
0};
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_lckQue.Pop();
char time_buf[128] = {
0};
sprintf(time_buf, "%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_lckQue.Push(msg);
}