-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherrorhandler.cpp
More file actions
277 lines (231 loc) · 9.89 KB
/
Copy patherrorhandler.cpp
File metadata and controls
277 lines (231 loc) · 9.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
#include "errorhandler.h"
#include <QStandardPaths> // 获取平台通用的可写目录
#include <QDir> // 目录操作(创建路径等)
#include <QApplication> // 退出应用 应用信息
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) // 仅Qt6需要显式编码转换类
#include <QStringConverter> // Qt6编码设置
#endif
#ifdef _MSC_VER
#pragma execution_character_set("utf-8") // MSVC编译时按UTF-8解释源文件
#endif
// 功能:构造函数,初始化成员并创建当日日志
ErrorHandler::ErrorHandler(QObject *parent)
: QObject{parent}
{}
// 功能:析构函数,关闭并释放日志资源
ErrorHandler::~ErrorHandler() {
if (logStream) {
// 先删流,再关文件
delete logStream;
}
if (logFile) {
logFile->close(); // 确保落盘
delete logFile;
}
}
// 功能:获取全局单例,首调时创建并初始化日志
ErrorHandler& ErrorHandler::instance(){
static ErrorHandler instance; // 首次调用创建,进程结束自动析构
return instance; // 返回引用,避免拷贝
}
void ErrorHandler::handleError(ErrorType type, ErrorLevel level, const QString &message, QWidget *parent) {
// 先写日志,context 取自错误类型
QString context = errorTypeToString(type);
logError(context,message,level);
// 再根据级别弹出消息框
QMessageBox::Icon icon;
QString title;
// 设置映射级别到 UI 图标与标题
switch (level) {
case Info:
icon = QMessageBox::Information; // 信息图标
title = "信息";
break;
case Warning:
icon = QMessageBox::Warning; // 警告图标
title = "警告";
break;
case Critical:
icon = QMessageBox::Critical; // 错误图标
title = "错误";
break;
case Fatal:
icon = QMessageBox::Critical; // 严重错误同样用 Critical 图标
title = "严重错误";
break;
default:
icon = QMessageBox::Critical; // 未知错误按照严重处理
title = "严重错误";
break;
}
// 以父窗口作为归属,避免顶层无父导致窗口乱序
QMessageBox msgBox(parent);
msgBox.setIcon(icon); // 设置图标
msgBox.setWindowTitle(title); // 设置标题
msgBox.setText(message); // 主要文本
msgBox.setStandardButtons(QMessageBox::Ok); // 仅确认按钮
msgBox.exec(); // 阻塞显示
// Fatal 级别:记录后直接请求退出应用
if (level == Fatal) {
QApplication::quit();
}
}
// 功能:仅记录日志(不弹窗),包含时间/级别/来源/内容
void ErrorHandler::logError(const QString &context, const QString &error, ErrorLevel level)
{
// 日志关闭或流未初始化时直接返回
if (!loggingEnabled || !logStream) {
return;
}
QMutexLocker locker(&logMutex); // 保护日志写入的互斥
rolloverIfNeeded_unlocked(); // 按大小判断是否滚动
reopenForToday_unlocked(); // 按日期判断是否切换
QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); // 当前时间
QString levelStr = errorLevelToString(level); // 级别文本
// 统一的日志格式:[时间][级别][来源] 内容
QString logMessage = QString("[%1] [%2] [%3] %4").arg(timestamp, levelStr, context, error);
writeToLog(logMessage); // 持锁写入
}
// 功能:设置/切换日志文件路径(追加),后续按日期/大小自动流动
void ErrorHandler::setLogFile(const QString &filename)
{
QMutexLocker locker(&logMutex); // 切换文件也需互斥保护
// 若已有流/文件,先释放
if (logStream) {
delete logStream;
logStream = nullptr;
}
if (logFile) {
logFile->close();
delete logFile;
logFile = nullptr;
}
// 尝试打开新的日志文件(追加模式)
logFile = new QFile(filename);
if (logFile->open(QIODevice::WriteOnly | QIODevice::Append)) {
logStream = new QTextStream(logFile); // 绑定文本流
currentLogPath = filename; // 记录当前路径
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
logStream->setCodec("UTF-8"); // Qt5设置编码
#else
logStream->setEncoding(QStringConverter::Utf8); // Qt6设置编码
#endif
} else { // 打开失败:释放指针并发出警告
delete logFile;
logFile = nullptr;
qWarning() << "无法打开日志文件:" << filename;
}
}
// 功能:在持锁状态下写入日志行并刷新
void ErrorHandler::writeToLog(const QString &message)
{
if (logStream) { // 仅在流可用时写入
*logStream << message << Qt::endl; // Qt::endl 刷新缓冲区
logStream->flush(); // 再显式刷新,确保立即落盘
}
}
// 功能:错误来源枚举转可读字符串
QString ErrorHandler::errorTypeToString(ErrorType type)
{
switch (type) {
case NetworkError: return "网络错误";
case ValidationError: return "验证错误";
case FileError: return "文件错误";
case ConfigError: return "配置错误";
case UnknownError:
default: return "未知错误"; // 兜底,避免空字符串
}
}
// 功能:错误级别枚举转可读字符串
QString ErrorHandler::errorLevelToString(ErrorLevel level)
{
switch (level) {
case Info: return "信息";
case Warning: return "警告";
case Critical: return "错误";
case Fatal: return "致命";
default: return "未知"; // 兜底
}
}
void ErrorHandler::rolloverIfNeeded_unlocked(qint64 maxBytes)
{
if (currentLogPath.isEmpty() || !logFile) return; // 未初始化则跳过
QFileInfo info(*logFile); // 查询当前大小
if (info.size() < maxBytes) return; // 未超阈值则不滚动
// 生成带时间戳的滚动文件名
QString timeSuffix = QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss");
QString rolled = currentLogPath;
rolled.replace(".log", QString("_%1.log").arg(timeSuffix));
logStream->flush(); // 刷新缓冲
logFile->close(); // 关闭当前文件
QFile::rename(currentLogPath, rolled); // 重命名为历史文件
// 释放旧指针
delete logStream;
delete logFile;
logStream = nullptr;
logFile = nullptr;
setLogFile(currentLogPath); // 重新创建一个同名新文件
}
// 功能:按日期切换日志(需先持锁)
void ErrorHandler::reopenForToday_unlocked()
{
if (currentLogPath.isEmpty()) return; // 未初始化则跳过
QString today = QDate::currentDate().toString("yyyyMMdd"); // 今日日期
if (currentLogPath.contains(today)) return; // 已是今日文件则不切换
QString appDataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QDir appDataDir(appDataPath);
// 如果路径以应用名称结尾,则使用父目录(与initializeLogFile逻辑一致)
QString appName = QApplication::applicationName();
if (!appName.isEmpty() && (appDataPath.endsWith("/" + appName) ||
appDataPath.endsWith("\\" + appName))) {
QDir parentDir = appDataDir;
if (parentDir.cdUp()) {
appDataPath = parentDir.absolutePath();
}
}
QDir().mkpath(appDataPath); // 确保目录存在
QString newPath = QString("%1/NDAssistantTools_%2.log").arg(appDataPath, today); // 新日期文件
logStream->flush(); // 刷新当前文件
logFile->close(); // 关闭旧文件
delete logStream; // 释放流
delete logFile; // 释放文件
logStream = nullptr;
logFile = nullptr;
setLogFile(newPath); // 打开新日期文件
}
// 功能:开启/关闭日志写盘
void ErrorHandler::setLoggingEnabled(bool enabled)
{
loggingEnabled = enabled; // 简单开关,未持锁因bool原子读写即可
}
// 功能:初始化,切换当前日志,创建目录与文件
void ErrorHandler::initializeLogFile()
{
// 日志目录:使用AppDataLocation的父目录,与QSettings配置文件在同一目录
// 如果组织名称为"NDATools",应用名称为"NDATools"
// AppDataLocation = %APPDATA%\NDATools\NDATools
// 使用父目录 = %APPDATA%\NDATools,与INI文件在同一目录
QString appDataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
QDir appDataDir(appDataPath);
QString logDir = appDataPath;
// 如果路径以应用名称结尾(如 ...\NDATools\NDATools),则使用父目录
// 这样日志文件和INI配置文件都在 %APPDATA%\NDATools 目录下
QString appName = QApplication::applicationName();
if (!appName.isEmpty() && (appDataPath.endsWith("/" + appName) ||
appDataPath.endsWith("\\" + appName))) {
QDir parentDir = appDataDir;
if (parentDir.cdUp()) { // 进入父目录
logDir = parentDir.absolutePath();
}
}
QDir().mkpath(logDir); // 确保目录存在
qDebug() << "日志文件目录:" << logDir;
// 日志文件名:按日期分片,比如:NDATools_20260101.log
QString logFileName = QString("%1/NDATools_%2.log")
.arg(logDir, QDate::currentDate().toString("yyyyMMdd"));
setLogFile(logFileName); // 打开/切换日志文件
// 写入启动标记,便于分隔进程启动
if (logStream) {
writeToLog("=== NDATools 启动 ===");
}
}