maxLibQt
AppDebugMessageHandler.cpp
Go to the documentation of this file.
1 /*
2  AppDebugMessageHandler
3 
4  COPYRIGHT: (c)2017 Maxim Paperno; All Right Reserved.
5  Contact: http://www.WorldDesign.com/contact
6 
7  LICENSE:
8 
9  Commercial License Usage
10  Licensees holding valid commercial licenses may use this file in
11  accordance with the terms contained in a written agreement between
12  you and the copyright holder.
13 
14  GNU General Public License Usage
15  Alternatively, this file may be used under the terms of the GNU
16  General Public License as published by the Free Software Foundation,
17  either version 3 of the License, or (at your option) any later version.
18 
19  This program is distributed in the hope that it will be useful,
20  but WITHOUT ANY WARRANTY; without even the implied warranty of
21  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22  GNU General Public License for more details.
23 
24  A copy of the GNU General Public License is available at <http://www.gnu.org/licenses/>.
25 */
26 
27 #include "AppDebugMessageHandler.h"
28 
29 #include <cstdlib>
30 #include <iostream>
31 #include <QFileDevice>
32 
33 AppDebugMessageHandler::AppDebugMessageHandler() :
34  QObject(),
35  m_defaultHandler(nullptr)
36 {
37  setAppDebugOutputLevel(APP_DBG_HANDLER_DEFAULT_LEVEL);
38  setShowSourcePath(APP_DBG_HANDLER_SHOW_SRC_PATH);
39  setSourceBasePath(QStringLiteral(APP_DBG_HANDLER_SRC_PATH));
40  setShowFunctionDeclarations(APP_DBG_HANDLER_SHOW_FUNCTION_DECL);
41  setShowTimestamp(APP_DBG_HANDLER_SHOW_TIMESTAMP);
42  setTimestampFormat(QStringLiteral(APP_DBG_HANDLER_TIMESTAMP_FORMAT));
43  defaultMessagePattern(); // preload
44 #if (QT_VERSION < QT_VERSION_CHECK(5, 4, 0))
45  m_functionFilter = QRegularExpression("^.+?(\\w+\\().+$");
46 #endif
47 }
48 
49 // static
51 {
53  return &instance;
54 }
55 
56 void AppDebugMessageHandler::setAppDebugOutputLevel(quint8 appDebugOutputLevel)
57 {
58  QWriteLocker locker(&m_mutex);
59  m_appDebugOutputLevel = qMin<quint8>(appDebugOutputLevel, 4);
60 }
61 
63 {
64  QWriteLocker locker(&m_mutex);
65  m_showSourcePath = showSourcePath;
66  m_defaultPattern.clear();
67 }
68 
70 {
71  QWriteLocker locker(&m_mutex);
72  m_srcPathFilter = path.isEmpty() ? QRegularExpression() : QRegularExpression(QStringLiteral("^%1[\\\\\\/](.*?)$").arg(path), QRegularExpression::InvertedGreedinessOption);
73 }
74 
75 void AppDebugMessageHandler::setShowFunctionDeclarations(bool showFunctionDeclarations)
76 {
77  QWriteLocker locker(&m_mutex);
78  m_showFunctionDeclarations = showFunctionDeclarations;
79  m_defaultPattern.clear();
80 }
81 
83 {
84  QWriteLocker locker(&m_mutex);
85  m_showTimestamp = showTimestamp;
86  m_defaultPattern.clear();
87 }
88 
90 {
91  QWriteLocker locker(&m_mutex);
92  m_tsFormat = timeFormat;
93  m_defaultPattern.clear();
94 }
95 
97 {
98  QWriteLocker locker(&m_mutex);
99  m_msgPattern = pattern;
100 }
101 
103 {
104  QWriteLocker locker(&m_mutex);
105  if (device && !m_outputDevices.contains(device))
106  m_outputDevices.append(device);
107 }
108 
110 {
111  if (!device)
112  return;
113  QWriteLocker locker(&m_mutex);
114 #if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
115  m_outputDevices.removeAll(device);
116 #else
117  int i = 0;
118  foreach (QIODevice * d, m_outputDevices) {
119  if (d == device)
120  m_outputDevices.remove(i);
121  ++i;
122  }
123 #endif
124 }
125 
127 {
128  if (!m_defaultPattern.isEmpty())
129  return m_defaultPattern;
130 
131  QString msgPattern;
132  if (m_showTimestamp)
133  msgPattern.append(QStringLiteral("[%{time %1}] ").arg(m_tsFormat));
134 
135  msgPattern.append(QLatin1String("[%{short-type}] "));
136 
137  if (m_showSourcePath)
138  msgPattern.append(QLatin1String("%{file}::"));
139 
140  if (m_showFunctionDeclarations)
141  msgPattern.append(QLatin1String("%{full-function}"));
142  else
143  msgPattern.append(QLatin1String("%{function}()"));
144 
145  msgPattern.append(QLatin1String(":%{line} -%{if-category} [%{category}]%{endif} %{message}"));
146 
147 #ifndef QT_NO_GLIB
148  msgPattern.append("%{if-fatal}\nBACKTRACE:\n%{backtrace depth=12 separator=\"\n\"}%{endif}");
149 #endif
150 
151  m_defaultPattern = msgPattern;
152  return msgPattern;
153 }
154 
156 {
157  return m_msgPattern.isEmpty() ? defaultMessagePattern() : m_msgPattern;
158 }
159 
161 {
162 #if APP_DBG_HANDLER_ENABLE
163  m_defaultHandler = qInstallMessageHandler(g_appDebugMessageHandler);
164 #else
165  qInstallMessageHandler(nullptr);
166 #endif
167 }
168 
169 void AppDebugMessageHandler::messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
170 {
171  // normalize types, QtDebugMsg stays 0, QtInfoMsg becomes 1, rest are QtMsgType + 1
172  quint8 lvl = type;
173  if (type == QtInfoMsg)
174  lvl = 1;
175  else if (type > QtDebugMsg)
176  ++lvl;
177 
178  QReadLocker locker(&m_mutex);
179 
180  if (lvl < m_appDebugOutputLevel)
181  return;
182 
183 #if defined(Q_OS_LINUX) && (QT_VERSION < QT_VERSION_CHECK(5, 3, 0))
184  // Filter out lots of QPainter warnings from undocked QDockWidgets... hackish but effective (only workaround found so far)
185  if (lvl == 2 && QString(context.function).contains("QPainter::"))
186  return;
187 #endif
188 
189  QString msgPattern = messagePattern();
190 
191  QString file = context.file;
192  if (m_srcPathFilter.isValid())
193  file.replace(m_srcPathFilter, "\\1");
194 
195  const bool hasDevices = !m_outputDevices.isEmpty();
196 
197  locker.unlock();
198 
199  msgPattern.replace(QLatin1String("%{short-type}"), shortTypeNames().value(type, QStringLiteral("?")));
200  msgPattern.replace(QLatin1String("%{full-function}"), context.function);
201 
202  QMessageLogContext newContext(qPrintable(file), context.line, context.function, context.category);
203 
204  qSetMessagePattern(msgPattern);
205 
206  if (!m_defaultHandler || hasDevices || receivers(SIGNAL(messageOutput(quint8, const QString &)))) {
207 #if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
208  msgPattern = qFormatLogMessage(type, newContext, msg);
209 #else
210  msgPattern.replace("%{type}", fullTypeNames().value(type, "unknown"));
211  msgPattern.replace("%{line}", QString::number(context.line));
212  msgPattern.replace("%{if-category} [%{category}]%{endif}", QString(context.category));
213  msgPattern.replace("%{message}", msg);
214  msgPattern.replace("%{function}", QString(context.function).replace(m_functionFilter, "\\1)"));
215 #endif
216 
217  emit messageOutput(lvl, msgPattern);
218 
219  if (hasDevices) {
220  locker.relock();
221  const QVector<QIODevice *> devices(m_outputDevices);
222  locker.unlock();
223  for (QIODevice * d : devices) {
224  if (d && d->isWritable() && (!d->property("level").isValid() || d->property("level").toInt() <= lvl)) {
225  d->write(qPrintable(msgPattern + "\n"));
226  if (QFileDevice * fd = qobject_cast<QFileDevice *>(d))
227  fd->flush();
228  }
229  }
230  }
231  }
232 
233  // if (QThread::currentThread() == qApp->thread()) // gui thread
234 
235  if (m_defaultHandler) {
236  m_defaultHandler(type, newContext, msg);
237  }
238  else {
239  fprintf(stderr, "%s", qPrintable(msgPattern));
240  if (type == QtFatalMsg)
241  abort();
242  }
243 }
244 
245 // static
247 {
248  static QHash<int, QString> symbols({
249  {QtDebugMsg, tr("D")},
250  {QtWarningMsg, tr("W")},
251  {QtCriticalMsg, tr("C")},
252  {QtFatalMsg, tr("F")},
253  {QtInfoMsg, tr("I")}
254  });
255  return symbols;
256 }
257 
258 #if (QT_VERSION < QT_VERSION_CHECK(5, 4, 0))
259 // static
260 QHash<int, QString> &AppDebugMessageHandler::fullTypeNames()
261 {
262  static QHash<int, QString> symbols({
263  {QtDebugMsg, tr("debug")},
264  {QtWarningMsg, tr("warning")},
265  {QtCriticalMsg, tr("critical")},
266  {QtFatalMsg, tr("fatal")},
267  {QtInfoMsg, tr("info")}
268  });
269  return symbols;
270 }
271 #endif
272 
273 // Message handler which is installed using qInstallMessageHandler. This needs to be global.
274 void g_appDebugMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
275 {
277  AppDebugMessageHandler::instance()->messageHandler(type, context, msg);
278 }
void setShowTimestamp(bool showTimestamp)
static AppDebugMessageHandler * instance()
Get the singleton instance of this object. The instance is created automatically if necessary.
void messageOutput(quint8 level, const QString &msg)
Notifies when a new log massage is available.
QString & append(QChar ch)
int removeAll(const T &t)
void setTimestampFormat(const QString &timeFormat)
void append(const T &value)
#define APP_DBG_HANDLER_TIMESTAMP_FORMAT
Default timestamp format (as per Qt message pattern docs for %{time ...} options).
void unlock()
QString tr(const char *sourceText, const char *disambiguation, int n)
void installAppMessageHandler()
This function must be called to initialize this custom message handler. E.g. AppDebugMessageHandler::...
void clear()
static QHash< int, QString > & shortTypeNames()
Returns a reference to the mapping of QtMsgType enum values to abbreviated names.
QString defaultMessagePattern() const
Returns the default message pattern. This format will take into account any properties set previously...
void setSourceBasePath(const QString &path=QString())
Set the base path for source file name filter, everything after this is kept. The string could be lit...
QString number(int n, int base)
bool contains(const T &value) const const
void relock()
bool isEmpty() const const
void remove(int i)
void addOutputDevice(QIODevice *device)
Add a new I/O stream for receiving messages.
void g_appDebugMessageHandler(QtMsgType, const QMessageLogContext &, const QString &)
QString messagePattern() const
Returns the current message pattern in use. This will be either the one set specifically with setMess...
#define APP_DBG_HANDLER_SHOW_TIMESTAMP
Include timestamp in default message pattern.
#define APP_DBG_HANDLER_DEFAULT_LEVEL
Default log level.
void setAppDebugOutputLevel(quint8 appDebugOutputLevel)
#define APP_DBG_HANDLER_SRC_PATH
base path for source file name filter, everything after this is kept; RegEx, non-greedy
void setShowSourcePath(bool showSourcePath)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
#define APP_DBG_HANDLER_SHOW_FUNCTION_DECL
Show full function declaration with return and attribute types instead of just the Class::Name.
void setMessagePattern(const QString &pattern=QString())
Specifies a debug message pattern to use instead of the default.
void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
Handle a debug message. This is typically called by the installed global message handler callback ( g...
void setShowFunctionDeclarations(bool showFunctionDeclarations)
QString & replace(int position, int n, QChar after)
#define APP_DBG_HANDLER_SHOW_SRC_PATH
Include source file path/name in output (filtered with APP_DBG_HANDLER_SRC_PATH).
Custom debug/message handler class to work in conjunction with qDebug() family of functions.
bool isEmpty() const const
void removeOutputDevice(QIODevice *device)
Remove a previously-added I/O stream.
bool isValid() const const
int receivers(const char *signal) const const