兼具崩溃堆栈分析性能检测的日志系统
本文深入解析了一个工业级C++高性能日志系统的设计与实现。该系统采用分层架构设计,包含编译期开关、RAII性能分析器、线程安全累积统计、多文件日志管理等核心模块。通过spdlog实现异步日志和多Sink组合,采用智能日志宏设计实现条件编译和防重复日志。系统特别注重性能优化、模块化控制和崩溃处理机制,支持代码级耗时统计和内存追踪,为PDF处理等复杂应用提供全面的日志和性能分析能力。实际应用中可精确定
深入解析高性能日志系统设计与实现
引言
在大型软件开发中,日志系统往往被当作"辅助工具"而轻视,直到线上问题出现、性能瓶颈难以定位、崩溃现场无法复现时,我们才会意识到一个完善的日志系统有多么重要。本文将深入解析一个核心库设计的工业级C++日志系统,它不仅提供了基础的日志记录功能,还集成了性能分析、崩溃处理、模块化控制等特性。
一、设计理念与架构概述
1.1 核心设计目标
这个日志系统的设计围绕以下几个核心理念展开:
- 性能至上:采用异步日志、编译期优化、阈值过滤等多重手段
- 模块化控制:支持按模块独立开关日志,便于生产环境部署
- 可观测性:内置性能分析工具,支持代码级耗时统计
- 健壮性:完善的崩溃处理机制,确保关键信息不丢失
- 易用性:简洁的宏接口,RAII风格的设计模式
1.2 整体架构
系统采用分层架构设计,从上到下依次是:
┌─────────────────────────────────────┐
│ 应用层(宏接口) │
│ PDFCORE_INFO / FONTMANAGER_DEBUG │
├─────────────────────────────────────┤
│ 工具层(性能分析) │
│ ScopeProfiler / AccumulativeProfiler│
├─────────────────────────────────────┤
│ 核心层(日志管理器) │
│ LoggerManager (单例) │
├─────────────────────────────────────┤
│ 底层(spdlog + 崩溃处理) │
│ 异步Sink / 信号处理 / SEH处理 │
└─────────────────────────────────────┘
二、核心模块详解
2.1 编译期开关设计
// 编译期开关
#define ENABLE_PDFCORE_DEBUG 0
#define ENABLE_FONTMANAGER_DEBUG 0
#define ENABLE_COMPRESSION_DEBUG 0
#define ENABLE_IMAGE_DEBUG 0
这种设计有两大优势:
-
零开销抽象:当开关关闭时,所有日志宏展开为空,编译器可以完全优化掉相关代码,不会产生任何运行时开销。
-
细粒度控制:可以为PDF解析核心、字体管理、压缩算法、图像处理等不同模块独立开关日志,便于在调试特定模块时减少日志干扰。
2.2 RAII风格的作用域性能分析器
class ScopeProfiler {
// 构造函数记录开始时间
// 析构函数自动计算并记录耗时
};
这是一个典型的RAII应用:
- 自动计时:对象创建时开始计时,销毁时自动结束并记录
- 阈值过滤:可以设置时间阈值,只有超过阈值的操作才被记录
- 无侵入性:只需要在代码块开始处声明对象即可
使用示例:
void process_pdf_page() {
PDFCORE_PROFILE_SCOPE(PageProcessing);
// 处理PDF页面的代码...
} // 离开作用域时自动输出执行时间
2.3 线程安全的累积性能分析器
class AccumulativeProfiler {
std::atomic<double> total_;
std::atomic<uint64_t> calls_;
};
这个类解决了一个常见痛点:统计多次调用的性能数据。比如需要统计"解析1000个PDF页面平均耗时"时,可以:
PDFCORE_PROFILE_ACC_DECLARE(PageParse);
void parse_page() {
PDFCORE_PROFILE_ACC_START(PageParse);
// 解析逻辑...
PDFCORE_PROFILE_ACC_END(PageParse);
}
void report() {
PDFCORE_PROFILE_ACC_REPORT(PageParse, LogLevel::INFO);
}
2.4 多文件日志管理器
LoggerManager采用单例模式,初始化时会创建四个独立的日志文件:
| 日志文件 | 记录级别 | 用途 |
|---|---|---|
| full.log | TRACE ~ CRITICAL | 完整调试信息 |
| error.log | ERROR, CRITICAL | 错误监控 |
| warn.log | WARN及以上 | 警告预警 |
| perf.log | INFO | 性能数据 |
这种分离设计的优势:
- 快速定位问题:查找错误时不需要在庞大的日志文件中翻找
- 磁盘空间优化:错误日志通常较小,可以保留更长时间
- 监控系统集成:可以单独监控error.log文件
2.5 跨平台崩溃处理机制
崩溃处理是生产环境系统的必备功能。这个实现同时支持三种崩溃类型:
class CrashHandler {
// 1. 未捕获的C++异常
std::set_terminate()
// 2. 致命信号(Linux/macOS)
std::signal(SIGSEGV) // 段错误
std::signal(SIGABRT) // 异常终止
std::signal(SIGFPE) // 浮点异常
std::signal(SIGILL) // 非法指令
// 3. Windows结构化异常(SEH)
SetUnhandledExceptionFilter()
};
当程序崩溃时,系统会:
- 记录崩溃原因和详细信息
- 输出完整的调用堆栈(使用Boost.Stacktrace)
- 刷新所有日志缓冲区
- 优雅退出
三、spdlog的高级应用
3.1 异步日志优化
spdlog::init_thread_pool(8192, 2);
- 8192:队列大小,可根据日志量调整
- 2:后台线程数,平衡CPU核心利用
异步日志将日志写入操作转移到后台线程,主线程只需将日志消息放入队列,大幅降低了日志对业务逻辑的性能影响。
3.2 多Sink组合设计
auto logger = std::make_shared<spdlog::async_logger>(
"pdfcore",
spdlog::sinks_init_list{
console_sink, // 控制台输出
full_sink, // 完整日志
error_sink, // 错误日志
warn_sink, // 警告日志
perf_sink // 性能日志
},
// ...
);
每个sink可以独立设置:
- 日志格式:full日志包含文件名、行号、线程ID,error日志保持简洁
- 日志级别:error sink只记录ERROR及以上级别
- 输出目标:控制台、文件、网络等
四、智能的日志宏设计
4.1 条件编译宏
#if ENABLE_PDFCORE_DEBUG
#define PDFCORE_INFO(...) SPDLOG_LOGGER_CALL(...)
#else
#define PDFCORE_INFO(...)
#endif
这种设计确保:
- 生产环境:所有日志宏展开为空,零开销
- 开发环境:通过修改头文件中的宏定义即可开启日志
4.2 防重复日志宏
#define PDFCORE_LOG_ONCE(lv, ...) \
do { static bool _once=false; \
if(!_once){_once=true; PDFCORE_LOG(lv,__VA_ARGS__);} } while(0)
#define PDFCORE_LOG_EVERY_N(n, lv, ...) \
do { static std::atomic<uint32_t> _c{0}; \
if((_c.fetch_add(1)%(n))==0) PDFCORE_LOG(lv,__VA_ARGS__);} while(0)
这两个宏解决了高频日志的痛点:
- LOG_ONCE:初始化信息、警告提示等只需记录一次的场景
- LOG_EVERY_N:循环中每N次记录一次,既保留信息又避免刷屏
五、实际应用场景
5.1 性能瓶颈定位
void render_complex_page() {
PDFCORE_PROFILE_SCOPE(RenderPage);
PDFCORE_PROFILE_SCOPE(ParseContent);
parse_page_content();
PDFCORE_PROFILE_SCOPE(RenderGraphics);
render_vector_graphics();
PDFCORE_PROFILE_SCOPE(RenderText);
render_text_elements();
}
通过嵌套的作用域分析,可以精确定位哪个环节最耗时。
5.2 内存问题排查
void process_large_image() {
IMAGE_DEBUG("Processing image: {}x{}, {} bytes",
width, height, file_size);
auto* buffer = allocate_memory(size);
IMAGE_TRACE("Buffer allocated at: {:p}, size: {}",
(void*)buffer, size);
// 处理图像...
free(buffer);
IMAGE_TRACE("Buffer freed");
}
开启IMAGE_DEBUG时,可以追踪内存分配和释放,帮助排查内存泄漏。
5.3 线上问题诊断
try {
parse_pdf_file(filename);
} catch (const std::exception& e) {
PDFCORE_ERROR("Failed to parse PDF: {}, error: {}",
filename, e.what());
// 记录额外上下文信息
PDFCORE_LOG_ONCE(LogLevel::WARN,
"This may be caused by corrupted file");
}
六、性能优化技巧
6.1 编译期优化
// 取消不需要的元数据
#define SPDLOG_NO_THREAD_ID
#define SPDLOG_NO_DATETIME
在不需要线程ID和时间戳的场景,可以通过这些宏减少日志格式化的开销。
6.2 阈值过滤
class ScopeProfiler {
void log_if_needed() {
if (threshold_ > 0.0 && elapsed < threshold_)
return; // 低于阈值不记录
// 记录日志...
}
};
避免记录大量短时间操作,只关注真正耗时的性能瓶颈。
6.3 异步刷新策略
// 错误级别立即刷新
spdlog::flush_on(spdlog::level::err);
// 定期刷新(可选)
std::thread flusher([]{
while(running) {
std::this_thread::sleep_for(1s);
spdlog::default_logger()->flush();
}
});
错误日志立即刷新确保不丢失关键信息,普通日志批量写入提高性能。
七、最佳实践建议
7.1 日志级别使用指南
| 级别 | 使用场景 | 示例 |
|---|---|---|
| TRACE | 最详细的执行流程 | 函数进入/退出、变量值变化 |
| DEBUG | 调试信息 | 算法中间结果、数据结构状态 |
| INFO | 关键业务流程 | 文件打开、处理完成 |
| WARN | 潜在问题 | 兼容性警告、降级处理 |
| ERROR | 可恢复错误 | 解析失败、资源不足 |
| CRITICAL | 致命错误 | 内存损坏、不可恢复异常 |
7.2 生产环境配置建议
// 生产环境通常关闭DEBUG日志
#define ENABLE_PDFCORE_DEBUG 0
// 但保留WARN和ERROR级别
LoggerManager::instance().init_async_file(
"logs/pdfcore.log",
10 * 1024 * 1024, // 10MB
5, // 保留5个文件
LogLevel::WARN // 只记录WARN及以上
);
7.3 性能敏感代码处理
void high_frequency_function() {
// 避免在高频函数中记录INFO日志
// PDFCORE_INFO("Enter function"); // 不要这样做
// 使用EVERY_N宏控制频率
PDFCORE_LOG_EVERY_N(1000, LogLevel::DEBUG,
"Processed {} items", count);
// 或者使用ONCE宏
PDFCORE_LOG_ONCE(LogLevel::INFO,
"First call initialization");
}
八、总结与展望
这个日志系统的设计充分体现了工业级C++项目的工程智慧:通过编译期开关实现零开销抽象,通过RAII简化性能分析,通过多文件分离提高可观测性,通过崩溃处理增强健壮性。
在实际应用中,这样的日志系统能够显著提升开发和运维效率:
- 开发阶段:快速定位问题,精确测量性能
- 测试阶段:全面记录执行轨迹,辅助自动化分析
- 生产环境:故障可追溯,性能可监控,崩溃可还原
未来可以考虑的扩展方向:
- 支持日志采样,进一步降低性能开销
- 集成分布式追踪ID,便于微服务架构
- 提供日志分析工具,自动检测异常模式
- 支持动态开关日志,无需重启服务
日志系统就像飞机的黑匣子,平时默默无闻,关键时刻却能提供无可替代的价值。希望本文的分析能帮助读者在自己的项目中构建更好的日志系统。
/*
* Copyright (c) 2025 Sylar Ding
*
* All rights reserved.
*
* This source file is part of a core runtime logging and profiling system.
* Unauthorized copying, modification, or distribution of this file,
* via any medium, is strictly prohibited without prior written permission.
*/
// 防止头文件被重复包含
#pragma once
// =====================================================
// 编译期开关
// 这些宏定义允许在编译时按模块开启或关闭调试日志功能
// =====================================================
// 控制PDF核心模块的调试日志
#define ENABLE_PDFCORE_DEBUG 0
// 控制字体管理模块的调试日志
#define ENABLE_FONTMANAGER_DEBUG 0
// 控制压缩模块的调试日志
#define ENABLE_COMPRESSION_DEBUG 0
// 控制图像处理模块的调试日志
#define ENABLE_IMAGE_DEBUG 0
// spdlog 编译期优化(可按需启用)
// 以下宏定义可以优化spdlog的性能和输出格式
// #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO
// #define SPDLOG_NO_THREAD_ID
// #define SPDLOG_NO_DATETIME
// =====================================================
// 包含spdlog日志库的相关头文件
#include <spdlog/spdlog.h>
#include <spdlog/async.h>
#include <spdlog/stopwatch.h>
#include <spdlog/sinks/rotating_file_sink.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/sinks/daily_file_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
// 包含标准库头文件
#include <atomic>
#include <chrono>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include <boost/stacktrace.hpp>
#include <csignal> // 必须添加此头文件以支持 std::signal 和 SIGSEGV 等宏
#ifdef _WIN32
#include <windows.h>
#include <direct.h> // 用于 _mkdir
#else
#include <sys/stat.h> // 用于 mkdir
#include <sys/types.h>
#endif
// =====================================================
// PDF核心库的日志命名空间
namespace pdfcore::logging {
// =====================================================
// 日志级别枚举
// 包装spdlog的日志级别,避免Windows平台ERROR宏定义冲突
// =====================================================
enum class LogLevel : int {
TRACE = SPDLOG_LEVEL_TRACE, // 最详细的跟踪信息
DEBUG = SPDLOG_LEVEL_DEBUG, // 调试信息
INFO = SPDLOG_LEVEL_INFO, // 一般信息
WARN = SPDLOG_LEVEL_WARN, // 警告信息
ERROR_ = SPDLOG_LEVEL_ERROR, // 错误信息(注意下划线避免冲突)
CRITICAL = SPDLOG_LEVEL_CRITICAL, // 严重错误
OFF = SPDLOG_LEVEL_OFF // 关闭所有日志
};
// =====================================================
// RAII作用域性能分析器
// 自动记录代码块的执行时间并在析构时输出
// =====================================================
class ScopeProfiler {
public:
// 构造函数,记录开始时间
ScopeProfiler(std::string tag,
LogLevel level = LogLevel::DEBUG,
double threshold_sec = 0.0)
: tag_(std::move(tag)),
level_(level),
threshold_(threshold_sec),
start_(clock::now()) {
}
// 析构函数,自动记录执行时间
~ScopeProfiler() { log_if_needed(); }
// 手动停止并记录
void stop_and_log() {
log_if_needed();
stopped_ = true;
}
private:
using clock = std::chrono::high_resolution_clock; // 使用高精度时钟
// 根据阈值判断是否需要记录日志
void log_if_needed() {
if (stopped_) return; // 如果已手动停止,则不重复记录
// 计算从开始到现在的耗时
const double elapsed =
std::chrono::duration<double>(clock::now() - start_).count();
// 如果设置了时间阈值且耗时未超过阈值,则不记录
if (threshold_ > 0.0 && elapsed < threshold_)
return;
// 调用spdlog记录性能信息
SPDLOG_LOGGER_CALL(
spdlog::default_logger(),
static_cast<spdlog::level::level_enum>(level_),
"[PERF] [{}] {:.6f}s", tag_, elapsed);
}
// 成员变量
std::string tag_; // 性能分析标签
LogLevel level_; // 日志级别
double threshold_; // 时间阈值(秒),超过此阈值才记录
clock::time_point start_; // 开始时间点
bool stopped_{ false }; // 是否已手动停止
};
// =====================================================
// 累积性能分析器(线程安全)
// 用于统计多次调用的总耗时和平均耗时
// =====================================================
class AccumulativeProfiler {
public:
// 构造函数
explicit AccumulativeProfiler(std::string tag)
: tag_(std::move(tag)) {
}
// 开始计时
void start() {
start_ = clock::now();
}
// 停止计时并累积数据
void stop() {
// 计算单次耗时
const double elapsed =
std::chrono::duration<double>(clock::now() - start_).count();
// 线程安全地更新累积数据
std::lock_guard<std::mutex> lock(mutex_);
total_ += elapsed;
calls_++;
}
// 重置统计信息
void reset() {
std::lock_guard<std::mutex> lock(mutex_);
total_ = 0.0;
calls_ = 0;
}
// 输出统计报告
void report(LogLevel level = LogLevel::INFO) const {
std::lock_guard<std::mutex> lock(mutex_);
if (calls_ == 0) return; // 没有调用记录时不输出
// 记录累积性能信息
SPDLOG_LOGGER_CALL(
spdlog::default_logger(),
static_cast<spdlog::level::level_enum>(level),
"[PERF-ACC] [{}] calls={}, total={:.3f}s avg={:.3f}s",
tag_, calls_, total_, total_ / calls_);
}
private:
using clock = std::chrono::high_resolution_clock; // 高精度时钟
// 成员变量
std::string tag_; // 分析器标签
mutable std::mutex mutex_; // 保护统计数据的互斥锁
double total_{ 0.0 }; // 总耗时
uint64_t calls_{ 0 }; // 调用次数
clock::time_point start_; // 单次计时的开始时间
};
// =====================================================
// 日志管理器
// 单例类,负责初始化和管理spdlog日志系统
// =====================================================
class LoggerManager {
public:
// 获取单例实例
static LoggerManager& instance() {
static LoggerManager inst;
return inst;
}
// =====================================================
// 初始化异步文件日志(多文件版本)
// 自动创建完整日志、错误日志、警告日志和性能日志
// =====================================================
bool init_async_file(const std::string& file,
size_t max_size = 5 * 1024 * 1024, // 单个日志文件最大5MB
size_t max_files = 3, // 最多保留3个日志文件
LogLevel level = LogLevel::DEBUG) { // 默认日志级别
try {
// 初始化异步线程池
spdlog::init_thread_pool(8192, 2);
// 创建控制台sink
auto console_sink =
std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
console_sink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] %v");
console_sink->set_level(spdlog::level::trace);
// 解析基础文件名和路径
std::string base_path = file;
std::string ext = ".log";
size_t dot_pos = file.find_last_of('.');
if (dot_pos != std::string::npos) {
base_path = file.substr(0, dot_pos);
ext = file.substr(dot_pos);
}
// 确保日志目录存在
std::string dir_path = extract_directory(base_path);
if (!dir_path.empty()) {
create_directory(dir_path);
}
// =====================================================
// 1. 完整日志sink - 记录所有级别(包含详细信息)
// =====================================================
auto full_sink = std::make_shared<
spdlog::sinks::rotating_file_sink_mt>(
base_path + "_full" + ext, max_size, max_files);
// 设置完整日志的格式(包含文件名、行号、线程ID)
full_sink->set_pattern(
"[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] [%s:%#] [t:%t] %v");
full_sink->set_level(spdlog::level::trace);
// =====================================================
// 2. 错误日志sink - 只记录ERROR和CRITICAL
// =====================================================
auto error_sink = std::make_shared<
spdlog::sinks::rotating_file_sink_mt>(
base_path + "_error" + ext, max_size, max_files);
// 设置错误日志格式(简洁版本)
error_sink->set_pattern(
"[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] %v");
error_sink->set_level(spdlog::level::err); // 只记录err及以上
// =====================================================
// 3. 警告日志sink - 只记录WARN和以上级别
// =====================================================
auto warn_sink = std::make_shared<
spdlog::sinks::rotating_file_sink_mt>(
base_path + "_warn" + ext, max_size, max_files);
// 设置警告日志格式(简洁版本)
warn_sink->set_pattern(
"[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] %v");
warn_sink->set_level(spdlog::level::warn); // 记录warn及以上
// =====================================================
// 4. 性能日志sink - 单独记录性能数据
// =====================================================
auto perf_sink = std::make_shared<
spdlog::sinks::rotating_file_sink_mt>(
base_path + "_perf" + ext, max_size, max_files);
perf_sink->set_pattern(
"[%Y-%m-%d %H:%M:%S.%e] %v");
perf_sink->set_level(spdlog::level::info);
// 创建异步logger,组合所有sink
auto logger = std::make_shared<spdlog::async_logger>(
"pdfcore",
spdlog::sinks_init_list{
console_sink, // 控制台输出
full_sink, // 完整日志文件
error_sink, // 错误日志文件
warn_sink, // 警告日志文件
perf_sink // 性能日志文件
},
spdlog::thread_pool(),
spdlog::async_overflow_policy::block);
// 设置logger的全局级别(控制哪些级别的日志会被处理)
logger->set_level(
static_cast<spdlog::level::level_enum>(level));
// 设置为默认logger
spdlog::set_default_logger(logger);
// 设置错误级别日志立即刷新
spdlog::flush_on(spdlog::level::err);
// 安装崩溃处理
CrashHandler::install();
initialized_ = true;
// 记录初始化成功的日志
logger->info("==========================================");
logger->info("Logger initialized successfully");
logger->info("Log files:");
logger->info(" - Full log: {}{}", base_path, "_full" + ext);
logger->info(" - Error log: {}{}", base_path, "_error" + ext);
logger->info(" - Warning log: {}{}", base_path, "_warn" + ext);
logger->info(" - Performance log: {}{}", base_path, "_perf" + ext);
logger->info("Max file size: {} MB, Max files: {}", max_size / (1024 * 1024), max_files);
logger->info("==========================================");
return true;
}
catch (const std::exception& e) {
// 初始化失败时回退到基本控制台logger
try {
auto fallback = spdlog::stdout_color_mt("fallback");
fallback->error("Failed to initialize logger: {}", e.what());
spdlog::set_default_logger(fallback);
}
catch (...) {
// 最后的回退方案
}
return false;
}
}
// =====================================================
// 设置全局日志级别
// =====================================================
void set_level(LogLevel level) {
spdlog::set_level(
static_cast<spdlog::level::level_enum>(level));
}
// =====================================================
// 刷新日志缓冲区
// =====================================================
void flush() {
if (auto lg = spdlog::default_logger())
lg->flush();
}
// =====================================================
// 检查是否已初始化
// =====================================================
bool is_initialized() const {
return initialized_;
}
private:
// 私有构造函数,确保单例模式
LoggerManager() = default;
// 析构时关闭spdlog
~LoggerManager() {
if (initialized_) {
spdlog::shutdown();
}
}
// =====================================================
// 辅助函数:从文件路径中提取目录
// =====================================================
std::string extract_directory(const std::string& filepath) {
size_t last_sep = filepath.find_last_of("/\\");
if (last_sep != std::string::npos) {
return filepath.substr(0, last_sep + 1);
}
return "";
}
// =====================================================
// 辅助函数:创建目录(跨平台)
// =====================================================
void create_directory(const std::string& path) {
if (path.empty()) return;
#ifdef _WIN32
// Windows平台
_mkdir(path.c_str());
#else
// Linux/Unix平台
mkdir(path.c_str(), 0755);
#endif
}
// =====================================================
// CrashHandler 崩溃处理类
// =====================================================
class CrashHandler {
public:
static void install() {
install_terminate();
install_signals();
#ifdef _WIN32
install_seh();
#endif
}
private:
// 1️⃣ 处理未捕获异常
static void install_terminate() {
std::set_terminate([]() {
try {
auto ex = std::current_exception();
if (ex) std::rethrow_exception(ex);
}
catch (const std::exception& e) {
log("Unhandled std::exception", e.what());
}
catch (...) {
log("Unknown exception", "");
}
std::_Exit(EXIT_FAILURE);
});
}
// 2️⃣ Linux/macOS 信号
static void install_signals() {
std::signal(SIGSEGV, signal_handler);
std::signal(SIGABRT, signal_handler);
std::signal(SIGFPE, signal_handler);
std::signal(SIGILL, signal_handler);
}
static void signal_handler(int sig) {
log("Fatal signal", std::to_string(sig));
std::_Exit(EXIT_FAILURE);
}
#ifdef _WIN32
// 3️⃣ Windows SEH
static void install_seh() {
SetUnhandledExceptionFilter(seh_handler);
}
static LONG WINAPI seh_handler(EXCEPTION_POINTERS*) {
log("Windows SEH exception", "");
return EXCEPTION_EXECUTE_HANDLER;
}
#endif
// 4️⃣ 输出堆栈
static void log(const std::string& reason,
const std::string& detail)
{
try {
auto logger = spdlog::default_logger();
if (!logger)
return;
logger->critical("========== CRASH DETECTED ==========");
logger->critical("Reason: {}", reason);
if (!detail.empty())
logger->critical("Detail: {}", detail);
logger->critical("Stacktrace:\n{}",
boost::stacktrace::to_string(boost::stacktrace::stacktrace()));
spdlog::apply_all([](std::shared_ptr<spdlog::logger> l) {
l->flush();
});
}
catch (...) {
// 崩溃日志记录失败时的静默处理
}
}
};
bool initialized_{ false }; // 标记是否已初始化
};
// =====================================================
// 通用日志宏定义
// 当ENABLE_PDFCORE_DEBUG开启时生效
// =====================================================
#if ENABLE_PDFCORE_DEBUG
// 基础日志宏,调用spdlog记录日志
#define PDFCORE_LOG(lv, ...) \
SPDLOG_LOGGER_CALL( \
spdlog::default_logger(), \
static_cast<spdlog::level::level_enum>(lv), \
__VA_ARGS__)
// 各级别日志的快捷宏
#define PDFCORE_TRACE(...) PDFCORE_LOG(pdfcore::logging::LogLevel::TRACE, __VA_ARGS__)
#define PDFCORE_DEBUG(...) PDFCORE_LOG(pdfcore::logging::LogLevel::DEBUG, __VA_ARGS__)
#define PDFCORE_INFO(...) PDFCORE_LOG(pdfcore::logging::LogLevel::INFO, __VA_ARGS__)
#define PDFCORE_WARN(...) PDFCORE_LOG(pdfcore::logging::LogLevel::WARN, __VA_ARGS__)
#define PDFCORE_ERROR(...) PDFCORE_LOG(pdfcore::logging::LogLevel::ERROR_,__VA_ARGS__)
#define PDFCORE_CRITICAL(...) PDFCORE_LOG(pdfcore::logging::LogLevel::CRITICAL,__VA_ARGS__)
// 只记录一次的日志宏
#define PDFCORE_LOG_ONCE(lv, ...) \
do { static bool _once=false; \
if(!_once){_once=true; PDFCORE_LOG(lv,__VA_ARGS__);} } while(0)
// 每N次记录一次的日志宏
#define PDFCORE_LOG_EVERY_N(n, lv, ...) \
do { static std::atomic<uint32_t> _c{0}; \
if((_c.fetch_add(1)%(n))==0) PDFCORE_LOG(lv,__VA_ARGS__);} while(0)
// 作用域性能分析宏
#define PDFCORE_PROFILE_SCOPE(name) \
pdfcore::logging::ScopeProfiler _prof_##name(#name)
// 带时间阈值的作用域性能分析宏
#define PDFCORE_PROFILE_SCOPE_MS(name, ms) \
pdfcore::logging::ScopeProfiler _prof_##name(#name, \
pdfcore::logging::LogLevel::DEBUG, (ms)/1000.0)
// 累积性能分析器声明宏
#define PDFCORE_PROFILE_ACC_DECLARE(name) \
static pdfcore::logging::AccumulativeProfiler _acc_##name(#name)
// 累积性能分析器操作宏
#define PDFCORE_PROFILE_ACC_START(name) _acc_##name.start()
#define PDFCORE_PROFILE_ACC_END(name) _acc_##name.stop()
#define PDFCORE_PROFILE_ACC_REPORT(name, lv) _acc_##name.report(lv)
#else
// 当PDFCORE_DEBUG关闭时,所有日志宏定义为空
#define PDFCORE_TRACE(...)
#define PDFCORE_DEBUG(...)
#define PDFCORE_INFO(...)
#define PDFCORE_WARN(...)
#define PDFCORE_ERROR(...)
#define PDFCORE_CRITICAL(...)
#define PDFCORE_LOG(...)
#define PDFCORE_LOG_ONCE(...)
#define PDFCORE_LOG_EVERY_N(...)
#define PDFCORE_PROFILE_SCOPE(...)
#define PDFCORE_PROFILE_SCOPE_MS(...)
#define PDFCORE_PROFILE_ACC_DECLARE(...)
#define PDFCORE_PROFILE_ACC_START(...)
#define PDFCORE_PROFILE_ACC_END(...)
#define PDFCORE_PROFILE_ACC_REPORT(...)
#endif
// =====================================================
// 字体管理器专用日志宏
// 当ENABLE_FONTMANAGER_DEBUG开启时生效
// =====================================================
#if ENABLE_FONTMANAGER_DEBUG
#define FONTMANAGER_INFO(...) PDFCORE_INFO("[Font] " __VA_ARGS__)
#define FONTMANAGER_DEBUG(...) PDFCORE_DEBUG("[Font] " __VA_ARGS__)
#define FONTMANAGER_WARN(...) PDFCORE_WARN ("[Font] " __VA_ARGS__)
#define FONTMANAGER_ERROR(...) PDFCORE_ERROR ("[Font] " __VA_ARGS__)
#define FONTMANAGER_PROFILE_SCOPE(name) \
PDFCORE_PROFILE_SCOPE(Font_##name)
#else
#define FONTMANAGER_INFO(...)
#define FONTMANAGER_DEBUG(...)
#define FONTMANAGER_WARN(...)
#define FONTMANAGER_ERROR(...)
#define FONTMANAGER_PROFILE_SCOPE(...)
#endif
// =====================================================
// 压缩模块专用日志宏
// 当ENABLE_COMPRESSION_DEBUG开启时生效
// =====================================================
#if ENABLE_COMPRESSION_DEBUG
#define COMPRESSION_DEBUG(...) PDFCORE_DEBUG("[Compress] " __VA_ARGS__)
#define COMPRESSION_PROFILE_SCOPE(name) \
PDFCORE_PROFILE_SCOPE(Compress_##name)
#else
#define COMPRESSION_DEBUG(...)
#define COMPRESSION_PROFILE_SCOPE(...)
#endif
// =====================================================
// 图像模块专用日志宏
// 当ENABLE_IMAGE_DEBUG开启时生效
// =====================================================
#if ENABLE_IMAGE_DEBUG
#define IMAGE_DEBUG(...) PDFCORE_DEBUG("[Image] " __VA_ARGS__)
#define IMAGE_TRACE(...) PDFCORE_TRACE("[Image] " __VA_ARGS__)
#define IMAGE_INFO(...) PDFCORE_INFO("[Image] " __VA_ARGS__)
#define IMAGE_WARN(...) PDFCORE_WARN("[Image] " __VA_ARGS__)
#define IMAGE_ERROR(...) PDFCORE_ERROR("[Image] " __VA_ARGS__)
#else
#define IMAGE_DEBUG(...)
#define IMAGE_TRACE(...)
#define IMAGE_INFO(...)
#define IMAGE_WARN(...)
#define IMAGE_ERROR(...)
#endif
} // namespace pdfcore::logging
更多推荐


所有评论(0)