Qt: improve log routing, clean up output

This commit is contained in:
Adam Higerd 2025-05-09 11:23:11 -05:00
parent f3663c86ad
commit daa97adce9
27 changed files with 199 additions and 96 deletions

View File

@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "AudioDevice.h"
#include "GBAApp.h"
#include "LogController.h"
#include <mgba/core/core.h>
@ -31,7 +32,7 @@ AudioDevice::~AudioDevice() {
void AudioDevice::setFormat(const QAudioFormat& format) {
if (!m_context || !mCoreThreadIsActive(m_context)) {
qInfo() << tr("Can't set format of context-less audio device");
LOG(QT, INFO) << tr("Can't set format of context-less audio device");
return;
}
mCoreSyncLockAudio(&m_context->impl->sync);
@ -52,7 +53,7 @@ void AudioDevice::setInput(mCoreThread* input) {
qint64 AudioDevice::readData(char* data, qint64 maxSize) {
if (!m_context->core) {
qWarning() << tr("Audio device is missing its core");
LOG(QT, WARN) << tr("Audio device is missing its core");
return 0;
}
@ -80,7 +81,7 @@ qint64 AudioDevice::readData(char* data, qint64 maxSize) {
}
qint64 AudioDevice::writeData(const char*, qint64) {
qWarning() << tr("Writing data to read-only audio device");
LOG(QT, WARN) << tr("Writing data to read-only audio device");
return 0;
}

View File

@ -53,7 +53,7 @@ void AudioProcessorQt::stop() {
bool AudioProcessorQt::start() {
if (!input()) {
qWarning() << tr("Can't start an audio processor without input");
LOG(QT, WARN) << tr("Can't start an audio processor without input");
return false;
}
@ -78,7 +78,7 @@ bool AudioProcessorQt::start() {
QAudioDevice device(QMediaDevices::defaultAudioOutput());
m_audioOutput = std::make_unique<QAudioSink>(device, format);
qInfo() << "Audio outputting to " << device.description();
LOG(QT, INFO) << tr("Audio outputting to %1").arg(device.description());
connect(m_audioOutput.get(), &QAudioSink::stateChanged, this, [this](QAudio::State state) {
if (state != QAudio::IdleState) {
return;

View File

@ -31,7 +31,7 @@ void AudioProcessorSDL::stop() {
bool AudioProcessorSDL::start() {
if (!input()) {
qWarning() << tr("Can't start an audio processor without input");
LOG(QT, WARN) << tr("Can't start an audio processor without input");
return false;
}

View File

@ -136,6 +136,7 @@ set(SOURCE_FILES
InputProfile.cpp
KeyEditor.cpp
LoadSaveState.cpp
Log.cpp
LogController.cpp
LogConfigModel.cpp
LogView.cpp
@ -309,6 +310,7 @@ if(ENABLE_SCRIPTING)
scripting/ScriptingView.ui)
set(TEST_QT_autoscript_SRC
Log.cpp
test/autoscript.cpp
scripting/AutorunScriptModel.cpp)
endif()

View File

@ -208,7 +208,7 @@ void CheatsModel::endAppendRow() {
void CheatsModel::loadFile(const QString& path) {
VFile* vf = VFileDevice::open(path, O_RDONLY);
if (!vf) {
qWarning() << tr("Failed to open cheats file: %1").arg(path);
LOG(QT, WARN) << tr("Failed to open cheats file: %1").arg(path);
return;
}
beginResetModel();

View File

@ -183,7 +183,7 @@ void CheatsView::enterCheat() {
set->refresh(set, m_controller->cheatDevice());
}
if (failure) {
qCritical() << tr("Some cheats could not be added. Please ensure they're formatted correctly and/or try other cheat types.");
LOG(QT, ERROR) << tr("Some cheats could not be added. Please ensure they're formatted correctly and/or try other cheat types.");
}
m_ui.codeEntry->clear();
}

View File

@ -824,7 +824,7 @@ void CoreController::loadSave(const QString& path, bool temporary) {
m_resetActions.append([this, path, temporary]() {
VFile* vf = VFileDevice::open(path, temporary ? O_RDONLY : O_RDWR);
if (!vf) {
qCritical() << tr("Failed to open save file: %1").arg(path);
LOG(QT, ERROR) << tr("Failed to open save file: %1").arg(path);
return;
}
@ -882,7 +882,7 @@ void CoreController::loadPatch(const QString& patchPath) {
void CoreController::replaceGame(const QString& path) {
QFileInfo info(path);
if (!info.isReadable()) {
qCritical() << tr("Failed to open game file: %1").arg(path);
LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(path);
return;
}
QString fname = info.canonicalFilePath();
@ -912,7 +912,7 @@ void CoreController::yankPak() {
break;
#endif
case mPLATFORM_NONE:
qCritical() << tr("Can't yank pack in unexpected platform!");
LOG(QT, ERROR) << tr("Can't yank pack in unexpected platform!");
break;
}
}
@ -1027,7 +1027,7 @@ void CoreController::importSharkport(const QString& path) {
}
VFile* vf = VFileDevice::open(path, O_RDONLY);
if (!vf) {
qCritical() << tr("Failed to open snapshot file for reading: %1").arg(path);
LOG(QT, ERROR) << tr("Failed to open snapshot file for reading: %1").arg(path);
return;
}
Interrupter interrupter(this);
@ -1044,7 +1044,7 @@ void CoreController::exportSharkport(const QString& path) {
}
VFile* vf = VFileDevice::open(path, O_WRONLY | O_CREAT | O_TRUNC);
if (!vf) {
qCritical() << tr("Failed to open snapshot file for writing: %1").arg(path);
LOG(QT, ERROR) << tr("Failed to open snapshot file for writing: %1").arg(path);
return;
}
Interrupter interrupter(this);

View File

@ -49,7 +49,7 @@ CoreController* CoreManager::loadGame(const QString& path) {
dir->close(dir);
return loadGame(vf, fname, base);
} else {
qCritical() << tr("Failed to open game file: %1").arg(path);
LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(path);
}
return nullptr;
}
@ -87,7 +87,7 @@ CoreController* CoreManager::loadGame(VFile* vf, const QString& path, const QStr
mCore* core = mCoreFindVF(vf);
if (!core) {
vf->close(vf);
qCritical() << tr("Could not load game. Are you sure it's in the correct format?");
LOG(QT, ERROR) << tr("Could not load game. Are you sure it's in the correct format?");
return nullptr;
}
@ -114,7 +114,7 @@ CoreController* CoreManager::loadGame(VFile* vf, const QString& path, const QStr
bytes = info.dir().canonicalPath().toUtf8();
mDirectorySetAttachBase(&core->dirs, VDirOpen(bytes.constData()));
if (!mCoreAutoloadSave(core)) {
qCritical() << tr("Failed to open save file; in-game saves cannot be updated. Please ensure the save directory is writable without additional privileges (e.g. UAC on Windows).");
LOG(QT, ERROR) << tr("Failed to open save file; in-game saves cannot be updated. Please ensure the save directory is writable without additional privileges (e.g. UAC on Windows).");
}
mCoreAutoloadCheats(core);

View File

@ -173,7 +173,7 @@ void DebuggerConsoleController::historyLoad() {
void DebuggerConsoleController::historySave() {
QFile log(ConfigController::configDir() + "/cli_history.log");
if (!log.open(QIODevice::WriteOnly | QIODevice::Text)) {
qWarning() << tr("Could not open CLI history for writing");
LOG(QT, WARN) << tr("Could not open CLI history for writing");
return;
}
for (const QString& line : m_history) {

View File

@ -310,7 +310,7 @@ bool DisplayGL::highestCompatible(QSurfaceFormat& format) {
#ifdef BUILD_GL
#if defined(BUILD_GLES2) || defined(BUILD_GLES3) || defined(USE_EPOXY)
qWarning() << tr("Failed to create an OpenGL 3 context, trying old-style...");
LOG(QT, WARN) << tr("Failed to create an OpenGL 3 context, trying old-style...");
#endif
if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL) {
format.setVersion(1, 4);

View File

@ -40,8 +40,6 @@ using namespace QGBA;
static GBAApp* g_app = nullptr;
mLOG_DEFINE_CATEGORY(QT, "Qt", "platform.qt");
GBAApp::GBAApp(int& argc, char* argv[], ConfigController* config)
: QApplication(argc, argv)
, m_configController(config)

View File

@ -25,10 +25,6 @@
struct NoIntroDB;
#include <mgba/core/log.h>
mLOG_DECLARE_CATEGORY(QT);
namespace QGBA {
class ConfigController;

View File

@ -60,7 +60,7 @@ void GIFView::startRecording() {
}
FFmpegEncoderSetLooping(&m_encoder, m_ui.loop->isChecked());
if (!FFmpegEncoderOpen(&m_encoder, m_filename.toUtf8().constData())) {
qCritical() << tr("Failed to open output file: %1").arg(m_filename);
LOG(QT, ERROR) << tr("Failed to open output file: %1").arg(m_filename);
return;
}
m_ui.start->setEnabled(false);

View File

@ -724,9 +724,9 @@ void InputController::prepareCamFormat() {
}
}
if (!goodFormatFound) {
qWarning() << "Could not find a valid camera format!";
LOG(QT, WARN) << tr("Could not find a valid camera format!");
for (const auto& format : cameraFormats) {
qWarning() << "Camera supported format: " << QString::number(format);
LOG(QT, WARN) << tr("Camera supported format: %1").arg(format);
}
}
m_camera->setViewfinderSettings(settings);
@ -745,7 +745,7 @@ void InputController::prepareCamFormat() {
}
}
if (!goodFormatFound) {
qWarning() << "Could not find a valid camera format!";
LOG(QT, WARN) << tr("Could not find a valid camera format!");
}
m_camera->setCameraFormat(bestFormat);
#endif

78
src/platform/qt/Log.cpp Normal file
View File

@ -0,0 +1,78 @@
/* Copyright (c) 2013-2025 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "Log.h"
#include <QLoggingCategory>
mLOG_DEFINE_CATEGORY(QT, "Qt", "platform.qt");
using namespace QGBA;
Log* Log::s_target = nullptr;
Log::Stream Log::log(int level, int category) {
return Stream(s_target, level, category);
}
void Log::setDefaultTarget(Log* target) {
s_target = target;
}
Log::Log() {
// Nothing to do
}
Log::~Log() {
if (s_target == this) {
s_target = nullptr;
}
}
void Log::postLog(int level, int category, const QString& string) {
QLoggingCategory cat(mLogCategoryName(category));
switch (level) {
case mLOG_DEBUG:
case mLOG_STUB:
qCDebug(cat).noquote() << string;
return;
case mLOG_INFO:
qCInfo(cat).noquote() << string;
return;
case mLOG_ERROR:
case mLOG_GAME_ERROR:
qCCritical(cat).noquote() << string;
return;
case mLOG_FATAL:
// qFatal doesn't have a stream API
qFatal("%s: %s", mLogCategoryName(category), qPrintable(string));
return;
case mLOG_WARN:
default:
qCWarning(cat).noquote() << string;
return;
}
}
Log::Stream::Stream(Log* target, int level, int category)
: m_level(level)
, m_category(category)
, m_log(target)
{
}
Log::Stream::~Stream() {
if (m_log) {
m_log->postLog(m_level, m_category, m_queue.join(" "));
} else {
Log().postLog(m_level, m_category, m_queue.join(" "));
}
}
Log::Stream& Log::Stream::operator<<(const QString& string) {
m_queue.append(string);
return *this;
}

57
src/platform/qt/Log.h Normal file
View File

@ -0,0 +1,57 @@
/* Copyright (c) 2013-2025 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#pragma once
#include <QtDebug>
#include <mgba/core/log.h>
mLOG_DECLARE_CATEGORY(QT);
namespace QGBA {
class LogController;
class Log {
private:
class Stream {
public:
Stream(Log* target, int level, int category);
~Stream();
Stream& operator<<(const QString&);
template <typename T>
Stream& operator<<(const T& value) {
QString formatted;
QDebug dbg(&formatted);
dbg.noquote() << value;
*this << formatted;
return *this;
}
private:
int m_level;
int m_category;
Log* m_log;
QStringList m_queue;
};
static Log* s_target;
public:
static Stream log(int level, int category);
static void setDefaultTarget(Log* target);
Log();
~Log();
virtual void postLog(int level, int category, const QString& string);
};
}
#define LOG(C, L) (QGBA::Log::log(mLOG_ ## L, _mLOG_CAT_ ## C))

View File

@ -5,17 +5,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "LogController.h"
#include <QLoggingCategory>
#include <QMessageBox>
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
#include <QtLogging>
#endif
#include "ConfigController.h"
using namespace QGBA;
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
#define endl Qt::endl
#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
namespace Qt {
using QTextStreamFunctions::endl;
}
#endif
LogController LogController::s_global(mLOG_ALL);
@ -55,7 +55,9 @@ LogController::LogController(int levels, QObject* parent)
m_filter.defaultLevels = levels;
s_qtCat = mLogCategoryById("platform.qt");
if (this != &s_global) {
if (this == &s_global) {
setDefaultTarget(this);
} else {
connect(&s_global, &LogController::logPosted, this, &LogController::postLog);
connect(this, static_cast<void (LogController::*)(int)>(&LogController::levelsSet), &s_global, static_cast<void (LogController::*)(int)>(&LogController::setLevels));
connect(this, static_cast<void (LogController::*)(int)>(&LogController::levelsEnabled), &s_global, static_cast<void (LogController::*)(int)>(&LogController::enableLevels));
@ -71,10 +73,6 @@ int LogController::levels(int category) const {
return mLogFilterLevels(&m_filter, category);
}
LogController::Stream LogController::operator()(int category, int level) {
return Stream(this, category, level);
}
void LogController::load(const ConfigController* config) {
mLogFilterLoad(&m_filter, config->config());
if (!levels(mLogCategoryById("gba.bios"))) {
@ -96,7 +94,7 @@ void LogController::postLog(int level, int category, const QString& string) {
if (!mLogFilterTest(&m_filter, category, static_cast<mLogLevel>(level))) {
return;
}
if (m_logToStdout || m_logToFile) {
if ((m_logToStdout || m_logToFile) && this == &s_global) {
QString line = tr("[%1] %2: %3").arg(LogController::toString(level)).arg(mLogCategoryName(category)).arg(string);
if (m_logToStdout) {
@ -196,19 +194,3 @@ QString LogController::toString(int level) {
}
return QString();
}
LogController::Stream::Stream(LogController* controller, int level, int category)
: m_level(level)
, m_category(category)
, m_log(controller)
{
}
LogController::Stream::~Stream() {
m_log->postLog(m_level, m_category, m_queue.join(" "));
}
LogController::Stream& LogController::Stream::operator<<(const QString& string) {
m_queue.append(string);
return *this;
}

View File

@ -6,9 +6,12 @@
#pragma once
#include "GBAApp.h"
#include "Log.h"
#include <mgba/core/log.h>
#include <QFile>
#include <QLoggingCategory>
#include <QObject>
#include <QStringList>
#include <QTextStream>
@ -18,25 +21,9 @@ namespace QGBA {
class ConfigController;
class LogController : public QObject {
class LogController : public QObject, public Log {
Q_OBJECT
private:
class Stream {
public:
Stream(LogController* controller, int level, int category);
~Stream();
Stream& operator<<(const QString&);
private:
int m_level;
int m_category;
LogController* m_log;
QStringList m_queue;
};
public:
LogController(int levels, QObject* parent = nullptr);
~LogController();
@ -45,8 +32,6 @@ public:
int levels(int category) const;
mLogFilter* filter() { return &m_filter; }
Stream operator()(int category, int level);
static LogController* global();
static QtMessageHandler installMessageHandler();
static QString toString(int level);
@ -65,7 +50,7 @@ signals:
void levelsDisabled(int levels, int category);
public slots:
void postLog(int level, int category, const QString& string);
void postLog(int level, int category, const QString& string) override;
void setLevels(int levels);
void enableLevels(int levels);
void disableLevels(int levels);
@ -89,6 +74,4 @@ private:
static int s_qtCat;
};
#define LOG(C, L) (*LogController::global())(mLOG_ ## L, _mLOG_CAT_ ## C)
}

View File

@ -114,7 +114,7 @@ void MemoryAccessLogController::load(bool loadExisting) {
}
VFile* vf = VFileDevice::open(m_path, flags);
if (!vf) {
qCritical() << tr("Failed to open memory log file");
LOG(QT, ERROR) << tr("Failed to open memory log file");
return;
}
@ -123,7 +123,7 @@ void MemoryAccessLogController::load(bool loadExisting) {
m_controller->attachDebuggerModule(&m_logger.d);
if (!mDebuggerAccessLoggerOpen(&m_logger, vf, flags)) {
mDebuggerAccessLoggerDeinit(&m_logger);
qCritical() << tr("Failed to open memory log file");
LOG(QT, ERROR) << tr("Failed to open memory log file");
return;
}
emit loaded(true);

View File

@ -27,7 +27,7 @@ void MemoryDump::save() {
}
QFile outfile(filename);
if (!outfile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
qWarning() << tr("Failed to open output file: %1").arg(filename);
LOG(QT, WARN) << tr("Failed to open output file: %1").arg(filename);
return;
}
QByteArray out(serialize());

View File

@ -219,7 +219,7 @@ void MemoryModel::save() {
}
QFile outfile(filename);
if (!outfile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
qWarning() << tr("Failed to open output file: %1").arg(filename);
LOG(QT, WARN) << tr("Failed to open output file: %1").arg(filename);
return;
}
QByteArray out(serialize());
@ -233,7 +233,7 @@ void MemoryModel::load() {
}
QFile infile(filename);
if (!infile.open(QIODevice::ReadOnly)) {
qWarning() << tr("Failed to open input file: %1").arg(filename);
LOG(QT, WARN) << tr("Failed to open input file: %1").arg(filename);
return;
}
QByteArray bytestring(infile.readAll());

View File

@ -310,7 +310,7 @@ bool MultiplayerController::attachGame(CoreController* controller) {
break;
}
if (!player.saveId) {
qCritical() << "Couldn't find available save ID";
LOG(QT, ERROR) << tr("Couldn't find available save ID");
player.saveId = 1;
}
} else if (saveId) {
@ -364,7 +364,7 @@ void MultiplayerController::detachGame(CoreController* controller) {
interrupters.append(playerController);
}
if (pid < 0) {
qWarning() << tr("Trying to detach a multiplayer player that's not attached");
LOG(QT, WARN) << tr("Trying to detach a multiplayer player that's not attached");
return;
}
switch (controller->platform()) {
@ -404,7 +404,7 @@ void MultiplayerController::detachGame(CoreController* controller) {
QPair<QString, QString> path(controller->path(), controller->baseDirectory());
Player& p = m_pids.find(pid).value();
if (!p.saveId) {
qWarning() << tr("Clearing invalid save ID");
LOG(QT, WARN) << tr("Clearing invalid save ID");
} else {
m_claimedSaves[path] &= ~(1 << (p.saveId - 1));
if (!m_claimedSaves[path]) {
@ -413,7 +413,7 @@ void MultiplayerController::detachGame(CoreController* controller) {
}
if (p.preferredId < 0) {
qWarning() << tr("Clearing invalid preferred ID");
LOG(QT, WARN) << tr("Clearing invalid preferred ID");
} else {
m_claimedIds &= ~(1 << p.preferredId);
}
@ -434,7 +434,7 @@ int MultiplayerController::playerId(CoreController* controller) const {
for (int i = 0; i < m_players.count(); ++i) {
const Player* p = player(i);
if (!p) {
qCritical() << tr("Trying to get player ID for a multiplayer player that's not attached");
LOG(QT, ERROR) << tr("Trying to get player ID for a multiplayer player that's not attached");
return -1;
}
if (p->controller == controller) {
@ -448,7 +448,7 @@ int MultiplayerController::saveId(CoreController* controller) const {
for (int i = 0; i < m_players.count(); ++i) {
const Player* p = player(i);
if (!p) {
qCritical() << tr("Trying to get save ID for a multiplayer player that's not attached");
LOG(QT, ERROR) << tr("Trying to get save ID for a multiplayer player that's not attached");
return -1;
}
if (p->controller == controller) {

View File

@ -139,7 +139,7 @@ void PaletteView::exportPalette(int start, int length) {
}
VFile* vf = VFileDevice::open(filename, O_WRONLY | O_CREAT | O_TRUNC);
if (!vf) {
qCritical() << tr("Failed to open output palette file: %1").arg(filename);
LOG(QT, ERROR) << tr("Failed to open output palette file: %1").arg(filename);
return;
}
if (filename.endsWith(".pal", Qt::CaseInsensitive)) {

View File

@ -656,7 +656,7 @@ QByteArray SaveConverter::AnnotatedSave::convertTo(const SaveConverter::Annotate
}
if (platform != target.platform) {
qCritical() << tr("Cannot convert save games between platforms");
LOG(QT, ERROR) << tr("Cannot convert save games between platforms");
return {};
}

View File

@ -223,7 +223,7 @@ void VideoView::startRecording() {
return;
}
if (!FFmpegEncoderOpen(&m_encoder, m_filename.toUtf8().constData())) {
qCritical() << tr("Failed to open output video file: %1").arg(m_filename);
LOG(QT, ERROR) << tr("Failed to open output video file: %1").arg(m_filename);
return;
}
m_ui.start->setEnabled(false);

View File

@ -1055,7 +1055,7 @@ void Window::reloadDisplayDriver() {
}
m_display = std::unique_ptr<QGBA::Display>(Display::create(this));
if (!m_display) {
qCritical() << tr("Failed to create an appropriate display device, falling back to software display. "
LOG(QT, ERROR) << tr("Failed to create an appropriate display device, falling back to software display. "
"Games may run slowly, especially with larger windows.");
Display::setDriver(Display::Driver::QT);
m_display = std::unique_ptr<Display>(Display::create(this));
@ -1128,7 +1128,7 @@ void Window::reloadAudioDriver() {
m_audioProcessor->setInput(m_controller);
m_audioProcessor->configure(m_config);
if (!m_audioProcessor->start()) {
qWarning() << "Failed to start audio processor";
LOG(QT, WARN) << tr("Failed to start audio processor");
}
}

View File

@ -5,8 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "scripting/AutorunScriptModel.h"
#include <QtDebug>
#include <iostream>
#include "Log.h"
QDataStream& operator<<(QDataStream& stream, const QGBA::AutorunScriptModel::ScriptInfo& object) {
stream << QGBA::AutorunScriptModel::ScriptInfo::VERSION;
@ -16,6 +15,7 @@ QDataStream& operator<<(QDataStream& stream, const QGBA::AutorunScriptModel::Scr
}
QDataStream& operator>>(QDataStream& stream, QGBA::AutorunScriptModel::ScriptInfo& object) {
static bool displayedError = false;
uint16_t version = 0;
stream >> version;
if (version == 1) {
@ -23,7 +23,13 @@ QDataStream& operator>>(QDataStream& stream, QGBA::AutorunScriptModel::ScriptInf
stream >> filename;
object.filename = QString::fromUtf8(filename);
} else {
qCritical() << QGBA::AutorunScriptModel::tr("Could not load autorun script settings: unknown script info format %1").arg(version);
QString logMessage = QGBA::AutorunScriptModel::tr("Could not load autorun script settings: unknown script info format %1").arg(version);
if (displayedError) {
LOG(QT, WARN) << logMessage;
} else {
LOG(QT, ERROR) << logMessage;
displayedError = true;
}
stream.setStatus(QDataStream::ReadCorruptData);
return stream;
}