一、基本介绍
spdlog
是由 Gustav S. 在 2015 年开发的一个高性能 C++ 日志库。开发这个库的主要目的是为了提供一个非常快速、轻量、易于使用的日志工具,特别适合需要高性能、低延迟日志记录的 C++ 应用程序。(由于源码现在比较难下载,我把压缩包 上传附件)
开发背景
当时,许多现有的 C++ 日志库(如 log4cpp
和 Boost.Log
)虽然功能强大,但在性能和资源消耗方面存在较大的开销,特别是在需要高吞吐量的环境中。尤其是在多线程或高并发的应用程序中,传统日志库可能会因为锁的竞争、内存分配等原因造成显著的性能瓶颈。因此,spdlog
的目标是通过简化设计和优化性能,来解决这些问题。
主要目标
- 高性能:
spdlog
旨在提供最低的性能开销,支持多线程和异步日志记录,减少日志对应用程序主流程的干扰。 - 简单易用:提供直观的 API,支持常见的日志需求,如日志级别、文件输出、异步日志等,方便开发者集成。
- 低依赖:
spdlog
不依赖任何外部库,并且库本身非常小巧,易于部署。
为什么开发 spdlog
?
- 性能需求:许多现有的 C++ 日志库虽然功能强大,但它们的设计考虑了丰富的功能和可扩展性,导致性能开销较大。在需要高吞吐量日志记录的应用中,性能是一个重要的瓶颈。
- 简化使用:许多现有的日志库较为复杂,需要配置和学习的时间较长。
spdlog
旨在提供一个简单、直接的日志工具,降低集成的难度。 - 多线程支持:日志记录常常涉及多线程环境,在并发环境下,传统的日志库可能会带来性能问题,
spdlog
则致力于提供线程安全的高效日志系统。
适用的项目和场景
spdlog
被广泛应用于需要高效日志记录的 C++ 项目中,特别是在以下几种场景中非常有用:
1. 高性能应用程序
- 游戏开发:例如,开发高性能游戏引擎时,日志记录可能会成为一个性能瓶颈,
spdlog
提供的异步和低延迟日志能力可以帮助减少游戏运行时的开销。 - 金融服务:如高频交易系统、银行系统等,需要实时记录大量日志,
spdlog
的高效性非常适合这类应用。
2. 多线程和并发系统
- Web 服务器:如处理大量请求的高并发 Web 服务,
spdlog
能确保在多线程环境下高效、线程安全地记录日志。 - 分布式系统:在微服务架构或分布式计算环境中,
spdlog
可以帮助记录每个服务和节点的日志,同时确保高效性。
3. 嵌入式和物联网(IoT)
- 在嵌入式设备或 IoT 项目中,日志记录需要高效且不消耗太多系统资源。
spdlog
的轻量级设计非常适合这些资源受限的环境。
4. 实时数据处理和流处理
- 在处理实时数据流、日志聚合或事件流时,
spdlog
提供的异步日志功能能显著提高性能,减少对实时计算的影响。
5. 日志监控和调试
- 在开发、调试和生产环境中,
spdlog
提供了丰富的日志级别(如trace
,debug
,info
,warn
,error
,critical
),开发者可以根据需要选择不同的日志输出级别来进行调试和错误监控。
6. 数据中心和大规模服务器
spdlog
在大规模服务器和数据中心中非常有用,尤其是在需要集中管理大量日志数据时。它能够有效地支持多线程和异步写入,减少日志记录对系统的影响。
总结
- 开发者:Gustav S.,2015 年开发
- 目标:提供一个高性能、低开销、易用的 C++ 日志库,专为需要高吞吐量和多线程支持的应用设计。
- 适用项目:
- 高性能应用(如游戏、金融服务等)
- 多线程或并发系统(如 Web 服务器、分布式系统等)
- 嵌入式系统和 IoT 设备
- 实时数据处理和流处理
- 日志监控和调试
- 数据中心和大规模服务器
spdlog
通过简化设计并优化性能,解决了许多传统日志库在高并发环境下的问题,成为许多 C++ 项目中首选的日志库。
二、优缺点
spdlog
是一个高效且灵活的 C++ 日志库,但它也有一些局限性。以下是它的 优点 和 缺点 的详细分析:
优点
1. 高性能
- 低开销:
spdlog
专注于高性能,特别适合需要频繁记录日志的应用程序。它使用了非常高效的日志记录方式,减少了内存分配和锁操作。 - 异步日志支持:通过异步日志功能,
spdlog
能够把日志消息先放入队列,再由独立线程处理写入操作,从而避免了在主线程中的 I/O 阻塞,减少了性能损失。
2. 多种日志输出方式
spdlog
支持多种日志输出方式,如控制台、文件、滚动日志、异步日志和syslog
等。可以灵活配置,满足不同应用的需求。- 日志文件轮换:支持文件大小限制、日志文件数量限制、日期切割等轮换策略,避免日志文件无限增长。
3. 丰富的日志级别和格式化
- 支持多种日志级别(如
trace
,debug
,info
,warn
,error
,critical
),便于根据重要性和严重性过滤和处理日志。 - 自定义格式化:可以通过设置格式化字符串来自定义日志的输出格式,支持日志输出中包括时间戳、日志级别、文件名、行号等信息。
4. 线程安全
spdlog
默认是线程安全的,支持多线程环境中同时写入日志。它利用锁机制来保证多个线程对同一日志文件的安全访问。- 提供了异步日志,进一步减少了多线程环境中日志写入的冲突。
5. 轻量级
spdlog
是一个轻量级的库,依赖少,并且在编译时不需要链接任何重量级的第三方库。即使是异步模式下,它的性能开销也非常小。
6. 跨平台支持
spdlog
支持多个平台,包括 Linux、Windows 和 macOS,适合跨平台的 C++ 应用程序开发。- 提供了多种平台特定的日志输出(例如
syslog
),也支持自定义日志输出目标。
7. 易于集成
- API 简洁易用,安装和配置过程也非常简单。可以方便地嵌入到现有的 C++ 项目中。
缺点
1. 不支持跨进程日志管理
spdlog
是一个单进程日志库,默认不支持跨进程共享日志文件。在多个进程同时写日志时,可能会遇到并发写入冲突的问题,虽然可以使用文件锁或异步日志缓解,但这些方法并不能像logd
或logcat
那样提供真正的跨进程日志管理和同步。
2. 不支持复杂的日志过滤与查询
spdlog
主要集中在日志的输出和格式化上,不像logd
、logcat
或其他日志系统那样提供强大的日志查询和过滤功能。虽然你可以在写入时控制日志级别,但无法像系统级日志工具那样对日志进行复杂的筛选、过滤、搜索和集中管理。
3. 对日志的持久化管理有限
- 对于日志的持久化和管理,
spdlog
主要依赖文件系统。虽然支持文件大小限制和轮换,但它并不像logd
等系统级日志管理器那样提供系统级的日志聚合、集中管理和持久化。 - 如果需要将日志集成到集中式日志收集系统(如 ELK、Fluentd 等),则需要自己实现日志转发功能。
4. 无内建的日志压缩或备份功能
spdlog
支持日志轮换,但并不内建日志压缩、备份等功能。对于需要保留大量历史日志的应用程序,可能需要额外的工具或脚本来处理日志压缩和备份。
5. 日志管理功能较简单
spdlog
更注重日志记录本身,缺乏一些高级的日志管理功能,如日志推送、日志集成、自动清理等。这使得它对于单一应用程序非常合适,但在大型分布式系统或需要跨应用程序日志集中管理的环境中可能不太适用。
6. 依赖于 C++ 特性
spdlog
是为 C++ 设计的,依赖于 C++ 的一些特性,如异常处理、模板和类等。如果你在 C 环境中工作,或者想在 C 语言项目中使用它,可能需要通过 C++ 封装或者其他适配方式来使用。
总结
适合的场景:
- 高性能要求:
spdlog
是一个高效的日志库,适合需要低延迟和高吞吐量的 C++ 应用程序。 - 单进程应用:如果你的应用程序是单进程或只有少量的线程,
spdlog
可以提供快速的日志记录。 - 多线程环境:
spdlog
通过锁机制和异步日志支持,可以在多线程环境中安全高效地记录日志。 - 简单的日志管理需求:如果你不需要复杂的日志查询和管理功能,只是简单地记录日志,
spdlog
非常合适。
不适合的场景:
- 多进程日志管理:如果需要跨进程的日志集中管理,
spdlog
并不提供这类功能。 - 复杂日志分析:如果你需要复杂的日志筛选、查询和分析,
spdlog
并不适合这种需求。 - 日志聚合系统:如果你需要将日志发送到一个日志服务器或集中式日志系统,
spdlog
并没有内建的支持,你需要额外的配置或工具来实现。
总体来说,spdlog
是一个非常优秀的 C++ 日志库,在高性能和灵活性方面表现非常好,但它的跨进程支持和日志管理功能相对较简单。如果你在一个单进程或小规模多线程环境中工作,spdlog
非常适合;但如果你需要复杂的日志系统功能或跨进程日志管理,可能需要其他工具来补充。
三、与logd/logcat比较
spdlog
和 logd
(或 logcat
)在设计上有一些不同,特别是在处理多个进程和日志的集中管理方面,logd
和 logcat
更加适合多进程和多线程环境中对日志进行统一管理。
spdlog
与 logd/logcat
比较
1. 用途和设计目标
-
spdlog
:这是一个高性能的 C++ 日志库,主要用于应用程序中记录日志,具有快速的性能和灵活的格式化功能。它的目标是提供高效、轻量的日志记录,并支持多种输出方式(如控制台、文件、异步日志等)。它适合单个应用进程中使用。 -
logd/logcat
:这两个是 Android 平台上的日志系统,logd
是日志守护进程,负责收集和管理系统日志,而logcat
是用于查看和查询日志的工具。logd
允许多个进程同时写入日志,并提供了集中管理、过滤、优先级控制等功能,适合在操作系统级别使用,特别是在多进程环境中。
2. 多进程支持
-
spdlog
:默认情况下,spdlog
主要是单进程的日志系统。虽然它支持异步日志和文件锁来避免并发写入问题,但它并没有专门针对跨进程的日志协调机制。每个进程将独立管理自己的日志输出。 -
logd/logcat
:logd
是为多进程和多线程设计的,允许多个进程同时写入日志,且通过操作系统统一管理。这种设计确保了跨进程的日志统一性和可访问性。在 Android 中,所有进程的日志都通过logd
管理,进程之间不会直接竞争日志输出。
3. 日志集中管理
-
spdlog
:日志通常是写入到文件或控制台的,管理和查询日志需要额外的工具支持。例如,日志文件可能会受到文件系统的限制,并且可能需要自己实现日志轮换或聚合。 -
logd/logcat
:日志是集中管理的,可以通过logcat
查看所有进程的日志,并且支持多种过滤、格式化选项。所有进程的日志都可以通过一个中心化的日志服务(logd
)进行管理,减少了进程间的管理复杂性。
4. 性能与日志输出
-
spdlog
:spdlog
非常注重性能,尤其是在单进程、多线程环境下。它支持异步日志,可以减少对主线程的影响,并且能够高效地写入日志文件或控制台。它对文件写入操作进行了优化,并且可以高效地进行日志轮换等操作。 -
logd/logcat
:logd
设计时考虑了多进程的日志管理,因此它的性能足够支撑 Android 操作系统中大量的进程日志写入。在处理日志时,logcat
提供了强大的查询功能,能够对日志进行筛选和排序,特别适合调试和故障排查。
5. 跨平台支持
-
spdlog
:spdlog
是一个跨平台的 C++ 日志库,支持 Linux、Windows 和 macOS 等操作系统。它非常灵活,可以配置日志输出方式(控制台、文件、syslog 等),适合嵌入式、桌面和服务器应用。 -
logd/logcat
:logd
和logcat
是 Android 专用的日志系统,主要用于移动设备的调试和日志管理。如果你在 Android 环境外工作,logd
和logcat
就无法使用。
适用场景
-
spdlog
更适用于:- 单进程或小型的多线程应用,尤其是那些对性能要求较高的场景。
- 需要灵活配置和高性能日志输出的 C++ 应用程序。
- 对日志管理有较高要求的开发环境,适合开发者使用。
-
logd/logcat
更适用于:- 多进程或分布式系统,特别是操作系统级别的日志管理。
- Android 平台或类似的嵌入式平台,需要集中管理和查询日志的场景。
- 跨进程日志管理、日志过滤和分析,尤其是调试阶段。
总结
- 如果你是在 Android 或 Linux 等操作系统平台上开发,特别是需要跨进程日志管理,
logd/logcat
提供了集中的日志管理和多进程支持,适合大规模应用或系统级日志。 - 如果你是在 C++ 开发环境中,尤其是单进程或小规模多线程环境中,并且对日志性能有较高要求,
spdlog
是一个非常高效且灵活的日志库,但对于跨进程日志管理则需要额外的工作和考虑。
从你的需求来看,如果涉及到多个进程写日志到同一个地方,并且希望集中管理日志,logd/logcat
确实比 spdlog
更适合这种场景。
四、基本安装使用
spdlog
是一个非常高效的 C++ 日志库,具有多线程安全性,并且支持多种日志级别、日志输出格式以及日志文件管理。它的使用非常简单,以下是基本用法:
1. 安装 spdlog
你可以通过以下命令安装 spdlog
(如果你没有安装的话):
- 使用
vcpkg
:vcpkg install spdlog
- 使用
CMake
:git clone https://github.com/gabime/spdlog.git cd spdlog mkdir build && cd build cmake .. make && sudo make install
2. 基本使用
#include <spdlog/spdlog.h>
int main() {
// 控制台日志
spdlog::info("Hello, {}!", "world");
// 支持不同级别的日志
spdlog::debug("This is a debug message");
spdlog::info("This is an info message");
spdlog::warn("This is a warning message");
spdlog::error("This is an error message");
spdlog::critical("This is a critical message");
// 格式化日志
spdlog::info("Formatted log: {} + {} = {}", 1, 2, 1 + 2);
return 0;
}
3. 日志文件输出
如果你想将日志输出到文件而不是控制台,可以这样做:
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
int main() {
// 创建一个文件日志器,日志将被写入 logs.txt
auto file_logger = spdlog::basic_logger_mt("file_logger", "logs.txt");
file_logger->info("This is an info message logged to a file");
return 0;
}
4. 设置日志级别
你可以设置全局的日志级别,只有该级别及以上的日志才会被输出:
#include <spdlog/spdlog.h>
int main() {
// 设置全局日志级别为 debug
spdlog::set_level(spdlog::level::debug);
// debug 级别的日志将被输出
spdlog::debug("This is a debug message");
// info 级别的日志将被输出
spdlog::info("This is an info message");
// warn 级别的日志将被输出
spdlog::warn("This is a warning message");
// error 级别的日志将被输出
spdlog::error("This is an error message");
return 0;
}
5. 自定义格式
你可以通过格式字符串自定义日志的输出格式:
#include <spdlog/spdlog.h>
int main() {
// 设置输出格式为时间戳 + 日志级别 + 消息
spdlog::set_pattern("%Y-%m-%d %H:%M:%S [%l] %v");
// 输出日志
spdlog::info("This is an info message with custom pattern");
return 0;
}
6. 异常捕获
spdlog
支持日志输出时捕获异常并记录堆栈信息:
#include <spdlog/spdlog.h>
int main() {
try {
throw std::runtime_error("An error occurred");
} catch (const std::exception& e) {
spdlog::error("Caught an exception: {}", e.what());
}
return 0;
}
五、在C项目中使用的方法
是的,spdlog
是专门为 C++ 设计的日志库,它利用了 C++ 的特性,如类、模板和异常处理等,因此不能直接在 C 语言中使用。
不过,如果你想在 C 语言中使用类似的日志功能,有以下几种替代方案:
1. 使用 C 语言的日志库
对于 C 语言,你可以考虑使用其他日志库,例如:
log4c
:这是 C 语言的日志库,提供类似于log4j
的功能。zlog
:这是一个 C 语言编写的高效的日志库,支持多种日志级别和输出格式。syslog
:你可以使用标准的 Unix/Linux 系统日志接口syslog()
,它支持将日志消息发送到系统日志服务。
2. 使用简单的 C 语言日志功能
如果不需要复杂的功能,你可以使用简单的 C 代码来实现一个基础的日志系统:
#include <stdio.h>
#include <time.h>
void log_info(const char *message) {
time_t t;
time(&t);
struct tm *tm_info = localtime(&t);
char time_str[20];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
printf("[%s] INFO: %s\n", time_str, message);
}
void log_error(const char *message) {
time_t t;
time(&t);
struct tm *tm_info = localtime(&t);
char time_str[20];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
printf("[%s] ERROR: %s\n", time_str, message);
}
int main() {
log_info("This is an info message");
log_error("This is an error message");
return 0;
}
这种方式虽然简单,但无法提供像 spdlog
那样的多线程支持、格式化功能和文件日志等高级特性。
3. 使用 C++ 封装
如果你需要在 C 语言程序中使用 spdlog
,可以考虑通过 C++ 封装的方式来实现。这种方法可以在 C 语言中调用 C++ 编写的日志功能,具体方法如下:
- 在 C++ 中封装日志功能。
- 使用
extern "C"
来暴露 C 风格的接口。 - 在 C 程序中调用这些接口。
例如:
// log.cpp (C++ 部分)
#include <spdlog/spdlog.h>
extern "C" {
void log_info(const char *message) {
spdlog::info(message);
}
void log_error(const char *message) {
spdlog::error(message);
}
}
然后在 C 程序中调用这些接口:
// log.h (C 部分)
#ifdef __cplusplus
extern "C" {
#endif
void log_info(const char *message);
void log_error(const char *message);
#ifdef __cplusplus
}
#endif
// main.c (C 部分)
#include <stdio.h>
#include "log.h"
int main() {
log_info("This is an info message from C");
log_error("This is an error message from C");
return 0;
}
这样,就能在 C 语言中间接使用 spdlog
的日志功能了。
六、异步日志使用方法
spdlog
默认情况下,日志输出到文件时如果是在多个进程中共享同一个日志文件,可能会遇到并发写入的问题,导致日志内容出现混乱或者丢失。这是因为多个进程同时写入同一个文件时,文件操作是非原子的。
静态库与多个进程
spdlog编译出来的是一个静态库,表示把 spdlog
封装成了一个库文件(.a
或 .lib
),并且该库可以被多个进程或线程链接和调用。不过,这与并发日志输出问题是两回事。静态库并不会自动处理跨进程的日志同步问题。
解决方案:使用 spdlog
支持的多进程日志管理
为了在多个进程中共享日志文件并确保线程安全,可以通过以下几种方式解决并发写入问题:
1. 使用 spdlog
的 异步日志 功能
spdlog
提供了异步日志记录的功能,它通过一个独立的日志线程来处理日志消息,从而避免了主线程或进程的性能瓶颈和并发问题。异步日志本质上是将日志消息先放入一个队列,然后由一个专门的日志线程负责写入文件。这样,即使在多个进程中写入日志,也不会发生并发写入问题。
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/async.h>
int main() {
// 启动异步日志
spdlog::init_thread_pool(8192, 1); // 设置日志队列大小和线程池大小
// 创建一个异步的文件日志器
auto logger = spdlog::basic_logger_mt<spdlog::async_factory>("async_file_logger", "logs.txt");
logger->info("This is an asynchronous log message.");
return 0;
}
这样,你可以保证多个进程同时向文件写入日志时不会造成问题。
2. 使用 spdlog
的 文件锁 功能
如果不想使用异步日志,也可以考虑使用 spdlog
的文件锁机制来确保每次只有一个进程能写入日志。spdlog
支持通过 sinks::rotating_file_sink_mt
或 sinks::daily_file_sink_mt
来实现文件锁。
#include <spdlog/spdlog.h>
#include <spdlog/sinks/rotating_file_sink.h>
int main() {
// 创建一个带文件锁的日志器,支持日志轮换
auto logger = spdlog::rotating_logger_mt("rotating_logger", "logs.txt", 1024 * 1024 * 5, 3); // 文件大小为5MB,最多保留3个文件
logger->info("This is a log message that will be written to a rotating file.");
return 0;
}
该方法适合日志轮换,并且通过文件锁避免了并发写入问题。不同进程之间通过文件锁来同步日志写入。
3. 使用 日志服务器 或 日志收集系统
如果你有多个进程,并且这些进程的日志输出量比较大,可以考虑将日志发送到一个中心化的日志系统,而不是直接写入文件。例如,使用类似 syslog、logstash、Fluentd 或 Kafka 等集中式日志收集系统。
这种方案的好处是可以避免本地文件系统的并发写入问题,同时可以方便地进行日志管理和分析。
例如,使用 spdlog
配合 syslog
进行日志记录:
#include <spdlog/spdlog.h>
#include <spdlog/sinks/syslog_sink.h>
int main() {
// 创建一个 syslog 日志器
auto logger = spdlog::syslog_logger_mt("syslog_logger", "myapp");
logger->info("This is an info message sent to syslog");
return 0;
}
通过这种方法,日志将发送到系统的日志管理器(如 rsyslog
或 syslogd
),并且可以在多个进程间共享。
4. 使用日志轮换和合并工具
如果你依然想将日志写入文件,但担心多个进程之间的日志竞争问题,可以使用外部工具(如 logrotate
)来定期轮换和合并日志文件。这样,即使多个进程同时写入日志,它们也会被分配到不同的文件中,避免冲突。
总结
- 异步日志 是处理多个进程写入日志时最简单和最有效的方法之一。
- 文件锁机制(如使用
rotating_file_sink_mt
)可以确保同一时刻只有一个进程写入日志。 - 集中式日志系统(如
syslog
、logstash
等)适合大规模日志收集和管理。 - 使用 日志轮换工具 可以解决日志文件的管理问题。
七、作者提供的使用例程
基本方法
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG // 必须在头文件之前 或者编译时-DSPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_DEBUG
#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");
// 编译时的log设置SPDLOG_ACTIVE_LEVEL宏的等级 和 set_level运行时等级都匹配下面的等级才会被打印
SPDLOG_TRACE("Some trace message with param {}", 42);
SPDLOG_DEBUG("Some debug message");
return 0;
}
[2025-01-17 16:05:58.130] [info] Welcome to spdlog!
[2025-01-17 16:05:58.130] [error] Some error message with arg: 1
[2025-01-17 16:05:58.130] [warning] Easy padding in numbers like 00000012
[2025-01-17 16:05:58.130] [critical] Support for int: 42; hex: 2a; oct: 52; bin: 101010
[2025-01-17 16:05:58.130] [info] Support for floats 1.23
[2025-01-17 16:05:58.130] [info] Positional args are supported too..
[2025-01-17 16:05:58.130] [info] left aligned
[2025-01-17 16:05:58.130] [debug] This message should be displayed..
[16:05:58 +08:00] [] [---D---] [thread 6001] Some debug message
Create stdout/stderr logger object
#include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_color_sinks.h"
void stdout_example()
{
// 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)");
}
Basic file logger
#include "spdlog/sinks/basic_file_sink.h"
void basic_logfile_example()
{
try
{
auto logger = spdlog::basic_logger_mt("basic_logger", "logs/basic-log.txt");
}
catch (const spdlog::spdlog_ex &ex)
{
std::cout << "Log init failed: " << ex.what() << std::endl;
}
}
Rotating files
#include "spdlog/sinks/rotating_file_sink.h"
void rotating_example()
{
// 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);
}
Daily files
#include "spdlog/sinks/daily_file_sink.h"
void daily_example()
{
// 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);
}
Backtrace support
// 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)..
Periodic flush
// periodically flush all *registered* loggers every 3 seconds:
// warning: only use if all your loggers are thread-safe ("_mt" loggers)
spdlog::flush_every(std::chrono::seconds(3));
Stopwatch
// Stopwatch support for spdlog
#include "spdlog/stopwatch.h"
void stopwatch_example()
{
spdlog::stopwatch sw;
spdlog::debug("Elapsed {}", sw);
spdlog::debug("Elapsed {:.3}", sw);
}
Log binary data in hex
// many types of std::container<char> types can be used.
// ranges are supported too.
// format flags:
// {:X} - print in uppercase.
// {:s} - don't separate each byte with space.
// {:p} - don't print the position on each line start.
// {:n} - don't split the output into lines.
// {:a} - show ASCII if :n is not set.
#include "spdlog/fmt/bin_to_hex.h"
void binary_example()
{
auto console = spdlog::get("console");
std::array<char, 80> buf;
console->info("Binary example: {}", spdlog::to_hex(buf));
console->info("Another binary example:{:n}", spdlog::to_hex(std::begin(buf), std::begin(buf) + 10));
// more examples:
// logger->info("uppercase: {:X}", spdlog::to_hex(buf));
// logger->info("uppercase, no delimiters: {:Xs}", spdlog::to_hex(buf));
// logger->info("uppercase, no delimiters, no position info: {:Xsp}", spdlog::to_hex(buf));
}
Logger with multi sinks - each with a different format and log level
// 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");
}
User-defined callbacks about log events
// create a logger with a lambda function callback, the callback will be called
// each time something is logged to the logger
void callback_example()
{
auto callback_sink = std::make_shared<spdlog::sinks::callback_sink_mt>([](const spdlog::details::log_msg &msg) {
// for example you can be notified by sending an email to yourself
});
callback_sink->set_level(spdlog::level::err);
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
spdlog::logger logger("custom_callback_logger", {console_sink, callback_sink});
logger.info("some info log");
logger.error("critical issue"); // will notify you
}
Asynchronous logging
#include "spdlog/async.h"
#include "spdlog/sinks/basic_file_sink.h"
void async_example()
{
// default thread pool settings can be modified *before* creating the async logger:
// spdlog::init_thread_pool(8192, 1); // queue with 8k items and 1 backing thread.
auto async_file = spdlog::basic_logger_mt<spdlog::async_factory>("async_file_logger", "logs/async_log.txt");
// alternatively:
// auto async_file = spdlog::create_async<spdlog::sinks::basic_file_sink_mt>("async_file_logger", "logs/async_log.txt");
}
Asynchronous logger with multi sinks
#include "spdlog/async.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/sinks/rotating_file_sink.h"
void multi_sink_example2()
{
spdlog::init_thread_pool(8192, 1);
auto stdout_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt >();
auto rotating_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>("mylog.txt", 1024*1024*10, 3);
std::vector<spdlog::sink_ptr> sinks {stdout_sink, rotating_sink};
auto logger = std::make_shared<spdlog::async_logger>("loggername", sinks.begin(), sinks.end(), spdlog::thread_pool(), spdlog::async_overflow_policy::block);
spdlog::register_logger(logger);
}
User-defined types
template<>
struct fmt::formatter<my_type> : fmt::formatter<std::string>
{
auto format(my_type my, format_context &ctx) const -> decltype(ctx.out())
{
return fmt::format_to(ctx.out(), "[my_type i={}]", my.i);
}
};
void user_defined_example()
{
spdlog::info("user defined type: {}", my_type(14));
}
User-defined flags in the log pattern
// Log patterns can contain custom flags.
// the following example will add new flag '%*' - which will be bound to a <my_formatter_flag> instance.
#include "spdlog/pattern_formatter.h"
class my_formatter_flag : public spdlog::custom_flag_formatter
{
public:
void format(const spdlog::details::log_msg &, const std::tm &, spdlog::memory_buf_t &dest) override
{
std::string some_txt = "custom-flag";
dest.append(some_txt.data(), some_txt.data() + some_txt.size());
}
std::unique_ptr<custom_flag_formatter> clone() const override
{
return spdlog::details::make_unique<my_formatter_flag>();
}
};
void custom_flags_example()
{
auto formatter = std::make_unique<spdlog::pattern_formatter>();
formatter->add_flag<my_formatter_flag>('*').set_pattern("[%n] [%*] [%^%l%$] %v");
spdlog::set_formatter(std::move(formatter));
}
Custom error handler
void err_handler_example()
{
// can be set globally or per logger(logger->set_error_handler(..))
spdlog::set_error_handler([](const std::string &msg) { spdlog::get("console")->error("*** LOGGER ERROR ***: {}", msg); });
spdlog::get("console")->info("some invalid message to trigger an error {}{}{}{}", 3);
}
syslog
#include "spdlog/sinks/syslog_sink.h"
void syslog_example()
{
std::string ident = "spdlog-example";
auto syslog_logger = spdlog::syslog_logger_mt("syslog", ident, LOG_PID);
syslog_logger->warn("This is warning that will end up in syslog.");
}
Android example
#include "spdlog/sinks/android_sink.h"
void android_example()
{
std::string tag = "spdlog-android";
auto android_logger = spdlog::android_logger_mt("android", tag);
android_logger->critical("Use \"adb shell logcat\" to view this message.");
}
Load log levels from the env variable or argv
#include "spdlog/cfg/env.h"
int main (int argc, char *argv[])
{
spdlog::cfg::load_env_levels();
// or from the command line:
// ./example SPDLOG_LEVEL=info,mylogger=trace
// #include "spdlog/cfg/argv.h" // for loading levels from argv
// spdlog::cfg::load_argv_levels(argc, argv);
}
$ export SPDLOG_LEVEL=info,mylogger=trace
$ ./example
Log file open/close event handlers
// You can get callbacks from spdlog before/after a log file has been opened or closed.
// This is useful for cleanup procedures or for adding something to the start/end of the log file.
void file_events_example()
{
// pass the spdlog::file_event_handlers to file sinks for open/close log file notifications
spdlog::file_event_handlers handlers;
handlers.before_open = [](spdlog::filename_t filename) { spdlog::info("Before opening {}", filename); };
handlers.after_open = [](spdlog::filename_t filename, std::FILE *fstream) { fputs("After opening\n", fstream); };
handlers.before_close = [](spdlog::filename_t filename, std::FILE *fstream) { fputs("Before closing\n", fstream); };
handlers.after_close = [](spdlog::filename_t filename) { spdlog::info("After closing {}", filename); };
auto my_logger = spdlog::basic_logger_st("some_logger", "logs/events-sample.txt", true, handlers);
}
Replace the Default Logger
void replace_default_logger_example()
{
auto new_logger = spdlog::basic_logger_mt("new_default_logger", "logs/new-default-log.txt", true);
spdlog::set_default_logger(new_logger);
spdlog::info("new logger log message");
}
Log to Qt with nice colors
#include "spdlog/spdlog.h"
#include "spdlog/sinks/qt_sinks.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
setMinimumSize(640, 480);
auto log_widget = new QTextEdit(this);
setCentralWidget(log_widget);
int max_lines = 500; // keep the text widget to max 500 lines. remove old lines if needed.
auto logger = spdlog::qt_color_logger_mt("qt_logger", log_widget, max_lines);
logger->info("Some info message");
}
Mapped Diagnostic Context
// Mapped Diagnostic Context (MDC) is a map that stores key-value pairs (string values) in thread local storage.
// Each thread maintains its own MDC, which loggers use to append diagnostic information to log outputs.
// Note: it is not supported in asynchronous mode due to its reliance on thread-local storage.
#include "spdlog/mdc.h"
void mdc_example()
{
spdlog::mdc::put("key1", "value1");
spdlog::mdc::put("key2", "value2");
// if not using the default format, use the %& formatter to print mdc data
// spdlog::set_pattern("[%H:%M:%S %z] [%^%L%$] [%&] %v");
}