diff --git a/cmake/qet_compilation_vars.cmake b/cmake/qet_compilation_vars.cmake
index 57a012916..9150f21fe 100644
--- a/cmake/qet_compilation_vars.cmake
+++ b/cmake/qet_compilation_vars.cmake
@@ -116,6 +116,10 @@ set(QET_RES_FILES
set(QET_SRC_FILES
${QET_DIR}/sources/cli_export.cpp
${QET_DIR}/sources/cli_export.h
+ ${QET_DIR}/sources/logging/logring.cpp
+ ${QET_DIR}/sources/logging/logring.h
+ ${QET_DIR}/sources/logging/qetlogger.cpp
+ ${QET_DIR}/sources/logging/qetlogger.h
${QET_DIR}/sources/pdf_links.cpp
${QET_DIR}/sources/pdf_links.h
${QET_DIR}/sources/import/edz/edzarchive.cpp
diff --git a/sources/logging/logring.cpp b/sources/logging/logring.cpp
new file mode 100644
index 000000000..91eb7583c
--- /dev/null
+++ b/sources/logging/logring.cpp
@@ -0,0 +1,91 @@
+/*
+ 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 .
+*/
+#include "logring.h"
+
+#include
+#include
+
+/**
+ @brief LogRing::LogRing
+ Preallocates all kCapacityEntries slots up front -- the only
+ allocation this class ever does.
+*/
+LogRing::LogRing()
+{
+ m_entries.resize(kCapacityEntries);
+}
+
+/**
+ @brief LogRing::append
+ @param line one already-formatted log line (no further formatting
+ is done here). Truncated to kEntryBytes - 1 bytes if longer, with a
+ trailing marker, so the stored entry is always a complete,
+ independently-readable line.
+*/
+void LogRing::append(const QByteArray &line)
+{
+ static const char kMarker[] = "...[ring-truncated]\n";
+ const int marker_len = static_cast(sizeof(kMarker)) - 1;
+
+ QMutexLocker locker(&m_mutex);
+
+ Entry &slot = m_entries[static_cast(m_next_index)];
+
+ if (line.size() < kEntryBytes) {
+ std::memcpy(slot.data, line.constData(), static_cast(line.size()));
+ slot.length = line.size();
+ } else {
+ const int keep = kEntryBytes - marker_len;
+ std::memcpy(slot.data, line.constData(), static_cast(keep));
+ std::memcpy(slot.data + keep, kMarker, static_cast(marker_len));
+ slot.length = kEntryBytes;
+ }
+
+ m_next_index = (m_next_index + 1) % kCapacityEntries;
+ if (m_count < kCapacityEntries) {
+ ++m_count;
+ }
+}
+
+/**
+ @brief LogRing::snapshot
+ @return the entries currently held, oldest first. Safe to call from
+ normal (non-signal) code only.
+*/
+QVector LogRing::snapshot() const
+{
+ QMutexLocker locker(&m_mutex);
+
+ QVector result;
+ result.reserve(m_count);
+
+ const int start = (m_count < kCapacityEntries) ? 0 : m_next_index;
+ for (int i = 0; i < m_count; ++i) {
+ const Entry &slot = m_entries[static_cast((start + i) % kCapacityEntries)];
+ result.append(QByteArray(slot.data, slot.length));
+ }
+
+ return result;
+}
+
+void LogRing::clear()
+{
+ QMutexLocker locker(&m_mutex);
+ m_next_index = 0;
+ m_count = 0;
+}
diff --git a/sources/logging/logring.h b/sources/logging/logring.h
new file mode 100644
index 000000000..26f0d492e
--- /dev/null
+++ b/sources/logging/logring.h
@@ -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 .
+*/
+#ifndef LOGRING_H
+#define LOGRING_H
+
+#include
+#include
+#include
+#include
+
+/**
+ @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 snapshot() const;
+
+ void clear();
+
+ private:
+ struct Entry {
+ char data[kEntryBytes] = {};
+ int length = 0;
+ };
+
+ mutable QMutex m_mutex;
+ std::vector m_entries; // preallocated once, capacity fixed
+ int m_next_index = 0;
+ int m_count = 0;
+};
+
+#endif // LOGRING_H
diff --git a/sources/logging/qetlogger.cpp b/sources/logging/qetlogger.cpp
new file mode 100644
index 000000000..df4dd6736
--- /dev/null
+++ b/sources/logging/qetlogger.cpp
@@ -0,0 +1,335 @@
+/*
+ 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 .
+*/
+#include "qetlogger.h"
+
+#include "../qetapp.h"
+
+#include
+#include
+#include
+#include
+
+namespace {
+
+/**
+ @brief legacyStderrOutput
+ The QET_LOG_DISABLE=1 escape hatch. Deliberately independent of
+ every other function in this file -- including sanitize()/
+ formatLine(), which are exactly the new code a problem might be in
+ -- so this path stays usable even if the rest of the rework
+ misbehaves. No ring, no file, no rotation, no mutex.
+*/
+void legacyStderrOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
+{
+ const QByteArray local_msg = msg.toLocal8Bit();
+ const char *file = context.file ? context.file : "";
+ const char *function = context.function ? context.function : "";
+
+ const char *level = "Unknown";
+ switch (type) {
+ case QtDebugMsg: level = "Debug"; break;
+ case QtInfoMsg: level = "Info"; break;
+ case QtWarningMsg: level = "Warning"; break;
+ case QtCriticalMsg: level = "Critical"; break;
+ case QtFatalMsg: level = "Fatal"; break;
+ }
+
+ fprintf(stderr, "%s: %s (%s:%u, %s)\n",
+ level, local_msg.constData(), file, context.line, function);
+}
+
+/**
+ @brief ReentrancyGuard
+ Sets the referenced flag on construction, clears it on destruction
+ (including via early return / exception unwinding). Used as the
+ per-thread guard against the logger recursing into itself.
+*/
+struct ReentrancyGuard
+{
+ bool &flag;
+ explicit ReentrancyGuard(bool &f) : flag(f) {flag = true;}
+ ~ReentrancyGuard() {flag = false;}
+};
+
+} // namespace
+
+/**
+ @brief 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.
+*/
+QetLogger &QetLogger::instance()
+{
+ static QetLogger logger;
+ return logger;
+}
+
+void QetLogger::init()
+{
+ m_disabled = (qgetenv("QET_LOG_DISABLE") == "1");
+ if (m_disabled) {
+ return;
+ }
+
+ m_log_dir = QETApp::dataDir();
+ m_base_name = QDate::currentDate().toString(QStringLiteral("yyyyMMdd"));
+
+ QMutexLocker locker(&m_file_mutex);
+ m_file_output_ok = ensureFileOpenLocked();
+}
+
+/**
+ @brief 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.
+*/
+bool QetLogger::ensureFileOpenLocked()
+{
+ if (m_file.isOpen()) {
+ return true;
+ }
+
+ QDir().mkpath(m_log_dir);
+
+ const QString path = m_log_dir % QStringLiteral("/") % m_base_name % QStringLiteral(".log");
+
+ const QFileInfo info(path);
+ if (info.exists() && info.isSymLink()) {
+ // Filesystem hardening: refuse a pre-planted symlink rather than
+ // silently appending to whatever it points at.
+ return false;
+ }
+
+ m_file.setFileName(path);
+ if (!m_file.open(QIODevice::WriteOnly | QIODevice::Append)) {
+ return false;
+ }
+ m_file.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner);
+ m_bytes_written_current_file = m_file.size();
+ return true;
+}
+
+QString QetLogger::rotatedPath(int index) const
+{
+ return m_log_dir % QStringLiteral("/") % m_base_name % QStringLiteral(".") % QString::number(index) % QStringLiteral(".log");
+}
+
+/**
+ @brief 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 QetLogger::rotateLocked()
+{
+ m_file.close();
+
+ const QString base_path = m_log_dir % QStringLiteral("/") % m_base_name % QStringLiteral(".log");
+
+ for (int i = kRotationKeep; i >= 1; --i) {
+ const QString from = (i == 1) ? base_path : rotatedPath(i - 1);
+ const QString to = rotatedPath(i);
+
+ if (QFile::exists(to)) {
+ QFile::remove(to);
+ }
+ if (QFile::exists(from)) {
+ QFile::rename(from, to);
+ }
+ }
+
+ m_bytes_written_current_file = 0;
+ m_file_output_ok = ensureFileOpenLocked();
+}
+
+void QetLogger::writeToFile(const QByteArray &line, QtMsgType type)
+{
+ QMutexLocker locker(&m_file_mutex);
+
+ if (!m_file_output_ok) {
+ // Write-failure policy: once file output has failed, stop
+ // attempting it rather than spin-retrying every message. The
+ // ring keeps running regardless.
+ return;
+ }
+
+ const qint64 written = m_file.write(line);
+ if (written != line.size()) {
+ m_file_output_ok = false;
+ m_file.close();
+ return;
+ }
+ m_bytes_written_current_file += written;
+
+ if (type >= QtWarningMsg) {
+ m_file.flush();
+ }
+
+ if (m_bytes_written_current_file >= kMaxFileBytes) {
+ rotateLocked();
+ }
+}
+
+/**
+ @brief 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 '\n' 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.
+*/
+QByteArray QetLogger::sanitize(const QByteArray &input)
+{
+ QByteArray out;
+ out.reserve(input.size());
+
+ for (unsigned char c : input) {
+ if (c == '\n') {
+ out += "\\n";
+ } else if (c == '\r') {
+ out += "\\r";
+ } else if (c == '\t') {
+ out += static_cast(c);
+ } else if (c < 0x20 || c == 0x7F) {
+ out += "\\x";
+ out += QByteArray::number(c, 16).rightJustified(2, '0');
+ } else {
+ out += static_cast(c);
+ }
+ }
+
+ return out;
+}
+
+/**
+ @brief 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.
+*/
+QByteArray QetLogger::truncateMessage(const QByteArray &input, int max_bytes)
+{
+ if (input.size() <= max_bytes) {
+ return input;
+ }
+
+ const int dropped = input.size() - max_bytes;
+ QByteArray out = input.left(max_bytes);
+ out += " ...[truncated ";
+ out += QByteArray::number(dropped);
+ out += " bytes]";
+ return out;
+}
+
+QByteArray QetLogger::formatLine(QtMsgType type, const QMessageLogContext &context, const QByteArray &sanitized_msg)
+{
+ // Includes the date (not just the time) so that a session crossing
+ // midnight -- now kept in a single file -- doesn't read as ambiguous.
+ const QByteArray timestamp = QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd hh:mm:ss.zzz")).toUtf8();
+
+ const char *level = "Unknown";
+ switch (type) {
+ case QtDebugMsg: level = "Debug"; break;
+ case QtInfoMsg: level = "Info"; break;
+ case QtWarningMsg: level = "Warning"; break;
+ case QtCriticalMsg: level = "Critical"; break;
+ case QtFatalMsg: level = "Fatal"; break;
+ }
+
+ const char *file = context.file ? context.file : "";
+ const char *function = context.function ? context.function : "";
+
+ QByteArray line = timestamp;
+ line += ' ';
+ line += level;
+ line += ": ";
+ line += sanitized_msg;
+
+ if (type == QtInfoMsg) {
+ line += " \n";
+ } else {
+ line += " (";
+ line += file;
+ line += ":";
+ line += QByteArray::number(context.line ? context.line : 0);
+ line += ", ";
+ line += function;
+ line += ")\n";
+ }
+
+ return line;
+}
+
+void QetLogger::handleMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg)
+{
+ if (m_disabled) {
+ legacyStderrOutput(type, context, msg);
+ return;
+ }
+
+ static thread_local bool in_handler = false;
+ if (in_handler) {
+ // The logger itself triggered a message (e.g. from inside a Qt
+ // call it made) -- drop it rather than recurse.
+ return;
+ }
+ ReentrancyGuard guard(in_handler);
+
+ const QByteArray sanitized = truncateMessage(sanitize(msg.toUtf8()), kMaxMessageBytes);
+ const QByteArray line = formatLine(type, context, sanitized);
+
+ fwrite(line.constData(), 1, static_cast(line.size()), stderr);
+
+ m_ring.append(line);
+ writeToFile(line, type);
+}
+
+void QetLogger::pruneOldLogFiles(int days)
+{
+ if (m_disabled) {
+ return;
+ }
+
+ const QDate today = QDate::currentDate();
+ const QStringList filters = {
+ QStringLiteral("????????.log"), // base files, e.g. 20260803.log
+ QStringLiteral("????????.?.log"), // rotated files, e.g. 20260803.1.log
+ };
+
+ const QDir dir(m_log_dir);
+ const auto entries = dir.entryInfoList(filters, QDir::Files);
+ for (const QFileInfo &file_info : entries) {
+ if (!file_info.isFile()) {
+ continue;
+ }
+ // lastModified(), not lastRead(): reading the log (opening it to
+ // attach to a bug report, a backup job, an indexer) must not
+ // reset the retention clock and keep it alive indefinitely.
+ if (file_info.lastModified().date().daysTo(today) > days) {
+ QFile::remove(file_info.absoluteFilePath());
+ }
+ }
+}
diff --git a/sources/logging/qetlogger.h b/sources/logging/qetlogger.h
new file mode 100644
index 000000000..eb576bec2
--- /dev/null
+++ b/sources/logging/qetlogger.h
@@ -0,0 +1,114 @@
+/*
+ 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 .
+*/
+#ifndef QETLOGGER_H
+#define QETLOGGER_H
+
+#include "logring.h"
+
+#include
+#include
+#include
+#include
+
+/**
+ @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 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
diff --git a/sources/main.cpp b/sources/main.cpp
index eede1bfb8..a605f593c 100644
--- a/sources/main.cpp
+++ b/sources/main.cpp
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see .
*/
#include "cli_export.h"
+#include "logging/qetlogger.h"
#include "machine_info.h"
#include "qet.h"
#include "qetapp.h"
@@ -62,131 +63,16 @@ class EarlyFileOpenCatcher : public QObject
#endif
/**
- @brief myMessageOutput
- for debugging
- @param type : the messages that can be sent to a message handler
- @param context : were? wat?
- @param msg : Message
+ @brief qetLogMessageHandler
+ Installed via qInstallMessageHandler(); forwards to QetLogger, which
+ holds all the actual formatting/ring/rotation state. See
+ logging/qetlogger.h for the rationale (discussion #644).
*/
-void myMessageOutput(QtMsgType type,
+void qetLogMessageHandler(QtMsgType type,
const QMessageLogContext &context,
const QString &msg)
{
-
- QString txt=QTime::currentTime().toString("hh:mm:ss.zzz");
- QByteArray dbs =txt.toLocal8Bit();
- QByteArray localMsg = msg.toLocal8Bit();
- const char *file = context.file ? context.file : "";
- const char *function = context.function ? context.function : "";
-
- switch (type) {
- case QtDebugMsg:
- fprintf(stderr,
- "%s Debug: %s (%s:%u, %s)\n",
- dbs.constData(),
- localMsg.constData(),
- file,
- context.line,
- function);
- txt+=" Debug: ";
- break;
- case QtInfoMsg:
- fprintf(stderr,
- "%s Info: %s \n",
- dbs.constData(),
- localMsg.constData());
- txt+=" Info: ";
- break;
- case QtWarningMsg:
- fprintf(stderr,
- "%s Warning: %s (%s:%u, %s)\n",
- dbs.constData(),
- localMsg.constData(),
- file, context.line,
- function);
- txt+=" Warning: ";
- break;
- case QtCriticalMsg:
- fprintf(stderr,
- "%s Critical: %s (%s:%u, %s)\n",
- dbs.constData(),
- localMsg.constData(),
- file,
- context.line,
- function);
- txt+=" Critical: ";
- break;
- case QtFatalMsg:
- fprintf(stderr,
- "%s Fatal: %s (%s:%u, %s)\n",
- dbs.constData(),
- localMsg.constData(),
- file,
- context.line,
- function);
- txt+=" Fatal: ";
- break;
- default:
- fprintf(stderr,
- "%s Unknown: %s (%s:%u, %s)\n",
- dbs.constData(),
- localMsg.constData(),
- file,
- context.line,
- function);
- txt+=" Unknown: ";
- }
- txt+= msg;
- if(type==QtInfoMsg){
- txt+=" \n";
- } else {
- txt+= " (";
- txt+= context.file ? context.file : "";
- txt+= ":";
- txt+=QString::number(context.line ? context.line :0);
- txt+= ", ";
- txt+= context.function ? context.function : "";
- txt+=")\n";
- }
- QFile outFile(QETApp::dataDir()
- +"/"
- +QDate::currentDate().toString("yyyyMMdd")
- +".log");
- if(outFile.open(QIODevice::WriteOnly | QIODevice::Append))
- {
- QTextStream ts(&outFile);
- ts << txt;
- }
- outFile.close();
-}
-
-/**
- @brief delete_old_log_files
- delete old log files
- @param days : max days old
-*/
-void delete_old_log_files(int days)
-{
- const QDate today = QDate::currentDate();
- const QString path = QETApp::dataDir() % "/";
-
- QString filter("%1%1%1%1%1%1%1%1.log"); // pattern
- filter = filter.arg("[0123456789]"); // valid characters
-
- Q_FOREACH (auto fileInfo,
- QDir(path).entryInfoList(
- QStringList(filter),
- QDir::Files))
- {
- if (fileInfo.lastRead().date().daysTo(today) > days)
- {
- QString filepath = fileInfo.absoluteFilePath();
- QDir deletefile;
- deletefile.setPath(filepath);
- deletefile.remove(filepath);
- qDebug() << "File " % filepath % " is deleted!";
- }
- }
+ QetLogger::instance().handleMessage(type, context, msg);
}
/**
@@ -253,13 +139,19 @@ QGuiApplication::setHighDpiScaleFactorRoundingPolicy(QetSettings::hdpiScaleFacto
}
}
+ // Resolve the logger's state (log directory, session filename, open
+ // file handle) explicitly here, immediately before installing the
+ // handler -- not implicitly on whichever thread happens to log
+ // first. See QetLogger::init().
+ //
// Install the log-file message handler BEFORE the application starts:
// QETApp's constructor does the whole startup (collections, editor,
// opening the projects given on the command line), so installing the
// handler afterwards - as was done in the startup worker below - meant
// exactly the interesting lines (collection and project load timers)
// went to stderr, which is invisible in a Windows GUI session.
- qInstallMessageHandler(myMessageOutput);
+ QetLogger::instance().init();
+ qInstallMessageHandler(qetLogMessageHandler);
SingleApplication app(argc, argv, true);
#ifdef Q_OS_MACOS
@@ -308,7 +200,7 @@ QGuiApplication::setHighDpiScaleFactorRoundingPolicy(QetSettings::hdpiScaleFacto
{
qInfo("Start-up");
// delete old log files of max 7 days old.
- delete_old_log_files(7);
+ QetLogger::instance().pruneOldLogFiles(7);
MachineInfo::instance()->send_info_to_debug();
});
return app.exec();