feat(xml-logs): finish tests

Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
This commit is contained in:
Rachel Powers
2025-04-18 15:22:39 -07:00
parent bfdc77665d
commit 21570a03fb
15 changed files with 2006 additions and 91 deletions

View File

@ -151,9 +151,6 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
void setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version);
void copyManagedPack(BaseInstance& other);
/// guess log level from a line of game log
virtual MessageLevel::Enum guessLevel([[maybe_unused]] const QString& line, MessageLevel::Enum level) { return level; }
virtual QStringList extraArguments();
/// Traits. Normally inside the version, depends on instance implementation.

View File

@ -2,21 +2,22 @@
MessageLevel::Enum MessageLevel::getLevel(const QString& levelName)
{
if (levelName == "Launcher")
QString name = levelName.toUpper();
if (name == "LAUNCHER")
return MessageLevel::Launcher;
else if (levelName == "Trace")
else if (name == "TRACE")
return MessageLevel::Trace;
else if (levelName == "Debug")
else if (name == "DEBUG")
return MessageLevel::Debug;
else if (levelName == "Info")
else if (name == "INFO")
return MessageLevel::Info;
else if (levelName == "Message")
else if (name == "MESSAGE")
return MessageLevel::Message;
else if (levelName == "Warning")
else if (name == "WARNING" || name == "WARN")
return MessageLevel::Warning;
else if (levelName == "Error")
else if (name == "ERROR")
return MessageLevel::Error;
else if (levelName == "Fatal")
else if (name == "FATAL")
return MessageLevel::Fatal;
// Skip PrePost, it's not exposed to !![]!
// Also skip StdErr and StdOut

View File

@ -37,7 +37,6 @@
#include "launch/LaunchTask.h"
#include <assert.h>
#include <qlogging.h>
#include <QAnyStringView>
#include <QCoreApplication>
#include <QDebug>
@ -237,9 +236,9 @@ bool LaunchTask::parseXmlLogs(QString const& line, MessageLevel::Enum level)
model.append(MessageLevel::Error, tr("[Log4j Parse Error] Failed to parse log4j log event: %1").arg(err.value().errMessage));
return false;
} else {
if (items.has_value()) {
if (!items.isEmpty()) {
auto& model = *getLogModel();
for (auto const& item : items.value()) {
for (auto const& item : items) {
if (std::holds_alternative<LogParser::LogEntry>(item)) {
auto entry = std::get<LogParser::LogEntry>(item);
auto msg = QString("[%1] [%2/%3] [%4]: %5")
@ -252,7 +251,7 @@ bool LaunchTask::parseXmlLogs(QString const& line, MessageLevel::Enum level)
model.append(entry.level, msg);
} else if (std::holds_alternative<LogParser::PlainText>(item)) {
auto msg = std::get<LogParser::PlainText>(item).message;
level = m_instance->guessLevel(msg, level);
level = LogParser::guessLevel(msg, model.previousLevel());
msg = censorPrivateInfo(msg);
model.append(level, msg);
}
@ -281,15 +280,16 @@ void LaunchTask::onLogLine(QString line, MessageLevel::Enum level)
level = innerLevel;
}
auto& model = *getLogModel();
// If the level is still undetermined, guess level
if (level == MessageLevel::Unknown) {
level = m_instance->guessLevel(line, level);
level = LogParser::guessLevel(line, model.previousLevel());
}
// censor private user info
line = censorPrivateInfo(line);
auto& model = *getLogModel();
model.append(level, line);
}

View File

@ -166,3 +166,11 @@ bool LogModel::isOverFlow()
{
return m_numLines >= m_maxLines && m_stopOnOverflow;
}
MessageLevel::Enum LogModel::previousLevel() {
if (!m_content.isEmpty()) {
return m_content.last().level;
}
return MessageLevel::Unknown;
}

View File

@ -31,6 +31,8 @@ class LogModel : public QAbstractListModel {
void setColorLines(bool state);
bool colorLines() const;
MessageLevel::Enum previousLevel();
enum Roles { LevelRole = Qt::UserRole };
private /* types */:

View File

@ -19,6 +19,9 @@
#include "LogParser.h"
#include <QRegularExpression>
#include "MessageLevel.h"
void LogParser::appendLine(QAnyStringView data)
{
if (!m_partialData.isEmpty()) {
@ -202,7 +205,7 @@ std::optional<LogParser::ParsedItem> LogParser::parseNext()
}
}
std::optional<QList<LogParser::ParsedItem>> LogParser::parseAvailable()
QList<LogParser::ParsedItem> LogParser::parseAvailable()
{
QList<LogParser::ParsedItem> items;
bool doNext = true;
@ -320,3 +323,48 @@ std::optional<LogParser::ParsedItem> LogParser::parseLog4J()
throw std::runtime_error("unreachable: already verified this was a complete log4j:Event");
}
MessageLevel::Enum LogParser::guessLevel(const QString& line, MessageLevel::Enum level)
{
static const QRegularExpression LINE_WITH_LEVEL("^\\[(?<timestamp>[0-9:]+)\\] \\[[^/]+/(?<level>[^\\]]+)\\]");
auto match = LINE_WITH_LEVEL.match(line);
if (match.hasMatch()) {
// New style logs from log4j
QString timestamp = match.captured("timestamp");
QString levelStr = match.captured("level");
if (levelStr == "INFO")
level = MessageLevel::Info;
if (levelStr == "WARN")
level = MessageLevel::Warning;
if (levelStr == "ERROR")
level = MessageLevel::Error;
if (levelStr == "FATAL")
level = MessageLevel::Fatal;
if (levelStr == "TRACE" || levelStr == "DEBUG")
level = MessageLevel::Debug;
} else {
// Old style forge logs
if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || line.contains("[FINER]") ||
line.contains("[FINEST]"))
level = MessageLevel::Info;
if (line.contains("[SEVERE]") || line.contains("[STDERR]"))
level = MessageLevel::Error;
if (line.contains("[WARNING]"))
level = MessageLevel::Warning;
if (line.contains("[DEBUG]"))
level = MessageLevel::Debug;
}
if (level != MessageLevel::Unknown)
return level;
if (line.contains("overwriting existing"))
return MessageLevel::Fatal;
// NOTE: this diverges from the real regexp. no unicode, the first section is + instead of *
// static const QRegularExpression JAVA_EXCEPTION(
// R"(Exception in thread|...\d more$|(\s+at |Caused by: )([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*)|([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*(Exception|Error|Throwable)");
//
// if (line.contains(JAVA_EXCEPTION))
// return MessageLevel::Error;
return MessageLevel::Info;
}

View File

@ -55,9 +55,12 @@ class LogParser {
void appendLine(QAnyStringView data);
std::optional<ParsedItem> parseNext();
std::optional<QList<ParsedItem>> parseAvailable();
QList<ParsedItem> parseAvailable();
std::optional<Error> getError();
/// guess log level from a line of game log
static MessageLevel::Enum guessLevel(const QString& line, MessageLevel::Enum level);
protected:
MessageLevel::Enum parseLogLevel(const QString& level);
std::optional<LogEntry> parseAttributes();

View File

@ -1004,49 +1004,6 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess
return filter;
}
MessageLevel::Enum MinecraftInstance::guessLevel(const QString& line, MessageLevel::Enum level)
{
if (line.contains("overwriting existing"))
return MessageLevel::Fatal;
// NOTE: this diverges from the real regexp. no unicode, the first section is + instead of *
static const QRegularExpression JAVA_EXCEPTION(
R"(Exception in thread|...\d more$|(\s+at |Caused by: )([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*)|([a-zA-Z_$][a-zA-Z\d_$]*\.)+[a-zA-Z_$][a-zA-Z\d_$]*(Exception|Error|Throwable)");
if (line.contains(JAVA_EXCEPTION))
return MessageLevel::Error;
static const QRegularExpression LINE_WITH_LEVEL("\\[(?<timestamp>[0-9:]+)\\] \\[[^/]+/(?<level>[^\\]]+)\\]");
auto match = LINE_WITH_LEVEL.match(line);
if (match.hasMatch()) {
// New style logs from log4j
QString timestamp = match.captured("timestamp");
QString levelStr = match.captured("level");
if (levelStr == "INFO")
level = MessageLevel::Message;
if (levelStr == "WARN")
level = MessageLevel::Warning;
if (levelStr == "ERROR")
level = MessageLevel::Error;
if (levelStr == "FATAL")
level = MessageLevel::Fatal;
if (levelStr == "TRACE" || levelStr == "DEBUG")
level = MessageLevel::Debug;
} else {
// Old style forge logs
if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || line.contains("[FINER]") ||
line.contains("[FINEST]"))
level = MessageLevel::Message;
if (line.contains("[SEVERE]") || line.contains("[STDERR]"))
level = MessageLevel::Error;
if (line.contains("[WARNING]"))
level = MessageLevel::Warning;
if (line.contains("[DEBUG]"))
level = MessageLevel::Debug;
}
return level;
}
IPathMatcher::Ptr MinecraftInstance::getLogFileMatcher()
{
auto combined = std::make_shared<MultiMatcher>();

View File

@ -139,9 +139,6 @@ class MinecraftInstance : public BaseInstance {
QProcessEnvironment createEnvironment() override;
QProcessEnvironment createLaunchEnvironment() override;
/// guess log level from a line of minecraft log
MessageLevel::Enum guessLevel(const QString& line, MessageLevel::Enum level) override;
IPathMatcher::Ptr getLogFileMatcher() override;
QString getLogFileRoot() override;

View File

@ -179,7 +179,9 @@ void OtherLogsPage::on_btnReload_clicked()
showTooBig();
return;
}
auto handleLine = [this](QString line) {
MessageLevel::Enum last = MessageLevel::Unknown;
auto handleLine = [this, &last](QString line) {
if (line.isEmpty())
return false;
if (line.back() == '\n')
@ -194,9 +196,10 @@ void OtherLogsPage::on_btnReload_clicked()
// If the level is still undetermined, guess level
if (level == MessageLevel::StdErr || level == MessageLevel::StdOut || level == MessageLevel::Unknown) {
level = m_instance->guessLevel(line, level);
level = LogParser::guessLevel(line, last);
}
last = level;
m_model->append(level, line);
return m_model->isOverFlow();
};