spdlog介绍 spdlog
是一个高性能 、超快速 、零配置 的C++
日志库,它旨在提供简洁的API
和丰富的功能,同时保持高性能 的日志记录。它支持多种输出目标 、格式化选项 、线程安全 以及异步 日志记录
为什么用spdlog 它有如下优点
高性能 :spdlog 专为速度而设计,即使在高负载情况下也能保持良好的性能。
零配置 :无需复杂的配置,只需包含头文件即可在项目中使用。
异步日志 :支持异步日志记录,减少对主线程的影响。
格式化 :支持自定义日志消息的格式化,包括时间戳、线程 ID、日志级别等。
多平台 :跨平台兼容,支持 Windows、Linux、macOS 等操作系统。
丰富的 API :提供丰富的日志级别和操作符重载,方便记录各种类型的日志。
spdlog安装 前置依赖库安装 spdlog
依赖fmt
库,如果没有,需要提前安装
1 2 3 sudo apt update -y sudo apt install -y libfmt-dev
同时编译的时候要加上选项 -lfmt
apt安装 1 2 sudo apt update sudo apt install -y libspdlog-dev
源码安装 1 2 3 4 5 git clone https://github.com/gabime/spdlog.git cd spdlog/ mkdir build && cd build cmake -DCMAKE_INSTALL_PREFIX=/usr .. make && sudo make install
spdlog使用 目前用到的头文件均来自于:
1 2 3 4 5 #include <spdlog/spdlog.h> #include <spdlog/logger.h> #include <spdlog/async_logger.h> #include <spdlog/async.h>
编译时要添加 -lpthread 和 -fmt 库链接
日志输出等级枚举 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 namespace spdlog{namespace level {enum level_enum { trace = SPDLOG_LEVEL_TRACE, debug = SPDLOG_LEVEL_DEBUG, info = SPDLOG_LEVEL_INFO, warn = SPDLOG_LEVEL_WARN, err = SPDLOG_LEVEL_ERROR, critical = SPDLOG_LEVEL_CRITICAL, off = SPDLOG_LEVEL_OFF, n_levels }; } }
日志输出格式自定义 假定我们现在有一个日志器对象的指针logger
,我们可以调用它的set_pattern
接口来设置格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 logger->set_pattern ("%Y-%m-%d %H:%M:%S [%t] [%-7l] %v" );
日志记录器类的接口声明 spdlog
库提供了日志记录器的工厂模式 ,我们可以直接构造日志器对象sdplog::logger
,也可以通过工厂构造。
下面是它的接口声明
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 namespace spdlog { class logger { logger (std::string name); logger (std::string name, sink_ptr single_sink) logger (std::string name, sinks_init_list sinks) void set_level (level::level_enum log_level) ; void set_formatter (std::unique_ptr<formatter> f) ; template <typename ... Args> void trace (fmt::format_string<Args...> fmt, Args &&...args) ; template <typename ... Args> void debug (fmt::format_string<Args...> fmt, Args &&...args) ; template <typename ... Args> void info (fmt::format_string<Args...> fmt, Args &&...args) ; template <typename ... Args> void warn (fmt::format_string<Args...> fmt, Args &&...args) ; template <typename ... Args> void error (fmt::format_string<Args...> fmt, Args &&...args) ; template <typename ... Args> void critical (fmt::format_string<Args...> fmt, Args &&...args) ; void flush () ; void flush_on (level::level_enum log_level) ; }; }
异步日志记录器类 上面的那个只是基类,而现在这个spdlog::async_logger
支持异步写日志
接口声明如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class async_logger final : public logger { async_logger (std::string logger_name, sinks_init_list sinks_list, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy = async_overflow_policy::block); async_logger (std::string logger_name, sink_ptr single_sink, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy = async_overflow_policy::block); } class SPDLOG_API thread_pool { thread_pool (size_t q_max_items, size_t threads_n, std::function<void ()> on_thread_start, std::function<void ()> on_thread_stop); thread_pool (size_t q_max_items, size_t threads_n, std::function<void ()> on_thread_start); thread_pool (size_t q_max_items, size_t threads_n); };
日志记录器工厂类 异步日志器工厂模式由spdlog/async.h
提供
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 namespace spdlog{template <async_overflow_policy OverflowPolicy = async_overflow_policy::block>struct async_factory_impl{ template <typename Sink, typename ... SinkArgs> static std::shared_ptr<async_logger> create (std::string logger_name, SinkArgs &&...args); }; using async_factory = async_factory_impl<async_overflow_policy::block>;using async_factory_nonblock = async_factory_impl<async_overflow_policy::overrun_oldest>;template <typename Sink, typename ... SinkArgs>inline std::shared_ptr<spdlog::logger> create_async (std::string logger_name, SinkArgs &&...sink_args) ;template <typename Sink, typename ... SinkArgs>inline std::shared_ptr<spdlog::logger> create_async_nb (std::string logger_name, SinkArgs &&...sink_args) ;inline void init_thread_pool (size_t q_size, size_t thread_count, std::function<void ()> on_thread_start) ;inline void init_thread_pool (size_t q_size, size_t thread_count) ;inline std::shared_ptr<spdlog::details::thread_pool> thread_pool () { return details::registry::instance ().get_tp (); } }
各种同步日志器工厂模式由spdlog/sinks/
下的特殊头文件提供
比如下面的工厂来自头文件<spdlog/sinks/stdout_color_sinks.h>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 namespace spdlog{template <typename Factory = spdlog::synchronous_factory>std::shared_ptr<logger> stdout_color_mt (const std::string &logger_name, color_mode mode = color_mode::automatic) ;template <typename Factory = spdlog::synchronous_factory>std::shared_ptr<logger> stdout_color_st (const std::string &logger_name, color_mode mode = color_mode::automatic) ;template <typename Factory = spdlog::synchronous_factory>std::shared_ptr<logger> stderr_color_mt (const std::string &logger_name, color_mode mode = color_mode::automatic) ;template <typename Factory = spdlog::synchronous_factory>std::shared_ptr<logger> stderr_color_st (const std::string &logger_name, color_mode mode = color_mode::automatic) ;}
日志落地类 日志落地类的实现方式有很多,其中还区分了单线程无锁版本
和多线程锁保护版本
,这里不多赘述,只展示基类
*_st
:single thread 单线程版本,不用加锁,效率更高。
*_mt
:multi 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 namespace spdlog {namespace sinks {class SPDLOG_API sink{ public : virtual ~sink () = default ; virtual void log (const details::log_msg &msg) = 0 ; virtual void flush () = 0 ; virtual void set_pattern (const std::string &pattern) = 0 ; virtual void set_formatter (std::unique_ptr<spdlog::formatter> sink_formatter) = 0 ; void set_level (level::level_enum log_level) ; level::level_enum level () const ; bool should_log (level::level_enum msg_level) const ; protected : level_t level_{level::trace}; }; } }
全局接口 1 2 3 4 5 6 void set_level (level::level_enum log_level) ; void flush_every (std::chrono::seconds interval) void flush_on (level::level_enum log_level) ;
代码样例 我们来测试如下要点
多落地方向同步输出
设置输出等级下限
异步输出日志
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 #include <spdlog/spdlog.h> #include <spdlog/sinks/stdout_color_sinks.h> #include <spdlog/sinks/basic_file_sink.h> #include <spdlog/async.h> void multi_sink_example () { auto console_sink = std::make_shared <spdlog::sinks::stdout_color_sink_mt>(); console_sink->set_level (spdlog::level::warn); console_sink->set_pattern ("[multi_sink_example] [%^%l%$] %v" ); auto file_sink = std::make_shared <spdlog::sinks::basic_file_sink_mt>( "logs/multisink.txt" , true ); file_sink->set_level (spdlog::level::trace); spdlog::logger logger ("multi_sink" , {console_sink, file_sink}) ; logger.set_level (spdlog::level::debug); logger.set_pattern ("%Y-%m-%d %H:%M:%S [%l] %v" ); logger.warn ("this should appear in both console and file" ); logger.info ("this message should not appear in the console, only in the file" ); } void async_example () { spdlog::init_thread_pool (32768 , 1 ); auto async_logger = spdlog::basic_logger_mt <spdlog::async_factory>( "async_file_logger" , "logs/async_log.txt" ); async_logger->set_pattern ("%Y-%m-%d %H:%M:%S [%l] %v" ); for (int i = 1 ; i < 101 ; ++i) { async_logger->info ("Async message #{} {}" , i, "hello" ); } } int main () { multi_sink_example (); return 0 ; }
运行结果如下
多落地方向
异步输出
二次封装spdlog 因为spdlog的日志输出对文件名和行号并不是很友好,以及因为spdlog本身实现了线程安全,如果使用默认日志器每次进行单例获取,效率会有降低,因此进行二次封装,简化使用。
我们主要实现如下几点功能:
日志的初始化封装接口
日志的输出接口封装宏
仅使用唯一的全局日志器对象
logger.hpp
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 #pragma once #include <spdlog/spdlog.h> #include <spdlog/sinks/stdout_color_sinks.h> #include <spdlog/sinks/basic_file_sink.h> #include <spdlog/async.h> #include <iostream> namespace btyGoose{std::shared_ptr<spdlog::logger> g_default_logger; void init_logger (bool mode, const std::string &file, int32_t level) { if (mode == false ) { g_default_logger = spdlog::stdout_color_mt ("default-logger" ); g_default_logger->set_level (spdlog::level::level_enum::trace); g_default_logger->flush_on (spdlog::level::level_enum::trace); g_default_logger->set_pattern ("[%n][%H:%M:%S][%t]%^[%-8l]%$%v" ); }else { g_default_logger = spdlog::basic_logger_mt ("default-logger" , file); g_default_logger->set_level ((spdlog::level::level_enum)level); g_default_logger->flush_on ((spdlog::level::level_enum)level); g_default_logger->set_pattern ("[%n][%H:%M:%S][%t][%-8l]%v" ); } } #define LOG_TRACE(format, ...) btyGoose::g_default_logger->trace(std::string("[{}:{}] " ) + format, __FILE__, __LINE__, ##__VA_ARGS__) #define LOG_DEBUG(format, ...) btyGoose::g_default_logger->debug(std::string("[{}:{}] " ) + format, __FILE__, __LINE__, ##__VA_ARGS__) #define LOG_INFO(format, ...) btyGoose::g_default_logger->info(std::string("[{}:{}] " ) + format, __FILE__, __LINE__, ##__VA_ARGS__) #define LOG_WARN(format, ...) btyGoose::g_default_logger->warn(std::string("[{}:{}] " ) + format, __FILE__, __LINE__, ##__VA_ARGS__) #define LOG_ERROR(format, ...) btyGoose::g_default_logger->error (std::string("[{}:{}] " ) + format, __FILE__, __LINE__, ##__VA_ARGS__) #define LOG_FATAL(format, ...) btyGoose::g_default_logger->critical(std::string("[{}:{}] " ) + format, __FILE__, __LINE__, ##__VA_ARGS__) }
小小地测试一下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include "logger.hpp" #include <string> #include <string.h> const std::string log_file ("log_file.txt" ) ;const int32_t level = spdlog::level::warn;const bool release_mode = false ;int main () { btyGoose::init_logger (release_mode,log_file,level); LOG_TRACE ("trace" ); LOG_DEBUG ("I have {} bugs" ,555 ); LOG_WARN ("this is a warn" ); LOG_ERROR ("error! error str: {}" ," I am a error str" ); LOG_FATAL ("fatal, fail to solve" ); return 0 ; }
输出结果