#include "log.h" #include #include #include #include #include using namespace Sink::Log; 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) { qputenv("SINKDEBUGLEVEL", debugLevelName(debugLevel)); } Sink::Log::DebugLevel Sink::Log::debugOutputLevel() { return debugLevelFromName(qgetenv("SINKDEBUGLEVEL")); } void Sink::Log::setDebugOutputFilter(FilterType type, const QByteArrayList &filter) { switch (type) { case ApplicationName: qputenv("SINKDEBUGFILTER", filter.join(',')); break; case Area: qputenv("SINKDEBUGAREAS", filter.join(',')); break; } } void Sink::Log::setDebugOutputFields(const QByteArrayList &output) { qputenv("SINKDEBUGOUTPUT", output.join(',')); } static QByteArray getProgramName() { if (QCoreApplication::instance()) { return QCoreApplication::instance()->applicationName().toLocal8Bit(); } else { return ""; } } static bool containsItemStartingWith(const QByteArray &pattern, const QByteArrayList &list) { for (const auto &item : list) { if (pattern.startsWith(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; } QDebug Sink::Log::debugStream(DebugLevel debugLevel, int line, const char* file, const char* function, const char* debugArea) { static NullStream nullstream; DebugLevel debugOutputLevel = debugLevelFromName(qgetenv("SINKDEBUGLEVEL")); if (debugLevel < debugOutputLevel) { return QDebug(&nullstream); } auto areas = qgetenv("SINKDEBUGAREAS").split(','); if (debugArea && !areas.isEmpty()) { if (!containsItemStartingWith(debugArea, areas)) { return QDebug(&nullstream); } } static QByteArray programName = getProgramName(); auto filter = qgetenv("SINKDEBUGFILTER").split(','); if (!filter.isEmpty() && !filter.contains(programName)) { if (!containsItemStartingWith(programName, filter)) { 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 = qgetenv("SINKDEBUGOUTPUT").split(','); 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(programName).leftJustified(width, ' ', true)).arg(unsigned(getpid())).rightJustified(width + 8, ' '); } if (debugArea) { if (useColor) { output += colorCommand(QList() << ANSI_Colors::Bold << prefixColorCode); } output += QString(" %1 ").arg(QString::fromLatin1(debugArea).leftJustified(25, ' ', true)); if (useColor) { output += resetColor; } } if (showFunction) { output += QString(" %3").arg(QString::fromLatin1(function).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; }