File indexing completed on 2024-11-10 04:41:03

0001 /*
0002     SPDX-FileCopyrightText: 2008 Volker Krause <vkrause@kde.org>
0003 
0004     Inspired by kdelibs/kdecore/io/kdebug.h
0005     SPDX-FileCopyrightText: 1997 Matthias Kalle Dalheimer <kalle@kde.org>
0006     SPDX-FileCopyrightText: 2002 Holger Freyther <freyther@kde.org>
0007 
0008     SPDX-License-Identifier: LGPL-2.0-or-later
0009 */
0010 
0011 #include "akdebug.h"
0012 
0013 #include "private/standarddirs_p.h"
0014 
0015 #include <QDateTime>
0016 #include <QDir>
0017 #include <QFile>
0018 #include <QFileInfo>
0019 #include <QLoggingCategory>
0020 #include <QMutex>
0021 
0022 #include <KCrash>
0023 
0024 #include <cassert>
0025 
0026 class FileDebugStream : public QIODevice
0027 {
0028     Q_OBJECT
0029 public:
0030     FileDebugStream()
0031         : mType(QtCriticalMsg)
0032     {
0033         open(WriteOnly);
0034     }
0035 
0036     bool isSequential() const override
0037     {
0038         return true;
0039     }
0040     qint64 readData(char * /*data*/, qint64 /*maxlen*/) override
0041     {
0042         return 0;
0043     }
0044     qint64 readLineData(char * /*data*/, qint64 /*maxlen*/) override
0045     {
0046         return 0;
0047     }
0048 
0049     qint64 writeData(const char *data, qint64 len) override
0050     {
0051         if (!mFileName.isEmpty()) {
0052             QFile outputFile(mFileName);
0053             outputFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Unbuffered);
0054             outputFile.write(data, len);
0055             outputFile.putChar('\n');
0056             outputFile.close();
0057         }
0058 
0059         return len;
0060     }
0061 
0062     void setFileName(const QString &fileName)
0063     {
0064         mFileName = fileName;
0065     }
0066 
0067     void setType(QtMsgType type)
0068     {
0069         mType = type;
0070     }
0071 
0072 private:
0073     QString mFileName;
0074     QtMsgType mType;
0075 };
0076 
0077 class DebugPrivate
0078 {
0079 public:
0080     DebugPrivate()
0081         : origHandler(nullptr)
0082     {
0083     }
0084 
0085     ~DebugPrivate()
0086     {
0087         qInstallMessageHandler(origHandler);
0088         file.close();
0089     }
0090 
0091     static QString errorLogFileName(const QString &name)
0092     {
0093         return Akonadi::StandardDirs::saveDir("data") + QDir::separator() + name + QLatin1StringView(".error");
0094     }
0095 
0096     QString errorLogFileName() const
0097     {
0098         return errorLogFileName(name);
0099     }
0100 
0101     void log(QtMsgType type, const QMessageLogContext &context, const QString &msg)
0102     {
0103         QMutexLocker locker(&mutex);
0104 #ifdef QT_NO_DEBUG_OUTPUT
0105         if (type == QtDebugMsg) {
0106             return;
0107         }
0108 #endif
0109         QByteArray buf;
0110         QTextStream str(&buf);
0111         str << QDateTime::currentDateTime().toString(Qt::ISODate) << " [";
0112         switch (type) {
0113         case QtDebugMsg:
0114             str << "DEBUG";
0115             break;
0116         case QtInfoMsg:
0117             str << "INFO ";
0118             break;
0119         case QtWarningMsg:
0120             str << "WARN ";
0121             break;
0122         case QtFatalMsg:
0123             str << "FATAL";
0124             break;
0125         case QtCriticalMsg:
0126             str << "CRITICAL";
0127             break;
0128         }
0129         str << "] " << context.category << ": ";
0130         if (context.file && *context.file && context.line) {
0131             str << context.file << ":" << context.line << ": ";
0132         }
0133         if (context.function && *context.function) {
0134             str << context.function << ": ";
0135         }
0136         str << msg << "\n";
0137         str.flush();
0138         file.write(buf.constData(), buf.size());
0139         file.flush();
0140 
0141         if (origHandler) {
0142             origHandler(type, context, msg);
0143         }
0144     }
0145 
0146     void setName(const QString &appName)
0147     {
0148         name = appName;
0149 
0150         if (file.isOpen()) {
0151             file.close();
0152         }
0153         QFileInfo finfo(errorLogFileName());
0154         if (!finfo.absoluteDir().exists()) {
0155             QDir().mkpath(finfo.absolutePath());
0156         }
0157         file.setFileName(errorLogFileName());
0158         file.open(QIODevice::WriteOnly | QIODevice::Unbuffered);
0159     }
0160 
0161     void setOrigHandler(QtMessageHandler origHandler_)
0162     {
0163         origHandler = origHandler_;
0164     }
0165 
0166     void setOrigCategoryFilter(QLoggingCategory::CategoryFilter origFilter_)
0167     {
0168         origFilter = origFilter_;
0169     }
0170 
0171     QMutex mutex;
0172     QFile file;
0173     QString name;
0174     QtMessageHandler origHandler;
0175     QLoggingCategory::CategoryFilter origFilter;
0176     QByteArray loggingCategory;
0177 };
0178 
0179 Q_GLOBAL_STATIC(DebugPrivate, sInstance) // NOLINT(readability-redundant-member-init)
0180 
0181 void akMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
0182 {
0183     switch (type) {
0184     case QtDebugMsg:
0185     case QtInfoMsg:
0186     case QtWarningMsg:
0187     case QtCriticalMsg:
0188         sInstance()->log(type, context, msg);
0189         break;
0190     case QtFatalMsg:
0191         sInstance()->log(QtInfoMsg, context, msg);
0192         abort();
0193     }
0194 }
0195 
0196 void akCategoryFilter(QLoggingCategory *category)
0197 {
0198     if ((qstrcmp(category->categoryName(), sInstance()->loggingCategory.constData()) == 0)
0199         || (qstrcmp(category->categoryName(), "org.kde.pim.akonadiprivate") == 0)) {
0200         category->setEnabled(QtDebugMsg, true);
0201         category->setEnabled(QtInfoMsg, true);
0202         category->setEnabled(QtWarningMsg, true);
0203         category->setEnabled(QtCriticalMsg, true);
0204         category->setEnabled(QtFatalMsg, true);
0205     } else if (sInstance()->origFilter) {
0206         sInstance()->origFilter(category);
0207     }
0208 }
0209 
0210 void akInit(const QString &appName)
0211 {
0212     KCrash::initialize();
0213 
0214     const QString name = QFileInfo(appName).fileName();
0215     const auto errorLogFile = DebugPrivate::errorLogFileName(name);
0216     QFileInfo infoOld(errorLogFile + QLatin1StringView(".old"));
0217     if (infoOld.exists()) {
0218         QFile fileOld(infoOld.absoluteFilePath());
0219         const bool success = fileOld.remove();
0220         if (!success) {
0221             qFatal("Cannot remove old log file '%s': %s", qUtf8Printable(fileOld.fileName()), qUtf8Printable(fileOld.errorString()));
0222         }
0223     }
0224 
0225     QFileInfo info(errorLogFile);
0226     if (info.exists()) {
0227         QFile file(info.absoluteFilePath());
0228         const QString oldName = errorLogFile + QLatin1StringView(".old");
0229         const bool success = file.copy(oldName);
0230         if (!success) {
0231             qFatal("Cannot rename log file '%s' to '%s': %s", qUtf8Printable(file.fileName()), qUtf8Printable(oldName), qUtf8Printable(file.errorString()));
0232         }
0233     }
0234 
0235     QtMessageHandler origHandler = qInstallMessageHandler(akMessageHandler);
0236     sInstance()->setName(name);
0237     sInstance()->setOrigHandler(origHandler);
0238 }
0239 
0240 void akMakeVerbose(const QByteArray &category)
0241 {
0242     sInstance()->loggingCategory = category;
0243     QLoggingCategory::CategoryFilter oldFilter = QLoggingCategory::installFilter(akCategoryFilter);
0244     sInstance()->setOrigCategoryFilter(oldFilter);
0245 }
0246 
0247 #include "akdebug.moc"