日志类实现
功能需求:
- 允许多个线程同时写,但刷入磁盘必须保持相对顺序
实现思路:
-
组件:
- 阻塞队列 std::unique_ptr<BlockQueuestd::string> queue_;
- 刷盘线程 std::unique_ptrstd::thread writeThread_;
- 日志容器 Buffer buff_;
- 互斥锁 std::mutex mtx_;
-
思路:
- 单例模式实现一个日志类;
- 外界调用不断添加日志进阻塞队列queue_;
- 写线程不断从阻塞队列取日志刷盘;
- 互斥锁用来控制对buff_的访问
- 单例模式实现一个日志类;
头文件
#ifndef LOG_H
#define LOG_H
#include "blockqueue.hpp"
#include "../buffer/buffer.h"
#include <mutex>
#include <string>
#include <thread>
#include <sys/time.h>
#include <string.h>
#include <stdarg.h> // vastart va_end
#include <assert.h>
#include <sys/stat.h> //mkdir
class Log {
public:
static Log* Instance(); //静态实例
static void FlushLogThread();//刷盘线程传函
void init(int level, const char* path = "./log",
int maxQueueCapacity = 1024);
void write(int level, const char *format,...);//写入阻塞队列
void flush();//清空队列
int GetLevel();
private:
Log();
void AppendLogLevelTitle_(int level);
virtual ~Log();
void AsyncWrite_();
private:
const char* path_;
int level_;
FILE* fp_;
Buffer buff_;
std::unique_ptr<BlockQueue<std::string>> queue_;
std::unique_ptr<std::thread> writeThread_;
std::mutex mtx_;
};
#define LOG_BASE(level, format, ...) \
do {\
Log* log = Log::Instance();\
if (log->GetLevel() <= level) {\
log->write(level, format, ##__VA_ARGS__); \
log->flush();\
}\
} while(0);
#define LOG_DEBUG(format, ...) do {LOG_BASE(0, format, ##__VA_ARGS__)} while(0);
#define LOG_INFO(format, ...) do {LOG_BASE(1, format, ##__VA_ARGS__)} while(0);
#define LOG_WARN(format, ...) do {LOG_BASE(2, format, ##__VA_ARGS__)} while(0);
#define LOG_ERROR(format, ...) do {LOG_BASE(3, format, ##__VA_ARGS__)} while(0);
#endif //LOG_H
源文件
#include "log.h"
using namespace std;
Log::Log() {
writeThread_ = nullptr;
queue_ = nullptr;
fp_ = nullptr;
}
Log::~Log() {
if(writeThread_ && writeThread_->joinable()) {
while(! queue_->empty()) {
queue_->flush();
};
queue_->Close();
writeThread_->join();
}
if(fp_) {
lock_guard<mutex> locker(mtx_);
flush();
fclose(fp_);
}
}
int Log::GetLevel() {
lock_guard<mutex> locker(mtx_);
return level_;
}
void Log::init(int level = 1, const char* path, int maxQueueSize) {
level_ = level;
path_ = path;
unique_ptr<BlockQueue<std::string>> newQueue(new BlockQueue<std::string>);
queue_ = move(newQueue);
std::unique_ptr<std::thread> NewThread(new thread(FlushLogThread));
writeThread_ = move(NewThread);
time_t timer = time(nullptr);
struct tm *sysTime = localtime(&timer);
struct tm t = *sysTime;
char fileName[256] = {0};
snprintf(fileName, 256 - 1, "%s/%04d_%02d_%02d%s",
path_, t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, ".log");
lock_guard<mutex> locker(mtx_);
buff_.RetrieveAll();
if(fp_) {
flush();
fclose(fp_);
}
fp_ = fopen(fileName, "a");
if(fp_ == nullptr) {
mkdir(path_, 0777);
fp_ = fopen(fileName, "a");
}
assert(fp_ != nullptr);
}
void Log::write(int level, const char *format, ...) {
struct timeval now = {0, 0};
gettimeofday(&now, nullptr);
time_t tSec = now.tv_sec;
struct tm *sysTime = localtime(&tSec);
struct tm t = *sysTime;
va_list vaList;
unique_lock<mutex> locker(mtx_);
int n = snprintf(buff_.BeginWrite(), 128, "%d-%02d-%02d %02d:%02d:%02d.%06ld ",
t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,
t.tm_hour, t.tm_min, t.tm_sec, now.tv_usec);
buff_.HasWritten(n);
AppendLogLevelTitle_(level);
va_start(vaList, format);
int m = vsnprintf(buff_.BeginWrite(), buff_.WritableBytes(), format, vaList);
va_end(vaList);
buff_.HasWritten(m);
buff_.Append("\n\0", 2);
if(queue_ && ! queue_->full()) {
queue_->push(buff_.RetrieveAllToStr());
} else {
fputs(buff_.Peek(), fp_);
}
buff_.RetrieveAll();
}
void Log::AppendLogLevelTitle_(int level) {
switch(level) {
case 0:
buff_.Append("[debug]: ", 9);
break;
case 1:
buff_.Append("[info] : ", 9);
break;
case 2:
buff_.Append("[warn] : ", 9);
break;
case 3:
buff_.Append("[error]: ", 9);
break;
default:
buff_.Append("[info] : ", 9);
break;
}
}
void Log::flush() {
queue_->flush();
fflush(fp_);
}
void Log::AsyncWrite_() {
string str = "";
while( queue_->pop(str)) {
lock_guard<mutex> locker(mtx_);
fputs(str.c_str(), fp_);
}
}
Log* Log::Instance() {
static Log inst;
return &inst;
}
void Log::FlushLogThread() {
Log::Instance()->AsyncWrite_();
}
测试代码1
#include "../log/blockqueue.hpp"
#include "../log/log.h"
#include "pthread.h"
#include <cstdlib>
#include <ctime>
#include <unistd.h>
void * writeLog(void *arg)
{
srand(time(0));
for(int i=0; i<5; i++)
{
sleep(rand() % 3);
LOG_DEBUG("DEBUG: %ld>>%d",pthread_self(), i);
LOG_INFO("INFO: %ld>>%d",pthread_self(), i);
LOG_WARN("WARN: %ld>>%d",pthread_self(), i);
LOG_ERROR("ERROR: %ld>>%d",pthread_self(), i);
}
}
int main()
{
int logLevel = 0;
Log::Instance()->init(logLevel, "./log");
for(int i=0; i<3; i++)
{
pthread_t tid;
pthread_create(&tid, NULL, writeLog, NULL);
pthread_join(tid, NULL);// log析构函数中用到了join方法
}
return 0;
}
引用
- 源代码:https://github.com/Aged-cat/WebServer