1#include "QLoggerWriter.h"
2
3#include <QDateTime>
4#include <QFile>
5#include <QTextStream>
6#include <QDir>
7#include <QDebug>
8
9namespace
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 */
16QString 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
38namespace QLogger
39{
40
41QLoggerWriter::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
64void 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
78QString 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
107QString 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
124void 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
159void 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
225void 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
252void QLoggerWriter::closeDestination()
253{
254 QMutexLocker locker(&mutex);
255 mQuit = true;
256 mQueueNotEmpty.wakeAll();
257}
258
259}
260