mirror of
https://github.com/chenx-dust/EZ4Connect.git
synced 2026-09-20 10:23:33 +08:00
refactor: 引入新的日志系统
This commit is contained in:
@@ -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
|
||||
|
||||
143
application/applicationlogger.cpp
Normal file
143
application/applicationlogger.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
#include "applicationlogger.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDateTime>
|
||||
#include <QIODevice>
|
||||
#include <QMetaObject>
|
||||
#include <QRecursiveMutex>
|
||||
#include <QThread>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
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();
|
||||
}
|
||||
37
application/applicationlogger.h
Normal file
37
application/applicationlogger.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifndef APPLICATIONLOGGER_H
|
||||
#define APPLICATIONLOGGER_H
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QObject>
|
||||
#include <QTextStream>
|
||||
|
||||
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
|
||||
@@ -1,8 +1,11 @@
|
||||
#include "authinfowindow.h"
|
||||
|
||||
#include "utils/utils.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QKeyEvent>
|
||||
@@ -34,14 +37,13 @@ AuthInfoWindow::AuthInfoWindow(QWidget *parent)
|
||||
connect(proc_, QOverload<int, QProcess::ExitStatus>::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<QProcess::ProcessError>().valueToKey(error));
|
||||
qWarning().noquote()
|
||||
<< QString("获取可用认证方式失败:")
|
||||
+ QMetaEnum::fromType<QProcess::ProcessError>().valueToKey(error);
|
||||
});
|
||||
|
||||
mainWindow = qobject_cast<MainWindow *>(parent);
|
||||
if (!mainWindow)
|
||||
mainWindow = qobject_cast<MainWindow *>(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() << "正在获取可用认证的方式...";
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
#include <QDialog>
|
||||
#include <QProcess>
|
||||
|
||||
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_;
|
||||
|
||||
59
infrastructure/corelogfile.cpp
Normal file
59
infrastructure/corelogfile.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#include "corelogfile.h"
|
||||
|
||||
#include <QMetaObject>
|
||||
#include <QThread>
|
||||
|
||||
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();
|
||||
}
|
||||
26
infrastructure/corelogfile.h
Normal file
26
infrastructure/corelogfile.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef CORELOGFILE_H
|
||||
#define CORELOGFILE_H
|
||||
|
||||
#include <QFile>
|
||||
#include <QObject>
|
||||
#include <QTextStream>
|
||||
|
||||
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
|
||||
5
main.cpp
5
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);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <QNetworkInterface>
|
||||
#include <QClipboard>
|
||||
#include <QDesktopServices>
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
#include <QCoreApplication>
|
||||
#include <QActionGroup>
|
||||
@@ -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"));
|
||||
|
||||
@@ -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;
|
||||
|
||||
60
tests/applicationlogger_test.cpp
Normal file
60
tests/applicationlogger_test.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QLoggingCategory>
|
||||
#include <QRegularExpression>
|
||||
#include <QThread>
|
||||
|
||||
#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;
|
||||
}
|
||||
53
tests/corelogfile_test.cpp
Normal file
53
tests/corelogfile_test.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -3,9 +3,8 @@
|
||||
#include "core/coreoutputparser.h"
|
||||
#include "utils/utils.h"
|
||||
#include <qcontainerfwd.h>
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QStandardPaths>
|
||||
|
||||
@@ -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<QByteArray> &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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +55,6 @@ private:
|
||||
|
||||
QString graphFile;
|
||||
|
||||
QFile *logFile = nullptr;
|
||||
QTextStream *logStream = nullptr;
|
||||
CoreOutputBuffer standardOutputBuffer;
|
||||
CoreOutputBuffer standardErrorBuffer;
|
||||
bool stopRequested = false;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <QMessageBox>
|
||||
#include <QCheckBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDebug>
|
||||
#include <QInputDialog>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
@@ -8,6 +9,8 @@
|
||||
#include <QApplication>
|
||||
|
||||
#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);
|
||||
|
||||
Reference in New Issue
Block a user