1
0
forked from BRT/arc

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.

This commit is contained in:
slava 2026-06-04 13:01:47 +05:00
parent 2b4bd5a9d1
commit c2994d9041
4 changed files with 56 additions and 66 deletions

View File

@ -16,7 +16,7 @@ public:
static constexpr qint64 kOfflineDebounceSecs = 30; static constexpr qint64 kOfflineDebounceSecs = 30;
// Как часто проверять/слать кадр истории экрана на сервер логов (с дедупом). // Как часто проверять/слать кадр истории экрана на сервер логов (с дедупом).
static constexpr qint64 kMonitoringScreenIntervalSecs = 30; static constexpr qint64 kMonitoringScreenIntervalSecs = 120;
// Таймаут одной adb-команды в scanDeviceIo. На порядок выше нормального // Таймаут одной adb-команды в scanDeviceIo. На порядок выше нормального
// времени (<1с), но ограничивает зависшую команду на флапающем устройстве, // времени (<1с), но ограничивает зависшую команду на флапающем устройстве,
// чтобы один телефон не держал весь цикл опроса. // чтобы один телефон не держал весь цикл опроса.

View File

@ -804,7 +804,7 @@ void EventHandler::startThreadForTask(const MaterialInfo &account, const EventIn
event.externalId, event.bankName, arr, event.id, /*updateStatus=*/false); event.externalId, event.bankName, arr, event.id, /*updateStatus=*/false);
// Логируем в arc-monitoring сразу по каждой транзакции — как на apiBase. // Логируем в arc-monitoring сразу по каждой транзакции — как на apiBase.
sendTaskSuccess("FETCH_TRANSACTIONS", event.externalId, event.bankName, 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); {}, event.id, event.accountId);
// Фиксируем в глобальном кэше: будущие задачи того же материала // Фиксируем в глобальном кэше: будущие задачи того же материала
// не будут повторно подтверждать эту транзакцию (TTL 20 минут). // не будут повторно подтверждать эту транзакцию (TTL 20 минут).

View File

@ -56,6 +56,9 @@ bool awaitReply(QNetworkReply *reply, int timeoutMs) {
constexpr int kMaxRequestAttempts = 3; constexpr int kMaxRequestAttempts = 3;
constexpr int kDefaultTimeoutMs = 30000; constexpr int kDefaultTimeoutMs = 30000;
constexpr int kDumpTimeoutMs = 120000; constexpr int kDumpTimeoutMs = 120000;
// Мониторинг — best-effort: одна попытка, короткий таймаут. Failure не должен
// блокировать потоки и накапливать QThread'ы при недоступном сервере.
constexpr int kMonitoringTimeoutMs = 10000;
} // namespace } // namespace
NetworkService::NetworkService(QObject *parent) : QObject(parent) { 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(); const int internalTaskId = m_extraData.value("internal_task_id").toInt();
QNetworkReply *reply = nullptr; auto *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
bool timedOut = true; addFormField(multiPart, "type", type.toUpper());
for (int attempt = 1; attempt <= kMaxRequestAttempts; ++attempt) { addFormField(multiPart, "event", event);
if (reply) { reply->deleteLater(); reply = nullptr; } addFormField(multiPart, "message", message);
if (internalTaskId > 0) {
auto *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); addFormField(multiPart, "internal_task_id", QString::number(internalTaskId));
addFormField(multiPart, "type", type.toUpper()); }
addFormField(multiPart, "event", event); if (!image.isEmpty()) {
addFormField(multiPart, "message", message); QHttpPart imagePart;
if (internalTaskId > 0) { imagePart.setHeader(QNetworkRequest::ContentTypeHeader, "image/png");
addFormField(multiPart, "internal_task_id", QString::number(internalTaskId)); imagePart.setHeader(
} QNetworkRequest::ContentDispositionHeader,
if (!image.isEmpty()) { QStringLiteral("form-data; name=\"image\"; filename=\"screenshot.png\"")
QHttpPart imagePart; );
imagePart.setHeader(QNetworkRequest::ContentTypeHeader, "image/png"); imagePart.setBody(image);
imagePart.setHeader( multiPart->append(imagePart);
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();
} }
if (timedOut) { QNetworkReply *reply = m_manager->post(request, multiPart);
qWarning() << "[NetworkService] postMonitoringLog timeout after" multiPart->setParent(reply);
<< kMaxRequestAttempts << "attempts";
if (!awaitReply(reply, kMonitoringTimeoutMs)) {
qWarning() << "[NetworkService] postMonitoringLog timeout";
reply->abort();
} else if (reply->error() != QNetworkReply::NoError) { } else if (reply->error() != QNetworkReply::NoError) {
qWarning() << "[NetworkService] postMonitoringLog failed:" << reply->errorString() qWarning() << "[NetworkService] postMonitoringLog failed:" << reply->errorString();
<< "\n response body:" << reply->readAll();
} }
reply->deleteLater(); reply->deleteLater();
} }
@ -1778,39 +1769,27 @@ void NetworkService::postMonitoringScreenshot(const QString &desktopId,
request.setRawHeader("X-Monitoring-Token", m_monitoringToken.toUtf8()); request.setRawHeader("X-Monitoring-Token", m_monitoringToken.toUtf8());
} }
QNetworkReply *reply = nullptr; auto *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
bool timedOut = true; addFormField(multiPart, "desktop_id", desktopId);
for (int attempt = 1; attempt <= kMaxRequestAttempts; ++attempt) { addFormField(multiPart, "device_id", deviceId);
if (reply) { reply->deleteLater(); reply = nullptr; } 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); QNetworkReply *reply = m_manager->post(request, multiPart);
addFormField(multiPart, "desktop_id", desktopId); multiPart->setParent(reply);
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);
reply = m_manager->post(request, multiPart); if (!awaitReply(reply, kMonitoringTimeoutMs)) {
multiPart->setParent(reply); qWarning() << "[NetworkService] postMonitoringScreenshot timeout";
if (awaitReply(reply, kDefaultTimeoutMs)) { timedOut = false; break; }
qWarning() << "[NetworkService] postMonitoringScreenshot timeout attempt"
<< attempt << "/" << kMaxRequestAttempts;
reply->abort(); reply->abort();
}
if (timedOut) {
qWarning() << "[NetworkService] postMonitoringScreenshot timeout after"
<< kMaxRequestAttempts << "attempts";
} else if (reply->error() != QNetworkReply::NoError) { } else if (reply->error() != QNetworkReply::NoError) {
qWarning() << "[NetworkService] postMonitoringScreenshot failed:" << reply->errorString() qWarning() << "[NetworkService] postMonitoringScreenshot failed:" << reply->errorString();
<< "\n response body:" << reply->readAll();
} }
reply->deleteLater(); reply->deleteLater();
} }

View File

@ -522,6 +522,17 @@ def monitoring_log():
return ok({}) 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/<image_id>") @app.get("/_admin/screenshot/<image_id>")
def get_screenshot(image_id): def get_screenshot(image_id):
path = _SCREENSHOTS_DIR / f"{image_id}.png" path = _SCREENSHOTS_DIR / f"{image_id}.png"