mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-08-13 10:04:13 +02:00
ff812f221a
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.
115 lines
4.3 KiB
C++
115 lines
4.3 KiB
C++
/*
|
|
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 QETLOGGER_H
|
|
#define QETLOGGER_H
|
|
|
|
#include "logring.h"
|
|
|
|
#include <QFile>
|
|
#include <QMutex>
|
|
#include <QString>
|
|
#include <QtGlobal>
|
|
|
|
/**
|
|
@brief The QetLogger class
|
|
Rework of QET's diagnostic logging (discussion #644, steps 1-3):
|
|
|
|
- Step 1: one file handle held open for the session under a mutex
|
|
instead of opening/closing per message; the log path (including
|
|
the date-stamped filename) is resolved exactly once, at init(),
|
|
instead of being recomputed on every message -- a session that
|
|
crosses midnight now stays in one file; retention now uses
|
|
lastModified() instead of lastRead(); stderr and file output both
|
|
use UTF-8 explicitly (previously stderr used the local 8-bit
|
|
codec and the file's encoding silently differed between Qt5 and
|
|
Qt6).
|
|
- Step 2: the previously-unbounded daily file is now size-capped
|
|
and rotated (kMaxFileBytes per file, kRotationKeep old files kept
|
|
beyond the current one); each message is truncated to
|
|
kMaxMessageBytes and control characters are escaped before being
|
|
written, so one pathological caller can't blow the size budget or
|
|
forge log lines; the log file is refused if it already exists as
|
|
a symlink and is created owner-read/write only.
|
|
- Step 3: every formatted line is also appended to an in-memory
|
|
LogRing (see logring.h) -- always on, fixed capacity, allocation-
|
|
free on the hot path.
|
|
|
|
Deliberately NOT included in this step (see discussion #644): no
|
|
signal handler / crash-flush (step 4), no diagnostics export UI
|
|
(step 5), no log categories, no session header, no repeat collapsing
|
|
or rate limiting. Those are independent, separately-scoped follow-ups.
|
|
|
|
Escape hatch: if QET_LOG_DISABLE=1 is set in the environment at
|
|
init() time, this class does nothing beyond a minimal, independent
|
|
stderr passthrough -- no ring, no file, no rotation -- so a problem
|
|
in this rework can be worked around without a rebuild.
|
|
*/
|
|
class QetLogger
|
|
{
|
|
public:
|
|
static constexpr qint64 kMaxFileBytes = 2 * 1024 * 1024; // 2 MiB per file
|
|
static constexpr int kRotationKeep = 4; // .1.log .. .4.log
|
|
static constexpr int kMaxMessageBytes = 4096; // per-message truncation
|
|
|
|
static QetLogger &instance();
|
|
|
|
/// Must be called exactly once, from main(), before
|
|
/// qInstallMessageHandler(). Resolves the log directory and the
|
|
/// session's log filename, and opens the file.
|
|
void init();
|
|
|
|
/// The function installed via qInstallMessageHandler() forwards here.
|
|
void handleMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg);
|
|
|
|
/// Replaces the old delete_old_log_files(): same call shape, fixed
|
|
/// to use lastModified() (not lastRead()) and to also match rotated
|
|
/// file names.
|
|
void pruneOldLogFiles(int days);
|
|
|
|
/// Snapshot of the in-memory ring, oldest first. For future use
|
|
/// (e.g. a diagnostics export action) -- not wired to any UI here.
|
|
QVector<QByteArray> ringSnapshot() const {return m_ring.snapshot();}
|
|
|
|
private:
|
|
QetLogger() = default;
|
|
QetLogger(const QetLogger &) = delete;
|
|
|
|
bool ensureFileOpenLocked();
|
|
void rotateLocked();
|
|
void writeToFile(const QByteArray &line, QtMsgType type);
|
|
QString rotatedPath(int index) const;
|
|
|
|
static QByteArray sanitize(const QByteArray &input);
|
|
static QByteArray truncateMessage(const QByteArray &input, int max_bytes);
|
|
static QByteArray formatLine(QtMsgType type, const QMessageLogContext &context, const QByteArray &sanitized_msg);
|
|
|
|
bool m_disabled = false;
|
|
|
|
QString m_log_dir;
|
|
QString m_base_name; // e.g. "20260803", resolved once in init()
|
|
|
|
QMutex m_file_mutex;
|
|
QFile m_file;
|
|
qint64 m_bytes_written_current_file = 0;
|
|
bool m_file_output_ok = false;
|
|
|
|
LogRing m_ring;
|
|
};
|
|
|
|
#endif // QETLOGGER_H
|