1
0
forked from BRT/arc

Refactor LogArchiveSender to add collectInto for file bundling; update TaskDumpRecorder to unify archive creation for tasks and logs.

This commit is contained in:
slava 2026-06-01 12:45:10 +05:00
parent c5f0c035fe
commit f6d895ab34
3 changed files with 50 additions and 40 deletions

View File

@ -270,16 +270,11 @@ void TaskDumpRecorder::finishInternal(const QString &errorMessage) {
const QString archivePath = m_folder + ".zip"; const QString archivePath = m_folder + ".zip";
const QString text = buildCaption(errorMessage); const QString text = buildCaption(errorMessage);
// Тот же идентификатор профиля/материала/телефона добавляем в caption архива // Имя файла архива на relay: <bank>_<task>_<timestamp>.zip — чтобы дамп
// логов ([LOGS]), чтобы по общему api-токену можно было разделить операторов. // сразу читался по банку и типу задачи без открытия caption'а.
QStringList logsIdLines; const QString stamp = QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss");
if (!m_meta.bankProfileId.isEmpty()) const QString archiveFilename =
logsIdLines << QStringLiteral("Profile: %1").arg(m_meta.bankProfileId.toHtmlEscaped()); QStringLiteral("%1_%2_%3.zip").arg(m_bank, m_taskKind, stamp);
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"));
// Финальный скриншот шлём отдельным полем `image`, поэтому сразу читаем // Финальный скриншот шлём отдельным полем `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; QByteArray archiveBytes;
if (zipFolder(folder, archivePath)) { if (zipFolder(folder, archivePath)) {
archiveBytes = readFileBytes(archivePath); archiveBytes = readFileBytes(archivePath);
@ -314,7 +314,8 @@ void TaskDumpRecorder::finishInternal(const QString &errorMessage) {
Qt::QueuedConnection, Qt::QueuedConnection,
Q_ARG(QString, text), Q_ARG(QString, text),
Q_ARG(QByteArray, archiveBytes), Q_ARG(QByteArray, archiveBytes),
Q_ARG(QByteArray, finalScreenshot)); Q_ARG(QByteArray, finalScreenshot),
Q_ARG(QString, archiveFilename));
if (!dispatched) { if (!dispatched) {
qCritical() << "[TaskDumpRecorder] invokeMethod(postMonitoringDump) FAILED — " qCritical() << "[TaskDumpRecorder] invokeMethod(postMonitoringDump) FAILED — "
"slot not found or wrong signature for" << folder; "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 qWarning() << "[TaskDumpRecorder] no NetworkService — dump dropped for" << folder
<< "(setNetworkService was not called at app startup?)"; << "(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); QObject::connect(bg, &QThread::finished, bg, &QObject::deleteLater);
bg->start(); bg->start();

View File

@ -30,31 +30,11 @@ QString resolveDbPath() {
// Запускается в worker-потоке. Возвращает байты zip или пустой массив. // Запускается в worker-потоке. Возвращает байты zip или пустой массив.
QByteArray buildArchive() { 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_") + const QString tmpDir = QDir::tempPath() + QStringLiteral("/arc_logs_auto_") +
QString::number(QDateTime::currentMSecsSinceEpoch()); QString::number(QDateTime::currentMSecsSinceEpoch());
QDir().mkpath(tmpDir); if (!LogArchiveSender::collectInto(tmpDir)) {
QDir(tmpDir).removeRecursively();
if (hasLog) { return {};
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);
}
} }
const QString archivePath = tmpDir + ".zip"; const QString archivePath = tmpDir + ".zip";
@ -90,6 +70,34 @@ QByteArray buildArchive() {
} // namespace } // 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) { bool LogArchiveSender::dispatch(const QString &label, int eventId, const QString &extraCaptionHtml) {
const QString apiToken = SettingsDAO::get("api_key"); const QString apiToken = SettingsDAO::get("api_key");
const QDateTime now = QDateTime::currentDateTime(); const QDateTime now = QDateTime::currentDateTime();

View File

@ -16,4 +16,10 @@ namespace LogArchiveSender {
// Возвращает true если задача поставлена в очередь; реальный результат отправки // Возвращает true если задача поставлена в очередь; реальный результат отправки
// не отслеживается (fire-and-forget, как у sendMonitoringLog). // не отслеживается (fire-and-forget, как у sendMonitoringLog).
bool dispatch(const QString &label, int eventId = -1, const QString &extraCaptionHtml = {}); bool dispatch(const QString &label, int eventId = -1, const QString &extraCaptionHtml = {});
// Копирует application.log + БД (+WAL/SHM) в destDir (создаёт каталог при
// необходимости). Возвращает true, если скопирован хотя бы один файл.
// Используется TaskDumpRecorder, чтобы вложить логи прямо в архив дампа
// задачи и слать всё одним файлом, а не двумя отдельными архивами.
bool collectInto(const QString &destDir);
} }