mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-08-13 10:04:13 +02:00
Rework diagnostic logging: fix the file writer, add rotation and a ring buffer
Implements steps 1-3 of discussion #644 (deliberately not steps 4/5 -- no signal handler / crash flush, no diagnostics UI; see below). ## Step 1 -- fix the existing logger (bugs, no new behavior) - One QFile handle held open for the whole session under a mutex, instead of opening and closing the log file on every single message. - The log directory and the session's date-stamped filename are resolved exactly once, in the new QetLogger::init() called explicitly from main() immediately before qInstallMessageHandler() -- not recomputed per message, so a session that runs past midnight now stays in one file instead of silently splitting. - Age-based retention now uses lastModified() instead of lastRead(): opening a log to attach it to a bug report no longer resets its retention clock. - stderr and file output both encode UTF-8 explicitly (toUtf8()), replacing stderr's toLocal8Bit() and the file stream's previously Qt5/Qt6-inconsistent default encoding. ## Step 2 -- size-capped rotation + hardening - The previously-unbounded daily file is now capped at 2 MiB and rotated (kMaxFileBytes/kRotationKeep in QetLogger), keeping <date>.log plus <date>.1.log .. <date>.4.log; oldest is dropped. - Each message is truncated to 4 KB with a "...[truncated N bytes]" marker before it reaches the ring or the file. - Control characters (newlines, tabs, other non-printables) in message content are escaped, since much of what QET logs is externally controlled (file paths, element names, font strings out of a .qet file) -- left unescaped, an embedded '\n' could forge log lines. - The log file is refused if a symlink already exists at that path, and is created/rotated owner-read/write only. ## Step 3 -- in-memory ring buffer - LogRing (sources/logging/logring.h) is a fixed-capacity, always-on ring of the last 4096 log lines, preallocated once at construction (4096 * 512 B = 2 MiB) so append() never allocates. Entries are stored as plain pre-formatted bytes in fixed-size slots -- the shape discussion #644 specifies so a *future* crash handler could dump it with nothing but write(2), even though no such handler exists yet. Thread-safe via a plain QMutex (the lock-free requirement in the discussion applies specifically to a signal-handler read path, which this step doesn't add). ## Escape hatch QET_LOG_DISABLE=1 in the environment at startup bypasses all of the above -- no ring, no file, no rotation -- falling back to a minimal, self-contained stderr passthrough that doesn't share any code with the new formatting/sanitization path, so it stays usable even if that path is what's misbehaving. ## Deliberately not included (per the discussion's own phasing) - No signal handler / crash-time ring flush (step 4) -- the discussion flags this as the highest-risk piece, explicitly meant to land last and behind its own switch once the rest is proven. - No diagnostics export UI (step 5). - No log categories, session header, repeat collapsing or rate limiting -- listed under "best practices worth building in", not part of steps 1-3. ## Testing Built clean, no new warnings. Verified with real runs (QT_QPA_PLATFORM=offscreen, isolated HOME): - Log file created at the expected dataDir()/YYYYMMDD.log path, mode 0600. - A full startup's worth of real messages (translations, MachineInfo's system dump, collection loading) written correctly; every one of the 231 lines in one run starts with a proper timestamp -- confirmed the sanitizer correctly escapes the raw embedded newlines/tabs in MachineInfo's multi-line CPU/GPU description fields into visible \n/\t sequences rather than letting them fragment the log. - QET_LOG_DISABLE=1: zero log files created, stderr still worked via the independent legacy path. - Rotation: pre-filled a log to just under the 2 MiB cap, ran a normal session, confirmed it rotated to <date>.1.log (still 0600) with a byte-clean split (no truncated/duplicated line at the boundary) and a fresh <date>.log picked up from the next line.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QElectroTech is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef LOGRING_H
|
||||
#define LOGRING_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QMutex>
|
||||
#include <QVector>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
@brief The LogRing class
|
||||
Fixed-capacity, always-on in-memory ring of the most recent log
|
||||
lines. Discussion #644 (step 3): the ring exists as forward-compatible
|
||||
infrastructure for a future crash-flush (step 4, not implemented
|
||||
here) as well as an on-demand "what just happened" snapshot, so its
|
||||
entries are stored pre-formatted as plain bytes in storage
|
||||
preallocated once at construction -- append() never allocates.
|
||||
|
||||
Entries are fixed-size slots rather than a byte-packed ring: with
|
||||
kCapacityEntries * kEntryBytes chosen to land exactly on the 2 MiB
|
||||
budget, this keeps wraparound trivial (whole-slot overwrite, so a
|
||||
slot is always either fully the old entry or fully the new one --
|
||||
no torn entries) at the cost of truncating any single line to
|
||||
kEntryBytes, independently of the logger's own (larger) per-message
|
||||
truncation.
|
||||
|
||||
Thread-safe via a plain QMutex. This is *not* the lock-free design
|
||||
discussion #644 specifies for a signal-handler crash path (step 4)
|
||||
-- no signal handler is installed by this code, so nothing calls
|
||||
into the ring from inside a signal context.
|
||||
*/
|
||||
class LogRing
|
||||
{
|
||||
public:
|
||||
static constexpr int kCapacityEntries = 4096;
|
||||
static constexpr int kEntryBytes = 512; // 4096 * 512 = 2 MiB total
|
||||
|
||||
LogRing();
|
||||
|
||||
/// Append one already-formatted, already-truncated log line.
|
||||
/// Bytes beyond kEntryBytes - 1 are dropped with a truncation marker.
|
||||
void append(const QByteArray &line);
|
||||
|
||||
/// Snapshot of the entries currently held, oldest first.
|
||||
QVector<QByteArray> snapshot() const;
|
||||
|
||||
void clear();
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
char data[kEntryBytes] = {};
|
||||
int length = 0;
|
||||
};
|
||||
|
||||
mutable QMutex m_mutex;
|
||||
std::vector<Entry> m_entries; // preallocated once, capacity fixed
|
||||
int m_next_index = 0;
|
||||
int m_count = 0;
|
||||
};
|
||||
|
||||
#endif // LOGRING_H
|
||||
Reference in New Issue
Block a user