Akonadi

akremotelog.cpp
1 /*
2  SPDX-FileCopyrightText: 2018 Daniel Vrátil <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "akremotelog.h"
8 
9 #include <QString>
10 #include <QCoreApplication>
11 #include <QTimer>
12 #include <QThread>
13 #include <QDateTime>
14 #include <QLoggingCategory>
15 
16 #include <QDBusConnection>
17 #include <QDBusInterface>
18 #include <QDBusPendingCallWatcher>
19 #include <QDBusPendingReply>
20 #include <QDBusServiceWatcher>
21 
22 #include <private/instance_p.h>
23 
24 #define AKONADICONSOLE_SERVICE "org.kde.akonadiconsole"
25 #define AKONADICONSOLE_LOGGER_PATH "/logger"
26 #define AKONADICONSOLE_LOGGER_INTERFACE "org.kde.akonadiconsole.logger"
27 
28 namespace {
29 
30 class RemoteLogger : public QObject
31 {
32  Q_OBJECT
33 public:
34  explicit RemoteLogger()
35  : mWatcher(akonadiConsoleServiceName(), QDBusConnection::sessionBus(),
36  QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration)
37  {
39  this, &RemoteLogger::deleteLater);
40 
41  sInstance = this;
42 
43  // Don't do remote logging for Akonadi Console because it deadlocks it
44  if (QCoreApplication::applicationName() == QLatin1String("akonadiconsole")) {
45  return;
46  }
47 
49  this, &RemoteLogger::serviceRegistered);
51  this, &RemoteLogger::serviceUnregistered);
52 
53  mOldHandler = qInstallMessageHandler(dbusLogger);
54  }
55 
56  ~RemoteLogger()
57  {
58  sInstance = nullptr;
59 
61  qInstallMessageHandler(mOldHandler);
62 
63  mEnabled = false;
64  }
65 
66  static RemoteLogger *self()
67  {
68  return sInstance;
69  }
70 
71 private Q_SLOTS:
72  void serviceRegistered(const QString &service)
73  {
74  mAkonadiConsoleInterface = std::make_unique<QDBusInterface>(service,
75  QStringLiteral(AKONADICONSOLE_LOGGER_PATH),
76  QStringLiteral(AKONADICONSOLE_LOGGER_INTERFACE),
78  if (!mAkonadiConsoleInterface->isValid()) {
79  mAkonadiConsoleInterface.reset();
80  return;
81  }
82 
83  connect(mAkonadiConsoleInterface.get(), SIGNAL(enabledChanged(bool)), // clazy:exclude=old-style-connect
84  this, SLOT(onAkonadiConsoleLoggingEnabled(bool)));
85 
86  QTimer::singleShot(0, this, [this]() {
87  auto *watcher = new QDBusPendingCallWatcher(mAkonadiConsoleInterface->asyncCall(QStringLiteral("enabled")));
89  this, [this](QDBusPendingCallWatcher *watcher) {
90  watcher->deleteLater();
91  QDBusPendingReply<bool> reply = *watcher;
92  if (reply.isError()) {
93  return;
94  }
95  onAkonadiConsoleLoggingEnabled(reply.argumentAt<0>());
96  });
97  });
98  }
99 
100  void serviceUnregistered(const QString & /*unused*/)
101  {
102  onAkonadiConsoleLoggingEnabled(false);
103  mAkonadiConsoleInterface.reset();
104  }
105 
106  void onAkonadiConsoleLoggingEnabled(bool enabled)
107  {
108  if (mEnabled == enabled) {
109  return;
110  }
111 
112  mEnabled = enabled;
113  if (mEnabled) {
114  // FIXME: Qt calls our categoryFilter from installFilter() but at that
115  // point we cannot refer to mOldFilter yet (as we only receive it after
116  // this call returns. So we set our category filter twice: once to get
117  // the original Qt filter and second time to force our category filter
118  // to be called when we already know the old filter.
119  mOldFilter = QLoggingCategory::installFilter(categoryFilter);
120  QLoggingCategory::installFilter(categoryFilter);
121  } else {
123  mOldFilter = nullptr;
124  }
125  }
126 
127 private:
128  QString akonadiConsoleServiceName()
129  {
130  QString service = QStringLiteral(AKONADICONSOLE_SERVICE);
131  if (Akonadi::Instance::hasIdentifier()) {
132  service += QStringLiteral("-%1").arg(Akonadi::Instance::identifier());
133  }
134  return service;
135  }
136 
137  static void categoryFilter(QLoggingCategory *cat)
138  {
139  auto *const that = self();
140  if (!that) {
141  return;
142  }
143 
144  if (qstrncmp(cat->categoryName(), "org.kde.pim.", 12) == 0) {
145  cat->setEnabled(QtDebugMsg, true);
146  cat->setEnabled(QtInfoMsg, true);
147  cat->setEnabled(QtWarningMsg, true);
148  cat->setEnabled(QtCriticalMsg, true);
149  } else if (that->mOldFilter) {
150  that->mOldFilter(cat);
151  }
152  }
153 
154  static void dbusLogger(QtMsgType type, const QMessageLogContext &ctx, const QString &msg)
155  {
156  auto *const that = self();
157  if (!that) {
158  return;
159  }
160 
161  // Log to previous logger
162  that->mOldHandler(type, ctx, msg);
163 
164  if (that->mEnabled) {
165  that->mAkonadiConsoleInterface->asyncCallWithArgumentList(
166  QStringLiteral("message"),
168  QDateTime::currentMSecsSinceEpoch(), qAppName(),
169  qApp->applicationPid(), static_cast<int>(type),
170  QString::fromUtf8(ctx.category), QString::fromUtf8(ctx.file),
171  QString::fromUtf8(ctx.function), ctx.line, ctx.version, msg });
172  }
173  }
174 
175 private:
176  QDBusServiceWatcher mWatcher;
177  QLoggingCategory::CategoryFilter mOldFilter = nullptr;
178  QtMessageHandler mOldHandler = nullptr;
179  std::unique_ptr<QDBusInterface> mAkonadiConsoleInterface;
180  bool mEnabled = false;
181 
182  static RemoteLogger *sInstance;
183 };
184 
185 RemoteLogger *RemoteLogger::sInstance = nullptr;
186 
187 } // namespace
188 
189 void akInitRemoteLog()
190 {
191  Q_ASSERT(qApp->thread() == QThread::currentThread());
192 
193  if (!RemoteLogger::self()) {
194  new RemoteLogger();
195  }
196 }
197 
198 
199 #include "akremotelog.moc"
200 
void setEnabled(QtMsgType type, bool enable)
void serviceRegistered(const QString &serviceName)
void finished(QDBusPendingCallWatcher *self)
QLoggingCategory::CategoryFilter installFilter(QLoggingCategory::CategoryFilter filter)
QDBusConnection sessionBus()
qint64 currentMSecsSinceEpoch()
QString fromUtf8(const char *str, int size)
typedef CategoryFilter
void serviceUnregistered(const QString &serviceName)
void deleteLater()
const char * categoryName() const const
QVariant argumentAt(int index) const const
QThread * currentThread()
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QString applicationName()
bool isError() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sat Jan 23 2021 07:17:47 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.