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"