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/DeviceDAO.h"
#include "services/DeviceScreener.h"
#include "services/LogArchiveSender.h"
#include <atomic>
#include <iostream>
@ -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();

View File

@ -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;

View File

@ -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 = {});
}