Core: Refactor logging viewer

* All log categories that were ever used are captured.
* Fixed various small issues.
* Allow disabling individual logging types.
* Add Splitter between messages and categories.
* No longer needs to interpret or change QT_LOGGING_RULES.

Change-Id: I33be4754d550064bc66274f655a59e7af67ae487
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
Marcus Tillmanns
2023-10-13 12:28:52 +02:00
parent 5d79b010bf
commit 755eefcbb2
7 changed files with 720 additions and 837 deletions

View File

@@ -209,8 +209,6 @@ add_qtc_plugin(Core
locator/spotlightlocatorfilter.h
locator/urllocatorfilter.cpp
locator/urllocatorfilter.h
loggingmanager.cpp
loggingmanager.h
loggingviewer.cpp
loggingviewer.h
manhattanstyle.cpp

View File

@@ -9,18 +9,18 @@
#include "icore.h"
#include "idocument.h"
#include "iwizardfactory.h"
#include "loggingviewer.h"
#include "modemanager.h"
#include "session.h"
#include "settingsdatabase.h"
#include "themechooser.h"
#include "actionmanager/actionmanager.h"
#include "coreconstants.h"
#include "documentmanager.h"
#include "editormanager/editormanager.h"
#include "fileutils.h"
#include "find/findplugin.h"
#include "locator/locator.h"
#include "coreconstants.h"
#include "fileutils.h"
#include <extensionsystem/pluginerroroverview.h>
#include <extensionsystem/pluginmanager.h>
@@ -490,6 +490,7 @@ QString CorePlugin::msgCrashpadInformation()
ExtensionSystem::IPlugin::ShutdownFlag CorePlugin::aboutToShutdown()
{
LoggingViewer::hideLoggingView();
Find::aboutToShutdown();
m_locator->aboutToShutdown();
ICore::aboutToShutdown();

View File

@@ -102,8 +102,6 @@ QtcPlugin {
"iwizardfactory.h",
"jsexpander.cpp",
"jsexpander.h",
"loggingmanager.cpp",
"loggingmanager.h",
"loggingviewer.cpp",
"loggingviewer.h",
"manhattanstyle.cpp",

View File

@@ -1,304 +0,0 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "loggingmanager.h"
#include <utils/environment.h>
#include <utils/filepath.h>
#include <QCoreApplication>
#include <QDateTime>
#include <QLibraryInfo>
#include <QRegularExpression>
#include <QSettings>
#include <QStandardPaths>
//
// WARNING! Do not use qDebug(), qWarning() or similar inside this file -
// same applies for indirect usages (e.g. QTC_ASSERT() and the like).
// Using static functions of QLoggingCategory may cause dead locks as well.
//
using namespace Utils;
namespace Core {
namespace Internal {
static QtMessageHandler s_originalMessageHandler = nullptr;
static LoggingViewManager *s_instance = nullptr;
static QString levelToString(QtMsgType t)
{
switch (t) {
case QtMsgType::QtCriticalMsg: return {"critical"};
case QtMsgType::QtDebugMsg: return {"debug"};
case QtMsgType::QtInfoMsg: return {"info"};
case QtMsgType::QtWarningMsg: return {"warning"};
default:
return {"fatal"}; // wrong but we don't care
}
}
static QtMsgType parseLevel(const QString &level)
{
switch (level.at(0).toLatin1()) {
case 'c': return QtMsgType::QtCriticalMsg;
case 'd': return QtMsgType::QtDebugMsg;
case 'i': return QtMsgType::QtInfoMsg;
case 'w': return QtMsgType::QtWarningMsg;
default:
return QtMsgType::QtFatalMsg; // wrong but we don't care
}
}
static bool parseLine(const QString &line, FilterRuleSpec *filterRule)
{
const QStringList parts = line.split('=');
if (parts.size() != 2)
return false;
const QString category = parts.at(0);
static const QRegularExpression regex("^(.+?)(\\.(debug|info|warning|critical))?$");
const QRegularExpressionMatch match = regex.match(category);
if (!match.hasMatch())
return false;
const QString categoryName = match.captured(1);
if (categoryName.size() > 2) {
if (categoryName.mid(1, categoryName.size() - 2).contains('*'))
return false;
} else if (categoryName.size() == 2) {
if (categoryName.count('*') == 2)
return false;
}
filterRule->category = categoryName;
if (match.capturedLength(2) == 0)
filterRule->level = std::nullopt;
else
filterRule->level = std::make_optional(parseLevel(match.captured(2).mid(1)));
const QString enabled = parts.at(1);
if (enabled == "true" || enabled == "false") {
filterRule->enabled = (enabled == "true");
return true;
}
return false;
}
static QList<FilterRuleSpec> fetchOriginalRules()
{
QList<FilterRuleSpec> rules;
auto appendRulesFromFile = [&rules](const QString &fileName) {
QSettings iniSettings(fileName, QSettings::IniFormat);
iniSettings.beginGroup("Rules");
const QStringList keys = iniSettings.allKeys();
for (const QString &key : keys) {
const QString value = iniSettings.value(key).toString();
FilterRuleSpec filterRule;
if (parseLine(key + "=" + value, &filterRule))
rules.append(filterRule);
}
iniSettings.endGroup();
};
Utils::FilePath iniFile = Utils::FilePath::fromString(
QLibraryInfo::path(QLibraryInfo::DataPath)).pathAppended("qtlogging.ini");
if (iniFile.exists())
appendRulesFromFile(iniFile.toString());
const QString qtProjectString = QStandardPaths::locate(QStandardPaths::GenericConfigLocation,
"QtProject/qtlogging.ini");
if (!qtProjectString.isEmpty())
appendRulesFromFile(qtProjectString);
iniFile = Utils::FilePath::fromString(qtcEnvironmentVariable("QT_LOGGING_CONF"));
if (iniFile.exists())
appendRulesFromFile(iniFile.toString());
if (qtcEnvironmentVariableIsSet("QT_LOGGING_RULES")) {
const QStringList rulesStrings = qtcEnvironmentVariable("QT_LOGGING_RULES").split(';');
for (const QString &rule : rulesStrings) {
FilterRuleSpec filterRule;
if (parseLine(rule, &filterRule))
rules.append(filterRule);
}
}
return rules;
}
LoggingViewManager::LoggingViewManager(QObject *parent)
: QObject(parent)
, m_originalLoggingRules(qtcEnvironmentVariable("QT_LOGGING_RULES"))
{
qRegisterMetaType<Core::Internal::LoggingCategoryEntry>();
s_instance = this;
s_originalMessageHandler = qInstallMessageHandler(logMessageHandler);
m_enabled = true;
m_originalRules = fetchOriginalRules();
prefillCategories();
QLoggingCategory::setFilterRules("*=true");
}
LoggingViewManager::~LoggingViewManager()
{
m_enabled = false;
qInstallMessageHandler(s_originalMessageHandler);
s_originalMessageHandler = nullptr;
qputenv("QT_LOGGING_RULES", m_originalLoggingRules.toLocal8Bit());
QLoggingCategory::setFilterRules("*=false");
resetFilterRules();
s_instance = nullptr;
}
LoggingViewManager *LoggingViewManager::instance()
{
return s_instance;
}
void LoggingViewManager::logMessageHandler(QtMsgType type, const QMessageLogContext &context,
const QString &mssg)
{
if (!s_instance->m_enabled) {
if (s_instance->enabledInOriginalRules(context, type))
s_originalMessageHandler(type, context, mssg);
return;
}
if (!context.category) {
s_originalMessageHandler(type, context, mssg);
return;
}
const QString category = QString::fromLocal8Bit(context.category);
auto it = s_instance->m_categories.find(category);
if (it == s_instance->m_categories.end()) {
if (!s_instance->m_listQtInternal && category.startsWith("qt."))
return;
LoggingCategoryEntry entry;
entry.level = QtMsgType::QtDebugMsg;
entry.enabled = (category == "default") || s_instance->enabledInOriginalRules(context, type);
it = s_instance->m_categories.insert(category, entry);
emit s_instance->foundNewCategory(category, entry);
}
const LoggingCategoryEntry entry = it.value();
if (entry.enabled && enabled(type, entry.level)) {
const QString timestamp = QDateTime::currentDateTime().toString("HH:mm:ss.zzz");
emit s_instance->receivedLog(timestamp, category,
LoggingViewManager::messageTypeToString(type), mssg);
}
}
bool LoggingViewManager::isCategoryEnabled(const QString &category)
{
auto entry = m_categories.find(category);
if (entry == m_categories.end()) // shall not happen - paranoia
return false;
return entry.value().enabled;
}
void LoggingViewManager::setCategoryEnabled(const QString &category, bool enabled)
{
auto entry = m_categories.find(category);
if (entry == m_categories.end()) // shall not happen - paranoia
return;
entry->enabled = enabled;
}
void LoggingViewManager::setLogLevel(const QString &category, QtMsgType type)
{
auto entry = m_categories.find(category);
if (entry == m_categories.end()) // shall not happen - paranoia
return;
entry->level = type;
}
void LoggingViewManager::setListQtInternal(bool listQtInternal)
{
m_listQtInternal = listQtInternal;
}
void LoggingViewManager::appendOrUpdate(const QString &category, const LoggingCategoryEntry &entry)
{
auto it = m_categories.find(category);
bool append = it == m_categories.end();
m_categories.insert(category, entry);
if (append)
emit foundNewCategory(category, entry);
else
emit updatedCategory(category, entry);
}
/*
* Does not check categories for being present, will perform early exit if m_categories is not empty
*/
void LoggingViewManager::prefillCategories()
{
if (!m_categories.isEmpty())
return;
for (int i = 0, end = m_originalRules.size(); i < end; ++i) {
const FilterRuleSpec &rule = m_originalRules.at(i);
if (rule.category.startsWith('*') || rule.category.endsWith('*'))
continue;
bool enabled = rule.enabled;
// check following rules whether they might overwrite
for (int j = i + 1; j < end; ++j) {
const FilterRuleSpec &secondRule = m_originalRules.at(j);
const QRegularExpression regex(
QRegularExpression::wildcardToRegularExpression(secondRule.category));
if (!regex.match(rule.category).hasMatch())
continue;
if (secondRule.level.has_value() && rule.level != secondRule.level)
continue;
enabled = secondRule.enabled;
}
LoggingCategoryEntry entry;
entry.level = rule.level.value_or(QtMsgType::QtInfoMsg);
entry.enabled = enabled;
m_categories.insert(rule.category, entry);
}
}
void LoggingViewManager::resetFilterRules()
{
for (const FilterRuleSpec &rule : std::as_const(m_originalRules)) {
const QString level = rule.level.has_value() ? '.' + levelToString(rule.level.value())
: QString();
const QString ruleString = rule.category + level + '=' + (rule.enabled ? "true" : "false");
QLoggingCategory::setFilterRules(ruleString);
}
}
bool LoggingViewManager::enabledInOriginalRules(const QMessageLogContext &context, QtMsgType type)
{
if (!context.category)
return false;
const QString category = QString::fromUtf8(context.category);
bool result = false;
for (const FilterRuleSpec &rule : std::as_const(m_originalRules)) {
const QRegularExpression regex(
QRegularExpression::wildcardToRegularExpression(rule.category));
if (regex.match(category).hasMatch()) {
if (rule.level.has_value()) {
if (rule.level.value() == type)
result = rule.enabled;
} else {
result = rule.enabled;
}
}
}
return result;
}
} // namespace Internal
} // namespace Core

View File

@@ -1,121 +0,0 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <QColor>
#include <QLoggingCategory>
#include <QMap>
#include <QObject>
#include <optional>
namespace Core {
namespace Internal {
struct FilterRuleSpec
{
QString category;
std::optional<QtMsgType> level;
bool enabled;
};
class LoggingCategoryEntry
{
public:
QtMsgType level = QtDebugMsg;
bool enabled = false;
QColor color;
};
class LoggingViewManager : public QObject
{
Q_OBJECT
public:
static inline QString messageTypeToString(QtMsgType type)
{
switch (type) {
case QtDebugMsg: return {"Debug"};
case QtInfoMsg: return {"Info"};
case QtCriticalMsg: return {"Critical"};
case QtWarningMsg: return {"Warning"};
case QtFatalMsg: return {"Fatal"};
default: return {"Unknown"};
}
}
static inline QtMsgType messageTypeFromString(const QString &type)
{
if (type.isEmpty())
return QtDebugMsg;
// shortcut - only handle expected
switch (type.at(0).toLatin1()) {
case 'I':
return QtInfoMsg;
case 'C':
return QtCriticalMsg;
case 'W':
return QtWarningMsg;
case 'D':
default:
return QtDebugMsg;
}
}
explicit LoggingViewManager(QObject *parent = nullptr);
~LoggingViewManager();
static LoggingViewManager *instance();
static inline bool enabled(QtMsgType current, QtMsgType stored)
{
if (stored == QtMsgType::QtInfoMsg)
return true;
if (current == stored)
return true;
if (stored == QtMsgType::QtDebugMsg)
return current != QtMsgType::QtInfoMsg;
if (stored == QtMsgType::QtWarningMsg)
return current == QtMsgType::QtCriticalMsg || current == QtMsgType::QtFatalMsg;
if (stored == QtMsgType::QtCriticalMsg)
return current == QtMsgType::QtFatalMsg;
return false;
}
static void logMessageHandler(QtMsgType type, const QMessageLogContext &context,
const QString &mssg);
void setEnabled(bool enabled) { m_enabled = enabled; }
bool isEnabled() const { return m_enabled; }
bool isCategoryEnabled(const QString &category);
void setCategoryEnabled(const QString &category, bool enabled);
void setLogLevel(const QString &category, QtMsgType type);
void setListQtInternal(bool listQtInternal);
QList<FilterRuleSpec> originalRules() const { return m_originalRules; }
QMap<QString, LoggingCategoryEntry> categories() const { return m_categories; }
void appendOrUpdate(const QString &category, const LoggingCategoryEntry &entry);
signals:
void receivedLog(const QString &timestamp, const QString &type, const QString &category,
const QString &msg);
void foundNewCategory(const QString &category, const LoggingCategoryEntry &entry);
void updatedCategory(const QString &category, const LoggingCategoryEntry &entry);
private:
void prefillCategories();
void resetFilterRules();
bool enabledInOriginalRules(const QMessageLogContext &context, QtMsgType type);
QMap<QString, LoggingCategoryEntry> m_categories;
const QString m_originalLoggingRules;
QList<FilterRuleSpec> m_originalRules;
bool m_enabled = false;
bool m_listQtInternal = false;
};
} // namespace Internal
} // namespace Core
Q_DECLARE_METATYPE(Core::Internal::LoggingCategoryEntry)

File diff suppressed because it is too large Load Diff

View File

@@ -3,14 +3,13 @@
#pragma once
namespace Core {
namespace Internal {
namespace Core::Internal {
class LoggingViewer
{
public:
static void showLoggingView();
static void hideLoggingView();
};
} // Internal
} // Core
} // namespace Core::Internal