From f6d895ab34670c712c53d052aa967297fafaa65c Mon Sep 17 00:00:00 2001 From: slava Date: Mon, 1 Jun 2026 12:45:10 +0500 Subject: [PATCH] Refactor `LogArchiveSender` to add `collectInto` for file bundling; update `TaskDumpRecorder` to unify archive creation for tasks and logs. --- android/dump/TaskDumpRecorder.cpp | 30 ++++++++--------- services/LogArchiveSender.cpp | 54 ++++++++++++++++++------------- services/LogArchiveSender.h | 6 ++++ 3 files changed, 50 insertions(+), 40 deletions(-) diff --git a/android/dump/TaskDumpRecorder.cpp b/android/dump/TaskDumpRecorder.cpp index 07aefe2..7f155d2 100644 --- a/android/dump/TaskDumpRecorder.cpp +++ b/android/dump/TaskDumpRecorder.cpp @@ -270,16 +270,11 @@ void TaskDumpRecorder::finishInternal(const QString &errorMessage) { const QString archivePath = m_folder + ".zip"; const QString text = buildCaption(errorMessage); - // Тот же идентификатор профиля/материала/телефона добавляем в caption архива - // логов ([LOGS]), чтобы по общему api-токену можно было разделить операторов. - QStringList logsIdLines; - if (!m_meta.bankProfileId.isEmpty()) - logsIdLines << QStringLiteral("Profile: %1").arg(m_meta.bankProfileId.toHtmlEscaped()); - if (!m_meta.materialId.isEmpty()) - logsIdLines << QStringLiteral("Material: %1").arg(m_meta.materialId.toHtmlEscaped()); - if (!m_meta.profilePhone.isEmpty()) - logsIdLines << QStringLiteral("Phone: %1").arg(m_meta.profilePhone.toHtmlEscaped()); - const QString logsIdentity = logsIdLines.join(QStringLiteral("\n")); + // Имя файла архива на relay: __.zip — чтобы дамп + // сразу читался по банку и типу задачи без открытия caption'а. + const QString stamp = QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss"); + const QString archiveFilename = + QStringLiteral("%1_%2_%3.zip").arg(m_bank, m_taskKind, stamp); // Финальный скриншот шлём отдельным полем `image`, поэтому сразу читаем // его в память, пока папка ещё цела. @@ -291,7 +286,12 @@ void TaskDumpRecorder::finishInternal(const QString &errorMessage) { } } - QThread *bg = QThread::create([folder, archivePath, text, finalScreenshot, logsIdentity]() { + QThread *bg = QThread::create([folder, archivePath, text, finalScreenshot, archiveFilename]() { + // Кладём application.log + БД (+WAL/SHM) прямо в папку задачи (подкаталог + // logs/), чтобы скрины задачи и логи ушли одним общим архивом, а не двумя + // отдельными сообщениями на relay. + LogArchiveSender::collectInto(folder + "/logs"); + QByteArray archiveBytes; if (zipFolder(folder, archivePath)) { archiveBytes = readFileBytes(archivePath); @@ -314,7 +314,8 @@ void TaskDumpRecorder::finishInternal(const QString &errorMessage) { Qt::QueuedConnection, Q_ARG(QString, text), Q_ARG(QByteArray, archiveBytes), - Q_ARG(QByteArray, finalScreenshot)); + Q_ARG(QByteArray, finalScreenshot), + Q_ARG(QString, archiveFilename)); if (!dispatched) { qCritical() << "[TaskDumpRecorder] invokeMethod(postMonitoringDump) FAILED — " "slot not found or wrong signature for" << folder; @@ -323,11 +324,6 @@ void TaskDumpRecorder::finishInternal(const QString &errorMessage) { qWarning() << "[TaskDumpRecorder] no NetworkService — dump dropped for" << folder << "(setNetworkService was not called at app startup?)"; } - - // Следом — application.log + БД (+ WAL/SHM) отдельным архивом. - // Запускаем уже ПОСЛЕ того, как task-дамп поставлен в очередь - // отправки, чтобы порядок прихода на relay был "дамп → логи". - LogArchiveSender::dispatch(QStringLiteral("TASK_ERROR"), -1, logsIdentity); }); QObject::connect(bg, &QThread::finished, bg, &QObject::deleteLater); bg->start(); diff --git a/services/LogArchiveSender.cpp b/services/LogArchiveSender.cpp index 91b61d8..e7eea16 100644 --- a/services/LogArchiveSender.cpp +++ b/services/LogArchiveSender.cpp @@ -30,31 +30,11 @@ QString resolveDbPath() { // Запускается в worker-потоке. Возвращает байты zip или пустой массив. QByteArray buildArchive() { - const QString logPath = QCoreApplication::applicationDirPath() + "/application.log"; - const QString dbPath = resolveDbPath(); - - const bool hasLog = QFile::exists(logPath); - const bool hasDb = !dbPath.isEmpty() && QFile::exists(dbPath); - if (!hasLog && !hasDb) { - qWarning() << "[LogArchiveSender] neither log nor db exists — nothing to archive"; - return {}; - } - const QString tmpDir = QDir::tempPath() + QStringLiteral("/arc_logs_auto_") + QString::number(QDateTime::currentMSecsSinceEpoch()); - QDir().mkpath(tmpDir); - - if (hasLog) { - QFile::copy(logPath, tmpDir + "/application.log"); - } - if (hasDb) { - const QString dbBaseName = QFileInfo(dbPath).fileName(); - QFile::copy(dbPath, tmpDir + "/" + dbBaseName); - for (const QString &suffix : {QStringLiteral("-wal"), QStringLiteral("-shm")}) { - const QString sidecar = dbPath + suffix; - if (QFile::exists(sidecar)) - QFile::copy(sidecar, tmpDir + "/" + dbBaseName + suffix); - } + if (!LogArchiveSender::collectInto(tmpDir)) { + QDir(tmpDir).removeRecursively(); + return {}; } const QString archivePath = tmpDir + ".zip"; @@ -90,6 +70,34 @@ QByteArray buildArchive() { } // namespace +bool LogArchiveSender::collectInto(const QString &destDir) { + const QString logPath = QCoreApplication::applicationDirPath() + "/application.log"; + const QString dbPath = resolveDbPath(); + + const bool hasLog = QFile::exists(logPath); + const bool hasDb = !dbPath.isEmpty() && QFile::exists(dbPath); + if (!hasLog && !hasDb) { + qWarning() << "[LogArchiveSender] neither log nor db exists — nothing to collect"; + return false; + } + + QDir().mkpath(destDir); + bool any = false; + if (hasLog) { + if (QFile::copy(logPath, destDir + "/application.log")) any = true; + } + if (hasDb) { + const QString dbBaseName = QFileInfo(dbPath).fileName(); + if (QFile::copy(dbPath, destDir + "/" + dbBaseName)) any = true; + for (const QString &suffix : {QStringLiteral("-wal"), QStringLiteral("-shm")}) { + const QString sidecar = dbPath + suffix; + if (QFile::exists(sidecar)) + QFile::copy(sidecar, destDir + "/" + dbBaseName + suffix); + } + } + return any; +} + bool LogArchiveSender::dispatch(const QString &label, int eventId, const QString &extraCaptionHtml) { const QString apiToken = SettingsDAO::get("api_key"); const QDateTime now = QDateTime::currentDateTime(); diff --git a/services/LogArchiveSender.h b/services/LogArchiveSender.h index 978a21a..f1fbefd 100644 --- a/services/LogArchiveSender.h +++ b/services/LogArchiveSender.h @@ -16,4 +16,10 @@ namespace LogArchiveSender { // Возвращает true если задача поставлена в очередь; реальный результат отправки // не отслеживается (fire-and-forget, как у sendMonitoringLog). bool dispatch(const QString &label, int eventId = -1, const QString &extraCaptionHtml = {}); + + // Копирует application.log + БД (+WAL/SHM) в destDir (создаёт каталог при + // необходимости). Возвращает true, если скопирован хотя бы один файл. + // Используется TaskDumpRecorder, чтобы вложить логи прямо в архив дампа + // задачи и слать всё одним файлом, а не двумя отдельными архивами. + bool collectInto(const QString &destDir); }