EasyKiConverter 日志架构设计
1. 设计目标
1.1 核心需求
- 多级别日志:TRACE < DEBUG < INFO < WARN < ERROR < FATAL
- 模块分类:按功能模块过滤日志(Network、Export、UI 等)
- 多输出目标:控制台、文件、自定义回调
- 线程安全:支持多线程并发日志输出
- 性能优化:异步写入、零拷贝、条件求值
- 上下文信息:时间戳、线程 ID、源码位置、模块名
- 运行时配置:动态调整日志级别和输出目标
1.2 设计原则
- 零开销:日志禁用时无性能损耗
- 易迁移:提供兼容 Qt 日志的适配器
- 可扩展:支持自定义 Formatter、Appender
- 类型安全:使用模板避免格式化错误
2. 架构概览
┌─────────────────────────────────────────────────────────────────┐
│ 用户代码层 │
│ LOG_DEBUG(Module::Network, "Fetching data from {}", url) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Logger (门面/单例) │
│ - 管理全局配置 │
│ - 分发日志到各 Appender │
│ - 维护模块级别映射 │
└─────────────────────────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
│ ConsoleAppender │ │ FileAppender │ │ CallbackAppender │
│ - 彩色输出 │ │ - 滚动文件 │ │ - 自定义处理 │
│ - 格式化 │ │ - 异步写入 │ │ - UI 集成 │
└───────────────────┘ └───────────────────┘ └───────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Formatter (格式化器) │
│ PatternFormatter: [时间] [级别] [模块] [线程] 消息 (文件:行) │
└─────────────────────────────────────────────────────────────────┘
3. 核心组件设计
3.1 日志级别 (LogLevel)
enum class LogLevel {
Trace = 0, // 详细追踪信息(仅开发调试)
Debug = 1, // 调试信息
Info = 2, // 常规运行信息
Warn = 3, // 警告信息
Error = 4, // 错误信息
Fatal = 5, // 致命错误
Off = 6 // 关闭日志
};
3.2 模块定义 (LogModule)
// 预定义模块(支持按模块过滤)
enum class LogModule : uint32_t {
None = 0,
Core = 1 << 0, // 核心功能
Network = 1 << 1, // 网络请求
Export = 1 << 2, // 导出功能
UI = 1 << 3, // 用户界面
Parser = 1 << 4, // 数据解析
KiCad = 1 << 5, // KiCad 导出
EasyEDA = 1 << 6, // EasyEDA API
Pipeline = 1 << 7, // 流水线
Worker = 1 << 8, // 工作线程
Config = 1 << 9, // 配置管理
I18N = 1 << 10, // 国际化
All = 0xFFFFFFFF
};
// 支持位运算组合:LogModule::Network | LogModule::Export
3.3 日志记录 (LogRecord)
struct LogRecord {
LogLevel level;
LogModule module;
QString message;
QString fileName;
QString functionName;
int line;
Qt::HANDLE threadId;
qint64 timestamp; // 毫秒级时间戳
QString threadName; // 可选:线程名称
};
class IFormatter {
public:
virtual ~IFormatter() = default;
virtual QString format(const LogRecord& record) = 0;
virtual QString formatHeader() { return QString(); }
};
// 默认模式格式化器
class PatternFormatter : public IFormatter {
// 支持占位符:
// %{timestamp} - 时间戳 (2024-01-15 10:30:45.123)
// %{level} - 日志级别 (DEBUG/WARN/ERROR)
// %{module} - 模块名 (Network/Export)
// %{thread} - 线程 ID
// %{file} - 文件名
// %{line} - 行号
// %{function} - 函数名
// %{message} - 日志消息
// 默认模式: "[%{timestamp}] [%{level}] [%{module}] [%{thread}] %{message} (%{file}:%{line})"
};
3.5 输出器接口 (Appender)
class IAppender {
public:
virtual ~IAppender() = default;
virtual void append(const LogRecord& record, const QString& formatted) = 0;
virtual void flush() {}
virtual void setFormatter(QSharedPointer<IFormatter> formatter) {
m_formatter = formatter;
}
protected:
QSharedPointer<IFormatter> m_formatter;
};
// 控制台输出器(支持彩色)
class ConsoleAppender : public IAppender {
// 根据日志级别着色:Trace(灰), Debug(青), Info(白), Warn(黄), Error(红), Fatal(红底白字)
};
// 文件输出器(支持滚动)
class FileAppender : public IAppender {
// 特性:
// - 按大小滚动(默认 10MB)
// - 按时间滚动(每日/每小时)
// - 保留历史文件数量限制
// - 异步写入队列
};
// 回调输出器(用于 UI 集成)
class CallbackAppender : public IAppender {
// 支持将日志转发到 QML UI 的日志查看器
};
3.6 日志管理器 (Logger)
class Logger : public QObject {
Q_OBJECT
public:
static Logger* instance();
// 配置
void setGlobalLevel(LogLevel level);
void setModuleLevel(LogModule module, LogLevel level);
void addAppender(QSharedPointer<IAppender> appender);
void removeAppender(QSharedPointer<IAppender> appender);
// 核心日志方法
void log(LogLevel level, LogModule module, const QString& message,
const char* file = nullptr, const char* function = nullptr, int line = 0);
// 级别检查(用于条件求值优化)
bool shouldLog(LogLevel level, LogModule module) const;
// 刷新所有 Appender
void flush();
// 性能追踪
void traceBegin(const QString& operation);
void traceEnd(const QString& operation);
signals:
void logRecord(const LogRecord& record);
private:
LogLevel m_globalLevel = LogLevel::Info;
QHash<LogModule, LogLevel> m_moduleLevels;
QList<QSharedPointer<IAppender>> m_appenders;
mutable QMutex m_mutex;
// 异步写入队列
BoundedThreadSafeQueue<LogRecord> m_asyncQueue;
QThread* m_writerThread;
};
4. 宏定义
4.1 基础日志宏
// 基础宏(支持模块过滤)
#define LOG_TRACE(module, ...) EasyKiConverter::Logger::instance()->log( \
EasyKiConverter::LogLevel::Trace, module, \
EasyKiConverter::LogUtils::format(__VA_ARGS__), \
__FILE__, __FUNCTION__, __LINE__)
#define LOG_DEBUG(module, ...) EasyKiConverter::Logger::instance()->log( \
EasyKiConverter::LogLevel::Debug, module, \
EasyKiConverter::LogUtils::format(__VA_ARGS__), \
__FILE__, __FUNCTION__, __LINE__)
#define LOG_INFO(module, ...) EasyKiConverter::Logger::instance()->log( \
EasyKiConverter::LogLevel::Info, module, \
EasyKiConverter::LogUtils::format(__VA_ARGS__), \
__FILE__, __FUNCTION__, __LINE__)
#define LOG_WARN(module, ...) EasyKiConverter::Logger::instance()->log( \
EasyKiConverter::LogLevel::Warn, module, \
EasyKiConverter::LogUtils::format(__VA_ARGS__), \
__FILE__, __FUNCTION__, __LINE__)
#define LOG_ERROR(module, ...) EasyKiConverter::Logger::instance()->log( \
EasyKiConverter::LogLevel::Error, module, \
EasyKiConverter::LogUtils::format(__VA_ARGS__), \
__FILE__, __FUNCTION__, __LINE__)
#define LOG_FATAL(module, ...) EasyKiConverter::Logger::instance()->log( \
EasyKiConverter::LogLevel::Fatal, module, \
EasyKiConverter::LogUtils::format(__VA_ARGS__), \
__FILE__, __FUNCTION__, __LINE__)
4.2 条件日志宏(性能优化)
// 仅当日志级别启用时才求值参数
#define LOG_DEBUG_IF(module, condition, ...) \
do { \
if (EasyKiConverter::Logger::instance()->shouldLog( \
EasyKiConverter::LogLevel::Debug, module) && (condition)) { \
LOG_DEBUG(module, __VA_ARGS__); \
} \
} while(0)
4.3 性能追踪宏
#define LOG_TRACE_SCOPE(module, operation) \
EasyKiConverter::ScopedTracer _scopedTracer(module, operation)
#define LOG_TIME(module, operation) \
EasyKiConverter::TimeLogger _timeLogger(module, operation)
4.4 兼容 Qt 日志的适配器
// 重定向 qDebug/qWarning/qCritical 到新日志系统
void installQtLogHandler();
// 原有 qDebug() 调用会自动转换为 LOG_DEBUG()
5. 使用示例
5.1 基本使用
#include "utils/Logger.h"
using namespace EasyKiConverter;
void FetchWorker::fetchData(const QString& url) {
LOG_INFO(LogModule::Network, "Starting fetch from: {}", url);
if (url.isEmpty()) {
LOG_WARN(LogModule::Network, "Empty URL provided");
return;
}
try {
// ... 网络请求 ...
LOG_DEBUG(LogModule::Network, "Received {} bytes", data.size());
} catch (const std::exception& e) {
LOG_ERROR(LogModule::Network, "Fetch failed: {}", e.what());
}
}
5.2 性能追踪
void ExportServicePipeline::exportComponents() {
LOG_TRACE_SCOPE(LogModule::Pipeline, "exportComponents");
// 或
LOG_TIME(LogModule::Pipeline, "exportComponents");
// 函数退出时自动打印耗时
}
5.3 初始化配置
// main.cpp
void setupLogging() {
auto logger = Logger::instance();
// 设置全局级别
logger->setGlobalLevel(LogLevel::Debug);
// 设置模块级别(网络模块仅记录 Info 及以上)
logger->setModuleLevel(LogModule::Network, LogLevel::Info);
// 添加控制台输出
auto consoleAppender = QSharedPointer<ConsoleAppender>::create();
consoleAppender->setFormatter(QSharedPointer<PatternFormatter>::create(
"[%{timestamp}] [%{level}] [%{module}] %{message}"
));
logger->addAppender(consoleAppender);
// 添加文件输出
auto fileAppender = QSharedPointer<FileAppender>::create(
"logs/easykiconverter.log",
10 * 1024 * 1024, // 10MB
5 // 保留 5 个历史文件
);
logger->addAppender(fileAppender);
// 安装 Qt 日志适配器
installQtLogHandler();
}
6. 文件结构
src/utils/
├── Logger.h # 主头文件(包含宏定义)
├── Logger.cpp # Logger 实现
├── LogLevel.h # 日志级别定义
├── LogModule.h # 模块定义
├── LogRecord.h # 日志记录结构
├── formatters/
│ ├── IFormatter.h # 格式化器接口
│ └── PatternFormatter.h/cpp
└── appenders/
├── IAppender.h # 输出器接口
├── ConsoleAppender.h/cpp
├── FileAppender.h/cpp
└── CallbackAppender.h/cpp
7. 配置文件支持
// config/logging.json
{
"globalLevel": "Debug",
"moduleLevels": {
"Network": "Info",
"Export": "Debug",
"UI": "Warn"
},
"appenders": [
{
"type": "console",
"pattern": "[%{timestamp}] [%{level}] [%{module}] %{message}",
"colors": true
},
{
"type": "file",
"path": "logs/app.log",
"maxSize": 10485760,
"maxFiles": 5,
"async": true
}
]
}
8. 性能考虑
8.1 零开销原则
- 使用
shouldLog() 在参数求值前检查级别
- 编译期可完全移除特定级别的日志代码
8.2 异步写入
- 文件 Appender 使用后台线程写入
- 使用无锁队列 (BoundedThreadSafeQueue) 减少锁竞争
8.3 内存优化
- 使用 QString 的隐式共享 (Copy-on-Write)
- 限制异步队列大小防止内存溢出
9. 迁移计划
9.1 第一阶段:基础设施
- 实现核心 Logger、LogLevel、LogModule
- 实现 ConsoleAppender 和 PatternFormatter
- 提供 Qt 日志适配器
9.2 第二阶段:功能完善
- 实现 FileAppender(滚动、异步)
- 实现 CallbackAppender(UI 集成)
- 添加配置文件支持
9.3 第三阶段:迁移现有代码
- 批量替换
qDebug() → LOG_DEBUG(module, ...)
- 批量替换
qWarning() → LOG_WARN(module, ...)
- 批量替换
qCritical() → LOG_ERROR(module, ...)
9.4 第四阶段:高级功能
- 添加性能追踪 (ScopedTracer)
- 添加远程日志上传(可选)
- 添加日志查看器 UI(可选)
10. 日志级别使用指南
| 级别 |
使用场景 |
示例 |
| Trace |
详细执行流程、变量状态 |
LOG_TRACE(Network, "HTTP header: {}", header) |
| Debug |
开发调试信息 |
LOG_DEBUG(Export, "Exporting {} symbols", count) |
| Info |
正常运行状态 |
LOG_INFO(Pipeline, "Export started for {} components", count) |
| Warn |
潜在问题、降级处理 |
LOG_WARN(Parser, "Unknown layer {}, using default", id) |
| Error |
错误但可恢复 |
LOG_ERROR(Network, "Request failed: {}", error) |
| Fatal |
致命错误、程序即将退出 |
LOG_FATAL(Core, "Failed to initialize: {}", reason) |
11. 模块使用指南
| 模块 |
包含范围 |
| Core |
main.cpp、LanguageManager、核心工具 |
| Network |
NetworkUtils、FetchWorker、所有网络请求 |
| Export |
ExportService、ExportWorker、导出流程 |
| UI |
ViewModels、QML 交互 |
| Parser |
BOM 解析、JSON 解析、数据转换 |
| KiCad |
Symbol/Footprint/3D 导出器 |
| EasyEDA |
EasyEDA API 集成 |
| Pipeline |
流水线管理、Worker 协调 |
| Worker |
所有 Worker 线程 |
| Config |
ConfigService、配置管理 |
| I18N |
翻译、语言管理 |