From 49f1323706525071dc9d7756a3f020485ec46a3e Mon Sep 17 00:00:00 2001 From: slava Date: Fri, 5 Jun 2026 11:01:42 +0500 Subject: [PATCH] Revise log entry processing in `LogArchiveSender`, disable screen history uploads in `DeviceScreener`, and replace `rowid` with `id` in database queries for consistency. --- monitoring/app.py | 14 ++++----- services/DeviceScreener.cpp | 30 +++++++++--------- services/LogArchiveSender.cpp | 57 ++++++++++++++++++++++++----------- 3 files changed, 62 insertions(+), 39 deletions(-) diff --git a/monitoring/app.py b/monitoring/app.py index eb6b331..7652bfb 100644 --- a/monitoring/app.py +++ b/monitoring/app.py @@ -239,14 +239,14 @@ def _emergency_purge_screenshots() -> int: # 1. Скрины из таблицы screenshots (самые старые первыми) with _db() as c: rows = c.execute( - "SELECT rowid, image_path FROM screenshots ORDER BY ts ASC" + "SELECT id, image_path FROM screenshots ORDER BY ts ASC" ).fetchall() for r in rows: if _disk_pct() < DISK_EMERGENCY_THRESHOLD: break _unlink_rel(r["image_path"]) with _db() as c: - c.execute("DELETE FROM screenshots WHERE rowid=?", (r["rowid"],)) + c.execute("DELETE FROM screenshots WHERE id=?", (r["id"],)) removed += 1 if _disk_pct() < DISK_EMERGENCY_THRESHOLD: @@ -257,7 +257,7 @@ def _emergency_purge_screenshots() -> int: # 2. Скрины из таблицы screen_dumps (самые старые первыми) with _db() as c: rows = c.execute( - "SELECT rowid, image_path, xml_path FROM screen_dumps ORDER BY ts ASC" + "SELECT id, image_path, xml_path FROM screen_dumps ORDER BY ts ASC" ).fetchall() for r in rows: if _disk_pct() < DISK_EMERGENCY_THRESHOLD: @@ -265,7 +265,7 @@ def _emergency_purge_screenshots() -> int: _unlink_rel(r["image_path"]) _unlink_rel(r["xml_path"]) with _db() as c: - c.execute("DELETE FROM screen_dumps WHERE rowid=?", (r["rowid"],)) + c.execute("DELETE FROM screen_dumps WHERE id=?", (r["id"],)) removed += 1 if _disk_pct() < DISK_EMERGENCY_THRESHOLD: @@ -275,14 +275,14 @@ def _emergency_purge_screenshots() -> int: # 3. Zip-дампы из entries (самые старые первыми) with _db() as c: rows = c.execute( - "SELECT rowid, file_path FROM entries WHERE file_path IS NOT NULL ORDER BY ts ASC" + "SELECT id, file_path FROM entries WHERE file_path IS NOT NULL ORDER BY ts ASC" ).fetchall() for r in rows: if _disk_pct() < DISK_EMERGENCY_THRESHOLD: break _unlink_rel(r["file_path"]) with _db() as c: - c.execute("UPDATE entries SET file_path=NULL WHERE rowid=?", (r["rowid"],)) + c.execute("UPDATE entries SET file_path=NULL WHERE id=?", (r["id"],)) removed += 1 LOG.warning( @@ -302,7 +302,7 @@ async def _retention_loop() -> None: LOG.info("retention: removed %d entries older than %dd", n, RETENTION_DAYS) except Exception as e: # noqa: BLE001 LOG.warning("retention loop error: %s", e) - await asyncio.sleep(600) + await asyncio.sleep(120) @app.on_event("startup") diff --git a/services/DeviceScreener.cpp b/services/DeviceScreener.cpp index 0d874d0..d9762c1 100644 --- a/services/DeviceScreener.cpp +++ b/services/DeviceScreener.cpp @@ -471,20 +471,22 @@ void DeviceScreener::start() { m_lastApiUpdate[device.id] = now; } - // История экрана на сервере логов: проверяем не чаще раза в - // kMonitoringScreenIntervalSecs и шлём кадр только если экран - // изменился относительно последнего отправленного (дедуп по sha256). - if (!device.image.isEmpty() - && now - m_lastMonitoringScreen.value(device.id, 0) >= kMonitoringScreenIntervalSecs) { - m_lastMonitoringScreen[device.id] = now; - const QByteArray hash = - QCryptographicHash::hash(device.image, QCryptographicHash::Sha256); - if (m_lastSentScreenHash.value(device.id) != hash) { - m_lastSentScreenHash[device.id] = hash; - dispatchScreenshotToMonitoring(SettingsDAO::get("desktop_id"), - device.id, device.name, device.image); - } - } + // Отправка истории экрана в arc-monitoring отключена по запросу. + // Код сохранён закомментированным на случай возврата функционала. + // // История экрана на сервере логов: проверяем не чаще раза в + // // kMonitoringScreenIntervalSecs и шлём кадр только если экран + // // изменился относительно последнего отправленного (дедуп по sha256). + // if (!device.image.isEmpty() + // && now - m_lastMonitoringScreen.value(device.id, 0) >= kMonitoringScreenIntervalSecs) { + // m_lastMonitoringScreen[device.id] = now; + // const QByteArray hash = + // QCryptographicHash::hash(device.image, QCryptographicHash::Sha256); + // if (m_lastSentScreenHash.value(device.id) != hash) { + // m_lastSentScreenHash[device.id] = hash; + // dispatchScreenshotToMonitoring(SettingsDAO::get("desktop_id"), + // device.id, device.name, device.image); + // } + // } } } diff --git a/services/LogArchiveSender.cpp b/services/LogArchiveSender.cpp index ca0161d..66c31e1 100644 --- a/services/LogArchiveSender.cpp +++ b/services/LogArchiveSender.cpp @@ -71,31 +71,52 @@ bool LogArchiveSender::collectInto(const QString &destDir) { QFile srcLog(logPath); if (srcLog.open(QIODevice::ReadOnly)) { const QDateTime cutoff = QDateTime::currentDateTime().addSecs(-3600); + const qint64 size = srcLog.size(); - // 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 + // Find the byte offset of the first log entry within the last hour by + // walking the file backwards in blocks from EOF. + // + // We can't binary-search this: error dumps log multi-line payloads + // (JSON, ADB screen XML, stack traces) whose continuation lines carry no + // leading timestamp. A binary probe landing on such a line parsed an + // invalid date, the old code treated that as "old" and advanced its + // marker — and when the very last line of the file was a continuation + // line (the usual case on error) the marker ran off the end and the dump + // came out empty. Scanning from the tail also keeps this cheap on a huge + // log: we stop the moment a block contains an entry older than the cutoff + // and never touch the older prefix. + static const qint64 BLOCK = 1 << 20; // 1 MiB + qint64 startPos = size; // lowest offset of an in-window entry; == size => none yet + qint64 hi = size; // region [hi, size) already scanned + bool sawOld = false; + while (hi > 0 && !sawOld) { + const qint64 lo = qMax(0, hi - BLOCK); + srcLog.seek(lo); + if (lo > 0) srcLog.readLine(); // skip the partial line owned by the earlier block + qint64 pos = srcLog.pos(); + while (pos < hi) { + const QByteArray probe = srcLog.readLine(); + if (probe.isEmpty()) break; + const QDateTime ts = QDateTime::fromString( + QString::fromLatin1(probe.left(23)), "yyyy-MM-dd hh:mm:ss.zzz"); + if (ts.isValid()) { + if (ts >= cutoff) { if (pos < startPos) startPos = pos; } + else sawOld = true; // an older entry → earlier blocks are older too + } + pos = srcLog.pos(); + } + hi = lo; } - srcLog.seek(lo); + + // Nothing within the last hour → ship the whole file rather than an empty + // one; stale error context still beats no log at all. + srcLog.seek(startPos < size ? startPos : 0); QFile dstLog(destDir + "/application.log"); if (dstLog.open(QIODevice::WriteOnly)) { while (!srcLog.atEnd()) dstLog.write(srcLog.readLine()); + dstLog.flush(); any = true; } } else {