spdlog介绍
spdlog是一个C++的日志库,它的名字来源于Speed + Log的组合,主打高性能与易用性。
spdlog的特点如下:
异步记录
使用异步日志记录,可以在保证性能的同时,避免了IO阻塞,提高系统资源利用率。
循环缓冲区
使用固定大小的循环缓冲区,可避免内存泄漏和重复分配内存以及减少磁盘I/O散乱度,进一步提高速度和效率。
多线程支持
spdlog支持多线程日志记录,线程安全且无需加锁,可有效减小潜在的性能损失。
支持多种日志输出方式
包括控制台、文件(支持自动轮转)、流式操作符等,方便实现不同场景下的日志处理。
简单易用
接口简单、文档齐全,只要几行代码就可以很容易地集成到你的项目中。
总之,spdlog是一个非常优秀的C++日志库,具有高性能、多功能、易用等优点,非常适合用于各类C++应用程序中的日志处理。
GitHub - gabime/spdlog: Fast C++ logging library.
如何在CMake项目中使用?
下载发布版本的源代码包
gabime/spdlog at v1.12.0 (github.com)
https://codeload.github.com/gabime/spdlog/zip/refs/tags/v1.12.0
解压并放到你的CMake项目下3rdparty子目录中
在CMake项目中包含spdlog的头文件目录(这里通过common项目)
创建demo的cpp文件(spdlog_demo_01.cpp),并建立到common的依赖
如何使用spdlog来写日志
基本使用
#include "spdlog/spdlog.h"
int main()
{
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");
spdlog::set_level(spdlog::level::debug); // Set global log level to debug
spdlog::debug("This message should be displayed..");
// change log pattern
spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v");
// Compile time log levels
// define SPDLOG_ACTIVE_LEVEL to desired level
SPDLOG_TRACE("Some trace message with param {}", 42);
SPDLOG_DEBUG("Some debug message");
}
带颜色的 stdout/stderr日志
#include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_color_sinks.h"
void main()
{
// create a color multi-threaded logger
auto console = spdlog::stdout_color_mt("console");
auto err_logger = spdlog::stderr_color_mt("stderr");
spdlog::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name)");
spdlog::get("stderr")->warn("loggers can be retrieved from a global registry using the spdlog::get(logger_name)");
}
基本文件日志
# include <iostream>
#include "spdlog.h"
#include "spdlog/sinks/basic_file_sink.h"
void main()
{
try
{
auto logger = spdlog::basic_logger_mt("basic_logger", "logs/basic-log.txt");
logger->warn("This is a warn message");
logger->info("This is a info message");
}
catch (const spdlog::spdlog_ex &ex)
{
std::cout << "Log init failed: " << ex.what() << std::endl;
}
}
滚动日志文件
# include <iostream>
#include "spdlog.h"
#include "spdlog/sinks/rotating_file_sink.h"
void main()
{
try
{
// Create a file rotating logger with 5 MB size max and 3 rotated files
auto max_size = 1048576 * 5;
auto max_files = 3;
auto logger = spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", max_size, max_files);
logger->warn("This is a warn message in rotating file");
logger->info("This is a info message in rotating file");
}
catch (const spdlog::spdlog_ex &ex)
{
std::cout << "Log init failed: " << ex.what() << std::endl;
}
}
按天生成日志文件
# include <iostream>
#include "spdlog.h"
#include "spdlog/sinks/daily_file_sink.h"
void main()
{
try
{
// Create a daily logger - a new file is created every day at 2:30 am
auto logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30);
logger->warn("This is a warn message in daily file");
logger->info("This is a info message in daily file");
}
catch (const spdlog::spdlog_ex &ex)
{
std::cout << "Log init failed: " << ex.what() << std::endl;
}
}
Backtrace日志支持
# include <iostream>
#include "spdlog.h"
#include "spdlog/sinks/stdout_color_sinks.h"
void main()
{
try
{
auto my_logger = spdlog::stdout_color_mt("console");
// Debug messages can be stored in a ring buffer instead of being logged immediately.
// This is useful to display debug logs only when needed (e.g. when an error happens).
// When needed, call dump_backtrace() to dump them to your log.
spdlog::enable_backtrace(32); // Store the latest 32 messages in a buffer.
// or my_logger->enable_backtrace(32)..
for(int i = 0; i < 100; i++)
{
spdlog::debug("Backtrace message {}", i); // not logged yet..
}
// e.g. if some error happened:
spdlog::dump_backtrace(); // log them now! show the last 32 messages
// or my_logger->dump_backtrace(32)..
}
catch (const spdlog::spdlog_ex &ex)
{
std::cout << "Log init failed: " << ex.what() << std::endl;
}
}
这里只显示最后32条日志:
按周期写入(Periodic flush)
# include <iostream>
#include <chrono>
#include <thread>
#include "spdlog.h"
#include "spdlog/sinks/stdout_color_sinks.h"
void main()
{
try
{
auto my_logger = spdlog::stdout_color_mt("console");
// periodically flush all *registered* loggers every 30 seconds:
// warning: only use if all your loggers are thread-safe ("_mt" loggers)
spdlog::flush_every(std::chrono::seconds(30));
for(int i = 0; i < 10; i++)
{
my_logger->info("Periodic flush message 1: {}", i);
}
std::this_thread::sleep_for(std::chrono::seconds(10));
for(int i = 0; i < 10; i++)
{
my_logger->info("Periodic flush message 2: {}", i); // not logged yet..
}
std::this_thread::sleep_for(std::chrono::seconds(100));
}
catch (const spdlog::spdlog_ex &ex)
{
std::cout << "Log init failed: " << ex.what() << std::endl;
}
}
从日志写的时间来看,2次写日志并没有间隔30秒,感觉按周期写入日志配置没有生效。
Stopwatch使用
#include <iostream>
#include "spdlog.h"
#include "spdlog/stopwatch.h"
void main()
{
spdlog::stopwatch sw;
spdlog::info("Elapsed {}", sw);
spdlog::info("Elapsed {:.3}", sw);
}
同时写多个格式不一样的日志
#include <iostream>
#include "spdlog.h"
#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/stdout_color_sinks.h"
// create a logger with 2 targets, with different log levels and formats.
// The console will show only warnings or errors, while the file will log all.
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.warn("this should appear in both console and file");
logger.info("this message should not appear in the console, only in the file");
}
void main()
{
multi_sink_example();
}
关于pattern格式,可以参考 3. Custom formatting · gabime/spdlog Wiki · GitHub
spdlog综合案例
同时把日志写入到控制台和回滚日志文件,日志保持相同的格式:
#pragma once
#include <string>
#include <vector>
#include <spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/rotating_file_sink.h>
namespace flaw_detect{
namespace common{
/*
日志类
*/
class Logger{
public:
/*
初始化日志,在每个可执行程序的入口类中进行初始化,然后通过spdlog::info(*)等方法来进行写日志.
*/
static void initLogger(const std::string& appName, const spdlog::filename_t& logFilePath, size_t maxSize = 1024*1024*10, size_t maxFiles=10, std::string logPattern = "%Y-%m-%dT%H:%M:%S.%e - %n - %l - %P - %t - %s - %# - %v", spdlog::level::level_enum level = spdlog::level::info);
};
}
}
#include <filesystem>
#include <sstream>
#include "common.hpp"
namespace fs = std::filesystem;
namespace flaw_detect{
namespace common{
void Logger::initLogger(const std::string& appName, const spdlog::filename_t& logFilePath, size_t maxSize, size_t maxFiles, std::string logPattern, spdlog::level::level_enum level){
auto consoleSink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
consoleSink->set_level(level);
consoleSink->set_pattern(logPattern);
auto fileSink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(logFilePath, maxSize, maxFiles);
fileSink->set_level(level);
fileSink->set_pattern(logPattern);
// spdlog::sinks_init_list sinkLst = { fileSink, consoleSink };
// spdlog::logger logger(appName, sinkLst.begin(), sinkLst.end());
// logger.set_level(level);
// logger.set_pattern(logPattern);
spdlog::set_default_logger(std::make_shared<spdlog::logger>(appName, spdlog::sinks_init_list({consoleSink, fileSink})));
};
}
}
#include <iostream>
#include "spdlog.h"
#include "common.hpp"
void write_log_test(){
for (int i = 0; i < 5; i++) {
spdlog::info("spdlog_demo_01 test {}", i);
}
}
void main()
{
flaw_detect::common::Logger::initLogger("spdlog_demo", "logs/spdlog_demo.txt");
write_log_test();
// Under VisualStudio, this must be called before main finishes to workaround a known VS issue
spdlog::drop_all();
}
遇到一个小问题:无法打印写日志的cpp文件名以及行号, 解决方法如下:
在“#include "spdlog.h"”前面添加“#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO”, 然后通过宏定义(比如“SPDLOG_INFO”)来写日志即可。