| 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 | |