简介
SPDLog 是一个开源的、快速的、仅有头文件的C++11 日志库,它提供了向流、标准输出、文件、系统日志、调试器等目标输出日志的能力。它支持的平台包括Windows、Linux、Mac、Android;有一下特性:
非常快,性能是它的主要目标;
仅包括头文件;
日志的格式化处理使用开源的fmt库 ;
可选的printf语法支持;
非常快的异步模式(可选),支持异步写日志;
自定义格式;
条件日志;
多线程/单线程日志;
各种日志目标:可对日志文件进行循环输出;可每日生成日志文件;支持控制台日志输出(支持颜色);系统日志;Windows debugger;较容易扩展自定义日志目标;
支持日志输出级别:阈值级别既可以在运行时也可以在编译时修改。
如何使用
将代码下载下来的压缩包解压后会得到以下文件,其中include文件夹里是所需的头文件和源码:
新建一个C++控制台应用程序项目,然后在项目属性页C/C++中常规的附加包含目录中加上include的路径,然后在.cpp中就可以开始测试了:
代码应用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include "spdlog/spdlog.h" int main () { spdlog::info ("Hello, {}!" , "World" ); spdlog::info ("Welcome to spdlog!" ); spdlog::error ("Some error message with arg: {}" , 1 ); spdlog::warn ("Easy padding in numbers like {:08d}" , 12 ); spdlog::critical ("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}" , 42 ); spdlog::info ("Support for floats {:03.2f}" , 1.23456 ); spdlog::info ("Positional args are {1} {0}.." , "too" , "supported" ); spdlog::info ("{:<30}" , "left aligned" ); }
warn,critical,info为不同等级的log,输出在控制台会以不同颜色表示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> #include "spdlog/spdlog.h" #include "spdlog/sinks/basic_file_sink.h" int main () { try { auto my_logger = spdlog::basic_logger_mt ("sbasic_logger" , "logs/basic.txt" ); my_logger->info ("Hello {}" , "world" ); } catch (const spdlog::spdlog_ex& ex) { std::cout << "Log initialization failed: " << ex.what () << std::endl; } }
auto my_logger = spdlog::basic_logger_mt(“basic_logger”, “logs/basic.txt”);中”my_logger“为logger名称,可以随意命名
注意,logger使用完,程序关闭之前需要调用drop函数释放logger对象,如果程序没有关闭,就无法建立同样名称的logger
这种basic log不带滚动,日志文件会一直被写入,不断变大
函数名带后缀_mt的意思是multi thread(速度稍微慢一点点,考虑了多线程并发),_st的意思是single thread(速度较快)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> #include "spdlog/spdlog.h" #include "spdlog/sinks/rotating_file_sink.h" int main () { try { auto file_logger = spdlog::rotating_logger_mt ("file_logger" , "myfilename" , 1024 * 1024 * 5 , 10 ); file_logger->set_level (spdlog::level::debug); int i = 0 ; while (i < 1000000 ) { file_logger->debug ("Async message #{}" , i); i++; } } catch (const spdlog::spdlog_ex& ex) { std::cout << "Log initialization failed: " << ex.what () << std::endl; } }
区别于单一文件,循环日志的生产者类是rotating_logger_mt。rotating_logger_mt初始化的时候需要4个参数。
生产者的名字,自定义即可;
日志文件路径,相对和绝对均可;
单一文件的大小,超过了设置大小就生成一个新的文件。上面代码中设置为5MB;
保留文件数量,超过数量的文件会直接删掉以节省空间。正常使用的时候此数字大一些较好。
代码中生成100万条日志,数据大约是65MB。那么在根目录下就会出现10个日志文件,后缀名由1~9。
rotating log 滚动日志,当日志文件超出规定大小时,会删除当前日志文件中所有内容,重新开始写入
daily log的使用
每天会新建一个日志文件,新建日志文件的时间可以自己设定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> #include "spdlog/spdlog.h" #include "spdlog/sinks/daily_file_sink.h" int main () { auto daily_logger = spdlog::daily_logger_mt ("daily_logger" , "logs/daily.txt" , 2 , 30 ); daily_logger->flush_on (spdlog::level::err); daily_logger->info (123.44 ); return 0 ; }
如果程序不退出,每天2:30会创建新的文件
异步打印日志文件
大型项目中经常有很多场景是对时间有着严苛要求的,此时异步调用打印日志功能就显得十分重要了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include "spdlog/spdlog.h" #include "spdlog/async.h" #include "spdlog/sinks/rotating_file_sink.h" int main () { spdlog::init_thread_pool (10000 , 1 ); auto file_logger = spdlog::rotating_logger_mt<spdlog::async_factory>("file_logger" , "mylogs" , 1024 * 1024 * 5 , 100 ); int i = 0 ; file_logger->set_level (spdlog::level::debug); while (i < 1000000 ) { file_logger->debug ("Async message #{}" , i); i++; } spdlog::drop_all (); return 0 ; }
在初始化的时候使用异步工厂spdlog::async_factory进行初始化即可。
技术分析
模板
源码中使用了模板技术
c++11
源码中使用了c++11 新规范,比如:mutex using 等
头文件与源码分离,使用include进行包含:
1 2 3 4 5 #ifdef SPDLOG_HEADER_ONLY # include "async_logger-inl.h" #endif
源码实现在 async_logger-inl.h中,而头文件async_logger.h 做类声明;
封装
对 PSDLog 进行单件模式封装是有意义的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 #pragma once #ifndef _SIM_LOG_H_ #define _SIM_LOG_H_ #include "spdlog/spdlog.h" #include "spdlog/sinks/rotating_file_sink.h" #include "spdlog/sinks/stdout_color_sinks.h" #ifdef _WIN32 #define __FILENAME__ (strrchr(__FILE__,'\\' )?(strrchr(__FILE__,'\\' )+1):__FILE__) #else #define __FILENAME__ (strrchr(__FILE__,'/' )?(strrchr(__FILE__,'/' )+1):__FILE__) #endif #ifndef SUFFIX #define SUFFIX(msg) std::string(msg).append(" <" )\ .append (__FILENAME__).append ("> <" ).append (__FUNCTION__)\ .append ("> <" ).append (std::to_string (__LINE__))\ .append (">" ).c_str () #endif #define LTrace(msg,...) { if (!SimLog::Instance().Init()) SimLog::Instance().InitSimLog(); SimLog::Instance().GetLogger()->trace(SUFFIX(msg),__VA_ARGS__);}; #define LDebug(...) { if (!SimLog::Instance().Init()) SimLog::Instance().InitSimLog(); SimLog::Instance().GetLogger()->debug(__VA_ARGS__);}; #define LInfo(...) { if (!SimLog::Instance().Init()) SimLog::Instance().InitSimLog(); SimLog::Instance().GetLogger()->info(__VA_ARGS__);}; #define LWarn(...) { if (!SimLog::Instance().Init()) SimLog::Instance().InitSimLog(); SimLog::Instance().GetLogger()->warn(__VA_ARGS__);}; #define LError(msg,...) { if (!SimLog::Instance().Init()) SimLog::Instance().InitSimLog(); SimLog::Instance().GetLogger()->error (SUFFIX(msg),__VA_ARGS__);}; #define LCritical(...) { if (!SimLog::Instance().Init()) SimLog::Instance().InitSimLog(); SimLog::Instance().GetLogger()->critical(__VA_ARGS__);}; class SimLog final { public : static SimLog& Instance () { static SimLog log; return log; }; std::atomic_bool & Init () { return m_init; } void InitSimLog () { ::AllocConsole (); if (m_stdout) fclose (m_stdout); freopen_s (&m_stdout, "CONOUT$" , "w+t" , stdout); my_logger_ = spdlog::stdout_color_mt ("console" ); SetLevel (); m_init = true ; } void InitSimLog (const std::string &file_name) { DWORD id = GetCurrentProcessId (); InitSimLog (file_name, std::to_string (id)); } void InitSimLog (const std::string& file_name, const std::string& logger_name) { InitSimLog (file_name, logger_name, 1048576 * 3 , 3 ); } void InitSimLog (const std::string &file_name, const std::string &logger_name, size_t max_size, size_t max_files) { my_logger_ = spdlog::rotating_logger_mt (logger_name, file_name, max_size, max_files); my_logger_->flush_on (spdlog::level::err); m_init = true ; } void EndLog () { if (my_logger_ && m_async) spdlog::shutdown (); }; void SetLevel (int level = spdlog::level::trace) { spdlog::set_level (static_cast <spdlog::level::level_enum>(level)); }; auto GetLogger () { return my_logger_; } private : SimLog ()= default ; ~SimLog () { EndLog (); if (m_stdout) { spdlog::drop ("console" ); FreeConsole (); fclose (m_stdout); } }; SimLog (const SimLog& other) = delete ; SimLog& operator =(const SimLog& other) = delete ; private : std::shared_ptr<spdlog::logger> my_logger_; std::atomic_bool m_init{false }; std::atomic_bool m_async{false }; FILE* m_stdout{ nullptr }; }; #endif
其他封装参见:
spdlog_wrapper
基于spdlog开源日志库的封装日志库
基于C++ spdlog日志库的完善封装
不足
目前这个库有一大缺陷就是不支持日志压缩,要知道项目中如果打开了Debug级别的日志,日志量可能是非常恐怖的,如果分割文件的时候不能压缩文件将是对硬盘空间的极大浪费(日志压缩率一般在95%左右)。
参考
spdlog 基本结构分析