LCOV - code coverage report
Current view: top level - src/utilities - FangLogging.cpp (source / functions) Coverage Total Hit
Test: coverage.info.cleaned Lines: 90.0 % 80 72
Test Date: 2026-03-23 10:19:47 Functions: 83.3 % 18 15

            Line data    Source code
       1              : #include "FangLogging.h"
       2              : 
       3              : #include <QCoreApplication>
       4              : #include <QDateTime>
       5              : #include <QDir>
       6              : #include <QFile>
       7              : #include <QMutex>
       8              : #include <QStandardPaths>
       9              : #include <QTextStream>
      10              : 
      11              : static constexpr qint64 MaxFileSize = 5 * 1024 * 1024; // 5 MB
      12              : static constexpr int MaxRotatedFiles = 3;
      13              : static constexpr int WriteCheckInterval = 100;
      14              : 
      15              : static QFile logFile;
      16              : static QMutex logMutex;
      17              : static int writeCount = 0;
      18              : static QString logFilePath;
      19              : static QtMessageHandler previousHandler = nullptr;
      20              : 
      21            0 : static QString logDir()
      22              : {
      23              : #if defined(Q_OS_MACOS)
      24              :     return QDir::homePath() + QStringLiteral("/Library/Logs/Fang");
      25              : #elif defined(Q_OS_WIN)
      26              :     return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)
      27              :            + QStringLiteral("/logs");
      28              : #else
      29            0 :     return QStandardPaths::writableLocation(QStandardPaths::StateLocation);
      30              : #endif
      31              : }
      32              : 
      33            6 : static void rotateIfNeeded()
      34              : {
      35            6 :     if (logFile.size() < MaxFileSize) {
      36            2 :         return;
      37              :     }
      38              : 
      39            4 :     logFile.close();
      40              : 
      41            4 :     const QString base = logFilePath;
      42            4 :     const QDir dir = QFileInfo(base).absoluteDir();
      43            4 :     const QString stem = QFileInfo(base).completeBaseName();
      44            4 :     const QString suffix = QFileInfo(base).suffix();
      45              : 
      46           16 :     for (int i = MaxRotatedFiles; i >= 1; --i) {
      47              :         const QString src = (i == 1)
      48           12 :             ? base
      49           20 :             : dir.filePath(QStringLiteral("%1.%2.%3").arg(stem, QString::number(i - 1), suffix));
      50           12 :         const QString dst = dir.filePath(QStringLiteral("%1.%2.%3").arg(stem, QString::number(i), suffix));
      51              : 
      52           12 :         QFile::remove(dst);
      53           12 :         QFile::rename(src, dst);
      54           12 :     }
      55              : 
      56            4 :     logFile.setFileName(base);
      57            4 :     if (!logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
      58            0 :         qWarning("Failed to reopen log file after rotation: %s", qPrintable(base));
      59              :     }
      60            4 : }
      61              : 
      62              : // Log level to string literal
      63          608 : static const char *levelString(QtMsgType type)
      64              : {
      65          608 :     switch (type) {
      66          602 :     case QtDebugMsg:
      67          602 :         return "debug";
      68            1 :     case QtInfoMsg:
      69            1 :         return "info";
      70            4 :     case QtWarningMsg:
      71            4 :         return "warning";
      72            1 :     case QtCriticalMsg:
      73            1 :         return "critical";
      74            0 :     case QtFatalMsg:
      75            0 :         return "fatal";
      76              :     }
      77            0 :     return "unknown";
      78              : }
      79              : 
      80          608 : static void fileMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
      81              : {
      82          608 :     const QString timestamp = QDateTime::currentDateTime().toString(Qt::ISODateWithMs);
      83          608 :     const QString category = context.category ? QString::fromUtf8(context.category) : QString();
      84         1216 :     const QString line = QStringLiteral("[%1] [%2] [%3] %4\n")
      85          608 :                              .arg(timestamp, QLatin1String(levelString(type)), category, msg);
      86              : 
      87              :     {
      88          608 :         QMutexLocker locker(&logMutex);
      89          608 :         if (logFile.isOpen()) {
      90          608 :             logFile.write(line.toUtf8());
      91          608 :             logFile.flush();
      92              : 
      93          608 :             if (++writeCount >= WriteCheckInterval) {
      94            6 :                 writeCount = 0;
      95            6 :                 rotateIfNeeded();
      96              :             }
      97              :         }
      98          608 :     }
      99              : 
     100          608 :     if (previousHandler) {
     101          608 :         previousHandler(type, context, msg);
     102              :     }
     103          608 : }
     104              : 
     105           17 : void initFileLogging(const QString &logDirOverride)
     106              : {
     107           17 :     const QString dir = logDirOverride.isEmpty() ? logDir() : logDirOverride;
     108           17 :     QDir().mkpath(dir);
     109              : 
     110           17 :     logFilePath = dir + QStringLiteral("/fang.log");
     111           17 :     logFile.setFileName(logFilePath);
     112              : 
     113           17 :     if (!logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
     114            1 :         qWarning("Failed to open log file: %s", qPrintable(logFilePath));
     115            1 :         return;
     116              :     }
     117              : 
     118           16 :     previousHandler = qInstallMessageHandler(fileMessageHandler);
     119           17 : }
     120              : 
     121           19 : void shutdownFileLogging()
     122              : {
     123           19 :     if (previousHandler) {
     124           16 :         qInstallMessageHandler(previousHandler);
     125           16 :         previousHandler = nullptr;
     126              :     }
     127           19 :     logFile.close();
     128           19 :     logFilePath.clear();
     129           19 :     writeCount = 0;
     130           19 : }
     131              : 
     132              : // Define logging categories
     133              : // In debug builds, these output at QtDebugMsg level
     134              : // In release builds, they're disabled except for warnings/critical
     135              : 
     136              : #ifdef QT_DEBUG
     137           22 : Q_LOGGING_CATEGORY(logApp, "fang.app", QtDebugMsg)
     138            2 : Q_LOGGING_CATEGORY(logDb, "fang.db", QtDebugMsg)
     139          585 : Q_LOGGING_CATEGORY(logModel, "fang.model", QtDebugMsg)
     140           24 : Q_LOGGING_CATEGORY(logOperation, "fang.operation", QtDebugMsg)
     141           16 : Q_LOGGING_CATEGORY(logParser, "fang.parser", QtDebugMsg)
     142           35 : Q_LOGGING_CATEGORY(logNetwork, "fang.network", QtDebugMsg)
     143            0 : Q_LOGGING_CATEGORY(logRewriter, "fang.rewriter", QtDebugMsg)
     144            0 : Q_LOGGING_CATEGORY(logServer, "fang.server", QtDebugMsg)
     145          138 : Q_LOGGING_CATEGORY(logUtility, "fang.utility", QtDebugMsg)
     146            4 : Q_LOGGING_CATEGORY(logFavicon, "fang.favicon", QtDebugMsg)
     147           74 : Q_LOGGING_CATEGORY(logWebPage, "fang.webpage", QtDebugMsg)
     148          580 : Q_LOGGING_CATEGORY(logMock, "fang.mock", QtDebugMsg)
     149              : #else
     150              : Q_LOGGING_CATEGORY(logApp, "fang.app", QtWarningMsg)
     151              : Q_LOGGING_CATEGORY(logDb, "fang.db", QtWarningMsg)
     152              : Q_LOGGING_CATEGORY(logModel, "fang.model", QtWarningMsg)
     153              : Q_LOGGING_CATEGORY(logOperation, "fang.operation", QtWarningMsg)
     154              : Q_LOGGING_CATEGORY(logParser, "fang.parser", QtWarningMsg)
     155              : Q_LOGGING_CATEGORY(logNetwork, "fang.network", QtWarningMsg)
     156              : Q_LOGGING_CATEGORY(logRewriter, "fang.rewriter", QtWarningMsg)
     157              : Q_LOGGING_CATEGORY(logServer, "fang.server", QtWarningMsg)
     158              : Q_LOGGING_CATEGORY(logUtility, "fang.utility", QtWarningMsg)
     159              : Q_LOGGING_CATEGORY(logFavicon, "fang.favicon", QtWarningMsg)
     160              : Q_LOGGING_CATEGORY(logWebPage, "fang.webpage", QtWarningMsg)
     161              : Q_LOGGING_CATEGORY(logMock, "fang.mock", QtWarningMsg)
     162              : #endif
        

Generated by: LCOV version 2.0-1