File indexing completed on 2024-10-06 12:54:05

0001 // SPDX-FileCopyrightText: 1997 Matthias Kalle Dalheimer <kalle@kde.org>
0002 // SPDX-FileCopyrightText: 2002 Holger Freyther <freyther@kde.org>
0003 // SPDX-FileCopyrightText: 2008 Volker Krause <vkrause@kde.org>
0004 // SPDX-FileCopyrightText: 2023 Tobias Fella <fella@posteo.de>
0005 // SPDX-License-Identifier: LGPL-2.0-or-later
0006 
0007 #include "logger.h"
0008 
0009 #include <QDateTime>
0010 #include <QDir>
0011 #include <QFileInfo>
0012 #include <QLoggingCategory>
0013 #include <QMutex>
0014 #include <QStandardPaths>
0015 
0016 static QLoggingCategory::CategoryFilter oldCategoryFilter = nullptr;
0017 static QtMessageHandler oldHandler = nullptr;
0018 static bool e2eeDebugEnabled = false;
0019 
0020 class FileDebugStream : public QIODevice
0021 {
0022     Q_OBJECT
0023 public:
0024     FileDebugStream()
0025         : mType(QtCriticalMsg)
0026     {
0027         open(WriteOnly);
0028     }
0029 
0030     bool isSequential() const override
0031     {
0032         return true;
0033     }
0034     qint64 readData(char *, qint64) override
0035     {
0036         return 0;
0037     }
0038     qint64 readLineData(char *, qint64) override
0039     {
0040         return 0;
0041     }
0042 
0043     qint64 writeData(const char *data, qint64 len) override
0044     {
0045         if (!mFileName.isEmpty()) {
0046             QFile outputFile(mFileName);
0047             outputFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Unbuffered);
0048             outputFile.write(data, len);
0049             outputFile.putChar('\n');
0050             outputFile.close();
0051         }
0052 
0053         return len;
0054     }
0055 
0056     void setFileName(const QString &fileName)
0057     {
0058         mFileName = fileName;
0059     }
0060 
0061     void setType(QtMsgType type)
0062     {
0063         mType = type;
0064     }
0065 
0066 private:
0067     QString mFileName;
0068     QtMsgType mType;
0069 };
0070 
0071 class DebugPrivate
0072 {
0073 public:
0074     DebugPrivate()
0075         : origHandler(nullptr)
0076     {
0077     }
0078 
0079     ~DebugPrivate()
0080     {
0081         qInstallMessageHandler(origHandler);
0082         file.close();
0083     }
0084 
0085     void log(QtMsgType type, const QMessageLogContext &context, const QString &message)
0086     {
0087         QMutexLocker locker(&mutex);
0088         QByteArray buf;
0089         QTextStream str(&buf);
0090         str << QDateTime::currentDateTime().toString(Qt::ISODate) << " [";
0091         switch (type) {
0092         case QtDebugMsg:
0093             str << "DEBUG";
0094             break;
0095         case QtInfoMsg:
0096             str << "INFO ";
0097             break;
0098         case QtWarningMsg:
0099             str << "WARN ";
0100             break;
0101         case QtFatalMsg:
0102             str << "FATAL";
0103             break;
0104         case QtCriticalMsg:
0105             str << "CRITICAL";
0106             break;
0107         }
0108         str << "] " << context.category << ": ";
0109         if (context.file && *context.file && context.line) {
0110             str << context.file << ":" << context.line << ": ";
0111         }
0112         if (context.function && *context.function) {
0113             str << context.function << ": ";
0114         }
0115         str << message << "\n";
0116         str.flush();
0117         file.write(buf.constData(), buf.size());
0118         file.flush();
0119 
0120         if (oldHandler && (strcmp(context.category, "quotient.e2ee") != 0 || e2eeDebugEnabled)) {
0121             oldHandler(type, context, message);
0122         }
0123     }
0124 
0125     void setName(const QString &appName)
0126     {
0127         name = appName;
0128 
0129         if (file.isOpen()) {
0130             file.close();
0131         }
0132         auto filePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QDir::separator() + appName;
0133 
0134         QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QDir::separator());
0135         auto entryList = dir.entryList({appName + QStringLiteral(".*")});
0136         std::sort(entryList.begin(), entryList.end(), [](const auto &left, const auto &right) {
0137             auto leftIndex = left.split(".").last().toInt();
0138             auto rightIndex = right.split(".").last().toInt();
0139             return leftIndex > rightIndex;
0140         });
0141         for (const auto &entry : entryList) {
0142             bool ok = false;
0143             const auto index = entry.split(".").last().toInt(&ok);
0144             if (!ok) {
0145                 continue;
0146             }
0147             QFileInfo info(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QDir::separator() + entry);
0148             if (info.exists()) {
0149                 QFile file(info.absoluteFilePath());
0150                 if (index > 50) {
0151                     file.remove();
0152                     continue;
0153                 }
0154                 const QString newName = filePath + QStringLiteral(".%1").arg(index + 1);
0155                 const bool success = file.copy(newName);
0156                 if (success) {
0157                     file.remove();
0158                 } else {
0159                     qFatal("Cannot rename log file '%s' to '%s': %s",
0160                            qUtf8Printable(file.fileName()),
0161                            qUtf8Printable(newName),
0162                            qUtf8Printable(file.errorString()));
0163                 }
0164             }
0165         }
0166 
0167         QFileInfo finfo(filePath);
0168         if (!finfo.absoluteDir().exists()) {
0169             QDir().mkpath(finfo.absolutePath());
0170         }
0171         file.setFileName(filePath + QStringLiteral(".0"));
0172         file.open(QIODevice::WriteOnly | QIODevice::Unbuffered);
0173     }
0174 
0175     void setOrigHandler(QtMessageHandler origHandler_)
0176     {
0177         origHandler = origHandler_;
0178     }
0179 
0180     QMutex mutex;
0181     QFile file;
0182     QString name;
0183     QtMessageHandler origHandler;
0184     QByteArray loggingCategory;
0185 };
0186 
0187 Q_GLOBAL_STATIC(DebugPrivate, sInstance)
0188 
0189 void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message)
0190 {
0191     switch (type) {
0192     case QtDebugMsg:
0193     case QtInfoMsg:
0194     case QtWarningMsg:
0195     case QtCriticalMsg:
0196         sInstance()->log(type, context, message);
0197         break;
0198     case QtFatalMsg:
0199         sInstance()->log(QtInfoMsg, context, message);
0200         abort();
0201     }
0202 }
0203 
0204 void filter(QLoggingCategory *category)
0205 {
0206     if (qstrcmp(category->categoryName(), "quotient.e2ee") == 0) {
0207         category->setEnabled(QtDebugMsg, true);
0208     } else if (oldCategoryFilter) {
0209         oldCategoryFilter(category);
0210     }
0211 }
0212 
0213 void initLogging()
0214 {
0215     e2eeDebugEnabled = QLoggingCategory("quotient.e2ee", QtDebugMsg).isEnabled(QtDebugMsg);
0216     oldCategoryFilter = QLoggingCategory::installFilter(filter);
0217     oldHandler = qInstallMessageHandler(messageHandler);
0218     sInstance->setOrigHandler(oldHandler);
0219     sInstance->setName("neochat.log");
0220 }
0221 
0222 #include "logger.moc"