#include "log.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Sink::Log; static QThreadStorage> sSettings; static QSettings &config() { if (!sSettings.hasLocalData()) { sSettings.setLocalData(QSharedPointer::create(Sink::configLocation() + "/log.ini", QSettings::IniFormat)); } return *sSettings.localData(); } static QByteArray sPrimaryComponent; void Sink::Log::setPrimaryComponent(const QString &component) { sPrimaryComponent = component.toUtf8(); } class DebugStream : public QIODevice { public: QString m_location; DebugStream() : QIODevice() { open(WriteOnly); } virtual ~DebugStream(); bool isSequential() const { return true; } qint64 readData(char *, qint64) { return 0; /* eof */ } qint64 readLineData(char *, qint64) { return 0; /* eof */ } qint64 writeData(const char *data, qint64 len) { std::cout << data << std::endl; return len; } private: Q_DISABLE_COPY(DebugStream) }; // Virtual method anchor DebugStream::~DebugStream() { } class NullStream : public QIODevice { public: NullStream() : QIODevice() { open(WriteOnly); } virtual ~NullStream(); bool isSequential() const { return true; } qint64 readData(char *, qint64) { return 0; /* eof */ } qint64 readLineData(char *, qint64) { return 0; /* eof */ } qint64 writeData(const char *data, qint64 len) { return len; } private: Q_DISABLE_COPY(NullStream) }; // Virtual method anchor NullStream::~NullStream() { } /* * ANSI color codes: * 0: reset colors/style * 1: bold * 4: underline * 30 - 37: black, red, green, yellow, blue, magenta, cyan, and white text * 40 - 47: black, red, green, yellow, blue, magenta, cyan, and white background */ enum ANSI_Colors { DoNothing = -1, Reset = 0, Bold = 1, Underline = 4, Black = 30, Red = 31, Green = 32, Yellow = 33, Blue = 34 }; static QString colorCommand(int colorCode) { return QString("\x1b[%1m").arg(colorCode); } static QString colorCommand(QList colorCodes) { colorCodes.removeAll(ANSI_Colors::DoNothing); if (colorCodes.isEmpty()) { return QString(); } QString string("\x1b["); for (int code : colorCodes) { string += QString("%1;").arg(code); } string.chop(1); string += "m"; return string; } QByteArray Sink::Log::debugLevelName(DebugLevel debugLevel) { switch (debugLevel) { case DebugLevel::Trace: return "Trace"; case DebugLevel::Log: return "Log"; case DebugLevel::Warning: return "Warning"; case DebugLevel::Error: return "Error"; default: break; }; Q_ASSERT(false); return QByteArray(); } DebugLevel Sink::Log::debugLevelFromName(const QByteArray &name) { const QByteArray lowercaseName = name.toLower(); if (lowercaseName == "trace") return DebugLevel::Trace; if (lowercaseName == "log") return DebugLevel::Log; if (lowercaseName == "warning") return DebugLevel::Warning; if (lowercaseName == "error") return DebugLevel::Error; return DebugLevel::Log; } void Sink::Log::setDebugOutputLevel(DebugLevel debugLevel) { config().setValue("level", debugLevel); } Sink::Log::DebugLevel Sink::Log::debugOutputLevel() { return static_cast(config().value("level", Sink::Log::Log).toInt()); } void Sink::Log::setDebugOutputFilter(FilterType type, const QByteArrayList &filter) { switch (type) { case ApplicationName: config().setValue("applicationfilter", QVariant::fromValue(filter)); break; case Area: config().setValue("areafilter", QVariant::fromValue(filter)); break; } } QByteArrayList Sink::Log::debugOutputFilter(FilterType type) { switch (type) { case ApplicationName: return config().value("applicationfilter").value(); case Area: return config().value("areafilter").value(); default: return QByteArrayList(); } } void Sink::Log::setDebugOutputFields(const QByteArrayList &output) { config().setValue("outputfields", QVariant::fromValue(output)); } QByteArrayList Sink::Log::debugOutputFields() { return config().value("outputfields").value(); } static QByteArray getProgramName() { if (QCoreApplication::instance()) { return QCoreApplication::instance()->applicationName().toLocal8Bit(); } else { return ""; } } static QSharedPointer debugAreasConfig() { return QSharedPointer::create(Sink::dataLocation() + "/debugAreas.ini", QSettings::IniFormat); } class DebugAreaCollector { public: DebugAreaCollector() { QMutexLocker locker(&mutex); mDebugAreas = debugAreasConfig()->value("areas").value().split(';').toSet(); } ~DebugAreaCollector() { QMutexLocker locker(&mutex); mDebugAreas += debugAreasConfig()->value("areas").value().split(';').toSet(); debugAreasConfig()->setValue("areas", QVariant::fromValue(mDebugAreas.toList().join(';'))); } void add(const QString &area) { QMutexLocker locker(&mutex); mDebugAreas << area; } QSet debugAreas() { QMutexLocker locker(&mutex); return mDebugAreas; } QMutex mutex; QSet mDebugAreas; }; static auto sDebugAreaCollector = std::unique_ptr(new DebugAreaCollector); QSet Sink::Log::debugAreas() { return sDebugAreaCollector->debugAreas(); } static void collectDebugArea(const QString &debugArea) { sDebugAreaCollector->add(debugArea); } static bool containsItemStartingWith(const QByteArray &pattern, const QByteArrayList &list) { for (const auto &item : list) { if (item.startsWith('*')) { auto stripped = item.mid(1); if (stripped.endsWith('*')) { stripped.chop(1); } if (pattern.contains(stripped)) { return true; } } else { if (pattern.contains(item)) { return true; } } } return false; } static bool caseInsensitiveContains(const QByteArray &pattern, const QByteArrayList &list) { for (const auto &item : list) { if (item.toLower() == pattern) { return true; } } return false; } static QByteArray getFileName(const char *file) { static char sep = QDir::separator().toLatin1(); auto filename = QByteArray(file).split(sep).last(); return filename.split('.').first(); } bool isFiltered(DebugLevel debugLevel, const QByteArray &fullDebugArea) { if (debugLevel < debugOutputLevel()) { return true; } auto areas = debugOutputFilter(Sink::Log::Area); if (debugLevel <= Sink::Log::Trace && !areas.isEmpty()) { if (!containsItemStartingWith(fullDebugArea, areas)) { return true; } } return false; } QDebug Sink::Log::debugStream(DebugLevel debugLevel, int line, const char *file, const char *function, const char *debugArea, const char *debugComponent) { if (sPrimaryComponent.isEmpty()) { sPrimaryComponent = getProgramName(); } const QByteArray fullDebugArea = sPrimaryComponent + "." + (debugComponent ? (QByteArray{debugComponent} + ".") : "") + (debugArea ? QByteArray{debugArea} : getFileName(file)); collectDebugArea(fullDebugArea); static NullStream nullstream; if (isFiltered(debugLevel, fullDebugArea)) { return QDebug(&nullstream); } QString prefix; int prefixColorCode = ANSI_Colors::DoNothing; switch (debugLevel) { case DebugLevel::Trace: prefix = "Trace: "; break; case DebugLevel::Log: prefix = "Log: "; prefixColorCode = ANSI_Colors::Green; break; case DebugLevel::Warning: prefix = "Warning:"; prefixColorCode = ANSI_Colors::Red; break; case DebugLevel::Error: prefix = "Error: "; prefixColorCode = ANSI_Colors::Red; break; }; auto debugOutput = debugOutputFields(); bool showLocation = debugOutput.isEmpty() ? false : caseInsensitiveContains("location", debugOutput); bool showFunction = debugOutput.isEmpty() ? false : caseInsensitiveContains("function", debugOutput); bool showProgram = debugOutput.isEmpty() ? false : caseInsensitiveContains("application", debugOutput); bool useColor = true; bool multiline = false; const QString resetColor = colorCommand(ANSI_Colors::Reset); QString output; if (useColor) { output += colorCommand(QList() << ANSI_Colors::Bold << prefixColorCode); } output += prefix; if (useColor) { output += resetColor; } if (showProgram) { int width = 10; output += QString(" %1(%2)").arg(QString::fromLatin1(getProgramName()).leftJustified(width, ' ', true)).arg(unsigned(getpid())).rightJustified(width + 8, ' '); } if (useColor) { output += colorCommand(QList() << ANSI_Colors::Bold << prefixColorCode); } static std::atomic maxDebugAreaSize{25}; maxDebugAreaSize = qMax(fullDebugArea.size(), maxDebugAreaSize.load()); output += QString(" %1 ").arg(QString{fullDebugArea}.leftJustified(maxDebugAreaSize, ' ', false)); if (useColor) { output += resetColor; } if (showFunction) { output += QString(" %3").arg(QString{fullDebugArea}.leftJustified(25, ' ', true)); } if (showLocation) { const auto filename = QString::fromLatin1(file).split('/').last(); output += QString(" %1:%2").arg(filename.right(25)).arg(QString::number(line).leftJustified(4, ' ')).leftJustified(30, ' ', true); } if (multiline) { output += "\n "; } output += ":"; static DebugStream stream; QDebug debug(&stream); debug.noquote().nospace() << output; return debug.space().quote(); }