C++ 忘掉std::cout吧,fmt和spdlog的结合
1. 忘掉std::cout吧
我们在学习C++时,经常使用std::cout
来输出信息,如:
std::cout << "Hello World!" << std::endl;
这种方式在学习或演示时非常方便,但实际开发中,我们通常会使用一些日志库,如:spdlog等。
以下是两者的简单对比:
特性 | std::cout | spdlog |
---|---|---|
基本定位 | C++标准库的输出流 | 现代C++日志库 |
主要用途 | 简单的控制台输出 | 全功能日志记录 |
性能 | 较慢,特别是默认与C stdio同步时 | 高性能,异步日志支持,优化的格式化 |
线程安全 | 基本线程安全,但输出可能交错 | 完全线程安全,支持异步日志记录 |
日志级别 | 无内置日志级别 | 支持多种日志级别(trace, debug, info, warn, error, critical) |
格式化 | 使用流操作符和操纵符,格式控制复杂 | 支持Python风格的格式化和fmt库集成,简洁直观 |
输出目标 | 主要是控制台 | 多种输出目标(控制台、文件、系统日志、网络等) |
文件操作 | 需要额外使用std::ofstream | 内置文件日志支持,自动文件轮换 |
错误处理 | 通过流状态标志,不直观 | 异常处理和错误回调 |
内存使用 | 较轻量,但格式化可能导致临时对象创建 | 可配置的内存使用,支持内存池优化 |
编译依赖 | C++标准库的一部分,无额外依赖 | 第三方库,需要额外引入 |
配置灵活性 | 有限,主要通过流操作符 | 高度可配置(日志模式、格式、轮换策略等) |
日志轮换 | 不支持 | 支持基于大小和时间的日志轮换 |
彩色输出 | 不直接支持,需要平台特定代码 | 内置彩色控制台输出支持 |
模式匹配过滤 | 不支持 | 支持基于模式的日志过滤 |
国际化支持 | 通过std::locale支持 | 基本Unicode支持 |
适用场景 | 简单程序、教学示例、快速调试 | 生产环境、复杂应用、需要高级日志功能的系统 |
代码示例 | std::cout << "Hello " << name << std::endl; | spdlog::info("Hello {}", name); |
初始化复杂度 | 低,直接可用 | 中等,需要创建和配置日志器 |
自定义类型支持 | 通过重载<<运算符 | 通过fmt库的格式化特性 |
补充说明
何时选择 std::cout
- 简单的程序或脚本
- 教学示例和快速原型
- 不需要复杂日志功能的小型应用
- 希望减少外部依赖的项目
何时选择 spdlog
- 生产环境应用
- 需要高性能日志记录的系统
- 多线程应用
- 需要日志轮换和多目标输出的项目
- 对日志格式和级别有严格要求的场景
性能优化提示
- 对于 std::cout,可以使用 std::ios::sync_with_stdio(false) 提高性能
- 对于 spdlog,可以配置异步日志和自定义线程池以获得最佳性能
我个人建议,无论是简单的程序还是复杂的应用,都使用spdlog,因为spdlog功能强大,配置灵活,而且性能高。
1.1. spdlog
与fmt
通常 spdlog
与fmt
结合使用,fmt
是一个现代的C++格式化库,它提供了类似于Python的格式化语法,可以简化日志格式化。
1.1.1. 安装
# 通过vcpkg安装
vcpkg install spdlog fmt
1.1.2. fmt
代码示例
#include <fmt/core.h>
#include <fmt/format.h>
int main() {int year = 2025;std::string name = "Vulkan";// 基本格式化输出fmt::print("Hello, {}!\n", name);// 格式化数字fmt::print("The year is {}.\n", year);// 格式化到字符串std::string message = fmt::format("Welcome to {} cookbook, year {}.", name, year);fmt::print("{}\n", message);return 0;
}
1.1.3. spdlog
代码示例
简单使用
#include <spdlog/spdlog.h>
int main(){spdlog::info("Hello, {}!", "World");
}
按天生成日志文件
#include <spdlog/spdlog.h>
#include <spdlog/sinks/daily_file_sink.h>void daily()
{fs::create_directories("logs");auto logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 0, 0);logger->info("Hello, {}!", "Vulkan");spdlog::set_default_logger(logger);spdlog::info("The year is {}", 2025);
}
将日志输出到控制台和文件
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/daily_file_sink.h>void multi_sink()
{// 创建控制台 sinkauto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();// 创建文件 sinkauto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("log.txt", true);// 创建多 sink loggerstd::vector<spdlog::sink_ptr> sinks{console_sink, file_sink};auto logger = std::make_shared<spdlog::logger>("multi_sink", sinks.begin(), sinks.end());spdlog::set_default_logger(logger);spdlog::info("Hello, {}!", "Vulkan");spdlog::warn("This is a warning!");spdlog::error("This is an error!");spdlog::info("The year is {}", 2025);spdlog::shutdown();
}
1.1.4. CMakeLists.txt
以下是一个示例配置:
我们采用find_package(fmt CONFIG REQUIRED)
来进行配置
cmake_minimum_required(VERSION 3.10)# 设置项目名称和语言
project(VulkanChecker LANGUAGES CXX)# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)find_package(fmt CONFIG REQUIRED)
find_package(spdlog CONFIG REQUIRED)# 添加可执行文件
add_executable(main main.cpp)target_link_libraries(main PRIVATE fmt::fmt)
target_link_libraries(main PRIVATE spdlog::spdlog)# 如果是Windows系统,设置可执行文件的输出目录
if(WIN32)set_target_properties(mainPROPERTIESRUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
endif()
1.1.5. windows 下编译配置 与 运行
# 生成构建目录并配置CMake工程
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=C:/Users/Admin/workspace/vcpkg/scripts/buildsystems/vcpkg.cmake# 编译工程,若成功则运行生成的可执行文件
cmake --build build ; if ($?) { .\build\bin\Debug\main.exe | Tee-Object -FilePath output.txt}
以下是一个输出结果示例:
[2025-05-20 06:26:16.578] [daily_logger] [info] Hello, Vulkan!
[2025-05-20 06:26:16.578] [daily_logger] [info] The year is 2025
由此可以看出,spdlog
会自动按天生成日志文件,且日志文件中包含时间戳、日志等级、日志内容。