diff --git a/android/adb/AdbUtils.cpp b/android/adb/AdbUtils.cpp index b22554e..fe69cc0 100644 --- a/android/adb/AdbUtils.cpp +++ b/android/adb/AdbUtils.cpp @@ -243,15 +243,22 @@ bool AdbUtils::tryToStartApplication(const QString &deviceId, const QString &pac logMultiline("[AdbUtils::tryToStartApplication] monkey stdout:", stdOut); logMultiline("[AdbUtils::tryToStartApplication] monkey stderr:", stdErr); - if (stdOut.contains("Events injected: 1")) { + const bool eventsInjected = stdOut.contains("Events injected: 1"); + const bool hadNativeCrash = stdOut.contains("NativeCrash"); + + if (eventsInjected && !hadNativeCrash) { return true; } // Фолбэк: am start с конкретной activity. У него осмысленный stderr и exit code, // в отличие от monkey, который при пустом resolve просто молча печатает "No activities found". + // Также используется когда monkey дал "Events injected: 1" но приложение упало (NativeCrash). if (!launcherActivity.isEmpty()) { - qDebug().noquote() << "[AdbUtils::tryToStartApplication] monkey не дал Events injected;" - << "пробую am start -n" << launcherActivity; + if (hadNativeCrash) + qWarning().noquote() << "[AdbUtils::tryToStartApplication] NativeCrash detected, retrying via am start -n" << launcherActivity; + else + qDebug().noquote() << "[AdbUtils::tryToStartApplication] monkey не дал Events injected;" + << "пробую am start -n" << launcherActivity; QProcess am; am.setWorkingDirectory(adbDir()); am.start(adbPath(), diff --git a/monitoring/app.py b/monitoring/app.py index a529924..d6e4295 100644 --- a/monitoring/app.py +++ b/monitoring/app.py @@ -19,6 +19,7 @@ import asyncio import hashlib import hmac import html as html_lib +import json as json_lib import logging import os import re @@ -666,22 +667,45 @@ _ISO_RE = re.compile(r"^\d{4}-\d{2}-\d{2}T") _META_PREFIX_RE = re.compile(r"^(Device|Material|Amount|Phone|Card):", re.IGNORECASE) +def _pretty_json(raw: str) -> str: + """Pretty-print JSON string; return original if not valid JSON.""" + try: + obj = json_lib.loads(html_lib.unescape(raw.strip())) + return html_lib.escape(json_lib.dumps(obj, ensure_ascii=False, indent=2)) + except (json_lib.JSONDecodeError, ValueError): + return raw + + def _render_msg(message: str) -> str: """Клиент шлёт логи ошибок уже обёрнутыми в структурные теги …, динамику внутри экранирует на стороне C++ (toHtmlEscaped). Прочие источники (AppLogger, task_success) шлют голый текст. Экранируем всё, затем возвращаем из белого списка только / — bold рендерится, инъекция HTML невозможна. - Тело JSON (всё после маркера «JSON: » до конца сообщения) выносим в - моноширинный
-блок. Саму подпись «JSON:» не показываем — и так понятно.
- Содержимое уже экранировано, инъекции нет."""
+ Тело JSON (всё после маркера «JSON: » до конца сообщения, либо bare-JSON на
+ последней строке) выносим в моноширинный -блок с pretty-print.
+ Содержимое уже экранировано, инъекций нет."""
s = html_lib.escape(message or "")
s = s.replace("<b>", "").replace("</b>", "")
+
+ json_body = None
+ head = s
+
marker = "\nJSON: "
idx = s.find(marker)
if idx != -1:
head = s[:idx]
- body = s[idx + len(marker):]
- s = f'{head}\n{body}'
+ json_body = s[idx + len(marker):]
+ else:
+ # Bare JSON block at end of message (no "JSON: " prefix)
+ nl = s.rfind("\n")
+ if nl != -1:
+ last = s[nl + 1:].lstrip()
+ if last.startswith("{") or last.startswith("["):
+ head = s[:nl]
+ json_body = s[nl + 1:]
+
+ if json_body is not None:
+ s = f'{head}\n{_pretty_json(json_body)}'
return s
diff --git a/services/LogArchiveSender.cpp b/services/LogArchiveSender.cpp
index e7eea16..8e4a883 100644
--- a/services/LogArchiveSender.cpp
+++ b/services/LogArchiveSender.cpp
@@ -84,7 +84,39 @@ bool LogArchiveSender::collectInto(const QString &destDir) {
QDir().mkpath(destDir);
bool any = false;
if (hasLog) {
- if (QFile::copy(logPath, destDir + "/application.log")) any = true;
+ QFile srcLog(logPath);
+ if (srcLog.open(QIODevice::ReadOnly)) {
+ const QDateTime cutoff = QDateTime::currentDateTime().addSecs(-3600);
+
+ // Binary search for the first line with ts >= cutoff.
+ // Relies on monotonically non-decreasing timestamps in the log.
+ qint64 lo = 0, hi = srcLog.size();
+ while (lo < hi) {
+ const qint64 mid = lo + (hi - lo) / 2;
+ srcLog.seek(mid);
+ if (mid > 0) srcLog.readLine(); // align to next line boundary
+ const qint64 linePos = srcLog.pos();
+ if (linePos >= hi) { hi = mid; continue; }
+ const QByteArray probe = srcLog.readLine();
+ if (probe.isEmpty()) { hi = mid; continue; }
+ const QDateTime ts = QDateTime::fromString(
+ QString::fromLatin1(probe.left(23)), "yyyy-MM-dd hh:mm:ss.zzz");
+ if (!ts.isValid() || ts < cutoff)
+ lo = srcLog.pos(); // this line is old, search upper half
+ else
+ hi = linePos; // this line is new, search lower half
+ }
+ srcLog.seek(lo);
+
+ QFile dstLog(destDir + "/application.log");
+ if (dstLog.open(QIODevice::WriteOnly)) {
+ while (!srcLog.atEnd())
+ dstLog.write(srcLog.readLine());
+ any = true;
+ }
+ } else {
+ if (QFile::copy(logPath, destDir + "/application.log")) any = true;
+ }
}
if (hasDb) {
const QString dbBaseName = QFileInfo(dbPath).fileName();