一、日志功能
在本项目中,日志有以下功能
1、异步写入:使用独立线程写日志,不影响主业务逻辑性能
2、分级日志:区分INFO和ERROR级别,便于过滤和处理
3、按日期分文件:每天生成独立的日志文件,便于管理和查找
4、维护系统稳定性和排查问题

多个线程指需要使用日志记录的线程,
二、日志管理
1、logger.h
cpp
#pragma once
#include "lockqueue.h"
#include<string>
enum LogLevel
{
INFO,//普通信息
ERROR,//错误信息
};
//Mprpc框架提供的日志系统
class Logger
{
public:
//获取日志唯一的实例对象
static Logger& GetInstance();
//设置日志级别
void SetLogLevel(LogLevel level);
//写日志
void Log(std::string msg);
private:
LogLevel m_loglevel;//记录日志级别
LockQueue<std::string> m_lockQueue;//日志缓冲队列
Logger();
Logger(const Logger& other) = delete;
Logger(Logger&&) = delete;
};
//定义宏
#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);
2、logger.cc
cpp
#include"logger.h"
#include<iostream>
//获取日志唯一的实例对象
Logger& Logger::GetInstance(){
static Logger logger;
return logger;
}
Logger::Logger(){
//启动专门的写日志线程
std::thread writeLogTask([&](){
for(;;){
//获取当前日期,然后从队列获取日志信息,写入对应的日志文件中
time_t now = time(NULL);
tm* now_tm = localtime(&now);
char file_name[128];
sprintf(file_name, "%d-%d-%d.log.txt", now_tm->tm_year + 1900, now_tm->tm_mon + 1, now_tm->tm_mday);
FILE* pf = fopen(file_name, "a+");
if(pf == NULL){
std::cout << "logger::open log file error" << std::endl;
exit(EXIT_FAILURE);
}
std::string msg = m_lockQueue.Pop();
char time_buf[128];
sprintf(time_buf, "%d:%d:%d =>[%s]", now_tm->tm_hour, now_tm->tm_min, now_tm->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_lockQueue.Push(msg);
}
三、异步日志队列
1、在MPRPC框架中的价值
-
RPC调用性能:RPC服务的响应时间不会受日志写入影响(工作线程只需将日志消息放入队列即可继续执行业务逻辑,无需等待磁盘I/O)
-
高并发支持:多个客户端同时请求时,日志记录不会成为瓶颈
-
可靠性:即使日志文件系统出现问题,也不会影响RPC服务的正常运行
异步日志的唯一潜在缺点是在程序异常终止时可能丢失队列中的日志,但对于大多数应用场景,其性能优势远大于这个风险。
2、logqueue.h
这里使用条件变量,当队列为空时,日志线程调用wait,释放lock锁,让工作线程能写日志
cpp
#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);
m_queue.push(data);
m_condvariable.notify_one();
}
//主线程会读取日志
T Pop(){
std::unique_lock<std::mutex> lock(m_mutex);
while(m_queue.empty()){
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;
};
四、应用例子
