diff --git a/CMakeLists.txt b/CMakeLists.txt index 226d140..700fdf9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,12 +36,14 @@ set(SOURCE_FILES main.cpp mainwindow.cpp zjuconnectmode.cpp + application/applicationlogger.cpp application/connectionsession.cpp application/systemproxysession.cpp core/corecommandbuilder.cpp core/coreoutputbuffer.cpp core/coreoutputparser.cpp core/connectionsessionstate.cpp + infrastructure/corelogfile.cpp infrastructure/settingsprofileloader.cpp infrastructure/platformsystemproxybackend.cpp zjuconnectcontroller/zjuconnectcontroller.cpp @@ -120,6 +122,22 @@ target_link_libraries(${PROJECT_NAME} include(CTest) if(BUILD_TESTING) + add_executable(applicationlogger_test + tests/applicationlogger_test.cpp + application/applicationlogger.cpp + ) + target_include_directories(applicationlogger_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(applicationlogger_test Qt::Core) + add_test(NAME applicationlogger_test COMMAND applicationlogger_test) + + add_executable(corelogfile_test + tests/corelogfile_test.cpp + infrastructure/corelogfile.cpp + ) + target_include_directories(corelogfile_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(corelogfile_test Qt::Core) + add_test(NAME corelogfile_test COMMAND corelogfile_test) + add_executable(corecommandbuilder_test tests/corecommandbuilder_test.cpp core/corecommandbuilder.cpp diff --git a/application/applicationlogger.cpp b/application/applicationlogger.cpp new file mode 100644 index 0000000..123b68d --- /dev/null +++ b/application/applicationlogger.cpp @@ -0,0 +1,143 @@ +#include "applicationlogger.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace +{ +ApplicationLogger *messageHandlerTarget = nullptr; +QRecursiveMutex messageHandlerMutex; + +QString currentTimestamp() +{ + return QDateTime::currentDateTime().toString("yyyy/MM/dd hh:mm:ss"); +} + +QString messageTypeName(QtMsgType type) +{ + switch (type) + { + case QtDebugMsg: + return "DEBUG"; + case QtInfoMsg: + return "INFO"; + case QtWarningMsg: + return "WARNING"; + case QtCriticalMsg: + return "CRITICAL"; + case QtFatalMsg: + return "FATAL"; + } + + return "UNKNOWN"; +} +} + +ApplicationLogger::ApplicationLogger(QObject *parent) + : QObject(parent), + standardOutput(stdout, QIODevice::WriteOnly) +{ + writeStandardOutput( + "=== Log started at " + currentTimestamp() + + " with " + QCoreApplication::applicationName() + + " " + QCoreApplication::applicationVersion() + + " ===" + ); + + { + QMutexLocker locker(&messageHandlerMutex); + messageHandlerTarget = this; + previousMessageHandler = qInstallMessageHandler(ApplicationLogger::qtMessageHandler); + } +} + +ApplicationLogger::~ApplicationLogger() +{ + { + QMutexLocker locker(&messageHandlerMutex); + if (messageHandlerTarget == this) + { + messageHandlerTarget = nullptr; + qInstallMessageHandler(previousMessageHandler); + } + } + + writeStandardOutput("=== Log ended at " + currentTimestamp() + " ==="); +} + +void ApplicationLogger::appendMessage(const QString &prefix, const QString &message) +{ + if (QThread::currentThread() != thread()) + { + QMetaObject::invokeMethod( + this, + [this, prefix, message]() { appendMessage(prefix, message); }, + Qt::QueuedConnection + ); + return; + } + + publishEntry(prefix + " " + currentTimestamp() + " " + message.trimmed()); +} + +void ApplicationLogger::appendCoreOutput(const QString &output) +{ + if (QThread::currentThread() != thread()) + { + QMetaObject::invokeMethod( + this, + [this, output]() { appendCoreOutput(output); }, + Qt::QueuedConnection + ); + return; + } + + QStringList lines = output.split('\n', Qt::KeepEmptyParts); + for (QString &line : lines) + { + line.prepend("[CORE] "); + } + publishEntry(lines.join('\n')); +} + +void ApplicationLogger::qtMessageHandler( + QtMsgType type, + const QMessageLogContext &context, + const QString &message +) +{ + Q_UNUSED(context) + + QMutexLocker locker(&messageHandlerMutex); + if (messageHandlerTarget != nullptr) + { + messageHandlerTarget->appendQtMessage(type, message); + } +} + +void ApplicationLogger::appendQtMessage(QtMsgType type, const QString &message) +{ + appendMessage("[" + messageTypeName(type) + "]", message); +} + +void ApplicationLogger::publishEntry(const QString &entry) +{ + emit entryAdded(entry); + writeStandardOutput(entry); +} + +void ApplicationLogger::writeStandardOutput(const QString &output) +{ + standardOutput << output; + if (!output.endsWith('\n')) + { + standardOutput << '\n'; + } + standardOutput.flush(); +} diff --git a/application/applicationlogger.h b/application/applicationlogger.h new file mode 100644 index 0000000..efc091b --- /dev/null +++ b/application/applicationlogger.h @@ -0,0 +1,37 @@ +#ifndef APPLICATIONLOGGER_H +#define APPLICATIONLOGGER_H + +#include +#include +#include + +class ApplicationLogger : public QObject +{ +Q_OBJECT + +public: + explicit ApplicationLogger(QObject *parent = nullptr); + ~ApplicationLogger() override; + + void appendCoreOutput(const QString &output); + +signals: + void entryAdded(const QString &entry); + +private: + static void qtMessageHandler( + QtMsgType type, + const QMessageLogContext &context, + const QString &message + ); + + void appendQtMessage(QtMsgType type, const QString &message); + void appendMessage(const QString &prefix, const QString &message); + void publishEntry(const QString &entry); + void writeStandardOutput(const QString &output); + + QTextStream standardOutput; + QtMessageHandler previousMessageHandler = nullptr; +}; + +#endif // APPLICATIONLOGGER_H diff --git a/authinfowindow/authinfowindow.cpp b/authinfowindow/authinfowindow.cpp index 6b31704..1e830f4 100644 --- a/authinfowindow/authinfowindow.cpp +++ b/authinfowindow/authinfowindow.cpp @@ -1,8 +1,11 @@ #include "authinfowindow.h" #include "utils/utils.h" -#include "mainwindow.h" +#include +#include +#include +#include #include #include #include @@ -34,14 +37,13 @@ AuthInfoWindow::AuthInfoWindow(QWidget *parent) connect(proc_, QOverload::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) { QString output = Utils::consoleOutputToQString(stdoutBuf_); - if (mainWindow) - mainWindow->addLog("可用认证方式:\n" + output); + qInfo().noquote() << "可用认证方式:\n" + output; QJsonParseError jsonError; QJsonDocument doc = QJsonDocument::fromJson(output.toUtf8(), &jsonError); - if (jsonError.error != QJsonParseError::NoError && mainWindow) - mainWindow->addLog("解析可用认证方式失败:" + jsonError.errorString()); - if (!doc.isArray() && mainWindow) - mainWindow->addLog("解析可用认证方式失败:可用认证方式不是列表"); + if (jsonError.error != QJsonParseError::NoError) + qWarning().noquote() << "解析可用认证方式失败:" + jsonError.errorString(); + if (!doc.isArray()) + qWarning().noquote() << "解析可用认证方式失败:可用认证方式不是列表"; QJsonArray arr = doc.array(); for (QJsonValueRef v : arr) { QJsonObject obj = v.toObject(); @@ -58,12 +60,10 @@ AuthInfoWindow::AuthInfoWindow(QWidget *parent) } }); connect(proc_, &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) { - mainWindow->addLog(QString("获取可用认证方式失败:") + QMetaEnum::fromType().valueToKey(error)); + qWarning().noquote() + << QString("获取可用认证方式失败:") + + QMetaEnum::fromType().valueToKey(error); }); - - mainWindow = qobject_cast(parent); - if (!mainWindow) - mainWindow = qobject_cast(parent->parent()); } AuthInfoWindow::~AuthInfoWindow() @@ -77,8 +77,5 @@ void AuthInfoWindow::fetchAuthInfo(const QString& serverAddress, int port) stderrBuf_.clear(); proc_->start(Utils::getCorePath(), {"-protocol", "atrust", "-server", serverAddress, "-port", QString::number(port), "-auth-info"}); - if (mainWindow) - { - mainWindow->addLog("正在获取可用认证的方式..."); - } + qInfo().noquote() << "正在获取可用认证的方式..."; } diff --git a/authinfowindow/authinfowindow.h b/authinfowindow/authinfowindow.h index 36b6df8..3927bd0 100644 --- a/authinfowindow/authinfowindow.h +++ b/authinfowindow/authinfowindow.h @@ -4,8 +4,6 @@ #include #include -class MainWindow; - class AuthInfoWindow : public QDialog { Q_OBJECT @@ -22,7 +20,6 @@ signals: private: Ui::AuthInfoWindow *ui; - MainWindow *mainWindow = nullptr; QProcess *proc_ = nullptr; QByteArray stdoutBuf_; QByteArray stderrBuf_; diff --git a/infrastructure/corelogfile.cpp b/infrastructure/corelogfile.cpp new file mode 100644 index 0000000..c24fec0 --- /dev/null +++ b/infrastructure/corelogfile.cpp @@ -0,0 +1,59 @@ +#include "corelogfile.h" + +#include +#include + +CoreLogFile::CoreLogFile(const QString &filePath, QObject *parent) + : QObject(parent), + file(filePath) +{ + if (file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) + { + stream.setDevice(&file); + stream.setEncoding(QStringConverter::Utf8); + } +} + +CoreLogFile::~CoreLogFile() +{ + if (file.isOpen()) + { + stream.flush(); + file.close(); + } +} + +QString CoreLogFile::filePath() const +{ + return file.fileName(); +} + +bool CoreLogFile::isOpen() const +{ + return file.isOpen(); +} + +void CoreLogFile::appendOutput(const QString &output) +{ + if (QThread::currentThread() != thread()) + { + QMetaObject::invokeMethod( + this, + [this, output]() { appendOutput(output); }, + Qt::QueuedConnection + ); + return; + } + + if (!file.isOpen()) + { + return; + } + + stream << output; + if (!output.endsWith('\n')) + { + stream << '\n'; + } + stream.flush(); +} diff --git a/infrastructure/corelogfile.h b/infrastructure/corelogfile.h new file mode 100644 index 0000000..7c5a3a1 --- /dev/null +++ b/infrastructure/corelogfile.h @@ -0,0 +1,26 @@ +#ifndef CORELOGFILE_H +#define CORELOGFILE_H + +#include +#include +#include + +class CoreLogFile : public QObject +{ +Q_OBJECT + +public: + explicit CoreLogFile(const QString &filePath, QObject *parent = nullptr); + ~CoreLogFile() override; + + QString filePath() const; + bool isOpen() const; + + void appendOutput(const QString &output); + +private: + QFile file; + QTextStream stream; +}; + +#endif // CORELOGFILE_H diff --git a/main.cpp b/main.cpp index 50fb552..4d51c08 100644 --- a/main.cpp +++ b/main.cpp @@ -2,6 +2,7 @@ #include "SingleApplication" +#include "application/applicationlogger.h" #include "mainwindow.h" #include "utils/utils.h" @@ -17,6 +18,8 @@ int main(int argc, char *argv[]) QApplication::setApplicationVersion(PROJ_VER); QLocale::setDefault(QLocale(QLocale::Chinese, QLocale::SimplifiedChineseScript, QLocale::China)); + ApplicationLogger applicationLogger; + #if defined(Q_OS_WINDOWS) QApplication::setFont(QFont("Microsoft YaHei UI", QApplication::font().pointSize())); #endif @@ -35,7 +38,7 @@ int main(int argc, char *argv[]) else qDebug() << "Failed to load transaction file for" << translateModule; - MainWindow mainWindow; + MainWindow mainWindow(&applicationLogger); QObject::connect(&app, &SingleApplication::aboutToQuit, &mainWindow, &MainWindow::cleanUpWhenQuit); diff --git a/mainwindow.cpp b/mainwindow.cpp index 109b5ea..34f803a 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -12,13 +13,16 @@ #include "mainwindow.h" +#include "application/applicationlogger.h" +#include "infrastructure/corelogfile.h" #include "ui_mainwindow.h" #include "utils/utils.h" #include "zjuconnectcontroller/zjuconnectcontroller.h" -MainWindow::MainWindow(QWidget *parent) : +MainWindow::MainWindow(ApplicationLogger *logger, QWidget *parent) : QMainWindow(parent), - ui(new Ui::MainWindow) + ui(new Ui::MainWindow), + applicationLogger(logger) { profileManager = new ProfileManager(); const QString overrideConfigPath = Utils::getArgValue(QCoreApplication::arguments(), "--config-path"); @@ -39,6 +43,12 @@ MainWindow::MainWindow(QWidget *parent) : isFirstTimeSetMode = true; ui->setupUi(this); + coreLogFile = new CoreLogFile(Utils::getLogFilePath(), this); + connect(applicationLogger, &ApplicationLogger::entryAdded, this, + [this](const QString &entry) + { + ui->logPlainTextEdit->appendPlainText(entry); + }); systemProxySession = new SystemProxySession(this); connect(systemProxySession, &SystemProxySession::busyChanged, this, [this](bool busy) @@ -92,9 +102,9 @@ MainWindow::MainWindow(QWidget *parent) : // 文件-打开日志文件 connect(ui->openLogAction, &QAction::triggered, this, - [&]() + [this]() { - QString logFilePath = Utils::getLogFilePath(); + const QString logFilePath = coreLogFile->filePath(); QFileInfo logFileInfo(logFilePath); if (logFileInfo.exists()) @@ -103,7 +113,7 @@ MainWindow::MainWindow(QWidget *parent) : } else { - QMessageBox::information(this, "日志文件", "日志文件还未生成,请先启动 VPN 连接。"); + QMessageBox::warning(this, "日志文件", "日志文件创建失败。"); } }); @@ -129,7 +139,7 @@ MainWindow::MainWindow(QWidget *parent) : { if (!enabled) { - addLog("已清理系统代理设置"); + qInfo().noquote() << "已清理系统代理设置"; } }, Qt::SingleShotConnection); @@ -154,7 +164,7 @@ MainWindow::MainWindow(QWidget *parent) : } Utils::clearClientData(currentProfileId); - addLog("已清理登录缓存"); + qInfo().noquote() << "已清理登录缓存"; }); // 文件-设置授信设备 @@ -168,12 +178,12 @@ MainWindow::MainWindow(QWidget *parent) : settings->value("ZJUConnect/ServerAddress").toString(), settings->value("ZJUConnect/ServerPort").toInt(), currentProfileId, true); - addLog("设置授信设备成功"); + qInfo().noquote() << "设置授信设备成功"; QMessageBox::information(this, "成功", "已设置授信设备"); } catch (const std::runtime_error &e) { - addLog("设置授信设备失败:" + QString(e.what())); + qWarning().noquote() << "设置授信设备失败:" + QString(e.what()); QMessageBox::critical(this, "错误", "设置授信设备失败:\n" + QString(e.what())); } }); @@ -189,12 +199,12 @@ MainWindow::MainWindow(QWidget *parent) : settings->value("ZJUConnect/ServerAddress").toString(), settings->value("ZJUConnect/ServerPort").toInt(), currentProfileId, false); - addLog("取消授信设备成功"); + qInfo().noquote() << "取消授信设备成功"; QMessageBox::information(this, "成功", "已取消授信设备"); } catch (const std::runtime_error &e) { - addLog("取消授信设备失败:" + QString(e.what())); + qWarning().noquote() << "取消授信设备失败:" + QString(e.what()); QMessageBox::critical(this, "错误", "取消授信设备失败:\n" + QString(e.what())); } }); @@ -267,7 +277,7 @@ MainWindow::MainWindow(QWidget *parent) : this, [&](QNetworkReply* reply) { if (reply->error() != QNetworkReply::NoError) { - addLog("检查 UI 更新失败。原因是:" + reply->errorString()); + qWarning().noquote() << "检查 UI 更新失败。原因是:" + reply->errorString(); ui->versionLabel->setText( "当前版本:" + QApplication::applicationVersion() + "\n检查 UI 更新失败\n" ); @@ -286,7 +296,7 @@ MainWindow::MainWindow(QWidget *parent) : { latestVersion = latestVersion.mid(1); } - addLog("检查 UI 更新成功。最新版本:" + latestVersion); + qInfo().noquote() << "检查 UI 更新成功。最新版本:" + latestVersion; versionInfo.ui_latest = latestVersion; updateVersionInfo(); @@ -319,7 +329,7 @@ MainWindow::MainWindow(QWidget *parent) : this, [&](QNetworkReply* reply) { if (reply->error() != QNetworkReply::NoError) { - addLog("检查核心更新失败。原因是:" + reply->errorString()); + qWarning().noquote() << "检查核心更新失败。原因是:" + reply->errorString(); ui->versionLabel->setText( "当前版本:" + QApplication::applicationVersion() + "\n检查核心更新失败\n" ); @@ -338,7 +348,7 @@ MainWindow::MainWindow(QWidget *parent) : { latestVersion = latestVersion.mid(1); } - addLog("检查核心更新成功。最新版本:" + latestVersion); + qInfo().noquote() << "检查核心更新成功。最新版本:" + latestVersion; versionInfo.core_latest = latestVersion; updateVersionInfo(); @@ -349,7 +359,7 @@ MainWindow::MainWindow(QWidget *parent) : if (latestVersionQ > nowVersionQ || (latestVersionQ == nowVersionQ && latestVersion.right(latestVersionSuffix) != nowVersion.right(nowVersionSuffix))) { - addLog("核心版本存在更新,可手动更新或通知开发者更新。"); + qInfo().noquote() << "核心版本存在更新,可手动更新或通知开发者更新。"; } }); @@ -404,12 +414,6 @@ void MainWindow::changeEvent(QEvent *event) } } -void MainWindow::addLog(const QString &log) -{ - QString timeString = QDateTime::currentDateTime().toString("yyyy/MM/dd hh:mm:ss"); - ui->logPlainTextEdit->appendPlainText(timeString + " " + log.trimmed()); -} - void MainWindow::clearLog() { ui->logPlainTextEdit->clear(); @@ -624,7 +628,7 @@ bool MainWindow::switchProfile(const QString &profileId) clearLog(); refreshProfileMenu(); - addLog("已切换到配置:" + currentProfileId); + qInfo().noquote() << "已切换到配置:" + currentProfileId; return true; } @@ -700,7 +704,7 @@ void MainWindow::renameCurrentProfile() updateVersionInfo(); refreshProfileMenu(); clearLog(); - addLog("当前配置已重命名为:" + currentProfileId); + qInfo().noquote() << "当前配置已重命名为:" + currentProfileId; } void MainWindow::deleteCurrentProfile() @@ -740,7 +744,7 @@ void MainWindow::deleteCurrentProfile() } refreshProfileMenu(); - addLog("已删除配置:" + removedProfileId); + qInfo().noquote() << "已删除配置:" + removedProfileId; } void MainWindow::checkUpdate() @@ -748,11 +752,11 @@ void MainWindow::checkUpdate() try { versionInfo.core_version = Utils::checkCoreVersion(this); - addLog("检查核心版本成功:" + versionInfo.core_version); + qInfo().noquote() << "检查核心版本成功:" + versionInfo.core_version; } catch (const std::runtime_error& e) { - addLog("检查核心版本失败:" + QString(e.what())); + qWarning().noquote() << "检查核心版本失败:" + QString(e.what()); versionInfo.core_version = "错误"; } QNetworkRequest request(QUrl("https://api.github.com/repos/" + Utils::REPO_NAME + "/releases/latest")); diff --git a/mainwindow.h b/mainwindow.h index 048e32a..5384168 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -24,17 +24,18 @@ namespace Ui class MainWindow; } +class ApplicationLogger; +class CoreLogFile; + class MainWindow : public QMainWindow { Q_OBJECT public: - explicit MainWindow(QWidget *parent = nullptr); + explicit MainWindow(ApplicationLogger *logger, QWidget *parent = nullptr); ~MainWindow() override; - void addLog(const QString &log); - public slots: void cleanUpWhenQuit(); @@ -100,6 +101,8 @@ private: QAction *deleteProfileAction; ConnectionSession *connectionSession = nullptr; SystemProxySession *systemProxySession = nullptr; + ApplicationLogger *applicationLogger; + CoreLogFile *coreLogFile = nullptr; QNetworkAccessManager *checkUpdateNAM; QNetworkAccessManager *checkCoreUpdateNAM; QSettings *settings; diff --git a/tests/applicationlogger_test.cpp b/tests/applicationlogger_test.cpp new file mode 100644 index 0000000..1f6e5fb --- /dev/null +++ b/tests/applicationlogger_test.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include + +#include "application/applicationlogger.h" + +Q_LOGGING_CATEGORY(backgroundLog, "logger.background") + +namespace +{ +bool publishesQtAndCoreEntries() +{ + QStringList entries; + { + ApplicationLogger logger; + QObject::connect(&logger, &ApplicationLogger::entryAdded, + [&entries](const QString &entry) { entries.append(entry); }); + + qInfo().noquote() << "application event"; + QThread *loggingThread = QThread::create( + []() { qCWarning(backgroundLog).noquote() << "background warning"; } + ); + loggingThread->start(); + loggingThread->wait(); + delete loggingThread; + QCoreApplication::processEvents(); + + logger.appendCoreOutput("standard output\ncontinued standard output"); + logger.appendCoreOutput("error output"); + } + + const QString timestampPattern = R"(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2})"; + const bool entriesArePublished = entries.size() == 4 + && QRegularExpression( + "^\\[INFO\\] " + timestampPattern + " application event$" + ).match(entries.at(0)).hasMatch() + && QRegularExpression( + "^\\[WARNING\\] " + timestampPattern + " background warning$" + ).match(entries.at(1)).hasMatch() + && entries.at(2) == "[CORE] standard output\n[CORE] continued standard output" + && entries.at(3) == "[CORE] error output"; + + if (!entriesArePublished) + { + qCritical() << "publishesQtAndCoreEntries failed"; + return false; + } + return true; +} +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + QCoreApplication::setApplicationName("LoggerTest"); + QCoreApplication::setApplicationVersion("1.0"); + return publishesQtAndCoreEntries() ? 0 : 1; +} diff --git a/tests/corelogfile_test.cpp b/tests/corelogfile_test.cpp new file mode 100644 index 0000000..db1da24 --- /dev/null +++ b/tests/corelogfile_test.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +#include "infrastructure/corelogfile.h" + +namespace +{ +bool writesOnlyRawCoreOutput() +{ + QTemporaryDir temporaryDirectory; + if (!temporaryDirectory.isValid()) + { + qCritical() << "Unable to create temporary directory"; + return false; + } + + const QString logPath = temporaryDirectory.filePath("core.log"); + { + CoreLogFile logFile(logPath); + if (!logFile.isOpen() || logFile.filePath() != logPath) + { + qCritical() << "Core log file did not open the requested path"; + return false; + } + + logFile.appendOutput("standard output\ncontinued standard output"); + logFile.appendOutput("error output"); + } + + QFile logFile(logPath); + if (!logFile.open(QIODevice::ReadOnly | QIODevice::Text)) + { + qCritical() << "Unable to read generated core log"; + return false; + } + + const QString contents = QString::fromUtf8(logFile.readAll()); + if (contents != "standard output\ncontinued standard output\nerror output\n") + { + qCritical().noquote() << "writesOnlyRawCoreOutput failed:\n" << contents; + return false; + } + return true; +} +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + return writesOnlyRawCoreOutput() ? 0 : 1; +} diff --git a/zjuconnectcontroller/zjuconnectcontroller.cpp b/zjuconnectcontroller/zjuconnectcontroller.cpp index 2ebc842..950e4d5 100644 --- a/zjuconnectcontroller/zjuconnectcontroller.cpp +++ b/zjuconnectcontroller/zjuconnectcontroller.cpp @@ -3,9 +3,8 @@ #include "core/coreoutputparser.h" #include "utils/utils.h" #include -#include +#include #include -#include #include #include @@ -13,19 +12,6 @@ ZjuConnectController::ZjuConnectController(QObject *parent) : QObject(parent) { zjuConnectProcess = new QProcess(this); - // 初始化日志文件 - logFile = new QFile(Utils::getLogFilePath()); - if (logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) - { - logStream = new QTextStream(logFile); - logStream->setEncoding(QStringConverter::Utf8); - QString startMsg = "=== Log started at " + QDateTime::currentDateTime().toString("yyyy/MM/dd hh:mm:ss") + - " with " + QCoreApplication::applicationName() + " " + QCoreApplication::applicationVersion() + - " ===\n"; - *logStream << startMsg; - logStream->flush(); - } - connect(zjuConnectProcess, &QProcess::readyReadStandardOutput, this, [this]() { processOutput(standardOutputBuffer, zjuConnectProcess->readAllStandardOutput()); @@ -42,13 +28,12 @@ ZjuConnectController::ZjuConnectController(QObject *parent) : QObject(parent) { return; } - QString timeString = QDateTime::currentDateTime().toString("yyyy/MM/dd hh:mm:ss"); QString errorString = zjuConnectProcess->errorString(); - emit outputRead(timeString + " 退出原因:" + errorString); + qWarning().noquote() << "退出原因:" + errorString; if (errorString.contains("No such file or directory") || errorString.contains("not found") || errorString.contains("找不到")) { - emit outputRead(timeString + " 核心路径:" + zjuConnectProcess->program()); + qWarning().noquote() << "核心路径:" + zjuConnectProcess->program(); emit error(ZJU_ERROR::PROGRAM_NOT_FOUND); } }); @@ -58,8 +43,7 @@ ZjuConnectController::ZjuConnectController(QObject *parent) : QObject(parent) processOutput(standardOutputBuffer, zjuConnectProcess->readAllStandardOutput(), true); processOutput(standardErrorBuffer, zjuConnectProcess->readAllStandardError(), true); stopRequested = false; - QString timeString = QDateTime::currentDateTime().toString("yyyy/MM/dd hh:mm:ss"); - emit outputRead(timeString + " 退出原因:" "进程已结束"); + qInfo().noquote() << "退出原因:进程已结束"; emit finished(); }); } @@ -96,12 +80,6 @@ void ZjuConnectController::processOutputLines(const QList &lines) const QString output = outputLines.join('\n'); emit outputRead(output); - if (logStream != nullptr) - { - *logStream << output << '\n'; - logStream->flush(); - } - for (const QString &line : outputLines) { switch (CoreOutputParser::parse(line)) @@ -225,16 +203,15 @@ void ZjuConnectController::start(const ConnectionProfile &profile) } const CoreCommand command = CoreCommandBuilder::build(profile, runtimePaths); - QString timeString = QDateTime::currentDateTime().toString("yyyy/MM/dd hh:mm:ss"); - emit outputRead(timeString + " VPN 启动!参数:" + command.loggableArguments.join(' ')); + qInfo().noquote() << "VPN 启动!参数:" + command.loggableArguments.join(' '); if (!profile.credentials.totpSecret.isEmpty()) { - emit outputRead(timeString + " 使用了 TOTP"); + qInfo().noquote() << "使用了 TOTP"; } if (!profile.credentials.certFile.isEmpty()) { - emit outputRead(timeString + " 使用了证书文件"); + qInfo().noquote() << "使用了证书文件"; } QString programToStart = profile.program; @@ -292,24 +269,4 @@ void ZjuConnectController::writeInput(const QByteArray &data) ZjuConnectController::~ZjuConnectController() { stop(); - - // 关闭日志文件 - if (logStream != nullptr) - { - QString endMsg = "=== Log ended at " + QDateTime::currentDateTime().toString("yyyy/MM/dd hh:mm:ss") + " ===\n"; - *logStream << endMsg; - logStream->flush(); - delete logStream; - logStream = nullptr; - } - - if (logFile != nullptr) - { - if (logFile->isOpen()) - { - logFile->close(); - } - delete logFile; - logFile = nullptr; - } } diff --git a/zjuconnectcontroller/zjuconnectcontroller.h b/zjuconnectcontroller/zjuconnectcontroller.h index 79e67e8..f134899 100644 --- a/zjuconnectcontroller/zjuconnectcontroller.h +++ b/zjuconnectcontroller/zjuconnectcontroller.h @@ -55,8 +55,6 @@ private: QString graphFile; - QFile *logFile = nullptr; - QTextStream *logStream = nullptr; CoreOutputBuffer standardOutputBuffer; CoreOutputBuffer standardErrorBuffer; bool stopRequested = false; diff --git a/zjuconnectmode.cpp b/zjuconnectmode.cpp index 194fe9f..f76c3f3 100644 --- a/zjuconnectmode.cpp +++ b/zjuconnectmode.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -8,6 +9,8 @@ #include #include "mainwindow.h" +#include "application/applicationlogger.h" +#include "infrastructure/corelogfile.h" #include "ui_mainwindow.h" #include "sudowindow/sudowindow.h" #include "loginwindow/loginwindow.h" @@ -28,14 +31,13 @@ void MainWindow::initZjuConnect() resetZjuConnectUi(); // 连接服务器 - connect(connectionSession, &ConnectionSession::outputRead, this, - [&](const QString &output) - { - ui->logPlainTextEdit->appendPlainText(output); - }); + connect(connectionSession, &ConnectionSession::outputRead, + applicationLogger, &ApplicationLogger::appendCoreOutput); + connect(connectionSession, &ConnectionSession::outputRead, + coreLogFile, &CoreLogFile::appendOutput); connect(connectionSession, &ConnectionSession::savedSudoPasswordRejected, this, - [&]() { addLog("sudo 密码可能有误,不使用记住的密码"); }); + [&]() { qWarning().noquote() << "sudo 密码可能有误,不使用记住的密码"; }); connect(connectionSession, &ConnectionSession::askSudoPass, this, [&]() @@ -50,18 +52,18 @@ void MainWindow::initZjuConnect() connect(connectionSession, &ConnectionSession::graphCaptcha, this, [&](const QString &graphFile) { - addLog("需要图形验证码"); + qInfo().noquote() << "需要图形验证码"; graphCaptchaWindow = new GraphCaptchaWindow(this); graphCaptchaWindow->setGraph(graphFile); graphCaptchaWindow->show(); connect(graphCaptchaWindow, &GraphCaptchaWindow::finishCaptcha, this, [&](const QByteArray &captcha) { - addLog("图形验证码已提交"); + qInfo().noquote() << "图形验证码已提交"; connectionSession->submitInput(captcha + "\n"); }); }); connect(connectionSession, &ConnectionSession::smsCode, this, [&](bool showSkipSecondaryAuthOption) { - addLog("需要短信验证码"); + qInfo().noquote() << "需要短信验证码"; QDialog smsCodeDialog(this); smsCodeDialog.setWindowTitle("短信验证码"); @@ -93,11 +95,12 @@ void MainWindow::initZjuConnect() if (smsCodeAccepted) { - addLog(skipSecondaryAuth ? "短信验证码已提交(跳过以后的短信验证)" : "短信验证码已提交"); + qInfo().noquote() + << (skipSecondaryAuth ? "短信验证码已提交(跳过以后的短信验证)" : "短信验证码已提交"); } else { - addLog("短信验证码输入已取消"); + qInfo().noquote() << "短信验证码输入已取消"; } QByteArray smsCodeInput = smsCode.toLocal8Bit(); if (skipSecondaryAuth) @@ -108,11 +111,11 @@ void MainWindow::initZjuConnect() }); connect(connectionSession, &ConnectionSession::totpCode, this, [&]() { - addLog("需要 TOTP 验证码"); + qInfo().noquote() << "需要 TOTP 验证码"; bool accepted = false; QString totp = QInputDialog::getText( this, "TOTP 验证码", "请输入 TOTP 验证码:", QLineEdit::Normal, "", &accepted); - addLog(accepted ? "TOTP 验证码已提交" : "TOTP 验证码输入已取消"); + qInfo().noquote() << (accepted ? "TOTP 验证码已提交" : "TOTP 验证码输入已取消"); connectionSession->submitInput(totp.toLocal8Bit() + "\n"); }); @@ -134,7 +137,7 @@ void MainWindow::initZjuConnect() if (ssoUrl.startsWith("/")) ssoUrl = "https://" + serverHost + ssoUrl; - addLog(QStringLiteral("单点登录:") + ssoUrl); + qInfo().noquote() << QStringLiteral("单点登录:") + ssoUrl; ssoLoginWebView->setInitialUrl(QUrl::fromUserInput(ssoUrl)); ssoLoginWebView->setCallbackServerHost(serverHost); ssoLoginWebView->show(); @@ -142,12 +145,12 @@ void MainWindow::initZjuConnect() connect(connectionSession, &ConnectionSession::reconnectScheduled, this, [&](int) { - addLog("正在尝试重新连接..."); + qInfo().noquote() << "正在尝试重新连接..."; }); connect(connectionSession, &ConnectionSession::finished, this, [&](ZJU_ERROR error) { - addLog("VPN 断开!"); + qInfo().noquote() << "VPN 断开!"; if (error != ZJU_ERROR::NONE) { showNotification("VPN", "VPN 意外断开!", QSystemTrayIcon::MessageIcon::Warning); @@ -322,7 +325,7 @@ void MainWindow::initZjuConnect() ).toBool(); if (suppressed) { - addLog("跳过系统代理覆盖警告,因为已设置了不再提示"); + qInfo().noquote() << "跳过系统代理覆盖警告,因为已设置了不再提示"; } else { @@ -350,10 +353,9 @@ void MainWindow::initZjuConnect() } } - addLog( - "设置系统代理:HTTP端口 " + QString::number(http_port) - + ",SOCKS5 端口 " + QString::number(socks_port) - ); + qInfo().noquote() + << "设置系统代理:HTTP端口 " + QString::number(http_port) + + ",SOCKS5 端口 " + QString::number(socks_port); systemProxySession->enable(proxyConfig); }, Qt::SingleShotConnection);