diff --git a/main.cpp b/main.cpp index cfe5414..d173348 100644 --- a/main.cpp +++ b/main.cpp @@ -9,6 +9,7 @@ #include "database/dao/CommonDataDAO.h" #include "database/dao/DeviceDAO.h" #include "services/DeviceScreener.h" +#include "services/LogArchiveSender.h" #include #include @@ -341,6 +342,19 @@ int main(int argc, char *argv[]) { lightPalette.setColor(QPalette::HighlightedText, Qt::white); 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"); // Очищаем лог-файл при каждом запуске if (!logFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { @@ -378,13 +392,13 @@ int main(int argc, char *argv[]) { // 2. Если нет api_key — показываем окно для ввода const QString apiKey = SettingsDAO::get("api_key"); - auto showRegistration = [&app]() { + auto showRegistration = [&app, prevLogPath]() { auto *registrationWindow = new RegistrationWindow(); registrationWindow->setAttribute(Qt::WA_DeleteOnClose); registrationWindow->show(); QObject::connect( registrationWindow, &RegistrationWindow::onRegisteredSuccess, registrationWindow, - [registrationWindow, &app]() { + [registrationWindow, &app, prevLogPath]() { auto *mainWindow = new MainWindow(); mainWindow->show(); registrationWindow->close(); @@ -396,6 +410,8 @@ int main(int argc, char *argv[]) { startDeviceScreener(app, net); startEventHandler(app); // startBlackPayByCardTest(); + // Авторизация прошла — шлём лог прошлой сессии в арк-мониторинг. + LogArchiveSender::dispatch("STARTUP", -1, {}, prevLogPath); }, Qt::QueuedConnection); }; @@ -406,7 +422,7 @@ int main(int argc, char *argv[]) { loader->setAttribute(Qt::WA_DeleteOnClose); 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) { auto *errorWindow = new ErrorWindow( "Сервер не отвечает.\n\nПроверьте подключение к интернету и попробуйте снова.", @@ -414,12 +430,12 @@ int main(int argc, char *argv[]) { ); errorWindow->setAttribute(Qt::WA_DeleteOnClose); errorWindow->show(); - QObject::connect(errorWindow, &ErrorWindow::retryRequested, errorWindow, [errorWindow, &app, showRegistration]() { + QObject::connect(errorWindow, &ErrorWindow::retryRequested, errorWindow, [errorWindow, &app, showRegistration, prevLogPath]() { errorWindow->close(); auto *newLoader = new LoaderWindow(); newLoader->setAttribute(Qt::WA_DeleteOnClose); 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) { auto *mainWindow = new MainWindow(); mainWindow->show(); @@ -431,6 +447,8 @@ int main(int argc, char *argv[]) { startDeviceScreener(app, net); startEventHandler(app); // startBlackPayByCardTest(); + // Авторизация прошла — шлём лог прошлой сессии в арк-мониторинг. + LogArchiveSender::dispatch("STARTUP", -1, {}, prevLogPath); } else if (r == 0) { SettingsDAO::remove("api_key"); showRegistration(); @@ -456,6 +474,8 @@ int main(int argc, char *argv[]) { startDeviceScreener(app, net); startEventHandler(app); // startBlackPayByCardTest(); + // Авторизация прошла — шлём лог прошлой сессии в арк-мониторинг. + LogArchiveSender::dispatch("STARTUP", -1, {}, prevLogPath); } else if (result == 0) { SettingsDAO::remove("api_key"); showRegistration(); diff --git a/services/LogArchiveSender.cpp b/services/LogArchiveSender.cpp index 66c31e1..abab421 100644 --- a/services/LogArchiveSender.cpp +++ b/services/LogArchiveSender.cpp @@ -17,10 +17,10 @@ namespace { // Запускается в worker-потоке. Возвращает байты zip или пустой массив. -QByteArray buildArchive() { +QByteArray buildArchive(const QString &sourceLogPath) { const QString tmpDir = QDir::tempPath() + QStringLiteral("/arc_logs_auto_") + QString::number(QDateTime::currentMSecsSinceEpoch()); - if (!LogArchiveSender::collectInto(tmpDir)) { + if (!LogArchiveSender::collectInto(tmpDir, sourceLogPath)) { QDir(tmpDir).removeRecursively(); return {}; } @@ -58,15 +58,25 @@ QByteArray buildArchive() { } // namespace -bool LogArchiveSender::collectInto(const QString &destDir) { - const QString logPath = QCoreApplication::applicationDirPath() + "/application.log"; +bool LogArchiveSender::collectInto(const QString &destDir, const QString &sourceLogPath) { + const bool wholeFile = !sourceLogPath.isEmpty(); + const QString logPath = wholeFile + ? sourceLogPath + : QCoreApplication::applicationDirPath() + "/application.log"; 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; } QDir().mkpath(destDir); + + // Явно заданный файл (напр. лог прошлой сессии) шлём целиком — обрезка по + // последнему часу относительно «сейчас» здесь не нужна и только урезала бы + // давно закрытую сессию. + if (wholeFile) + return QFile::copy(logPath, destDir + "/application.log"); + bool any = false; QFile srcLog(logPath); if (srcLog.open(QIODevice::ReadOnly)) { @@ -125,7 +135,8 @@ bool LogArchiveSender::collectInto(const QString &destDir) { 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 QDateTime now = QDateTime::currentDateTime(); 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); const QString archiveFilename = parts.join(QStringLiteral("_")) + QStringLiteral(".zip"); - auto *worker = QThread::create([text, archiveFilename]() { - const QByteArray archive = buildArchive(); + auto *worker = QThread::create([text, archiveFilename, sourceLogPath]() { + const QByteArray archive = buildArchive(sourceLogPath); + // Исходник прошлой сессии уже скопирован в архив — удаляем, чтобы файлы + // не плодились между запусками (zip и tmp-каталог чистит buildArchive). + if (!sourceLogPath.isEmpty()) + QFile::remove(sourceLogPath); if (archive.isEmpty()) { qWarning() << "[LogArchiveSender] empty archive, skipping dispatch"; return; diff --git a/services/LogArchiveSender.h b/services/LogArchiveSender.h index 17d09bb..6532d7a 100644 --- a/services/LogArchiveSender.h +++ b/services/LogArchiveSender.h @@ -15,11 +15,18 @@ namespace LogArchiveSender { // api-токену различать операторов. // Возвращает true если задача поставлена в очередь; реальный результат отправки // не отслеживается (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, если файл скопирован. + // Если `sourceLogPath` пуст — берётся текущий application.log рядом с exe и + // обрезается по последнему часу (поведение для дампов задач). Если задан — + // указанный файл копируется целиком. // Используется TaskDumpRecorder, чтобы вложить логи прямо в архив дампа // задачи и слать всё одним файлом, а не двумя отдельными архивами. - bool collectInto(const QString &destDir); + bool collectInto(const QString &destDir, const QString &sourceLogPath = {}); }