File indexing completed on 2024-05-12 05:26:00
0001 #include "log.h" 0002 0003 #include <QString> 0004 #include <QDir> 0005 #include <QIODevice> 0006 #include <QCoreApplication> 0007 #include <QSettings> 0008 #include <QSharedPointer> 0009 #include <QMutex> 0010 #include <QMutexLocker> 0011 #include <QDateTime> 0012 #include <iostream> 0013 #ifdef Q_OS_WIN 0014 #include <windows.h> 0015 #include <io.h> 0016 #include <process.h> 0017 #else 0018 #include <unistd.h> 0019 #endif 0020 #include <atomic> 0021 #include <definitions.h> 0022 #include <QThreadStorage> 0023 #include <QStringBuilder> 0024 0025 using namespace Sink::Log; 0026 0027 const char *getComponentName() { return nullptr; } 0028 0029 static QThreadStorage<QSharedPointer<QSettings>> sSettings; 0030 static QSettings &config() 0031 { 0032 if (!sSettings.hasLocalData()) { 0033 sSettings.setLocalData(QSharedPointer<QSettings>::create(Sink::configLocation() + "/log.ini", QSettings::IniFormat)); 0034 } 0035 return *sSettings.localData(); 0036 } 0037 0038 Q_GLOBAL_STATIC(QByteArray, sPrimaryComponent); 0039 0040 void Sink::Log::setPrimaryComponent(const QString &component) 0041 { 0042 if (!sPrimaryComponent.isDestroyed()) { 0043 *sPrimaryComponent = component.toUtf8(); 0044 } 0045 } 0046 0047 class DebugStream : public QIODevice 0048 { 0049 public: 0050 QString m_location; 0051 DebugStream() : QIODevice() 0052 { 0053 open(WriteOnly); 0054 } 0055 ~DebugStream() override; 0056 0057 bool isSequential() const override 0058 { 0059 return true; 0060 } 0061 qint64 readData(char *, qint64) override 0062 { 0063 return 0; /* eof */ 0064 } 0065 qint64 readLineData(char *, qint64) override 0066 { 0067 return 0; /* eof */ 0068 } 0069 qint64 writeData(const char *data, qint64 len) override 0070 { 0071 #ifdef Q_OS_WIN 0072 const auto string = QString::fromUtf8(data, len); 0073 OutputDebugStringW(reinterpret_cast<const wchar_t*>(string.utf16())); 0074 #else 0075 std::cout << data << std::endl; 0076 #endif 0077 return len; 0078 } 0079 0080 private: 0081 Q_DISABLE_COPY(DebugStream) 0082 }; 0083 0084 // Virtual method anchor 0085 DebugStream::~DebugStream() 0086 { 0087 } 0088 0089 class NullStream : public QIODevice 0090 { 0091 public: 0092 NullStream() : QIODevice() 0093 { 0094 open(WriteOnly); 0095 } 0096 ~NullStream() override; 0097 0098 bool isSequential() const override 0099 { 0100 return true; 0101 } 0102 qint64 readData(char *, qint64) override 0103 { 0104 return 0; /* eof */ 0105 } 0106 qint64 readLineData(char *, qint64) override 0107 { 0108 return 0; /* eof */ 0109 } 0110 qint64 writeData(const char *data, qint64 len) override 0111 { 0112 return len; 0113 } 0114 0115 private: 0116 Q_DISABLE_COPY(NullStream) 0117 }; 0118 0119 // Virtual method anchor 0120 NullStream::~NullStream() 0121 { 0122 } 0123 0124 /* 0125 * ANSI color codes: 0126 * 0: reset colors/style 0127 * 1: bold 0128 * 4: underline 0129 * 30 - 37: black, red, green, yellow, blue, magenta, cyan, and white text 0130 * 40 - 47: black, red, green, yellow, blue, magenta, cyan, and white background 0131 */ 0132 enum ANSI_Colors 0133 { 0134 DoNothing = -1, 0135 Reset = 0, 0136 Bold = 1, 0137 Underline = 4, 0138 Black = 30, 0139 Red = 31, 0140 Green = 32, 0141 Yellow = 33, 0142 Blue = 34 0143 }; 0144 0145 static QString colorCommand(int colorCode) 0146 { 0147 return QString("\x1b[%1m").arg(colorCode); 0148 } 0149 0150 static QString colorCommand(QList<int> colorCodes) 0151 { 0152 colorCodes.removeAll(ANSI_Colors::DoNothing); 0153 if (colorCodes.isEmpty()) { 0154 return QString(); 0155 } 0156 QString string("\x1b["); 0157 for (int code : colorCodes) { 0158 string += QString("%1;").arg(code); 0159 } 0160 string.chop(1); 0161 string += "m"; 0162 return string; 0163 } 0164 0165 QByteArray Sink::Log::debugLevelName(DebugLevel debugLevel) 0166 { 0167 switch (debugLevel) { 0168 case DebugLevel::Trace: 0169 return "Trace"; 0170 case DebugLevel::Log: 0171 return "Log"; 0172 case DebugLevel::Warning: 0173 return "Warning"; 0174 case DebugLevel::Error: 0175 return "Error"; 0176 default: 0177 break; 0178 } 0179 Q_ASSERT(false); 0180 return QByteArray(); 0181 } 0182 0183 DebugLevel Sink::Log::debugLevelFromName(const QByteArray &name) 0184 { 0185 const QByteArray lowercaseName = name.toLower(); 0186 if (lowercaseName == "trace") 0187 return DebugLevel::Trace; 0188 if (lowercaseName == "log") 0189 return DebugLevel::Log; 0190 if (lowercaseName == "warning") 0191 return DebugLevel::Warning; 0192 if (lowercaseName == "error") 0193 return DebugLevel::Error; 0194 return DebugLevel::Log; 0195 } 0196 0197 void Sink::Log::setDebugOutputLevel(DebugLevel debugLevel) 0198 { 0199 config().setValue("level", debugLevel); 0200 } 0201 0202 Sink::Log::DebugLevel Sink::Log::debugOutputLevel() 0203 { 0204 return static_cast<Sink::Log::DebugLevel>(config().value("level", Sink::Log::Log).toInt()); 0205 } 0206 0207 void Sink::Log::setDebugOutputFilter(FilterType type, const QByteArrayList &filter) 0208 { 0209 switch (type) { 0210 case ApplicationName: 0211 config().setValue("applicationfilter", QVariant::fromValue(filter)); 0212 break; 0213 case Area: 0214 config().setValue("areafilter", QVariant::fromValue(filter)); 0215 break; 0216 } 0217 } 0218 0219 QByteArrayList Sink::Log::debugOutputFilter(FilterType type) 0220 { 0221 switch (type) { 0222 case ApplicationName: 0223 return config().value("applicationfilter").value<QByteArrayList>(); 0224 case Area: 0225 return config().value("areafilter").value<QByteArrayList>(); 0226 default: 0227 return QByteArrayList(); 0228 } 0229 } 0230 0231 void Sink::Log::setDebugOutputFields(const QByteArrayList &output) 0232 { 0233 config().setValue("outputfields", QVariant::fromValue(output)); 0234 } 0235 0236 QByteArrayList Sink::Log::debugOutputFields() 0237 { 0238 return config().value("outputfields").value<QByteArrayList>(); 0239 } 0240 0241 static QByteArray getProgramName() 0242 { 0243 if (QCoreApplication::instance()) { 0244 return QCoreApplication::instance()->applicationName().toLocal8Bit(); 0245 } else { 0246 return "<unknown program name>"; 0247 } 0248 } 0249 0250 static QSharedPointer<QSettings> debugAreasConfig() 0251 { 0252 return QSharedPointer<QSettings>::create(Sink::dataLocation() + "/debugAreas.ini", QSettings::IniFormat); 0253 } 0254 0255 class DebugAreaCollector { 0256 public: 0257 DebugAreaCollector() 0258 { 0259 //This call can potentially print a log message (if we fail to remove the qsettings lockfile), which would result in a deadlock if we locked over all of it. 0260 const auto areas = debugAreasConfig()->value("areas").value<QString>().split(';').toSet(); 0261 { 0262 QMutexLocker locker(&mutex); 0263 mDebugAreas = areas; 0264 } 0265 } 0266 0267 ~DebugAreaCollector() 0268 { 0269 //This call can potentially print a log message (if we fail to remove the qsettings lockfile), which would result in a deadlock if we locked over all of it. 0270 const auto areas = debugAreasConfig()->value("areas").value<QString>().split(';').toSet(); 0271 { 0272 QMutexLocker locker(&mutex); 0273 mDebugAreas += areas; 0274 } 0275 debugAreasConfig()->setValue("areas", QVariant::fromValue(mDebugAreas.toList().join(';'))); 0276 } 0277 0278 void add(const QString &area) 0279 { 0280 QMutexLocker locker(&mutex); 0281 mDebugAreas << area; 0282 } 0283 0284 QSet<QString> debugAreas() 0285 { 0286 QMutexLocker locker(&mutex); 0287 return mDebugAreas; 0288 } 0289 0290 QMutex mutex; 0291 QSet<QString> mDebugAreas; 0292 }; 0293 0294 Q_GLOBAL_STATIC(DebugAreaCollector, sDebugAreaCollector); 0295 0296 QSet<QString> Sink::Log::debugAreas() 0297 { 0298 if (!sDebugAreaCollector.isDestroyed()) { 0299 return sDebugAreaCollector->debugAreas(); 0300 } 0301 return {}; 0302 } 0303 0304 static void collectDebugArea(const QString &debugArea) 0305 { 0306 if (!sDebugAreaCollector.isDestroyed()) { 0307 sDebugAreaCollector->add(debugArea); 0308 } 0309 } 0310 0311 static bool containsItemStartingWith(const QByteArray &pattern, const QByteArrayList &list) 0312 { 0313 for (const auto &item : list) { 0314 int start = 0; 0315 int end = item.size(); 0316 if (item.startsWith('*')) { 0317 start++; 0318 } 0319 if (item.endsWith('*')) { 0320 end--; 0321 } 0322 if (pattern.contains(item.mid(start, end - start))) { 0323 return true; 0324 } 0325 } 0326 return false; 0327 } 0328 0329 static bool caseInsensitiveContains(const QByteArray &pattern, const QByteArrayList &list) 0330 { 0331 for (const auto &item : list) { 0332 if (item.toLower() == pattern) { 0333 return true; 0334 } 0335 } 0336 return false; 0337 } 0338 0339 static QByteArray getFileName(const char *file) 0340 { 0341 static char sep = QDir::separator().toLatin1(); 0342 auto filename = QByteArray(file).split(sep).last(); 0343 return filename.split('.').first(); 0344 } 0345 0346 static QString assembleDebugArea(const char *debugArea, const char *debugComponent, const char *file) 0347 { 0348 if (!sPrimaryComponent.isDestroyed() && sPrimaryComponent->isEmpty()) { 0349 *sPrimaryComponent = getProgramName(); 0350 } 0351 if (!sPrimaryComponent.isDestroyed()) { 0352 //Using stringbuilder for fewer allocations 0353 return QLatin1String{*sPrimaryComponent} % QLatin1String{"."} % 0354 (debugComponent ? (QLatin1String{debugComponent} + QLatin1String{"."}) : QLatin1String{""}) % 0355 (debugArea ? QLatin1String{debugArea} : QLatin1String{getFileName(file)}); 0356 } else { 0357 return {}; 0358 } 0359 } 0360 0361 static bool isFiltered(DebugLevel debugLevel, const QByteArray &fullDebugArea) 0362 { 0363 if (debugLevel < debugOutputLevel()) { 0364 return true; 0365 } 0366 const auto areas = debugOutputFilter(Sink::Log::Area); 0367 if ((debugLevel <= Sink::Log::Trace) && !areas.isEmpty()) { 0368 if (!containsItemStartingWith(fullDebugArea, areas)) { 0369 return true; 0370 } 0371 } 0372 return false; 0373 } 0374 0375 bool Sink::Log::isFiltered(DebugLevel debugLevel, const char *debugArea, const char *debugComponent, const char *file) 0376 { 0377 //Avoid assembleDebugArea if we can, because it's fairly expensive. 0378 if (debugLevel < debugOutputLevel()) { 0379 return true; 0380 } 0381 return isFiltered(debugLevel, assembleDebugArea(debugArea, debugComponent, file).toLatin1()); 0382 } 0383 0384 Q_GLOBAL_STATIC(NullStream, sNullStream); 0385 Q_GLOBAL_STATIC(DebugStream, sDebugStream); 0386 0387 QDebug Sink::Log::debugStream(DebugLevel debugLevel, int line, const char *file, const char *function, const char *debugArea, const char *debugComponent) 0388 { 0389 const auto fullDebugArea = assembleDebugArea(debugArea, debugComponent, file); 0390 collectDebugArea(fullDebugArea); 0391 0392 if (isFiltered(debugLevel, fullDebugArea.toLatin1())) { 0393 if (!sNullStream.isDestroyed()) { 0394 return QDebug(sNullStream); 0395 } 0396 return QDebug{QtDebugMsg}; 0397 } 0398 0399 QString prefix; 0400 int prefixColorCode = ANSI_Colors::DoNothing; 0401 switch (debugLevel) { 0402 case DebugLevel::Trace: 0403 prefix = "Trace: "; 0404 break; 0405 case DebugLevel::Log: 0406 prefix = "Log: "; 0407 prefixColorCode = ANSI_Colors::Green; 0408 break; 0409 case DebugLevel::Warning: 0410 prefix = "Warning:"; 0411 prefixColorCode = ANSI_Colors::Red; 0412 break; 0413 case DebugLevel::Error: 0414 prefix = "Error: "; 0415 prefixColorCode = ANSI_Colors::Red; 0416 break; 0417 } 0418 0419 auto debugOutput = debugOutputFields(); 0420 0421 bool showTime = debugOutput.isEmpty() ? false : caseInsensitiveContains("time", debugOutput); 0422 bool showLocation = debugOutput.isEmpty() ? false : caseInsensitiveContains("location", debugOutput); 0423 bool showFunction = debugOutput.isEmpty() ? false : caseInsensitiveContains("function", debugOutput); 0424 bool showProgram = debugOutput.isEmpty() ? false : caseInsensitiveContains("application", debugOutput); 0425 #ifdef Q_OS_WIN 0426 bool useColor = false; 0427 #else 0428 bool useColor = true; 0429 #endif 0430 bool multiline = false; 0431 0432 const QString resetColor = colorCommand(ANSI_Colors::Reset); 0433 QString output; 0434 if (showTime) { 0435 output = QString("%1 ").arg(QDateTime::currentDateTime().toString(Qt::ISODateWithMs)); 0436 } 0437 if (useColor) { 0438 output += colorCommand(QList<int>() << ANSI_Colors::Bold << prefixColorCode); 0439 } 0440 output += prefix; 0441 if (useColor) { 0442 output += resetColor; 0443 } 0444 if (showProgram) { 0445 int width = 10; 0446 output += QString(" %1(%2)").arg(QString::fromLatin1(getProgramName()).leftJustified(width, ' ', true)).arg(unsigned(getpid())).rightJustified(width + 8, ' '); 0447 } 0448 if (useColor) { 0449 output += colorCommand(QList<int>() << ANSI_Colors::Bold << prefixColorCode); 0450 } 0451 static std::atomic<int> maxDebugAreaSize{25}; 0452 maxDebugAreaSize = qMax(fullDebugArea.size(), maxDebugAreaSize.load()); 0453 output += QString(" %1 ").arg(fullDebugArea.leftJustified(maxDebugAreaSize, ' ', false)); 0454 if (useColor) { 0455 output += resetColor; 0456 } 0457 if (showFunction) { 0458 output += QString(" %3").arg(fullDebugArea.leftJustified(25, ' ', true)); 0459 } 0460 if (showLocation) { 0461 const auto filename = QString::fromLatin1(file).split('/').last(); 0462 output += QString(" %1:%2").arg(filename.right(25)).arg(QString::number(line).leftJustified(4, ' ')).leftJustified(30, ' ', true); 0463 } 0464 if (multiline) { 0465 output += "\n "; 0466 } 0467 output += ":"; 0468 0469 if (sDebugStream.isDestroyed()) { 0470 return QDebug{QtDebugMsg}; 0471 } 0472 QDebug debug(sDebugStream); 0473 debug.noquote().nospace() << output; 0474 return debug.space().quote(); 0475 }