当前无论大中小系统基本都有日志系统,阿里云的飞天(Apsara)也是从logging模块开始写的.在c++中,用的比较多的就是log4cxx和google的glog,这两个都是目前比较流行的c++接口的日志系统,但使用这样的日志系统一方面比较庞大,另外一方面不便于学习.虽然我们不提倡重复造轮子,但还是需要知道轮子是怎么造出来的.另外一方面此次介绍的日志系统并非通常意义的直接调用接口就可完成输出的日志系统,而是将要输出的消息丢到一个队列中,另外会有一个detach的线程作为消费者写入文件.该系统的思想来自https://github.com/samblg/hurricane.git,但并不完全一致.
1.消息队列
消息队列是该日子系统的基础,也是线程池实现的基础,该系统队列为适用多种场合,首先必须是模板形式;再者,消息队列应该只在一个文件中,毕竟此代码比较少且使用频繁;此外,消息队列还必须借助c++11的条件量condition_variable和mutex作为是否有消息的控制,防止多线程中争用的情况.一个比较通用的消息队列头文件如下:
/*================================================================
* Copyright (C) 2018 xuboxuan. All rights reserved.
*
* 文件名称:syncqueue.h
* 创 建 者: xuboxuan
* 创建日期:2018年07月17日
* 描 述:
*
================================================================*/
#ifndef _SYNCQUEUE_H
#define _SYNCQUEUE_H
#include
#include
#include
#include
#include
template
class SyncQueue{
public:
explicit SyncQueue(): m_need_stop(false){};
void Put(T x){Add(x);}
void Pop(T& t){
std::unique_lock locker(m_mutex);
m_notempty.wait(locker, [this](){return m_need_stop || NotEmpty();});
if(m_need_stop ) return;
t = m_queue.front();
m_queue.pop();
}
void Stop(){
{
std::lock_guard lock(m_mutex);
m_need_stop = true;
}
m_notempty.notify_all();
}
bool Empty() {
std::lock_guard locker(m_mutex);
return m_queue.empty();
}
size_t Size(){
std::lock_guard locker(m_mutex);
return m_queue.size();
}
private:
bool NotEmpty(){
bool empty = m_queue.empty();
if(empty) std::cout << "empty, waiting, thred id" << std::this_thread::get_id() << std::endl;
return !empty;
}
template
void Add(F&& x){
std::unique_lock locker(m_mutex);
if(m_need_stop) return;
m_queue.push(std::forward(x));
m_notempty.notify_one();
}
private:
std::mutex m_mutex;
std::condition_variable m_notempty;
std::queue m_queue;
bool m_need_stop;
};
#endif //SYNCQUEUE_H
2.日志系统
在消息队列建立好之后就可以开始写logger日志的头部了,该日志系统分为5个等级,分别是:DEBUG,CONFIG,INFO,WARNING,ERROR.此外该日志系统必须是单例模式,因此初始化函数和析构函数均为private函数,通过static函数中的static变量调用单例实现.Logger内部定义了一个消息队列_queue.头文件如下:
/*================================================================
* Copyright (C) 2018 xuboxuan. All rights reserved.
*
* 文件名称:logger.h
* 创 建 者: xuboxuan
* 创建日期:2018年07月17日
* 描 述:
*
================================================================*/
#ifndef _LOGGER_H
#define _LOGGER_H
#include "syncqueue.h"
#include
#include
#include
#include
#include
#include
#include
namespace{
std::string filename = "info.log";
}
enum Priority{
DEBUG,
CONFIG,
INFO,
WARNING,
ERROR
};
class Logger{
Logger(const Logger& g_tmp) = delete;
Logger &operator=(const Logger&) = delete;
public:
static void initLogger(const std::string& info_log_filename);
static Logger *Get();
void SetPriority(Priority priority);
Priority GetPriority();
void WriteLog(Priority priority, const int line, const std::string &function,const std::string &log_content);
void SetFilename(std::string & info_log_filename);
private:
Logger(Priority priority);
virtual ~Logger();
void InitializeFileStream();
void WriteThread();
private:
SyncQueue _queue;
std::shared_ptr _filestream;
Priority _priority;
bool _shutdown;
};
#define LOG_DEBUG(LOG_CONTENT) \
Logger::Get()->WriteLog(DEBUG, __LINE__,__FUNCTION__,(std::string)LOG_CONTENT)
#define LOG_INFO(LOG_CONTENT) \
Logger::Get()->WriteLog(INFO, __LINE__,__FUNCTION__,(std::string)LOG_CONTENT)
#define LOG_WARNING(LOG_CONTENT) \
Logger::Get()->WriteLog(WARNING, __LINE__,__FUNCTION__,(std::string)LOG_CONTENT)
#define LOG_ERROR(LOG_CONTENT) \
Logger::Get()->WriteLog(ERROR, __LINE__,__FUNCTION__,(std::string)LOG_CONTENT)
#define LOG_CONFIG(LOG_CONTENT) \
Logger::Get()->WriteLog(CONFIG, __LINE__,__FUNCTION__,(std::string)LOG_CONTENT)
#endif //LOGGER_H
以上定义了多个宏作为接口调用,宏定义通过静态函数Get获得实例,并调用WriteLog实现消息投递到队列中.initLogger静态函数用于对日志文件名称的定义.
相应的实现程序如下:
/*================================================================
* Copyright (C) 2018 xuboxuan. All rights reserved.
*
* 文件名称:logger.cpp
* 创 建 者: xuboxuan
* 创建日期:2018年07月17日
* 描 述:
*
================================================================*/
#include "logger.h"
const std::string PRIORITY_STRING[] = {
"DEBUG",
"CONFIG",
"INFO",
"WARNING",
"ERROR"
};
Logger *Logger::Get(){
static Logger logger(DEBUG);
return &logger;
}
Logger::Logger(Priority priority) : _queue(),_filestream(nullptr),_shutdown(false){
_priority = priority;
InitializeFileStream();
auto func = std::bind(&Logger::WriteThread, this);
std::thread writeThread(func);
writeThread.detach();
}
Logger::~Logger(){
_shutdown = true;
if(_filestream){
_filestream->close();
}
}
void Logger::SetPriority(Priority priority){
_priority = priority;
}
Priority Logger::GetPriority(){
return _priority;
}
void Logger::initLogger(const std::string& info_log_filename){
filename = info_log_filename;
}
void Logger::InitializeFileStream(){
//Initialize file stream
_filestream = std::make_shared();
std::ios_base::openmode mode = std::ios_base::out;
mode |= std::ios_base::trunc;
_filestream->open(filename, mode);
if(!_filestream->is_open()){
std::ostringstream ss_error;
ss_error << "FATAL ERROR: could not open log file: [" << filename << "]";
ss_error << "\n\t\t std::ops_base state = " << _filestream->rdstate();
std::cerr << ss_error.str().c_str() << std::endl << std::flush;
_filestream->close();
}
}
void Logger::WriteLog(Priority priority, const int line, const std::string &function,const std::string &log_content){
if(priority < _priority)
return;
std::stringstream stream;
time_t tm;
time(&tm);
char time_string[128];
ctime_r(&tm, time_string);
stream << time_string << " [" << PRIORITY_STRING[priority]<< "]" << " line " << line << " :" << log_content;
_queue.Put(stream.str());
std::cout << stream.str() << std::endl;
}
void Logger::WriteThread(){
while(!_shutdown){
std::string log;
_queue.Pop(log);
std::cout << log << std::endl;
if(_filestream){
*_filestream << log << std::endl;
}
}
}
Logger构造函数在首次构造时调用InitializeFileStream和初始化writeThread,InitializeFileStream打开日志文件并初始化,writeThread则启动消费线程,并与主线程分离.
3.测试程序
以上一个简单的基于队列的日志系统即实现了,调用时首先需要在主函数中初始化日志文件Logger::initLogger("you log name");,然后即可通过LOG_INFO,LOG_WARNING和LOG_ERROR等宏定义调用输出.
/*================================================================
* Copyright (C) 2018 xuboxuan. All rights reserved.
*
* 文件名称:test.cpp
* 创 建 者: xuboxuan
* 创建日期:2018年07月17日
* 描 述:
*
================================================================*/
#include "logger.h"
#include
int main(){
Logger::initLogger("test.log");
LOG_INFO("this is first info test");
LOG_WARNING("this is first warning test");
LOG_ERROR("this is first error test");
std::this_thread::sleep_for(std::chrono::seconds(1));
}
编译使用:
#================================================================
# Copyright (C) 2018 xuboxuan. All rights reserved.
#
# 文件名称:Makefile
# 创 建 者: xuboxuan
# 创建日期:2018年07月17日
# 描 述:
#
#================================================================
all:
g++ -std=c++11 -o test test.cpp logger.cpp -lpthread
clean:
rm -rf test test.log
命令输入make即可.
但是需要注意的一点,由于消费者和生产者分离,所以并不是生产者投入消息到队列中即可退出,必须在退出前确认消息已经完全消费完才能退出,因此此处家了sleep_for函数.
详细代码在:
https://github.com/BoxuanXu/logger_queue.git