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:
+15
-123
@@ -16,6 +16,7 @@
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#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();
|
||||
|
||||
Reference in New Issue
Block a user