Line data Source code
1 : #include "FangLogging.h"
2 :
3 : #include <QCoreApplication>
4 : #include <QDateTime>
5 : #include <QDir>
6 : #include <QFile>
7 : #include <QMutex>
8 : #include <QStandardPaths>
9 : #include <QTextStream>
10 :
11 : static constexpr qint64 MaxFileSize = 5 * 1024 * 1024; // 5 MB
12 : static constexpr int MaxRotatedFiles = 3;
13 : static constexpr int WriteCheckInterval = 100;
14 :
15 : static QFile logFile;
16 : static QMutex logMutex;
17 : static int writeCount = 0;
18 : static QString logFilePath;
19 : static QtMessageHandler previousHandler = nullptr;
20 :
21 0 : static QString logDir()
22 : {
23 : #if defined(Q_OS_MACOS)
24 : return QDir::homePath() + QStringLiteral("/Library/Logs/Fang");
25 : #elif defined(Q_OS_WIN)
26 : return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)
27 : + QStringLiteral("/logs");
28 : #else
29 0 : return QStandardPaths::writableLocation(QStandardPaths::StateLocation);
30 : #endif
31 : }
32 :
33 6 : static void rotateIfNeeded()
34 : {
35 6 : if (logFile.size() < MaxFileSize) {
36 2 : return;
37 : }
38 :
39 4 : logFile.close();
40 :
41 4 : const QString base = logFilePath;
42 4 : const QDir dir = QFileInfo(base).absoluteDir();
43 4 : const QString stem = QFileInfo(base).completeBaseName();
44 4 : const QString suffix = QFileInfo(base).suffix();
45 :
46 16 : for (int i = MaxRotatedFiles; i >= 1; --i) {
47 : const QString src = (i == 1)
48 12 : ? base
49 20 : : dir.filePath(QStringLiteral("%1.%2.%3").arg(stem, QString::number(i - 1), suffix));
50 12 : const QString dst = dir.filePath(QStringLiteral("%1.%2.%3").arg(stem, QString::number(i), suffix));
51 :
52 12 : QFile::remove(dst);
53 12 : QFile::rename(src, dst);
54 12 : }
55 :
56 4 : logFile.setFileName(base);
57 4 : if (!logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
58 0 : qWarning("Failed to reopen log file after rotation: %s", qPrintable(base));
59 : }
60 4 : }
61 :
62 : // Log level to string literal
63 608 : static const char *levelString(QtMsgType type)
64 : {
65 608 : switch (type) {
66 602 : case QtDebugMsg:
67 602 : return "debug";
68 1 : case QtInfoMsg:
69 1 : return "info";
70 4 : case QtWarningMsg:
71 4 : return "warning";
72 1 : case QtCriticalMsg:
73 1 : return "critical";
74 0 : case QtFatalMsg:
75 0 : return "fatal";
76 : }
77 0 : return "unknown";
78 : }
79 :
80 608 : static void fileMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
81 : {
82 608 : const QString timestamp = QDateTime::currentDateTime().toString(Qt::ISODateWithMs);
83 608 : const QString category = context.category ? QString::fromUtf8(context.category) : QString();
84 1216 : const QString line = QStringLiteral("[%1] [%2] [%3] %4\n")
85 608 : .arg(timestamp, QLatin1String(levelString(type)), category, msg);
86 :
87 : {
88 608 : QMutexLocker locker(&logMutex);
89 608 : if (logFile.isOpen()) {
90 608 : logFile.write(line.toUtf8());
91 608 : logFile.flush();
92 :
93 608 : if (++writeCount >= WriteCheckInterval) {
94 6 : writeCount = 0;
95 6 : rotateIfNeeded();
96 : }
97 : }
98 608 : }
99 :
100 608 : if (previousHandler) {
101 608 : previousHandler(type, context, msg);
102 : }
103 608 : }
104 :
105 17 : void initFileLogging(const QString &logDirOverride)
106 : {
107 17 : const QString dir = logDirOverride.isEmpty() ? logDir() : logDirOverride;
108 17 : QDir().mkpath(dir);
109 :
110 17 : logFilePath = dir + QStringLiteral("/fang.log");
111 17 : logFile.setFileName(logFilePath);
112 :
113 17 : if (!logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
114 1 : qWarning("Failed to open log file: %s", qPrintable(logFilePath));
115 1 : return;
116 : }
117 :
118 16 : previousHandler = qInstallMessageHandler(fileMessageHandler);
119 17 : }
120 :
121 19 : void shutdownFileLogging()
122 : {
123 19 : if (previousHandler) {
124 16 : qInstallMessageHandler(previousHandler);
125 16 : previousHandler = nullptr;
126 : }
127 19 : logFile.close();
128 19 : logFilePath.clear();
129 19 : writeCount = 0;
130 19 : }
131 :
132 : // Define logging categories
133 : // In debug builds, these output at QtDebugMsg level
134 : // In release builds, they're disabled except for warnings/critical
135 :
136 : #ifdef QT_DEBUG
137 22 : Q_LOGGING_CATEGORY(logApp, "fang.app", QtDebugMsg)
138 2 : Q_LOGGING_CATEGORY(logDb, "fang.db", QtDebugMsg)
139 585 : Q_LOGGING_CATEGORY(logModel, "fang.model", QtDebugMsg)
140 24 : Q_LOGGING_CATEGORY(logOperation, "fang.operation", QtDebugMsg)
141 16 : Q_LOGGING_CATEGORY(logParser, "fang.parser", QtDebugMsg)
142 35 : Q_LOGGING_CATEGORY(logNetwork, "fang.network", QtDebugMsg)
143 0 : Q_LOGGING_CATEGORY(logRewriter, "fang.rewriter", QtDebugMsg)
144 0 : Q_LOGGING_CATEGORY(logServer, "fang.server", QtDebugMsg)
145 138 : Q_LOGGING_CATEGORY(logUtility, "fang.utility", QtDebugMsg)
146 4 : Q_LOGGING_CATEGORY(logFavicon, "fang.favicon", QtDebugMsg)
147 74 : Q_LOGGING_CATEGORY(logWebPage, "fang.webpage", QtDebugMsg)
148 580 : Q_LOGGING_CATEGORY(logMock, "fang.mock", QtDebugMsg)
149 : #else
150 : Q_LOGGING_CATEGORY(logApp, "fang.app", QtWarningMsg)
151 : Q_LOGGING_CATEGORY(logDb, "fang.db", QtWarningMsg)
152 : Q_LOGGING_CATEGORY(logModel, "fang.model", QtWarningMsg)
153 : Q_LOGGING_CATEGORY(logOperation, "fang.operation", QtWarningMsg)
154 : Q_LOGGING_CATEGORY(logParser, "fang.parser", QtWarningMsg)
155 : Q_LOGGING_CATEGORY(logNetwork, "fang.network", QtWarningMsg)
156 : Q_LOGGING_CATEGORY(logRewriter, "fang.rewriter", QtWarningMsg)
157 : Q_LOGGING_CATEGORY(logServer, "fang.server", QtWarningMsg)
158 : Q_LOGGING_CATEGORY(logUtility, "fang.utility", QtWarningMsg)
159 : Q_LOGGING_CATEGORY(logFavicon, "fang.favicon", QtWarningMsg)
160 : Q_LOGGING_CATEGORY(logWebPage, "fang.webpage", QtWarningMsg)
161 : Q_LOGGING_CATEGORY(logMock, "fang.mock", QtWarningMsg)
162 : #endif
|