QElectroTech 0.200.1-dev
Loading...
Searching...
No Matches
QetLogger Class Reference

The QetLogger class Rework of QET's diagnostic logging (discussion #644, steps 1-3): More...

#include <qetlogger.h>

Collaboration diagram for QetLogger:
Collaboration graph

Public Member Functions

void init ()
void installCrashHandler ()
void handleMessage (QtMsgType type, const QMessageLogContext &context, const QString &msg)
 The function installed via qInstallMessageHandler() forwards here.
void pruneOldLogFiles (int days)
QVector< QByteArray > ringSnapshot () const
 Snapshot of the in-memory ring, oldest first.
bool hasPendingCrashDump () const
QByteArray pendingCrashDumpContents () const
void clearPendingCrashDump ()
QByteArray buildDiagnosticsReport () const

Static Public Member Functions

static QetLoggerinstance ()
 QetLogger::instance Function-local static: guaranteed constructed exactly once, in a thread-safe way, on first use – but the meaningful initialisation (log path resolution, opening the file) happens in init(), called explicitly from main() at a defined point, not implicitly on whichever thread happens to log first.
static QByteArray redact (const QByteArray &input)
 QetLogger::redact Replaces the user's home directory with "~" wherever it appears. Applied before a crash dump or a diagnostics report is ever shown to the user: both are destined to be attached to a public bug tracker, and an absolute path under the home directory leaks the account name (discussion #644's privacy section: "/home/laurent/... leaks a username"). This is the one redaction implemented here; the discussion's fancier "optionally redact project filenames too" is not attempted – reliably telling a project path apart from arbitrary log text is a much fuzzier problem than a literal prefix match against a known directory.

Static Public Attributes

static constexpr qint64 kMaxFileBytes = 2 * 1024 * 1024
static constexpr int kRotationKeep = 4
static constexpr int kMaxMessageBytes = 4096

Private Member Functions

 QetLogger ()=default
 QetLogger (const QetLogger &)=delete
bool ensureFileOpenLocked ()
 QetLogger::ensureFileOpenLocked Caller must hold m_file_mutex. Opens the current session's log file if not already open. Refuses to follow a pre-existing symlink at that path, and creates the file owner-read/write only.
void rotateLocked ()
 QetLogger::rotateLocked Caller must hold m_file_mutex. Shifts .3.log -> .4.log (dropping the previous .4.log), .2.log -> .3.log, .1.log -> .2.log, .log -> .1.log, then opens a fresh, empty current file.
void writeToFile (const QByteArray &line, QtMsgType type)
QString rotatedPath (int index) const
QString crashDumpPath () const
QString currentLogFilePath () const

Static Private Member Functions

static QByteArray sanitize (const QByteArray &input)
 QetLogger::sanitize Escapes newlines, carriage returns and other control characters. Much of what QET logs is externally controlled (file paths, element names, font strings read out of a .qet file); left unescaped, a crafted string containing '
' can forge additional log lines. Operates on already-UTF-8-encoded bytes: this is safe because UTF-8 continuation bytes are always >= 0x80, so any byte < 0x20 found here is a genuine ASCII control character, never part of a multi-byte sequence.
static QByteArray truncateMessage (const QByteArray &input, int max_bytes)
 QetLogger::truncateMessage Caps a single message at max_bytes, appending a marker stating how many bytes were dropped, so one pathological caller (e.g. dumping an entire XML document to qDebug()) can't consume an unbounded amount of the ring's or file's byte budget.
static QByteArray formatLine (QtMsgType type, const QMessageLogContext &context, const QByteArray &sanitized_msg)

Private Attributes

bool m_disabled = false
QString m_log_dir
QString m_base_name
QMutex m_file_mutex
QFile m_file
qint64 m_bytes_written_current_file = 0
bool m_file_output_ok = false
LogRing m_ring

Detailed Description

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.
  • Step 4: installCrashHandler() wires the ring up to CrashHandler (see crashhandler.h), so a SIGSEGV/SIGABRT/SIGBUS/SIGFPE/SIGILL (or, on Windows, an unhandled structured exception) flushes the ring to a fixed crash-dump file before the process dies.
  • Step 5: hasPendingCrashDump()/pendingCrashDumpContents()/ clearPendingCrashDump() let startup code (see QETApp::checkBackupFiles()) notice and offer an unretrieved crash dump from the previous run; buildDiagnosticsReport() is the equivalent for a manual "save a report right now" action on the current, still-running session. Both go through redact() before ever reaching the user, since both are destined for a public bug tracker.

Deliberately NOT included: log categories, a full session header beyond what the crash dump/report already carry, repeat collapsing, rate limiting. Those are listed in discussion #644 under "best practices worth building in", not part of the numbered steps.

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.

Constructor & Destructor Documentation

◆ QetLogger() [1/2]

QetLogger::QetLogger ( )
privatedefault
Here is the caller graph for this function:

◆ QetLogger() [2/2]

QetLogger::QetLogger ( const QetLogger & )
privatedelete
Here is the call graph for this function:

Member Function Documentation

◆ buildDiagnosticsReport()

QByteArray QetLogger::buildDiagnosticsReport ( ) const

Builds a redacted diagnostics bundle from the current session (header + this session's log file so far) for the manual "Save report" action – as opposed to pendingCrashDumpContents(), which is about a previous, already-terminated session.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ clearPendingCrashDump()

void QetLogger::clearPendingCrashDump ( )

Deletes the pending crash dump file. Call after the user has been offered it (whether they chose to save it or not) so it is never offered a second time.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ crashDumpPath()

QString QetLogger::crashDumpPath ( ) const
private
Here is the caller graph for this function:

◆ currentLogFilePath()

QString QetLogger::currentLogFilePath ( ) const
private
Here is the caller graph for this function:

◆ ensureFileOpenLocked()

bool QetLogger::ensureFileOpenLocked ( )
private

QetLogger::ensureFileOpenLocked Caller must hold m_file_mutex. Opens the current session's log file if not already open. Refuses to follow a pre-existing symlink at that path, and creates the file owner-read/write only.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ formatLine()

QByteArray QetLogger::formatLine ( QtMsgType type,
const QMessageLogContext & context,
const QByteArray & sanitized_msg )
staticprivate
Here is the caller graph for this function:

◆ handleMessage()

void QetLogger::handleMessage ( QtMsgType type,
const QMessageLogContext & context,
const QString & msg )

The function installed via qInstallMessageHandler() forwards here.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ hasPendingCrashDump()

bool QetLogger::hasPendingCrashDump ( ) const

True if a previous run's crash handler left an unretrieved dump behind.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ init()

void QetLogger::init ( )

Must be called exactly once, from main(), before qInstallMessageHandler(). Resolves the log directory and the session's log filename, and opens the file.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ installCrashHandler()

void QetLogger::installCrashHandler ( )

Step 4: installs the crash handler (see crashhandler.h). Must be called after init() (the ring and the dump path must exist first) and, like init(), only once.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ instance()

QetLogger & QetLogger::instance ( )
static

QetLogger::instance Function-local static: guaranteed constructed exactly once, in a thread-safe way, on first use – but the meaningful initialisation (log path resolution, opening the file) happens in init(), called explicitly from main() at a defined point, not implicitly on whichever thread happens to log first.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ pendingCrashDumpContents()

QByteArray QetLogger::pendingCrashDumpContents ( ) const

Raw contents of the pending crash dump, or an empty array if there isn't one. Does not delete it – call clearPendingCrashDump() once it has been offered to the user.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ pruneOldLogFiles()

void QetLogger::pruneOldLogFiles ( int days)

Replaces the old delete_old_log_files(): same call shape, fixed to use lastModified() (not lastRead()) and to also match rotated file names.

Here is the caller graph for this function:

◆ redact()

QByteArray QetLogger::redact ( const QByteArray & input)
static

QetLogger::redact Replaces the user's home directory with "~" wherever it appears. Applied before a crash dump or a diagnostics report is ever shown to the user: both are destined to be attached to a public bug tracker, and an absolute path under the home directory leaks the account name (discussion #644's privacy section: "/home/laurent/... leaks a username"). This is the one redaction implemented here; the discussion's fancier "optionally redact project filenames too" is not attempted – reliably telling a project path apart from arbitrary log text is a much fuzzier problem than a literal prefix match against a known directory.

Replaces occurrences of the user's home directory with "~". Applied to both the crash dump and buildDiagnosticsReport() before they are ever shown to the user, since both are destined for a public bug tracker.

Here is the caller graph for this function:

◆ ringSnapshot()

QVector< QByteArray > QetLogger::ringSnapshot ( ) const
inline

Snapshot of the in-memory ring, oldest first.

◆ rotatedPath()

QString QetLogger::rotatedPath ( int index) const
private
Here is the caller graph for this function:

◆ rotateLocked()

void QetLogger::rotateLocked ( )
private

QetLogger::rotateLocked Caller must hold m_file_mutex. Shifts .3.log -> .4.log (dropping the previous .4.log), .2.log -> .3.log, .1.log -> .2.log, .log -> .1.log, then opens a fresh, empty current file.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ sanitize()

QByteArray QetLogger::sanitize ( const QByteArray & input)
staticprivate

QetLogger::sanitize Escapes newlines, carriage returns and other control characters. Much of what QET logs is externally controlled (file paths, element names, font strings read out of a .qet file); left unescaped, a crafted string containing '
' can forge additional log lines. Operates on already-UTF-8-encoded bytes: this is safe because UTF-8 continuation bytes are always >= 0x80, so any byte < 0x20 found here is a genuine ASCII control character, never part of a multi-byte sequence.

Here is the caller graph for this function:

◆ truncateMessage()

QByteArray QetLogger::truncateMessage ( const QByteArray & input,
int max_bytes )
staticprivate

QetLogger::truncateMessage Caps a single message at max_bytes, appending a marker stating how many bytes were dropped, so one pathological caller (e.g. dumping an entire XML document to qDebug()) can't consume an unbounded amount of the ring's or file's byte budget.

Here is the caller graph for this function:

◆ writeToFile()

void QetLogger::writeToFile ( const QByteArray & line,
QtMsgType type )
private
Here is the call graph for this function:
Here is the caller graph for this function:

Member Data Documentation

◆ kMaxFileBytes

qint64 QetLogger::kMaxFileBytes = 2 * 1024 * 1024
staticconstexpr

◆ kMaxMessageBytes

int QetLogger::kMaxMessageBytes = 4096
staticconstexpr

◆ kRotationKeep

int QetLogger::kRotationKeep = 4
staticconstexpr

◆ m_base_name

QString QetLogger::m_base_name
private

◆ m_bytes_written_current_file

qint64 QetLogger::m_bytes_written_current_file = 0
private

◆ m_disabled

bool QetLogger::m_disabled = false
private

◆ m_file

QFile QetLogger::m_file
private

◆ m_file_mutex

QMutex QetLogger::m_file_mutex
private

◆ m_file_output_ok

bool QetLogger::m_file_output_ok = false
private

◆ m_log_dir

QString QetLogger::m_log_dir
private

◆ m_ring

LogRing QetLogger::m_ring
private

The documentation for this class was generated from the following files: