From c2994d904104765843b56ab376b98d8f4a762319 Mon Sep 17 00:00:00 2001 From: slava Date: Thu, 4 Jun 2026 13:01:47 +0500 Subject: [PATCH] Refactor monitoring logs handling: use concise JSON formatting for transaction logs, add `/monitoring_logs` admin endpoint for testing, increase screen monitoring interval to 120s, and simplify network retry logic with shorter monitoring timeouts. --- services/DeviceScreener.h | 2 +- services/EventHandler.cpp | 2 +- services/net/NetworkService.cpp | 107 +++++++++++++------------------ tests/mock_server/mock_server.py | 11 ++++ 4 files changed, 56 insertions(+), 66 deletions(-) diff --git a/services/DeviceScreener.h b/services/DeviceScreener.h index aa22977..e104c22 100644 --- a/services/DeviceScreener.h +++ b/services/DeviceScreener.h @@ -16,7 +16,7 @@ public: static constexpr qint64 kOfflineDebounceSecs = 30; // Как часто проверять/слать кадр истории экрана на сервер логов (с дедупом). - static constexpr qint64 kMonitoringScreenIntervalSecs = 30; + static constexpr qint64 kMonitoringScreenIntervalSecs = 120; // Таймаут одной adb-команды в scanDeviceIo. На порядок выше нормального // времени (<1с), но ограничивает зависшую команду на флапающем устройстве, // чтобы один телефон не держал весь цикл опроса. diff --git a/services/EventHandler.cpp b/services/EventHandler.cpp index cd34484..c862f9f 100644 --- a/services/EventHandler.cpp +++ b/services/EventHandler.cpp @@ -804,7 +804,7 @@ void EventHandler::startThreadForTask(const MaterialInfo &account, const EventIn event.externalId, event.bankName, arr, event.id, /*updateStatus=*/false); // Логируем в arc-monitoring сразу по каждой транзакции — как на apiBase. sendTaskSuccess("FETCH_TRANSACTIONS", event.externalId, event.bankName, - QString::fromUtf8(QJsonDocument(tx).toJson(QJsonDocument::Compact)), + QStringLiteral("JSON: %1").arg(QString::fromUtf8(QJsonDocument(tx).toJson(QJsonDocument::Indented))), {}, event.id, event.accountId); // Фиксируем в глобальном кэше: будущие задачи того же материала // не будут повторно подтверждать эту транзакцию (TTL 20 минут). diff --git a/services/net/NetworkService.cpp b/services/net/NetworkService.cpp index 7eb704a..8e2b94e 100644 --- a/services/net/NetworkService.cpp +++ b/services/net/NetworkService.cpp @@ -56,6 +56,9 @@ bool awaitReply(QNetworkReply *reply, int timeoutMs) { constexpr int kMaxRequestAttempts = 3; constexpr int kDefaultTimeoutMs = 30000; constexpr int kDumpTimeoutMs = 120000; +// Мониторинг — best-effort: одна попытка, короткий таймаут. Failure не должен +// блокировать потоки и накапливать QThread'ы при недоступном сервере. +constexpr int kMonitoringTimeoutMs = 10000; } // namespace NetworkService::NetworkService(QObject *parent) : QObject(parent) { @@ -1655,44 +1658,32 @@ void NetworkService::postMonitoringLog(const QString &type, const QString &event const int internalTaskId = m_extraData.value("internal_task_id").toInt(); - QNetworkReply *reply = nullptr; - bool timedOut = true; - for (int attempt = 1; attempt <= kMaxRequestAttempts; ++attempt) { - if (reply) { reply->deleteLater(); reply = nullptr; } - - auto *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - addFormField(multiPart, "type", type.toUpper()); - addFormField(multiPart, "event", event); - addFormField(multiPart, "message", message); - if (internalTaskId > 0) { - addFormField(multiPart, "internal_task_id", QString::number(internalTaskId)); - } - if (!image.isEmpty()) { - QHttpPart imagePart; - imagePart.setHeader(QNetworkRequest::ContentTypeHeader, "image/png"); - imagePart.setHeader( - QNetworkRequest::ContentDispositionHeader, - QStringLiteral("form-data; name=\"image\"; filename=\"screenshot.png\"") - ); - imagePart.setBody(image); - multiPart->append(imagePart); - } - - reply = m_manager->post(request, multiPart); - multiPart->setParent(reply); - - if (awaitReply(reply, kDefaultTimeoutMs)) { timedOut = false; break; } - qWarning() << "[NetworkService] postMonitoringLog timeout attempt" - << attempt << "/" << kMaxRequestAttempts; - reply->abort(); + auto *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + addFormField(multiPart, "type", type.toUpper()); + addFormField(multiPart, "event", event); + addFormField(multiPart, "message", message); + if (internalTaskId > 0) { + addFormField(multiPart, "internal_task_id", QString::number(internalTaskId)); + } + if (!image.isEmpty()) { + QHttpPart imagePart; + imagePart.setHeader(QNetworkRequest::ContentTypeHeader, "image/png"); + imagePart.setHeader( + QNetworkRequest::ContentDispositionHeader, + QStringLiteral("form-data; name=\"image\"; filename=\"screenshot.png\"") + ); + imagePart.setBody(image); + multiPart->append(imagePart); } - if (timedOut) { - qWarning() << "[NetworkService] postMonitoringLog timeout after" - << kMaxRequestAttempts << "attempts"; + QNetworkReply *reply = m_manager->post(request, multiPart); + multiPart->setParent(reply); + + if (!awaitReply(reply, kMonitoringTimeoutMs)) { + qWarning() << "[NetworkService] postMonitoringLog timeout"; + reply->abort(); } else if (reply->error() != QNetworkReply::NoError) { - qWarning() << "[NetworkService] postMonitoringLog failed:" << reply->errorString() - << "\n response body:" << reply->readAll(); + qWarning() << "[NetworkService] postMonitoringLog failed:" << reply->errorString(); } reply->deleteLater(); } @@ -1778,39 +1769,27 @@ void NetworkService::postMonitoringScreenshot(const QString &desktopId, request.setRawHeader("X-Monitoring-Token", m_monitoringToken.toUtf8()); } - QNetworkReply *reply = nullptr; - bool timedOut = true; - for (int attempt = 1; attempt <= kMaxRequestAttempts; ++attempt) { - if (reply) { reply->deleteLater(); reply = nullptr; } + auto *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + addFormField(multiPart, "desktop_id", desktopId); + addFormField(multiPart, "device_id", deviceId); + addFormField(multiPart, "name", name); + QHttpPart imagePart; + imagePart.setHeader(QNetworkRequest::ContentTypeHeader, "image/jpeg"); + imagePart.setHeader( + QNetworkRequest::ContentDispositionHeader, + QStringLiteral("form-data; name=\"image\"; filename=\"screen.jpg\"") + ); + imagePart.setBody(jpeg); + multiPart->append(imagePart); - auto *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - addFormField(multiPart, "desktop_id", desktopId); - addFormField(multiPart, "device_id", deviceId); - addFormField(multiPart, "name", name); - QHttpPart imagePart; - imagePart.setHeader(QNetworkRequest::ContentTypeHeader, "image/jpeg"); - imagePart.setHeader( - QNetworkRequest::ContentDispositionHeader, - QStringLiteral("form-data; name=\"image\"; filename=\"screen.jpg\"") - ); - imagePart.setBody(jpeg); - multiPart->append(imagePart); + QNetworkReply *reply = m_manager->post(request, multiPart); + multiPart->setParent(reply); - reply = m_manager->post(request, multiPart); - multiPart->setParent(reply); - - if (awaitReply(reply, kDefaultTimeoutMs)) { timedOut = false; break; } - qWarning() << "[NetworkService] postMonitoringScreenshot timeout attempt" - << attempt << "/" << kMaxRequestAttempts; + if (!awaitReply(reply, kMonitoringTimeoutMs)) { + qWarning() << "[NetworkService] postMonitoringScreenshot timeout"; reply->abort(); - } - - if (timedOut) { - qWarning() << "[NetworkService] postMonitoringScreenshot timeout after" - << kMaxRequestAttempts << "attempts"; } else if (reply->error() != QNetworkReply::NoError) { - qWarning() << "[NetworkService] postMonitoringScreenshot failed:" << reply->errorString() - << "\n response body:" << reply->readAll(); + qWarning() << "[NetworkService] postMonitoringScreenshot failed:" << reply->errorString(); } reply->deleteLater(); } diff --git a/tests/mock_server/mock_server.py b/tests/mock_server/mock_server.py index 3a0c00e..be1a598 100644 --- a/tests/mock_server/mock_server.py +++ b/tests/mock_server/mock_server.py @@ -522,6 +522,17 @@ def monitoring_log(): return ok({}) +@app.get("/_admin/monitoring_logs") +def admin_monitoring_logs(): + """Return collected monitoring logs for testing.""" + event_filter = request.args.get("event") + with _lock: + logs = list(_monitoring_logs) + if event_filter: + logs = [l for l in logs if l["event"] == event_filter] + return jsonify(logs) + + @app.get("/_admin/screenshot/") def get_screenshot(image_id): path = _SCREENSHOTS_DIR / f"{image_id}.png"