Linux高级--3.3.1 C++ spdlog 开源异步日志方案
一、基本介绍
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.txtauto 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() {// 设置全局日志级别为 debugspdlog::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" {
#endifvoid 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 debugspdlog::debug("This message should be displayed.."); // change log patternspdlog::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 loggerauto 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 filesauto 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 amauto 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 notificationsspdlog::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");
}
https://github.com/0voice
相关文章:
Linux高级--3.3.1 C++ spdlog 开源异步日志方案
一、基本介绍 spdlog 是由 Gustav S. 在 2015 年开发的一个高性能 C 日志库。开发这个库的主要目的是为了提供一个非常快速、轻量、易于使用的日志工具,特别适合需要高性能、低延迟日志记录的 C 应用程序。(由于源码现在比较难下载,我把压缩…...

电梯系统的UML文档05
Dispatcher 不控制实际的电梯组件,但它在软件系统中是重要的。每一个电梯有一个ispatcher,主要功能是计算电梯的移动方向、移动目的地以及保持门的打开时间。它和系统中除灯控制器以外的几乎所有控制对象交互。 安全装置也是一个环境对象,它…...
如何使 LLaMA-Factory 支持 google/gemma-2-2b-jpn-it 的微调
如何使 LLaMA-Factory 支持 google/gemma-2-2b-jpn-it 的微调 追加, "Gemma-2-2B-JPN-Instruct": {DownloadSource.DEFAULT: "google/gemma-2-2b-jpn-it",},修改 constants.py, vi ./src/llamafactory/extras/constants.py---"…...
MySQL中日期和时间戳的转换:字符到DATE和TIMESTAMP的相互转换
在MySQL中,经常需要在 DATE、TIMESTAMP 和字符串之间进行相互转换。以下是一些常见的转换方法: 1. 字符串到日期/时间类型 字符串转 DATE: 使用 STR_TO_DATE() 函数将字符串转换为 DATE 类型。你需要提供字符串的格式。 SELECT STR_TO_DATE(2024-08-24,…...

HarmonyOS NEXT开发进阶(十):UIAbility 组件交互
文章目录 一、前言二、启动应用内的 UIAbility三、启动应用内的UIAbility并获取返回结果四、启动其他应用的UIAbility五、启动其他应用的 UIAbility 并获取返回结果六、启动 UIAbility 的指定页面6.1 调用方 UIAbility 指定启动页面6.2 目标 UIAbility 首次启动6.3 目标UIAbili…...
深入探索Math.NET:开启高效数值计算之旅
一、引言 在当今数字化时代,数值计算已然成为科学研究、工程设计、金融分析等众多领域的核心驱动力。从探索宇宙奥秘的物理学计算,到优化建筑结构的土木工程设计,再到预测市场趋势的金融建模,数值计算的身影无处不在,…...

AI编程工具横向评测--Cloudstudio塑造完全态的jupyter notebook助力数据分析应用开发
AI编程工具横向评测–Cloudstudio塑造完全态的jupyter notebook助力数据分析应用开发 数据分析类应用的开发,指的是首先进行数据分析,比如统计学分析、机器学习模型的构建等,然后将分析的流程开发成数据分析类的工具,或者将数据分…...

【2024 CSDN博客之星】技术洞察类:从DeepSeek-V3的成功,看MoE混合专家网络对深度学习算法领域的影响(MoE代码级实战)
目录 一、引言 1.1 本篇文章侧重点 1.2 技术洞察—MoE(Mixture-of-Experts,混合专家网络) 二、MoE(Mixture-of-Experts,混合专家网络) 2.1 技术原理 2.2 技术优缺点 2.3 业务代码实践 2.3.1 业务场…...

Linux——入门基本指令汇总
目录 1. ls指令2. pwd3. whoami指令4. cd指令5. clear指令6. touch指令7. mkdir指令8. rm指令9. man指令10. cp指令11. mv指令12. cat指令13. tac指令14. more指令15. less指令16. head指令17. tail指令18. date指令19. cal指令20. find指令21. which指令22. alias指令23. grep…...

54,【4】BUUCTF WEB GYCTF2020Ezsqli
进入靶场 吓我一跳,但凡放个彭于晏我都不说啥了 提交个1看看 1 and 11 1# 还尝试了很多,不过都被过滤了,头疼 看看别人的WP 竟然要写代码去跑!!!,不会啊,先用别人的代码吧…...
【Leetcode 热题 100】45. 跳跃游戏 II
问题背景 给定一个长度为 n n n 的 0 0 0 索引 整数数组 n u m s nums nums。初始位置为 n u m s [ 0 ] nums[0] nums[0]。 每个元素 n u m s [ i ] nums[i] nums[i] 表示从索引 i i i 向前跳转的最大长度。换句话说,如果你在 n u m s [ i ] nums[i] nums[i…...

C/C++ 时间复杂度(On)
定义: 在计算机科学中,时间复杂性,又称时间复杂度,算法的时间复杂度是一个函数,它定性描述该算法的运行时间。这是一个代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低…...

【STM32-学习笔记-10-】BKP备份寄存器+时间戳
文章目录 BKP备份寄存器Ⅰ、BKP简介1. BKP的基本功能2. BKP的存储容量3. BKP的访问和操作4. BKP的应用场景5. BKP的控制寄存器 Ⅱ、BKP基本结构Ⅲ、BKP函数Ⅳ、BKP使用示例 时间戳一、Unix时间戳二、时间戳的转换(time.h函数介绍)Ⅰ、time()Ⅱ、mktime()…...
React 中hooks之 React.memo 和 useMemo用法总结
1. React.memo 基础 React.memo 是一个高阶组件(HOC),用于优化函数组件的性能,通过记忆组件渲染结果来避免不必要的重新渲染。 1.1 基本用法 const MemoizedComponent React.memo(function MyComponent(props) {/* 渲染逻辑 *…...

日志收集Day001
1.ElasticSearch 作用:日志存储和检索 2.单点部署Elasticsearch与基础配置 rpm -ivh elasticsearch-7.17.5-x86_64.rpm 查看配置文件yy /etc/elasticsearch/elasticsearch.yml(这里yy做了别名,过滤掉空行和注释行) yy /etc/el…...

机器人“大脑+小脑”范式:算力魔方赋能智能自主导航
在机器人技术的发展中,“大脑小脑”的架构模式逐渐成为推动机器人智能化的关键。其中,“大脑”作为机器人的核心决策单元,承担着复杂任务规划、环境感知和决策制定的重要角色,而“小脑”则专注于运动控制和实时调整。这种分工明确…...
python程序跑起来后,然后引用的数据文件发生了更新,python读取的数据会发生变化吗
在 Python 程序运行过程中,如果引用的数据文件被更新,程序能否读取到更新后的数据,取决于以下几个因素: 1. 是否动态读取文件 如果 Python 程序在运行过程中动态读取文件(例如通过循环或定时机制反复打开文件读取&…...

VSCode最新离线插件拓展下载方式
之前在vscode商店有以下类似的download按钮,但是2025年更新之后这个按钮就不提供了,所以需要使用新的方式下载 ps:给自己的网站推广下~~(国内直连GPT/Claude) 新的下载方式1 首先打开vscode商店官网:vscode插件下载…...
算法题目总结-栈和队列
文章目录 1.有效的括号1.答案2.思路 2.最小栈1.答案2.思路 3.前 K 个高频元素1.答案2.思路 4.用栈实现队列1.答案2.思路 5.删除字符串中的所有相邻重复项1.答案2.思路 1.有效的括号 1.答案 package com.sunxiansheng.arithmetic.day10;import java.util.Stack;/*** Descripti…...

IO进程----进程
进程 什么是进程 进程和程序的区别 概念: 程序:编译好的可执行文件 存放在磁盘上的指令和数据的有序集合(文件) 程序是静态的,没有任何执行的概念 进程:一个独立的可调度的任务 执行一个程序分配资…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...

MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...

python执行测试用例,allure报乱码且未成功生成报告
allure执行测试用例时显示乱码:‘allure’ �����ڲ����ⲿ���Ҳ���ǿ�&am…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...

在 Spring Boot 中使用 JSP
jsp? 好多年没用了。重新整一下 还费了点时间,记录一下。 项目结构: pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...
鸿蒙(HarmonyOS5)实现跳一跳小游戏
下面我将介绍如何使用鸿蒙的ArkUI框架,实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...

Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践
在 Kubernetes 集群中,如何在保障应用高可用的同时有效地管理资源,一直是运维人员和开发者关注的重点。随着微服务架构的普及,集群内各个服务的负载波动日趋明显,传统的手动扩缩容方式已无法满足实时性和弹性需求。 Cluster Auto…...

ui框架-文件列表展示
ui框架-文件列表展示 介绍 UI框架的文件列表展示组件,可以展示文件夹,支持列表展示和图标展示模式。组件提供了丰富的功能和可配置选项,适用于文件管理、文件上传等场景。 功能特性 支持列表模式和网格模式的切换展示支持文件和文件夹的层…...

倒装芯片凸点成型工艺
UBM(Under Bump Metallization)与Bump(焊球)形成工艺流程。我们可以将整张流程图分为三大阶段来理解: 🔧 一、UBM(Under Bump Metallization)工艺流程(黄色区域ÿ…...