forked from quickshell/quickshell
core/log: add read-log --filter
This commit is contained in:
parent
0fc98652a8
commit
c2b4610acb
4 changed files with 232 additions and 19 deletions
|
@ -7,12 +7,14 @@
|
||||||
#include <qendian.h>
|
#include <qendian.h>
|
||||||
#include <qhash.h>
|
#include <qhash.h>
|
||||||
#include <qhashfunctions.h>
|
#include <qhashfunctions.h>
|
||||||
|
#include <qlist.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
#include <qnamespace.h>
|
#include <qnamespace.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qobjectdefs.h>
|
#include <qobjectdefs.h>
|
||||||
#include <qstring.h>
|
#include <qstring.h>
|
||||||
|
#include <qstringview.h>
|
||||||
#include <qsysinfo.h>
|
#include <qsysinfo.h>
|
||||||
#include <qtenvironmentvariables.h>
|
#include <qtenvironmentvariables.h>
|
||||||
#include <qtextstream.h>
|
#include <qtextstream.h>
|
||||||
|
@ -23,6 +25,7 @@
|
||||||
#include <sys/sendfile.h>
|
#include <sys/sendfile.h>
|
||||||
|
|
||||||
#include "logging_p.hpp"
|
#include "logging_p.hpp"
|
||||||
|
#include "logging_qtprivate.cpp" // NOLINT
|
||||||
#include "paths.hpp"
|
#include "paths.hpp"
|
||||||
|
|
||||||
namespace qs::log {
|
namespace qs::log {
|
||||||
|
@ -82,6 +85,16 @@ void LogMessage::formatMessage(
|
||||||
if (color && msg.type == QtFatalMsg) stream << "\033[0m";
|
if (color && msg.type == QtFatalMsg) stream << "\033[0m";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CategoryFilter::shouldDisplay(QtMsgType type) const {
|
||||||
|
switch (type) {
|
||||||
|
case QtDebugMsg: return this->debug;
|
||||||
|
case QtInfoMsg: return this->info;
|
||||||
|
case QtWarningMsg: return this->warn;
|
||||||
|
case QtCriticalMsg: return this->critical;
|
||||||
|
default: return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LogManager::LogManager(): stdoutStream(stdout) {}
|
LogManager::LogManager(): stdoutStream(stdout) {}
|
||||||
|
|
||||||
void LogManager::messageHandler(
|
void LogManager::messageHandler(
|
||||||
|
@ -98,14 +111,7 @@ void LogManager::messageHandler(
|
||||||
const auto* key = static_cast<const void*>(context.category);
|
const auto* key = static_cast<const void*>(context.category);
|
||||||
|
|
||||||
if (self->sparseFilters.contains(key)) {
|
if (self->sparseFilters.contains(key)) {
|
||||||
auto filter = self->sparseFilters.value(key);
|
display = self->sparseFilters.value(key).shouldDisplay(type);
|
||||||
switch (type) {
|
|
||||||
case QtDebugMsg: display = filter.debug; break;
|
|
||||||
case QtInfoMsg: display = filter.info; break;
|
|
||||||
case QtWarningMsg: display = filter.warn; break;
|
|
||||||
case QtCriticalMsg: display = filter.critical; break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (display) {
|
if (display) {
|
||||||
|
@ -520,7 +526,8 @@ start:
|
||||||
slot->time = this->lastMessageTime;
|
slot->time = this->lastMessageTime;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
auto category = this->categories.value(next - EncodedLogOpcode::BeginCategories);
|
auto categoryId = next - EncodedLogOpcode::BeginCategories;
|
||||||
|
auto category = this->categories.value(categoryId);
|
||||||
|
|
||||||
quint8 field = 0;
|
quint8 field = 0;
|
||||||
if (!this->reader.readU8(&field)) return false;
|
if (!this->reader.readU8(&field)) return false;
|
||||||
|
@ -555,6 +562,7 @@ start:
|
||||||
if (!this->readString(&body)) return false;
|
if (!this->readString(&body)) return false;
|
||||||
|
|
||||||
*slot = LogMessage(msgType, QLatin1StringView(category), body, this->lastMessageTime);
|
*slot = LogMessage(msgType, QLatin1StringView(category), body, this->lastMessageTime);
|
||||||
|
slot->readCategoryId = categoryId;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->recentMessages.emplace(*slot);
|
this->recentMessages.emplace(*slot);
|
||||||
|
@ -633,7 +641,17 @@ bool EncodedLogReader::registerCategory() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool readEncodedLogs(QIODevice* device) {
|
bool readEncodedLogs(QIODevice* device, const QString& rulespec) {
|
||||||
|
using namespace qt_logging_registry;
|
||||||
|
|
||||||
|
QList<QLoggingRule> rules;
|
||||||
|
|
||||||
|
{
|
||||||
|
QLoggingSettingsParser parser;
|
||||||
|
parser.setContent(rulespec);
|
||||||
|
rules = parser.rules();
|
||||||
|
}
|
||||||
|
|
||||||
auto reader = EncodedLogReader();
|
auto reader = EncodedLogReader();
|
||||||
reader.setDevice(device);
|
reader.setDevice(device);
|
||||||
|
|
||||||
|
@ -655,11 +673,36 @@ bool readEncodedLogs(QIODevice* device) {
|
||||||
|
|
||||||
auto color = LogManager::instance()->colorLogs;
|
auto color = LogManager::instance()->colorLogs;
|
||||||
|
|
||||||
|
auto filters = QHash<quint16, CategoryFilter>();
|
||||||
|
|
||||||
LogMessage message;
|
LogMessage message;
|
||||||
auto stream = QTextStream(stdout);
|
auto stream = QTextStream(stdout);
|
||||||
while (reader.read(&message)) {
|
while (reader.read(&message)) {
|
||||||
LogMessage::formatMessage(stream, message, color, true);
|
CategoryFilter filter;
|
||||||
stream << '\n';
|
if (filters.contains(message.readCategoryId)) {
|
||||||
|
filter = filters.value(message.readCategoryId);
|
||||||
|
} else {
|
||||||
|
for (const auto& rule: rules) {
|
||||||
|
auto filterpass = rule.pass(message.category, QtDebugMsg);
|
||||||
|
if (filterpass != 0) filter.debug = filterpass > 0;
|
||||||
|
|
||||||
|
filterpass = rule.pass(message.category, QtInfoMsg);
|
||||||
|
if (filterpass != 0) filter.info = filterpass > 0;
|
||||||
|
|
||||||
|
filterpass = rule.pass(message.category, QtWarningMsg);
|
||||||
|
if (filterpass != 0) filter.warn = filterpass > 0;
|
||||||
|
|
||||||
|
filterpass = rule.pass(message.category, QtCriticalMsg);
|
||||||
|
if (filterpass != 0) filter.critical = filterpass > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
filters.insert(message.readCategoryId, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.shouldDisplay(message.type)) {
|
||||||
|
LogMessage::formatMessage(stream, message, color, true);
|
||||||
|
stream << '\n';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stream << Qt::flush;
|
stream << Qt::flush;
|
||||||
|
|
|
@ -33,6 +33,7 @@ struct LogMessage {
|
||||||
QDateTime time;
|
QDateTime time;
|
||||||
QLatin1StringView category;
|
QLatin1StringView category;
|
||||||
QByteArray body;
|
QByteArray body;
|
||||||
|
quint16 readCategoryId = 0;
|
||||||
|
|
||||||
static void formatMessage(QTextStream& stream, const LogMessage& msg, bool color, bool timestamp);
|
static void formatMessage(QTextStream& stream, const LogMessage& msg, bool color, bool timestamp);
|
||||||
};
|
};
|
||||||
|
@ -63,6 +64,8 @@ struct CategoryFilter {
|
||||||
, warn(category->isWarningEnabled())
|
, warn(category->isWarningEnabled())
|
||||||
, critical(category->isCriticalEnabled()) {}
|
, critical(category->isCriticalEnabled()) {}
|
||||||
|
|
||||||
|
[[nodiscard]] bool shouldDisplay(QtMsgType type) const;
|
||||||
|
|
||||||
bool debug = true;
|
bool debug = true;
|
||||||
bool info = true;
|
bool info = true;
|
||||||
bool warn = true;
|
bool warn = true;
|
||||||
|
@ -95,7 +98,7 @@ private:
|
||||||
LoggingThreadProxy threadProxy;
|
LoggingThreadProxy threadProxy;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool readEncodedLogs(QIODevice* device);
|
bool readEncodedLogs(QIODevice* device, const QString& rulespec);
|
||||||
|
|
||||||
} // namespace qs::log
|
} // namespace qs::log
|
||||||
|
|
||||||
|
|
159
src/core/logging_qtprivate.cpp
Normal file
159
src/core/logging_qtprivate.cpp
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
// The logging rule parser from qloggingregistry_p.h and qloggingregistry.cpp.
|
||||||
|
|
||||||
|
// Was unable to properly link the functions when directly using the headers (which we depend
|
||||||
|
// on anyway), so below is a slightly stripped down copy. Making the originals link would
|
||||||
|
// be preferable.
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <qbytearrayview.h>
|
||||||
|
#include <qchar.h>
|
||||||
|
#include <qflags.h>
|
||||||
|
#include <qlist.h>
|
||||||
|
#include <qlogging.h>
|
||||||
|
#include <qloggingcategory.h>
|
||||||
|
#include <qstringtokenizer.h>
|
||||||
|
#include <qstringview.h>
|
||||||
|
#include <qtypes.h>
|
||||||
|
|
||||||
|
namespace qs::log {
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(logLogging);
|
||||||
|
|
||||||
|
namespace qt_logging_registry {
|
||||||
|
|
||||||
|
class QLoggingRule {
|
||||||
|
public:
|
||||||
|
QLoggingRule();
|
||||||
|
QLoggingRule(QStringView pattern, bool enabled);
|
||||||
|
[[nodiscard]] int pass(QLatin1StringView categoryName, QtMsgType type) const;
|
||||||
|
|
||||||
|
enum PatternFlag {
|
||||||
|
FullText = 0x1,
|
||||||
|
LeftFilter = 0x2,
|
||||||
|
RightFilter = 0x4,
|
||||||
|
MidFilter = LeftFilter | RightFilter
|
||||||
|
};
|
||||||
|
Q_DECLARE_FLAGS(PatternFlags, PatternFlag)
|
||||||
|
|
||||||
|
QString category;
|
||||||
|
int messageType;
|
||||||
|
PatternFlags flags;
|
||||||
|
bool enabled;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void parse(QStringView pattern);
|
||||||
|
};
|
||||||
|
|
||||||
|
class QLoggingSettingsParser {
|
||||||
|
public:
|
||||||
|
void setContent(QStringView content);
|
||||||
|
|
||||||
|
[[nodiscard]] QList<QLoggingRule> rules() const { return this->mRules; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void parseNextLine(QStringView line);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<QLoggingRule> mRules;
|
||||||
|
};
|
||||||
|
|
||||||
|
void QLoggingSettingsParser::setContent(QStringView content) {
|
||||||
|
this->mRules.clear();
|
||||||
|
for (auto line: qTokenize(content, u';')) this->parseNextLine(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QLoggingSettingsParser::parseNextLine(QStringView line) {
|
||||||
|
// Remove whitespace at start and end of line:
|
||||||
|
line = line.trimmed();
|
||||||
|
|
||||||
|
const qsizetype equalPos = line.indexOf(u'=');
|
||||||
|
if (equalPos != -1) {
|
||||||
|
if (line.lastIndexOf(u'=') == equalPos) {
|
||||||
|
const auto key = line.left(equalPos).trimmed();
|
||||||
|
const QStringView pattern = key;
|
||||||
|
const auto valueStr = line.mid(equalPos + 1).trimmed();
|
||||||
|
int value = -1;
|
||||||
|
if (valueStr == QString("true")) value = 1;
|
||||||
|
else if (valueStr == QString("false")) value = 0;
|
||||||
|
QLoggingRule rule(pattern, (value == 1));
|
||||||
|
if (rule.flags != 0 && (value != -1)) this->mRules.append(std::move(rule));
|
||||||
|
else
|
||||||
|
qCWarning(logLogging, "Ignoring malformed logging rule: '%s'", line.toUtf8().constData());
|
||||||
|
} else {
|
||||||
|
qCWarning(logLogging, "Ignoring malformed logging rule: '%s'", line.toUtf8().constData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QLoggingRule::QLoggingRule(QStringView pattern, bool enabled): messageType(-1), enabled(enabled) {
|
||||||
|
this->parse(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QLoggingRule::parse(QStringView pattern) {
|
||||||
|
QStringView p;
|
||||||
|
|
||||||
|
// strip trailing ".messagetype"
|
||||||
|
if (pattern.endsWith(QString(".debug"))) {
|
||||||
|
p = pattern.chopped(6); // strlen(".debug")
|
||||||
|
this->messageType = QtDebugMsg;
|
||||||
|
} else if (pattern.endsWith(QString(".info"))) {
|
||||||
|
p = pattern.chopped(5); // strlen(".info")
|
||||||
|
this->messageType = QtInfoMsg;
|
||||||
|
} else if (pattern.endsWith(QString(".warning"))) {
|
||||||
|
p = pattern.chopped(8); // strlen(".warning")
|
||||||
|
this->messageType = QtWarningMsg;
|
||||||
|
} else if (pattern.endsWith(QString(".critical"))) {
|
||||||
|
p = pattern.chopped(9); // strlen(".critical")
|
||||||
|
this->messageType = QtCriticalMsg;
|
||||||
|
} else {
|
||||||
|
p = pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QChar asterisk = u'*';
|
||||||
|
if (!p.contains(asterisk)) {
|
||||||
|
this->flags = FullText;
|
||||||
|
} else {
|
||||||
|
if (p.endsWith(asterisk)) {
|
||||||
|
this->flags |= LeftFilter;
|
||||||
|
p = p.chopped(1);
|
||||||
|
}
|
||||||
|
if (p.startsWith(asterisk)) {
|
||||||
|
this->flags |= RightFilter;
|
||||||
|
p = p.mid(1);
|
||||||
|
}
|
||||||
|
if (p.contains(asterisk)) // '*' only supported at start/end
|
||||||
|
this->flags = PatternFlags();
|
||||||
|
}
|
||||||
|
|
||||||
|
this->category = p.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
int QLoggingRule::pass(QLatin1StringView cat, QtMsgType msgType) const {
|
||||||
|
// check message type
|
||||||
|
if (this->messageType > -1 && this->messageType != msgType) return 0;
|
||||||
|
|
||||||
|
if (this->flags == FullText) {
|
||||||
|
// full match
|
||||||
|
if (this->category == cat) return (this->enabled ? 1 : -1);
|
||||||
|
else return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const qsizetype idx = cat.indexOf(this->category);
|
||||||
|
if (idx >= 0) {
|
||||||
|
if (this->flags == MidFilter) {
|
||||||
|
// matches somewhere
|
||||||
|
return (this->enabled ? 1 : -1);
|
||||||
|
} else if (this->flags == LeftFilter) {
|
||||||
|
// matches left
|
||||||
|
if (idx == 0) return (this->enabled ? 1 : -1);
|
||||||
|
} else if (this->flags == RightFilter) {
|
||||||
|
// matches right
|
||||||
|
if (idx == (cat.size() - this->category.size())) return (this->enabled ? 1 : -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace qt_logging_registry
|
||||||
|
|
||||||
|
} // namespace qs::log
|
|
@ -140,9 +140,17 @@ int qs_main(int argc, char** argv) {
|
||||||
);
|
);
|
||||||
|
|
||||||
/// ---
|
/// ---
|
||||||
QStringOption logpath;
|
QStringOption logPath;
|
||||||
|
QStringOption logFilter;
|
||||||
auto* readLog = app.add_subcommand("read-log", "Read a quickshell log file.");
|
auto* readLog = app.add_subcommand("read-log", "Read a quickshell log file.");
|
||||||
readLog->add_option("path", logpath, "Path to the log file to read")->required();
|
readLog->add_option("path", logPath, "Path to the log file to read")->required();
|
||||||
|
|
||||||
|
readLog->add_option(
|
||||||
|
"-f,--filter",
|
||||||
|
logFilter,
|
||||||
|
"Logging categories to display. (same syntax as QT_LOGGING_RULES)"
|
||||||
|
);
|
||||||
|
|
||||||
readLog->add_flag("--no-color", noColor, "Do not color the log output. (Env:NO_COLOR)");
|
readLog->add_flag("--no-color", noColor, "Do not color the log output. (Env:NO_COLOR)");
|
||||||
|
|
||||||
CLI11_PARSE(app, argc, argv);
|
CLI11_PARSE(app, argc, argv);
|
||||||
|
@ -153,15 +161,15 @@ int qs_main(int argc, char** argv) {
|
||||||
LogManager::init(!noColor, sparseLogsOnly);
|
LogManager::init(!noColor, sparseLogsOnly);
|
||||||
|
|
||||||
if (*readLog) {
|
if (*readLog) {
|
||||||
auto file = QFile(*logpath);
|
auto file = QFile(*logPath);
|
||||||
if (!file.open(QFile::ReadOnly)) {
|
if (!file.open(QFile::ReadOnly)) {
|
||||||
qCritical() << "Failed to open log for reading:" << *logpath;
|
qCritical() << "Failed to open log for reading:" << *logPath;
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
qInfo() << "Reading log" << *logpath;
|
qInfo() << "Reading log" << *logPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
return qs::log::readEncodedLogs(&file) ? 0 : -1;
|
return qs::log::readEncodedLogs(&file, *logFilter) ? 0 : -1;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// NOLINTBEGIN
|
// NOLINTBEGIN
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue