C++簡單日志系統(tǒng)實現(xiàn)代碼示例
前言
生產(chǎn)環(huán)境的產(chǎn)品為了穩(wěn)定性和安全性是不支持開發(fā)人員去使用調(diào)試器去排查問題的;上線的客戶端或產(chǎn)品出現(xiàn)BUG無法復(fù)現(xiàn)并解決時;在分布式、多線程/多進程代碼中問題難以定位;開發(fā)人員就可以通過日志系統(tǒng)打印的日志來排查問題并及時解決。
同樣也可以幫助新人了解開發(fā)人員理解的運行邏輯。
Util.hpp
實現(xiàn)工具類:用于實現(xiàn)通用功能,供LogMsg(日志內(nèi)容封裝模塊)以及Sink(日志落地模塊)調(diào)用。
獲取當(dāng)前系統(tǒng)時間
class Date{
public:
static time_t now(){
return (time_t)time(nullptr);
}
};判斷文件是否存在
class File
{
public:
static bool exists(const std::string &pathname){
struct stat st;
if (stat(pathname.c_str(), &st) < 0){
return false;
}
return true;
}
...
};獲取文件的當(dāng)前路徑
class File
{
public:
static std::string path(const std::string &pathname){
//./abc/bdc/fgh
// 查找 / 和 \ 中任意一個
size_t pos = pathname.find_last_of("/\\");
if (pos == std::string::npos)
return ".";
return pathname.substr(0, pos + 1);
}
...
};創(chuàng)建目錄
class File
{
public:
...
static void createDirectory(const std::string &pathname){
// ./abbc/sss/op
size_t pos = 0, index = 0;
while (index < pathname.size())
{
pos = pathname.find_first_of("/\\", index);
if (pos == std::string::npos){
mkdir(pathname.c_str(), 0777);
}
std::string parentdir = pathname.substr(0, pos + 1);
if (exists(parentdir) == true){
index = pos + 1;
continue;
}
mkdir(parentdir.c_str(), 0777);
index = pos + 1;
}
};Level.hpp
日志等級類:供LogMsg(日志內(nèi)容封裝模塊)調(diào)用。
定義日志等級: INFO DEBUG WARN ERROR FATAL OFF。
class Loglevel{
public:
//日志等級
enum class value{
UNKOWN = 0,
INFO,
DEBUG,
WARN,
ERROR,
FATAL,
OFF
};
...
};轉(zhuǎn)換字符串接口:將相應(yīng)的日志等級輸出成字符串。
class Loglevel{
public:
...
static const char* toString(Loglevel::value level){
switch (level){
case Loglevel::value::INFO:return "INFO";
case Loglevel::value::DEBUG:return "DEBUG";
case Loglevel::value::WARN:return "WARN";
case Loglevel::value::ERROR:return "ERROR";
case Loglevel::value::FATAL:return "FATAL";
case Loglevel::value::OFF:return "OFF";
default: break;
}
return "UNKOWN";
}
};LogMsg.hpp
日志內(nèi)容封裝類:用于實現(xiàn)日志內(nèi)容的封裝,以供Format.hpp(日志格式化模塊)。
struct LogMsg
{
time_t _ctime; //日志產(chǎn)生的時間戳
Loglevel::value _level; //日志等級
std::string _file; //源碼文件名
size_t _line; //行號
std::thread::id _tid; //線程id
std::string _logger; //日志器名稱
std::string _payload; //有效載荷
LogMsg(Loglevel::value level, size_t line,
const std::string file, const std::string logger,
const std::string msg)
: _ctime(Util::Date::now())
, _level(level)
, _file(file)
, _line(line)
, _tid(std::this_thread::get_id())
,_logger(logger)
, _payload(msg)
{
}
};Format.hpp
日志格式化類:主要負責(zé)日志的格式化,用于自定義格式,由格式化std::vector<FormatItem>來對格式進行存儲管理,需要使用到LogMsg(日志封裝模塊)。
其中給定了占位符,以及其將其替換的內(nèi)容:
%d:日期 ----子格式{%H:%M:%S}。%t:線程id。%T:縮進。%p:日志等級。%c:日志器名稱。%f:文件名。%l:行號。%m:日志信息。%n:換行。
FormatItem類主要負責(zé)?志消息?項的獲取及格式化。
其包含以下?類:
TimeFormatItem:從LogMsg中取出日志產(chǎn)生的系統(tǒng)時間。
FileFormatItem:從LogMsg中取出源碼文件名。
LineFormatItem:從LogMsg中取出源碼中調(diào)用日志的行號。
LevelFormatItem:從LogMsg中取出日志的等級。
ThreadFormatItem:從LogMsg中取出調(diào)用日志的線程ID。
LoggerFormatItem:從LogMsg中取出日志器的名稱
MsgFormatItem:從LogMsg中取出有效載荷。
TableFormatItem:制表符。
NlineFormatItem:換行符。
OtherFormatItem:非格式化的原始字符串。
// 抽象 格式化子項
// 從日志消息中取出特定元素,追加到一塊空間
// 包括:時間 源文件 行號 日志等級 線程id 日志器名稱 有效載荷 制表符 換行符 其他
class FormatItem{
public:
using ptr = std::shared_ptr<FormatItem>;
virtual void format(std::ostream &out,const LogMsg &msg){};
};
class TimeFormatItem : public FormatItem{
public:
TimeFormatItem(const std::string fmt = "%H:%M:%S") : _time_fmt(fmt) {}
void format(std::ostream &out,const LogMsg &msg) override{
struct tm t;
localtime_r(&msg._ctime, &t);
char tmp[32] = {0};
strftime(tmp, 31, _time_fmt.c_str(), &t);
out << tmp;
}
std::string _time_fmt;
};
class FileFormatItem : public FormatItem{
public:
void format(std::ostream &out,const LogMsg &msg) override{
out << msg._file;
}
};
class LineFormatItem : public FormatItem{
public:
void format(std::ostream &out,const LogMsg &msg) override{
out << msg._line;
}
};
class LevelFormatItem : public FormatItem{
public:
void format(std::ostream &out,const LogMsg &msg) override{
out << Loglevel::toString(msg._level);
}
};
class ThreadFormatItem : public FormatItem{
public:
void format(std::ostream &out,const LogMsg &msg) override{
out << msg._tid;
}
};
class LoggerFormatItem : public FormatItem{
public:
void format(std::ostream &out,const LogMsg &msg) override{
out << msg._logger;
}
};
class MsgFormatItem : public FormatItem{
public:
void format(std::ostream &out, const LogMsg &msg) override{
out << msg._payload;
}
};
class TableFormatItem : public FormatItem{
public:
void format(std::ostream &out, const LogMsg &msg) override{
out << "\t";
}
};
class NlineFormatItem : public FormatItem{
public:
void format(std::ostream &out,const LogMsg &msg) override{
out << "\n";
}
};
class OtherFormatItem : public FormatItem{
public:
OtherFormatItem(const std::string &str) : _str(str) {}
void format(std::ostream &out,const LogMsg &msg) override{
out << _str;
}
std::string _str;
};Formator類是對?志消息按格式化整理后輸出的執(zhí)行者。
std::string _pattern:用戶傳入的自定義格式。
class Formator{
public:
using ptr = std::shared_ptr<Formator>;
Formator(const std::string str = "[%d{%H:%M:%S}][%p][%t]%c %f %l %T%m%n")
:_pattern(str){
assert(parseFormat());
}
//格式化后輸出到緩沖區(qū)
void format(std::ostream &out, const LogMsg &msg){
for (auto &fmt : _format_item)
fmt->format(out, msg);
}
//格式化日志消息后返回內(nèi)容
std::string format(const LogMsg &msg){
std::stringstream ss;
format(ss, msg);
return ss.str();
}
...
private:
std::string _pattern;
std::vector<FormatItem::ptr> _format_item;//各個格式化子項
};bool parseFormat():解析傳入的自定義格式,并將格式及其對應(yīng)格式化子類指針填入std::vector<FormatItem>。
FormatItem::ptr createFormatItem():用于輔助parseFormat()填入格式對應(yīng)的指針。
class Formator{
...
private:
//解析格式化規(guī)則字符串
bool parseFormat(){
// 1.是否為%,否則為原始字符串
// 2.解析%
// 3.%后是否有{}
size_t pos = 0;
std::vector<std::pair<std::string, std::string>> fmt_order;
std::string key, val;
while (pos < _pattern.size()){
//普通字符串
if (_pattern[pos] != '%'){
val.push_back(_pattern[pos++]);
continue;
}
// 是%%,存%字符
if (pos + 1 < _pattern.size() && _pattern[pos + 1] == '%'){
val.push_back(_pattern[pos + 1]);
pos += 2;
continue;
}
// val不為空,說明有普通字符串存在
if (val.empty() == false){
fmt_order.emplace_back("", val);
val.clear();
}
pos += 1;
if (pos == _pattern.size()){
std::cout << "字符串格式錯誤!" << std::endl;
return false;
}
key = _pattern[pos];
pos += 1;
if (pos < _pattern.size() && _pattern[pos] == '{'){
pos += 1;
while (pos < _pattern.size() && _pattern[pos] != '}')
val.push_back(_pattern[pos++]);
// 沒有},說明格式錯誤
if (pos == _pattern.size()){
std::cout << "字符串格式錯誤!" << std::endl;
return false;
}
pos += 1;
}
fmt_order.emplace_back(key, val);
key.clear();
val.clear();
}
for (auto &it : fmt_order)
_format_item.push_back(createFormatItem(it.first, it.second));
return true;
}
// 創(chuàng)建格式化子項
FormatItem::ptr createFormatItem(std::string key, std::string val){
if (key == "d")
return std::make_shared<TimeFormatItem>(val);
if (key == "t")
return std::make_shared<ThreadFormatItem>();
if (key == "T")
return std::make_shared<TableFormatItem>();
if (key == "n")
return std::make_shared<NlineFormatItem>();
if (key == "m")
return std::make_shared<MsgFormatItem>();
if (key == "f")
return std::make_shared<FileFormatItem>();
if (key == "p")
return std::make_shared<LevelFormatItem>();
if (key == "c")
return std::make_shared<LoggerFormatItem>();
if (key == "l")
return std::make_shared<LineFormatItem>();
if(key.empty())
return std::make_shared<OtherFormatItem>(val);
std::cout << "沒有對應(yīng)的格式化字符: %" << key << std::endl;
abort();
return nullptr;
}
...
};Sink.hpp
日志落地類:將日志輸出到指定的模塊,采用簡單工廠模式,這里給出三種落地模式,支持拓展。
用戶可以繼承基類LogSink來自定義實現(xiàn)一個落地方式。
class LogSink{
public:
using ptr = std::shared_ptr<LogSink>;
virtual void log(const char *data, size_t len) = 0;
virtual ~LogSink() {}
};
提供一個log接口供子類實現(xiàn),用于日志落地。標(biāo)準(zhǔn)輸出:StdoutSink,將日志輸出到標(biāo)準(zhǔn)輸出。
class StdoutSink : public LogSink{
public:
// 將日志輸出到標(biāo)準(zhǔn)輸出
void log(const char *data, size_t len) override{
std::cout.write(data, len);
}
};指定文件:將日志輸出到指定文件。
查看文件是否存在,并創(chuàng)建文件。
獲取文件句柄。
class FileSink : public LogSink{
public:
FileSink(const std::string &pathname) : _pathname(pathname){
// 查看文件是否存在,并創(chuàng)建文件
Util::File::createDirectory(Util::File::path(_pathname));
// 獲取文件操作句柄
_ofs.open(_pathname, std::ios::binary | std::ios::app);
assert(_ofs.is_open());
}
// 將日志輸出到指定文件
void log(const char *data, size_t len) override
{
_ofs.write(data, len);
assert(_ofs.good());
}
private:
std::string _pathname; // 日志文件名
std::ofstream _ofs; // 文件句柄
};滾動文件(按大小):將日志輸出到滾動文件。其思想可分為時間和大小,這里實現(xiàn)了按大小滾動。
按文件大小滾動,超過1G時會創(chuàng)建新文件,并寫入新文件。
按時間滾動,按約定的時間來滾動文件。
class RollByFileSink : public LogSink{
public:
RollByFileSink(const std::string &basename, size_t max_size)
: _basename(basename), _max_size(max_size), _cur_size(0),_name_cnt(0){
createHelper();
}
// 將日志輸出到滾動文件
void log(const char *data, size_t len) override
{
if (_cur_size >= _max_size)
{
_ofs.close();
createHelper();
_cur_size = 0;
_name_cnt++;
}
_ofs.write(data, len);
assert(_ofs.good());
_cur_size += len;
}
private:
// 進行大小判斷,創(chuàng)建新文件
std::string createNewFile(){
struct tm lt;
time_t now = Util::Date::now();
localtime_r(&now, <);
std::stringstream filename;
filename << _basename;
filename << lt.tm_year + 1900;
filename << lt.tm_mon + 1;
filename << lt.tm_mday;
filename << lt.tm_hour;
filename << lt.tm_min;
filename << lt.tm_sec;
filename << "-";
filename << _name_cnt;
filename << ".log";
return filename.str();
}
void createHelper()
{
// 獲取文件名
std::string pathname = createNewFile();
// 創(chuàng)建文件
Util::File::createDirectory(Util::File::path(pathname));
// 獲取文件操作句柄
_ofs.open(pathname, std::ios::binary | std::ios::app);
assert(_ofs.is_open());
}
private:
size_t _name_cnt;
std::string _basename; // ./logs/base-(name)
std::ofstream _ofs;
size_t _max_size; // 記錄最大文件大小
size_t _cur_size; // 記錄當(dāng)前已寫入的數(shù)據(jù)大小
};傳入落地的方式即可--SinkType,如StdoutSink。
// 日志落地工廠
class SinkFactory{
public:
template <typename SinkType, typename ...Args>
static LogSink::ptr create(Args &&...args){
return std::make_shared<SinkType>(std::forward<Args>(args)...);
}
};Buffer.hpp
日志緩沖區(qū)類:此類配合Looper中的AsyncLooper(異步日志器),來實現(xiàn)異步線程+雙緩沖區(qū)策略。旨在減少生產(chǎn)者與消費者的鎖沖突,避免了空間的頻繁申請與釋放,優(yōu)化日志的讀寫性能。
其中日志器將消息寫到一個日志緩沖區(qū),日志緩存區(qū)再與任務(wù)緩沖區(qū)交互,由異步線程將任務(wù)緩沖區(qū)中的日志消息寫到磁盤。

_DEFAULT_SIZE:初始化緩沖區(qū)大小。_THRESHOLD_SIZE:增長邊界,小于_THRESHOLD_SIZE且空間不足時按原有空間兩倍增長,大于則一次增加_INCREASE_SZIE
class Buffer{
#define _DEFAULT_SIZE (1*1024*1024)
#define _THRESHOLD_SIZE (8*1024*1024)
#define _INCREASE_SZIE (1*1024*1024)
public:
Buffer():_buffer(_DEFAULT_SIZE),_write_idx(0),_read_idx(0){}
//寫入數(shù)據(jù)
void push(const char* data,size_t len){
//空間不夠則擴容
ensureEnough(len);
std::copy(data,data + len, &_buffer[_write_idx]);
moveWriter(len);
}
//返回可讀數(shù)據(jù)的起始地址
const char* begin(){
return &_buffer[_read_idx];
}
//移動可讀指針
void moveReader(size_t len){
assert(len <= readAbleSize());
_read_idx += len;
}
//可讀空間
size_t readAbleSize(){
return (_write_idx - _read_idx);
}
//可寫空間
size_t writeAbleSize(){
//只為固定大小緩沖區(qū)提供的接口
return (_buffer.size() - _write_idx);
}
//交換緩沖區(qū)
void swap(Buffer &buffer){
_buffer.swap(buffer._buffer);
std::swap(_write_idx,buffer._write_idx);
std::swap(_read_idx,buffer._read_idx);
}
//重置緩沖區(qū)
void reset(){
_write_idx = 0;
_read_idx = 0;
}
//緩沖區(qū)是否為空
bool empty(){
return (_write_idx == _read_idx);
}
private:
//判斷緩沖區(qū)大小是否足夠
void ensureEnough(size_t len){
//無需擴容
if(len < writeAbleSize()) return;
//空間不足
size_t newsize = 0;
if(_buffer.size() >= _THRESHOLD_SIZE){
newsize = _buffer.size() + _INCREASE_SZIE + len;
}
else{
newsize = _buffer.size() * 2 + len;
}
_buffer.resize(newsize);
}
//移動可寫指針
void moveWriter(size_t len){
assert(_write_idx + len < _buffer.size());
_write_idx += len;
}
private:
std::vector<char> _buffer;
size_t _write_idx; //寫指針
size_t _read_idx; //讀指針
};
Looper.hpp
異步工作器:提供停止工作器,以及寫入緩沖區(qū)兩個接口,內(nèi)置兩個緩沖器,內(nèi)含異步線程入口函數(shù)。內(nèi)部提供一個回調(diào)函數(shù)指針,用于異步工作線程回調(diào)使用。
提供兩種緩存沖區(qū)策略:
ASYNC_SAFE:安全策略,當(dāng)緩沖區(qū)滿時阻塞寫入緩沖區(qū),防止資源耗盡。
ASYNC_UNSAFE:緩沖區(qū)無限擴容策略。
using Functor = std::function<void(Buffer &)>;
enum class AsyncType
{
ASYNC_SAFE, //安全狀態(tài),緩沖區(qū)滿就阻塞,防止資源耗盡
ASYNC_UNSAFE //緩沖區(qū)無限擴容
};
// 異步日志器
class AsyncLooper{
public:
using ptr = std::shared_ptr<AsyncLooper>;
public:
AsyncLooper(const Functor& cb,AsyncType runtype):_running(true),_callback(cb),
_thread(std::thread(&AsyncLooper::threadEnter,this)),_runtype(AsyncType::ASYNC_SAFE)
{ _runtype = runtype;}
~AsyncLooper(){ stop(); }
void stop()
{
_running = false;
//喚醒所以線程
_cond_con.notify_all();
_thread.join();
}
void push(const char *data, size_t len)
{
std::unique_lock<std::mutex> lock(_mutex);
//寫入長度小于緩沖區(qū)可寫長度才可寫,否則阻塞
if(_runtype == AsyncType::ASYNC_SAFE)
_cond_pro.wait(lock, [&](){ return len <= _pro_buffer.writeAbleSize();});
_pro_buffer.push(data,len);
//喚醒消費者 處理數(shù)據(jù)
_cond_con.notify_one();
}
private:
//線程入口函數(shù)
void threadEnter()
{
while(1)
{
//給互斥鎖設(shè)定作用域
{
std::unique_lock<std::mutex> lock(_mutex);
//已設(shè)置退出狀態(tài)并且緩沖區(qū)為空,則退出
if(!_running && _pro_buffer.empty())break;
//程序結(jié)束前 或 生產(chǎn)者緩沖區(qū)不為空,進行數(shù)據(jù)寫入
_cond_con.wait(lock, [&](){ return !_running || !_pro_buffer.empty();});
_con_buffer.swap(_pro_buffer);
//喚醒生產(chǎn)者
if(_runtype == AsyncType::ASYNC_SAFE)
_cond_pro.notify_all();
}
//回調(diào)處理
_callback(_con_buffer);
//初始化消費緩沖區(qū)
_con_buffer.reset();
}
}
private:
// 回調(diào)函數(shù) 異步工作器使用者傳入
Functor _callback;
private:
AsyncType _runtype;
bool _running;
std::mutex _mutex;
//生產(chǎn)緩沖區(qū)
Buffer _pro_buffer;
//消費緩沖區(qū)
Buffer _con_buffer;
std::condition_variable _cond_pro;
std::condition_variable _cond_con;
std::thread _thread;
};Logger.hpp
日志類:使用建造者模式,來簡化使用難度。這個類主要用來與前端交互,需要打印日志時,只需創(chuàng)建Logger對象,并調(diào)用debug、info、warn等方法輸出日志即可。支持解析可變參數(shù)列表和輸出格式,可像printf一樣輸出日志。
這里提供兩種日志的輸出方式:同步輸入日志以及異步輸出日志。
同步輸出:同步輸出日志信息。
異步輸出:將日志寫入到緩沖區(qū)中,由異步工作線程來完成日志輸出。
因此,設(shè)計基類Logger,由子類SyncLogger(同步輸出)以及AsyncLogger(異步輸出)來繼承。設(shè)置log接口由子類重寫即可。
支持傳入多個日志落地模塊,實現(xiàn)多種落地方式共同執(zhí)行。
class Logger{
public:
using ptr = std::shared_ptr<Logger>;
Logger(std::string logger_name, Loglevel::value level,
Formator::ptr &formator, std::vector<LogSink::ptr> &sinks)
: _logger_name(logger_name), _limit_level(level), _formator(formator), _sinks(sinks.begin(), sinks.end())
{
}
// 完成對不同等級日志的構(gòu)造
void debug(const std::string &file, size_t line, const std::string &fmt, ...){
// 1.判斷日志輸出等級
if (Loglevel::value::DEBUG < _limit_level)
return;
// 2.對不定參數(shù)解包
va_list ap;
va_start(ap, fmt);
char *res;
int ret = vasprintf(&res, fmt.c_str(), ap);
if (ret == -1)
{
std::cout << "vasprintf failed!\n";
return;
}
va_end(ap);
serialize(Loglevel::value::DEBUG, file, line, res);
free(res);
}
void info(const std::string &file, size_t line, const std::string &fmt, ...){
...
}
void warn(const std::string &file, size_t line, const std::string &fmt, ...){
...
}
void error(const std::string &file, size_t line, const std::string &fmt, ...){
...
}
void fatal(const std::string &file, size_t line, const std::string &fmt, ...){
...
}
const std::string& name()
{
return _logger_name;
}
protected:
void serialize(Loglevel::value level, const std::string &file, size_t line, const char *str)
{
// 3.構(gòu)造LogMsg
LogMsg logmsg(level, line, file, _logger_name, str);
// 4.調(diào)用格式化工具格式化LogMsg
std::stringstream ss;
_formator->format(ss, logmsg);
// 5.調(diào)用log輸出
log(ss.str().c_str(), ss.str().size());
}
//由子類實現(xiàn)輸出邏輯
virtual void log(const char *data, size_t len) = 0;
protected:
std::mutex _mutex; // 互斥鎖
std::string _logger_name; // 日志器名稱
std::atomic<Loglevel::value> _limit_level; // 限制輸出的等級
Formator::ptr _formator; // 格式化
std::vector<LogSink::ptr> _sinks; // 落地方式
};同步日志器:
class SyncLogger : public Logger{
public:
SyncLogger(std::string logger_name, Loglevel::value level,
Formator::ptr &formator, std::vector<LogSink::ptr> &sinks)
: Logger(logger_name, level, formator, sinks)
{
}
protected:
void log(const char *data, size_t len) override{
std::unique_lock<std::mutex> lock(_mutex);
if (_sinks.empty())
return;
for (auto &sink : _sinks)
sink->log(data, len);
}
};異步日志器:
class AsyncLogger : public Logger{
public:
AsyncLogger(std::string logger_name, Loglevel::value level,
Formator::ptr &formator, std::vector<LogSink::ptr> &sinks,
AsyncType runtype)
: Logger(logger_name, level, formator, sinks)
, _looper(std::make_shared<AsyncLooper>(
std::bind(&AsyncLogger::realLog, this, std::placeholders::_1)
, runtype)
) //構(gòu)造時,傳入回調(diào)函數(shù)構(gòu)造異步日志器
{
}
void log(const char *data, size_t len) override{
_looper->push(data, len);
}
void realLog(Buffer &buf){
if (_sinks.empty())
return;
for (auto &sink : _sinks)
sink->log(buf.begin(), buf.readAbleSize());
}
protected:
AsyncLooper::ptr _looper; //異步日志器
};日志器建造者模式設(shè)計:
由于Logger是對多個模塊的整合,想要創(chuàng)建日志器需要多個參數(shù),使用起來相對麻煩,因此設(shè)計建造者模式來降低使用難度。
日志建造者基類:
//日志器建造者
class LoggerBuilder
{
public:
LoggerBuilder()
: _logger_type(LoggerType::LOGGER_SYNC)
, _limit_level(Loglevel::value::DEBUG)
, _runtype(AsyncType::ASYNC_SAFE)
{}
// 建造各種變量的接口
void buildUnSafeRuntype() { _runtype = AsyncType::ASYNC_UNSAFE; }
void buildLoggerType(LoggerType type) { _logger_type = type; }
void buildLoggerName(const std::string &name) { _logger_name = name; }
void buildLoggerlevel(Loglevel::value level) { _limit_level = level; }
void buildFormator(const std::string &pattern){
_formator = std::make_shared<Formator>(pattern);
}
template <class SinkType, class... Args>
void buildSink(Args &&...args)
{
LogSink::ptr sinkptr = SinkFactory::create<SinkType>(std::forward<Args>(args)...);
_sinks.push_back(sinkptr);
}
// 總建造函數(shù)由子類繼承重寫
virtual Logger::ptr build() = 0;
protected:
AsyncType _runtype;
LoggerType _logger_type;
std::string _logger_name;
Loglevel::value _limit_level;
Formator::ptr _formator;
std::vector<LogSink::ptr> _sinks;
};局部建造者:
// 局部建造者
class LoaclLoggerBuilder : public LoggerBuilder{
public:
// 重寫父類建造函數(shù)
Logger::ptr build() override{
assert(!_logger_name.empty());
if (_formator.get() == nullptr)
{
_formator = std::make_shared<Formator>();
}
if (_sinks.empty())
{
buildSink<StdoutSink>();
}
if (_logger_type == LoggerType::LOGGER_ASYNC)
{
return std::make_shared<AsyncLogger>(_logger_name, _limit_level, _formator, _sinks, _runtype);
}
return std::make_shared<SyncLogger>(_logger_name, _limit_level, _formator, _sinks);
}
};局部日志器的使用會受到作用域的限制,我們希望在全局各個地方都可以調(diào)用日志器進行輸出,因此設(shè)計了一個日志器管理的單例類,這樣我們就可以在任何地方調(diào)用這個管理器來獲取單例對象,來對日志進行打印。
我們基于日志類,繼承出一個全局日志器建造者類,建造出來的全局日志器直接添加到單例日志管理器中。
日志器管理器:
//日志器管理器 --默認日志器使用局部建造者創(chuàng)建并由管理器管理
class LoggerManager
{
public:
static LoggerManager& getInstance(){
static LoggerManager eton;
return eton;
}
void addLogger(Logger::ptr &logger){
if(hasLogger(logger->name()))return ;
std::unique_lock<std::mutex> lock(_mutex);
_loggers.insert(std::make_pair(logger->name(),logger));
}
bool hasLogger(const std::string& name){
if(_loggers.count(name))return true;
return false;
}
Logger::ptr getLogger(const std::string& name){
auto it = _loggers.find(name);
if(it ==_loggers.end())return Logger::ptr();
return it->second;
}
Logger::ptr rootLogger(){
return _root_logger;
}
private:
LoggerManager(){
std::unique_ptr<LoggerBuilder> _builder(new LoaclLoggerBuilder());
_builder->buildLoggerName("root");
_root_logger = _builder->build();
_loggers.insert(std::make_pair("root",_root_logger));
}
private:
std::mutex _mutex;
// 默認日志器
Logger::ptr _root_logger;
std::unordered_map<std::string, Logger::ptr> _loggers;
};全局建造者:
// 全局建造者
class GlobeLoggerBuilder : public LoggerBuilder{
public:
Logger::ptr build() override
{
assert(!_logger_name.empty());
if (_formator.get() == nullptr){
_formator = std::make_shared<Formator>();
}
if (_sinks.empty()){
buildSink<StdoutSink>();
}
Logger::ptr logger;
if (_logger_type == LoggerType::LOGGER_ASYNC){
logger = std::make_shared<AsyncLogger>(_logger_name, _limit_level, _formator, _sinks, _runtype);
}
else{
logger = std::make_shared<SyncLogger>(_logger_name, _limit_level, _formator, _sinks);
}
MyLogs::LoggerManager::getInstance().addLogger(logger);
return logger;
}
};MyLog.hpp
這里提供全局日志器的獲取接口。
使用代理模式通過全局函數(shù)和宏函數(shù)來代理Logger中的debug、info、warn、fatal等接口,以便控制輸出日志的文件名和行號,簡化用戶操作。
namespace MyLogs
{
//提供獲取指定日志器的全局接口
Logger::ptr getLogger(const std::string &name)
{
return LoggerManager::getInstance().getLogger(name);
}
Logger::ptr rootLogger()
{
return LoggerManager::getInstance().rootLogger();
}
//使用宏對日志器接口進行代理
#define debug(fmt, ...) debug(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
#define info(fmt, ...) info(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
#define warn(fmt, ...) warn(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
#define error(fmt, ...) error(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
#define fatal(fmt, ...) fatal(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
//提供宏,直接通過默認日志器進行輸出
#define DEBUG(fmt, ...) rootLogger()->debug(fmt, ##__VA_ARGS__)
#define INFO(fmt, ...) rootLogger()->debug(fmt, ##__VA_ARGS__)
#define WARN(fmt, ...) rootLogger()->debug(fmt, ##__VA_ARGS__)
#define ERROR(fmt, ...) rootLogger()->debug(fmt, ##__VA_ARGS__)
#define FATAL(fmt, ...) rootLogger()->debug(fmt, ##__VA_ARGS__)
}Bench.cc
測試環(huán)境:
CPU:AMD Ryzen 7 7700 8-Core Processor 5.35 GHz
RAM:32GB DDR5 6200
ROM:1T SSD PCIe3.0
OS:ubuntu-20.04.6TLS虛擬機
#include "../logs/MyLog.hpp"
#include <vector>
#include <thread>
#include <chrono>
using std::cout;
using std::endl;
void bench(const std::string &logger_name, size_t thr_cnt, size_t msg_cnt, size_t msg_len)
{
//1.獲取日志器
MyLogs::Logger::ptr logger = MyLogs::getLogger(logger_name);
if(logger.get() == nullptr)return ;
cout<< "測試日志: "<<msg_cnt<<"條," << "總大小: "<< (msg_cnt * msg_len) / 1024 << "KB" <<endl;
//2.指定長度的日志
std::string msg(msg_len-1 ,'A');
//3.創(chuàng)建指定數(shù)量的線程
std::vector<std::thread> threads;
std::vector<double> threads_cost(thr_cnt,0);
//每個線程需要輸出的日志數(shù)量
size_t msg_per_thr = msg_cnt / thr_cnt;
for(int i = 0; i < thr_cnt; i++)
{
threads.emplace_back([&,i](){
//4.線程函數(shù)內(nèi)計時
auto start = std::chrono::high_resolution_clock::now();
//5.寫日志
for(int j = 0; j < msg_per_thr; j++)
{
logger->fatal("%s",msg.c_str());
}
//6.結(jié)束計時
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> cost = end - start;
threads_cost[i] = cost.count();
cout << "線程" << i << "\t輸出日志數(shù)量: " << msg_per_thr <<", 耗時: " << cost.count() << "s" <<endl;
});
}
for(int i = 0; i < thr_cnt; i++)
threads[i].join();
//7.計算總耗時(多線程并發(fā),以線程最長耗時,為總耗時)
double max_cost = threads_cost[0];
for(auto thr_cost: threads_cost)
max_cost = max_cost < thr_cost ? thr_cost : max_cost;
size_t msg_per_sec = msg_cnt / max_cost;
size_t size_per_sec = (msg_cnt * msg_len) / (max_cost * 1024);
//8.打印消息
cout << "\t總耗時: " << max_cost <<"s"<<endl;
cout << "\t每秒輸出日志數(shù)量: " << msg_per_sec << "條" << endl;
cout << "\t每秒輸出日志大小: " << size_per_sec << "KB "<<endl;
}
void syncLog()
{
std::unique_ptr<MyLogs::LoggerBuilder> builder(new MyLogs::GlobeLoggerBuilder());
builder->buildLoggerName("SyncLogger");
builder->buildFormator("%m%n");
builder->buildLoggerType(MyLogs::LoggerType::LOGGER_SYNC);
builder->buildSink<MyLogs::FileSink>("./logfile/sync.log");
builder->build();
bench("SyncLogger",3,1000000,100);
}
void asyncLog()
{
std::unique_ptr<MyLogs::LoggerBuilder> builder(new MyLogs::GlobeLoggerBuilder());
builder->buildLoggerName("AsyncLogger");
builder->buildFormator("%m%n");
builder->buildLoggerType(MyLogs::LoggerType::LOGGER_ASYNC);
builder->buildUnSafeRuntype();
builder->buildSink<MyLogs::FileSink>("./logfile/async.log");
builder->build();
bench("AsyncLogger",3,1000000,100);
}
int main()
{
syncLog();
asyncLog();
return 0;
}測試結(jié)果:
同步日志:
測試日志: 1000000條,總大小: 97656KB
線程1 輸出日志數(shù)量: 333333, 耗時: 0.842788s
線程0 輸出日志數(shù)量: 333333, 耗時: 0.862125s
線程2 輸出日志數(shù)量: 333333, 耗時: 0.874029s
總耗時: 0.874029s
每秒輸出日志數(shù)量: 1144126條
每秒輸出日志大小: 111731KB
異步日志:
測試日志: 1000000條,總大小: 97656KB
線程0 輸出日志數(shù)量: 333333, 耗時: 0.246758s
線程1 輸出日志數(shù)量: 333333, 耗時: 0.247231s
線程2 輸出日志數(shù)量: 333333, 耗時: 0.250321s
總耗時: 0.250321s
每秒輸出日志數(shù)量: 3994875條
每秒輸出日志大小: 390124KB
總結(jié)
到此這篇關(guān)于C++簡單日志系統(tǒng)實現(xiàn)的文章就介紹到這了,更多相關(guān)C++日志系統(tǒng)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Qt掃盲篇之QRegularExpression正則匹配總結(jié)
QRegularExpression是Qt5.0引進的,修復(fù)了很多bug,提高了效率,使用時建議使用QRegularExpression,下面這篇文章主要給大家介紹了關(guān)于Qt掃盲篇之QRegularExpression正則匹配的相關(guān)資料,需要的朋友可以參考下2023-03-03
深入C/C++浮點數(shù)在內(nèi)存中的存儲方式詳解
本篇文章是對C/C++浮點數(shù)在內(nèi)存中的存儲方式進行了詳細的分析介紹,需要的朋友參考下2013-05-05
C++ 數(shù)據(jù)結(jié)構(gòu)之對稱矩陣及稀疏矩陣的壓縮存儲
這篇文章主要介紹了C++ 數(shù)據(jù)結(jié)構(gòu)之對稱矩陣及稀疏矩陣的壓縮存儲的相關(guān)資料,這里實現(xiàn)稀疏矩陣和對稱矩陣的壓縮存儲的實例,需要的朋友可以參考下2017-08-08

