1 | #include "QLoggerWriter.h" |
2 | |
3 | #include <QDateTime> |
4 | #include <QFile> |
5 | #include <QTextStream> |
6 | #include <QDir> |
7 | #include <QDebug> |
8 | |
9 | namespace |
10 | { |
11 | /** |
12 | * @brief Converts the given level in a QString. |
13 | * @param level The log level in LogLevel format. |
14 | * @return The string with the name of the log level. |
15 | */ |
16 | QString levelToText(const QLogger::LogLevel &level) |
17 | { |
18 | switch (level) |
19 | { |
20 | case QLogger::LogLevel::Trace: |
21 | return "Trace" ; |
22 | case QLogger::LogLevel::Debug: |
23 | return "Debug" ; |
24 | case QLogger::LogLevel::Info: |
25 | return "Info" ; |
26 | case QLogger::LogLevel::Warning: |
27 | return "Warning" ; |
28 | case QLogger::LogLevel::Error: |
29 | return "Error" ; |
30 | case QLogger::LogLevel::Fatal: |
31 | return "Fatal" ; |
32 | } |
33 | |
34 | return QString(); |
35 | } |
36 | } |
37 | |
38 | namespace QLogger |
39 | { |
40 | |
41 | QLoggerWriter::QLoggerWriter(const QString &fileDestination, LogLevel level, const QString &fileFolderDestination, |
42 | LogMode mode, LogFileDisplay fileSuffixIfFull, LogMessageDisplays messageOptions) |
43 | : mFileSuffixIfFull(fileSuffixIfFull) |
44 | , mMode(mode) |
45 | , mLevel(level) |
46 | , mMessageOptions(messageOptions) |
47 | { |
48 | mFileDestinationFolder = (fileFolderDestination.isEmpty() ? QDir::currentPath() : fileFolderDestination) + "/logs/" ; |
49 | mFileDestination = mFileDestinationFolder + fileDestination; |
50 | |
51 | QDir dir(mFileDestinationFolder); |
52 | if (fileDestination.isEmpty()) |
53 | { |
54 | mFileDestination = dir.filePath(QString::fromLatin1("%1.log" ).arg( |
55 | QDateTime::currentDateTime().date().toString(QString::fromLatin1("yyyy-MM-dd" )))); |
56 | } |
57 | else if (!fileDestination.contains(QLatin1Char('.'))) |
58 | mFileDestination.append(QString::fromLatin1(".log" )); |
59 | |
60 | if (mMode == LogMode::Full || mMode == LogMode::OnlyFile) |
61 | dir.mkpath(QStringLiteral("." )); |
62 | } |
63 | |
64 | void QLoggerWriter::setLogMode(LogMode mode) |
65 | { |
66 | mMode = mode; |
67 | |
68 | if (mMode == LogMode::Full || mMode == LogMode::OnlyFile) |
69 | { |
70 | QDir dir(mFileDestinationFolder); |
71 | dir.mkpath(QStringLiteral("." )); |
72 | } |
73 | |
74 | if (mode != LogMode::Disabled && !this->isRunning()) |
75 | start(); |
76 | } |
77 | |
78 | QString QLoggerWriter::renameFileIfFull() |
79 | { |
80 | QFile file(mFileDestination); |
81 | |
82 | // Rename file if it's full |
83 | if (file.size() >= mMaxFileSize) |
84 | { |
85 | QString newName; |
86 | |
87 | const auto fileDestination = mFileDestination.left(mFileDestination.lastIndexOf('.')); |
88 | const auto fileExtension = mFileDestination.mid(mFileDestination.lastIndexOf('.') + 1); |
89 | |
90 | if (mFileSuffixIfFull == LogFileDisplay::DateTime) |
91 | { |
92 | newName |
93 | = QString("%1_%2.%3" ) |
94 | .arg(fileDestination, QDateTime::currentDateTime().toString("dd_MM_yy__hh_mm_ss" ), fileExtension); |
95 | } |
96 | else |
97 | newName = generateDuplicateFilename(fileDestination, fileExtension); |
98 | |
99 | const auto renamed = file.rename(mFileDestination, newName); |
100 | |
101 | return renamed ? newName : QString(); |
102 | } |
103 | |
104 | return QString(); |
105 | } |
106 | |
107 | QString QLoggerWriter::generateDuplicateFilename(const QString &fileDestination, const QString &fileExtension, |
108 | int fileSuffixNumber) |
109 | { |
110 | QString path(fileDestination); |
111 | if (fileSuffixNumber > 1) |
112 | path = QString("%1(%2).%3" ).arg(fileDestination, QString::number(fileSuffixNumber), fileExtension); |
113 | else |
114 | path.append(QString(".%1" ).arg(fileExtension)); |
115 | |
116 | // A name already exists, increment the number and check again |
117 | if (QFileInfo::exists(path)) |
118 | return generateDuplicateFilename(fileDestination, fileExtension, fileSuffixNumber + 1); |
119 | |
120 | // No file exists at the given location, so no need to continue |
121 | return path; |
122 | } |
123 | |
124 | void QLoggerWriter::write(QVector<QString> messages) |
125 | { |
126 | // Write data to console |
127 | if (mMode == LogMode::OnlyConsole) |
128 | { |
129 | for (const auto &message : messages) |
130 | qInfo() << message; |
131 | |
132 | return; |
133 | } |
134 | |
135 | // Write data to file |
136 | QFile file(mFileDestination); |
137 | |
138 | const auto prevFilename = renameFileIfFull(); |
139 | |
140 | if (file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Append)) |
141 | { |
142 | QTextStream out(&file); |
143 | |
144 | if (!prevFilename.isEmpty()) |
145 | out << QString("Previous log %1\n" ).arg(prevFilename); |
146 | |
147 | for (const auto &message : messages) |
148 | { |
149 | out << message; |
150 | |
151 | if (mMode == LogMode::Full) |
152 | qInfo() << message; |
153 | } |
154 | |
155 | file.close(); |
156 | } |
157 | } |
158 | |
159 | void QLoggerWriter::enqueue(const QDateTime &date, const QString &threadId, const QString &module, LogLevel level, |
160 | const QString &function, const QString &fileName, int line, const QString &message) |
161 | { |
162 | QMutexLocker locker(&mutex); |
163 | |
164 | if (mMode == LogMode::Disabled) |
165 | return; |
166 | |
167 | QString fileLine; |
168 | if (mMessageOptions.testFlag(LogMessageDisplay::File) && mMessageOptions.testFlag(LogMessageDisplay::Line) |
169 | && !fileName.isEmpty() && line > 0 && mLevel <= LogLevel::Debug) |
170 | { |
171 | fileLine = QString("{%1:%2}" ).arg(fileName, QString::number(line)); |
172 | } |
173 | else if (mMessageOptions.testFlag(LogMessageDisplay::File) && mMessageOptions.testFlag(LogMessageDisplay::Function) |
174 | && !fileName.isEmpty() && !function.isEmpty() && mLevel <= LogLevel::Debug) |
175 | { |
176 | fileLine = QString("{%1}{%2}" ).arg(fileName, function); |
177 | } |
178 | |
179 | QString text; |
180 | if (mMessageOptions.testFlag(LogMessageDisplay::Default)) |
181 | { |
182 | text = QString("[%1][%2][%3][%4]%5 %6" ) |
183 | .arg(levelToText(level), module) |
184 | .arg(date.toSecsSinceEpoch()) |
185 | .arg(threadId, fileLine, message); |
186 | } |
187 | else |
188 | { |
189 | if (mMessageOptions.testFlag(LogMessageDisplay::LogLevel)) |
190 | text.append(QString("[%1]" ).arg(levelToText(level))); |
191 | |
192 | if (mMessageOptions.testFlag(LogMessageDisplay::ModuleName)) |
193 | text.append(QString("[%1]" ).arg(module)); |
194 | |
195 | if (mMessageOptions.testFlag(LogMessageDisplay::DateTime)) |
196 | text.append(QString("[%1]" ).arg(date.toSecsSinceEpoch())); |
197 | |
198 | if (mMessageOptions.testFlag(LogMessageDisplay::ThreadId)) |
199 | text.append(QString("[%1]" ).arg(threadId)); |
200 | |
201 | if (!fileLine.isEmpty()) |
202 | { |
203 | if (fileLine.startsWith(QChar::Space)) |
204 | fileLine = fileLine.right(1); |
205 | |
206 | text.append(fileLine); |
207 | } |
208 | if (mMessageOptions.testFlag(LogMessageDisplay::Message)) |
209 | { |
210 | if (text.isEmpty() || text.endsWith(QChar::Space)) |
211 | text.append(QString("%1" ).arg(message)); |
212 | else |
213 | text.append(QString(" %1" ).arg(message)); |
214 | } |
215 | } |
216 | |
217 | text.append(QString::fromLatin1("\n" )); |
218 | |
219 | mMessages.append({ threadId, text }); |
220 | |
221 | if (!mIsStop) |
222 | mQueueNotEmpty.wakeAll(); |
223 | } |
224 | |
225 | void QLoggerWriter::run() |
226 | { |
227 | if (!mQuit) |
228 | { |
229 | QMutexLocker locker(&mutex); |
230 | mQueueNotEmpty.wait(&mutex); |
231 | } |
232 | |
233 | while (!mQuit) |
234 | { |
235 | decltype(mMessages) copy; |
236 | |
237 | { |
238 | QMutexLocker locker(&mutex); |
239 | std::swap(copy, mMessages); |
240 | } |
241 | |
242 | write(std::move(copy)); |
243 | |
244 | if (!mQuit) |
245 | { |
246 | QMutexLocker locker(&mutex); |
247 | mQueueNotEmpty.wait(&mutex); |
248 | } |
249 | } |
250 | } |
251 | |
252 | void QLoggerWriter::closeDestination() |
253 | { |
254 | QMutexLocker locker(&mutex); |
255 | mQuit = true; |
256 | mQueueNotEmpty.wakeAll(); |
257 | } |
258 | |
259 | } |
260 | |