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

0001 /*
0002     SPDX-FileCopyrightText: 2018 Daniel Vrátil <dvratil@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "akremotelog.h"
0008 
0009 #include <QCoreApplication>
0010 #include <QDateTime>
0011 #include <QLoggingCategory>
0012 #include <QString>
0013 #include <QThread>
0014 #include <QTimer>
0015 
0016 #include <QDBusConnection>
0017 #include <QDBusInterface>
0018 #include <QDBusPendingCallWatcher>
0019 #include <QDBusPendingReply>
0020 #include <QDBusServiceWatcher>
0021 
0022 #include "private/instance_p.h"
0023 
0024 #define AKONADICONSOLE_SERVICE "org.kde.akonadiconsole"
0025 #define AKONADICONSOLE_LOGGER_PATH "/logger"
0026 #define AKONADICONSOLE_LOGGER_INTERFACE "org.kde.akonadiconsole.logger"
0027 
0028 namespace
0029 {
0030 class RemoteLogger : public QObject
0031 {
0032     Q_OBJECT
0033 public:
0034     explicit RemoteLogger()
0035         : mWatcher(akonadiConsoleServiceName(),
0036                    QDBusConnection::sessionBus(),
0037                    QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration)
0038     {
0039         connect(qApp, &QCoreApplication::aboutToQuit, this, &RemoteLogger::deleteLater);
0040 
0041         sInstance = this;
0042 
0043         // Don't do remote logging for Akonadi Console because it deadlocks it
0044         if (QCoreApplication::applicationName() == QLatin1StringView("akonadiconsole")) {
0045             return;
0046         }
0047 
0048         connect(&mWatcher, &QDBusServiceWatcher::serviceRegistered, this, &RemoteLogger::serviceRegistered);
0049         connect(&mWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &RemoteLogger::serviceUnregistered);
0050 
0051         mOldHandler = qInstallMessageHandler(dbusLogger);
0052     }
0053 
0054     ~RemoteLogger() override
0055     {
0056         sInstance = nullptr;
0057 
0058         QLoggingCategory::installFilter(mOldFilter);
0059         qInstallMessageHandler(mOldHandler);
0060 
0061         mEnabled = false;
0062     }
0063 
0064     static RemoteLogger *self()
0065     {
0066         return sInstance;
0067     }
0068 
0069 private Q_SLOTS:
0070     void serviceRegistered(const QString &service)
0071     {
0072         mAkonadiConsoleInterface = std::make_unique<QDBusInterface>(service,
0073                                                                     QStringLiteral(AKONADICONSOLE_LOGGER_PATH),
0074                                                                     QStringLiteral(AKONADICONSOLE_LOGGER_INTERFACE),
0075                                                                     QDBusConnection::sessionBus(),
0076                                                                     this);
0077         if (!mAkonadiConsoleInterface->isValid()) {
0078             mAkonadiConsoleInterface.reset();
0079             return;
0080         }
0081 
0082         connect(mAkonadiConsoleInterface.get(), // clazy:exclude=old-style-connect
0083                 SIGNAL(enabledChanged(bool)),
0084                 this,
0085                 SLOT(onAkonadiConsoleLoggingEnabled(bool)));
0086 
0087         QTimer::singleShot(0, this, [this]() {
0088             auto watcher = new QDBusPendingCallWatcher(mAkonadiConsoleInterface->asyncCall(QStringLiteral("enabled")));
0089             connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
0090                 watcher->deleteLater();
0091                 QDBusPendingReply<bool> reply = *watcher;
0092                 if (reply.isError()) {
0093                     return;
0094                 }
0095                 onAkonadiConsoleLoggingEnabled(reply.argumentAt<0>());
0096             });
0097         });
0098     }
0099 
0100     void serviceUnregistered(const QString & /*unused*/)
0101     {
0102         onAkonadiConsoleLoggingEnabled(false);
0103         mAkonadiConsoleInterface.reset();
0104     }
0105 
0106     void onAkonadiConsoleLoggingEnabled(bool enabled)
0107     {
0108         if (mEnabled == enabled) {
0109             return;
0110         }
0111 
0112         mEnabled = enabled;
0113         if (mEnabled) {
0114             // FIXME: Qt calls our categoryFilter from installFilter() but at that
0115             // point we cannot refer to mOldFilter yet (as we only receive it after
0116             // this call returns. So we set our category filter twice: once to get
0117             // the original Qt filter and second time to force our category filter
0118             // to be called when we already know the old filter.
0119             mOldFilter = QLoggingCategory::installFilter(categoryFilter);
0120             QLoggingCategory::installFilter(categoryFilter);
0121         } else {
0122             QLoggingCategory::installFilter(mOldFilter);
0123             mOldFilter = nullptr;
0124         }
0125     }
0126 
0127 private:
0128     QString akonadiConsoleServiceName()
0129     {
0130         QString service = QStringLiteral(AKONADICONSOLE_SERVICE);
0131         if (Akonadi::Instance::hasIdentifier()) {
0132             service += QStringLiteral("-%1").arg(Akonadi::Instance::identifier());
0133         }
0134         return service;
0135     }
0136 
0137     static void categoryFilter(QLoggingCategory *cat)
0138     {
0139         auto const that = self();
0140         if (!that) {
0141             return;
0142         }
0143 
0144         if (qstrncmp(cat->categoryName(), "org.kde.pim.", 12) == 0) {
0145             cat->setEnabled(QtDebugMsg, true);
0146             cat->setEnabled(QtInfoMsg, true);
0147             cat->setEnabled(QtWarningMsg, true);
0148             cat->setEnabled(QtCriticalMsg, true);
0149         } else if (that->mOldFilter) {
0150             that->mOldFilter(cat);
0151         }
0152     }
0153 
0154     static void dbusLogger(QtMsgType type, const QMessageLogContext &ctx, const QString &msg)
0155     {
0156         auto const that = self();
0157         if (!that) {
0158             return;
0159         }
0160 
0161         // Log to previous logger
0162         that->mOldHandler(type, ctx, msg);
0163 
0164         if (that->mEnabled) {
0165             that->mAkonadiConsoleInterface->asyncCallWithArgumentList(QStringLiteral("message"),
0166                                                                       QList<QVariant>{QDateTime::currentMSecsSinceEpoch(),
0167                                                                                       qAppName(),
0168                                                                                       qApp->applicationPid(),
0169                                                                                       static_cast<int>(type),
0170                                                                                       QString::fromUtf8(ctx.category),
0171                                                                                       QString::fromUtf8(ctx.file),
0172                                                                                       QString::fromUtf8(ctx.function),
0173                                                                                       ctx.line,
0174                                                                                       ctx.version,
0175                                                                                       msg});
0176         }
0177     }
0178 
0179 private:
0180     QDBusServiceWatcher mWatcher;
0181     QLoggingCategory::CategoryFilter mOldFilter = nullptr;
0182     QtMessageHandler mOldHandler = nullptr;
0183     std::unique_ptr<QDBusInterface> mAkonadiConsoleInterface;
0184     bool mEnabled = false;
0185 
0186     static RemoteLogger *sInstance;
0187 };
0188 
0189 RemoteLogger *RemoteLogger::sInstance = nullptr;
0190 
0191 } // namespace
0192 
0193 void akInitRemoteLog()
0194 {
0195     Q_ASSERT(qApp->thread() == QThread::currentThread());
0196 
0197     if (!RemoteLogger::self()) {
0198         new RemoteLogger();
0199     }
0200 }
0201 
0202 #include "akremotelog.moc"