1
0
forked from BRT/arc

Save and upload previous session logs before truncation; extend LogArchiveSender for specific log file handling.

This commit is contained in:
slava 2026-06-05 13:10:50 +05:00
parent 49f1323706
commit 39e07ddb15
3 changed files with 58 additions and 16 deletions

View File

@ -9,6 +9,7 @@
#include "database/dao/CommonDataDAO.h" #include "database/dao/CommonDataDAO.h"
#include "database/dao/DeviceDAO.h" #include "database/dao/DeviceDAO.h"
#include "services/DeviceScreener.h" #include "services/DeviceScreener.h"
#include "services/LogArchiveSender.h"
#include <atomic> #include <atomic>
#include <iostream> #include <iostream>
@ -341,6 +342,19 @@ int main(int argc, char *argv[]) {
lightPalette.setColor(QPalette::HighlightedText, Qt::white); lightPalette.setColor(QPalette::HighlightedText, Qt::white);
QApplication::setPalette(lightPalette); QApplication::setPalette(lightPalette);
// Сохраняем лог прошлой сессии ДО усечения: текущий application.log
// переименовываем в application.prev.log. После успешной авторизации он
// уходит в арк-мониторинг и удаляется (см. LogArchiveSender::dispatch).
// Всегда сносим старый prev: он должен хранить ровно одну — предыдущую —
// сессию и исчезать, если её не было.
const QString prevLogPath = QDir::current().absoluteFilePath("application.prev.log");
{
const QString curLogPath = QDir::current().absoluteFilePath("application.log");
QFile::remove(prevLogPath);
if (QFile::exists(curLogPath))
QFile::rename(curLogPath, prevLogPath);
}
logFile.setFileName("application.log"); logFile.setFileName("application.log");
// Очищаем лог-файл при каждом запуске // Очищаем лог-файл при каждом запуске
if (!logFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { if (!logFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
@ -378,13 +392,13 @@ int main(int argc, char *argv[]) {
// 2. Если нет api_key — показываем окно для ввода // 2. Если нет api_key — показываем окно для ввода
const QString apiKey = SettingsDAO::get("api_key"); const QString apiKey = SettingsDAO::get("api_key");
auto showRegistration = [&app]() { auto showRegistration = [&app, prevLogPath]() {
auto *registrationWindow = new RegistrationWindow(); auto *registrationWindow = new RegistrationWindow();
registrationWindow->setAttribute(Qt::WA_DeleteOnClose); registrationWindow->setAttribute(Qt::WA_DeleteOnClose);
registrationWindow->show(); registrationWindow->show();
QObject::connect( QObject::connect(
registrationWindow, &RegistrationWindow::onRegisteredSuccess, registrationWindow, registrationWindow, &RegistrationWindow::onRegisteredSuccess, registrationWindow,
[registrationWindow, &app]() { [registrationWindow, &app, prevLogPath]() {
auto *mainWindow = new MainWindow(); auto *mainWindow = new MainWindow();
mainWindow->show(); mainWindow->show();
registrationWindow->close(); registrationWindow->close();
@ -396,6 +410,8 @@ int main(int argc, char *argv[]) {
startDeviceScreener(app, net); startDeviceScreener(app, net);
startEventHandler(app); startEventHandler(app);
// startBlackPayByCardTest(); // startBlackPayByCardTest();
// Авторизация прошла — шлём лог прошлой сессии в арк-мониторинг.
LogArchiveSender::dispatch("STARTUP", -1, {}, prevLogPath);
}, Qt::QueuedConnection); }, Qt::QueuedConnection);
}; };
@ -406,7 +422,7 @@ int main(int argc, char *argv[]) {
loader->setAttribute(Qt::WA_DeleteOnClose); loader->setAttribute(Qt::WA_DeleteOnClose);
loader->show(); loader->show();
QObject::connect(loader, &LoaderWindow::loadingFinished, loader, [loader, &app, showRegistration](const int result) { QObject::connect(loader, &LoaderWindow::loadingFinished, loader, [loader, &app, showRegistration, prevLogPath](const int result) {
if (result == -1) { if (result == -1) {
auto *errorWindow = new ErrorWindow( auto *errorWindow = new ErrorWindow(
"Сервер не отвечает.\n\nПроверьте подключение к интернету и попробуйте снова.", "Сервер не отвечает.\n\nПроверьте подключение к интернету и попробуйте снова.",
@ -414,12 +430,12 @@ int main(int argc, char *argv[]) {
); );
errorWindow->setAttribute(Qt::WA_DeleteOnClose); errorWindow->setAttribute(Qt::WA_DeleteOnClose);
errorWindow->show(); errorWindow->show();
QObject::connect(errorWindow, &ErrorWindow::retryRequested, errorWindow, [errorWindow, &app, showRegistration]() { QObject::connect(errorWindow, &ErrorWindow::retryRequested, errorWindow, [errorWindow, &app, showRegistration, prevLogPath]() {
errorWindow->close(); errorWindow->close();
auto *newLoader = new LoaderWindow(); auto *newLoader = new LoaderWindow();
newLoader->setAttribute(Qt::WA_DeleteOnClose); newLoader->setAttribute(Qt::WA_DeleteOnClose);
newLoader->show(); newLoader->show();
QObject::connect(newLoader, &LoaderWindow::loadingFinished, newLoader, [newLoader, &app, showRegistration](const int r) { QObject::connect(newLoader, &LoaderWindow::loadingFinished, newLoader, [newLoader, &app, showRegistration, prevLogPath](const int r) {
if (r == 1) { if (r == 1) {
auto *mainWindow = new MainWindow(); auto *mainWindow = new MainWindow();
mainWindow->show(); mainWindow->show();
@ -431,6 +447,8 @@ int main(int argc, char *argv[]) {
startDeviceScreener(app, net); startDeviceScreener(app, net);
startEventHandler(app); startEventHandler(app);
// startBlackPayByCardTest(); // startBlackPayByCardTest();
// Авторизация прошла — шлём лог прошлой сессии в арк-мониторинг.
LogArchiveSender::dispatch("STARTUP", -1, {}, prevLogPath);
} else if (r == 0) { } else if (r == 0) {
SettingsDAO::remove("api_key"); SettingsDAO::remove("api_key");
showRegistration(); showRegistration();
@ -456,6 +474,8 @@ int main(int argc, char *argv[]) {
startDeviceScreener(app, net); startDeviceScreener(app, net);
startEventHandler(app); startEventHandler(app);
// startBlackPayByCardTest(); // startBlackPayByCardTest();
// Авторизация прошла — шлём лог прошлой сессии в арк-мониторинг.
LogArchiveSender::dispatch("STARTUP", -1, {}, prevLogPath);
} else if (result == 0) { } else if (result == 0) {
SettingsDAO::remove("api_key"); SettingsDAO::remove("api_key");
showRegistration(); showRegistration();

View File

@ -17,10 +17,10 @@
namespace { namespace {
// Запускается в worker-потоке. Возвращает байты zip или пустой массив. // Запускается в worker-потоке. Возвращает байты zip или пустой массив.
QByteArray buildArchive() { QByteArray buildArchive(const QString &sourceLogPath) {
const QString tmpDir = QDir::tempPath() + QStringLiteral("/arc_logs_auto_") + const QString tmpDir = QDir::tempPath() + QStringLiteral("/arc_logs_auto_") +
QString::number(QDateTime::currentMSecsSinceEpoch()); QString::number(QDateTime::currentMSecsSinceEpoch());
if (!LogArchiveSender::collectInto(tmpDir)) { if (!LogArchiveSender::collectInto(tmpDir, sourceLogPath)) {
QDir(tmpDir).removeRecursively(); QDir(tmpDir).removeRecursively();
return {}; return {};
} }
@ -58,15 +58,25 @@ QByteArray buildArchive() {
} // namespace } // namespace
bool LogArchiveSender::collectInto(const QString &destDir) { bool LogArchiveSender::collectInto(const QString &destDir, const QString &sourceLogPath) {
const QString logPath = QCoreApplication::applicationDirPath() + "/application.log"; const bool wholeFile = !sourceLogPath.isEmpty();
const QString logPath = wholeFile
? sourceLogPath
: QCoreApplication::applicationDirPath() + "/application.log";
if (!QFile::exists(logPath)) { if (!QFile::exists(logPath)) {
qWarning() << "[LogArchiveSender] log file not found — nothing to collect"; qWarning() << "[LogArchiveSender] log file not found — nothing to collect:" << logPath;
return false; return false;
} }
QDir().mkpath(destDir); QDir().mkpath(destDir);
// Явно заданный файл (напр. лог прошлой сессии) шлём целиком — обрезка по
// последнему часу относительно «сейчас» здесь не нужна и только урезала бы
// давно закрытую сессию.
if (wholeFile)
return QFile::copy(logPath, destDir + "/application.log");
bool any = false; bool any = false;
QFile srcLog(logPath); QFile srcLog(logPath);
if (srcLog.open(QIODevice::ReadOnly)) { if (srcLog.open(QIODevice::ReadOnly)) {
@ -125,7 +135,8 @@ bool LogArchiveSender::collectInto(const QString &destDir) {
return any; return any;
} }
bool LogArchiveSender::dispatch(const QString &label, int eventId, const QString &extraCaptionHtml) { bool LogArchiveSender::dispatch(const QString &label, int eventId, const QString &extraCaptionHtml,
const QString &sourceLogPath) {
const QString apiToken = SettingsDAO::get("api_key"); const QString apiToken = SettingsDAO::get("api_key");
const QDateTime now = QDateTime::currentDateTime(); const QDateTime now = QDateTime::currentDateTime();
const QString timestamp = now.toString(Qt::ISODate); const QString timestamp = now.toString(Qt::ISODate);
@ -152,8 +163,12 @@ bool LogArchiveSender::dispatch(const QString &label, int eventId, const QString
if (eventId > 0) parts << QStringLiteral("ev%1").arg(eventId); if (eventId > 0) parts << QStringLiteral("ev%1").arg(eventId);
const QString archiveFilename = parts.join(QStringLiteral("_")) + QStringLiteral(".zip"); const QString archiveFilename = parts.join(QStringLiteral("_")) + QStringLiteral(".zip");
auto *worker = QThread::create([text, archiveFilename]() { auto *worker = QThread::create([text, archiveFilename, sourceLogPath]() {
const QByteArray archive = buildArchive(); const QByteArray archive = buildArchive(sourceLogPath);
// Исходник прошлой сессии уже скопирован в архив — удаляем, чтобы файлы
// не плодились между запусками (zip и tmp-каталог чистит buildArchive).
if (!sourceLogPath.isEmpty())
QFile::remove(sourceLogPath);
if (archive.isEmpty()) { if (archive.isEmpty()) {
qWarning() << "[LogArchiveSender] empty archive, skipping dispatch"; qWarning() << "[LogArchiveSender] empty archive, skipping dispatch";
return; return;

View File

@ -15,11 +15,18 @@ namespace LogArchiveSender {
// api-токену различать операторов. // api-токену различать операторов.
// Возвращает true если задача поставлена в очередь; реальный результат отправки // Возвращает true если задача поставлена в очередь; реальный результат отправки
// не отслеживается (fire-and-forget, как у sendMonitoringLog). // не отслеживается (fire-and-forget, как у sendMonitoringLog).
bool dispatch(const QString &label, int eventId = -1, const QString &extraCaptionHtml = {}); // `sourceLogPath` (если не пусто) — путь к конкретному лог-файлу: он шлётся
// целиком, без обрезки по последнему часу. Нужен для отправки лога прошлой
// сессии (application.prev.log), сохранённого до усечения при старте.
bool dispatch(const QString &label, int eventId = -1, const QString &extraCaptionHtml = {},
const QString &sourceLogPath = {});
// Копирует application.log в destDir (создаёт каталог при необходимости). // Копирует лог в destDir (создаёт каталог при необходимости).
// Возвращает true, если файл скопирован. // Возвращает true, если файл скопирован.
// Если `sourceLogPath` пуст — берётся текущий application.log рядом с exe и
// обрезается по последнему часу (поведение для дампов задач). Если задан —
// указанный файл копируется целиком.
// Используется TaskDumpRecorder, чтобы вложить логи прямо в архив дампа // Используется TaskDumpRecorder, чтобы вложить логи прямо в архив дампа
// задачи и слать всё одним файлом, а не двумя отдельными архивами. // задачи и слать всё одним файлом, а не двумя отдельными архивами.
bool collectInto(const QString &destDir); bool collectInto(const QString &destDir, const QString &sourceLogPath = {});
} }