summaryrefslogtreecommitdiff
path: root/utils/rbutilqt/logger/src/AbstractStringAppender.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'utils/rbutilqt/logger/src/AbstractStringAppender.cpp')
-rw-r--r--utils/rbutilqt/logger/src/AbstractStringAppender.cpp460
1 files changed, 460 insertions, 0 deletions
diff --git a/utils/rbutilqt/logger/src/AbstractStringAppender.cpp b/utils/rbutilqt/logger/src/AbstractStringAppender.cpp
new file mode 100644
index 0000000000..ea5883f744
--- /dev/null
+++ b/utils/rbutilqt/logger/src/AbstractStringAppender.cpp
@@ -0,0 +1,460 @@
1/*
2 Copyright (c) 2010 Boris Moiseev (cyberbobs at gmail dot com) Nikolay Matyunin (matyunin.n at gmail dot com)
3
4 Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
5
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License version 2.1
8 as published by the Free Software Foundation and appearing in the file
9 LICENSE.LGPL included in the packaging of this file.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU Lesser General Public License for more details.
15*/
16// Local
17#include "AbstractStringAppender.h"
18
19// Qt
20#include <QReadLocker>
21#include <QWriteLocker>
22#include <QDateTime>
23#include <QRegularExpression>
24#include <QCoreApplication>
25#include <QThread>
26
27
28/**
29 * \class AbstractStringAppender
30 *
31 * \brief The AbstractStringAppender class provides a convinient base for appenders working with plain text formatted
32 * logs.
33 *
34 * AbstractSringAppender is the simple extension of the AbstractAppender class providing the convinient way to create
35 * custom log appenders working with a plain text formatted log targets.
36 *
37 * It have the formattedString() protected function that formats the logging arguments according to a format set with
38 * setFormat().
39 *
40 * This class can not be directly instantiated because it contains pure virtual function inherited from AbstractAppender
41 * class.
42 *
43 * For more detailed description of customizing the log output format see the documentation on the setFormat() function.
44 */
45
46
47const char formattingMarker = '%';
48
49
50//! Constructs a new string appender object
51AbstractStringAppender::AbstractStringAppender()
52 : m_format(QLatin1String("%{time}{yyyy-MM-ddTHH:mm:ss.zzz} [%{type:-7}] <%{function}> %{message}\n"))
53{}
54
55
56//! Returns the current log format string.
57/**
58 * The default format is set to "%{time}{yyyy-MM-ddTHH:mm:ss.zzz} [%{type:-7}] <%{function}> %{message}\n". You can set a different log record
59 * format using the setFormat() function.
60 *
61 * \sa setFormat(const QString&)
62 */
63QString AbstractStringAppender::format() const
64{
65 QReadLocker locker(&m_formatLock);
66 return m_format;
67}
68
69
70//! Sets the logging format for writing strings to the log target with this appender.
71/**
72 * The string format seems to be very common to those developers who have used a standart sprintf function.
73 *
74 * Log output format is a simple QString with the special markers (starting with % sign) which will be replaced with
75 * it's internal meaning when writing a log record.
76 *
77 * Controlling marker begins with the percent sign (%) which is followed by the command inside {} brackets
78 * (the command describes, what will be put to log record instead of marker).
79 * Optional field width argument may be specified right after the command (through the colon symbol before the closing bracket)
80 * Some commands requires an additional formatting argument (in the second {} brackets).
81 *
82 * Field width argument works almost identically to the \c QString::arg() \c fieldWidth argument (and uses it
83 * internally). For example, \c "%{type:-7}" will be replaced with the left padded debug level of the message
84 * (\c "Debug ") or something. For the more detailed description of it you may consider to look to the Qt
85 * Reference Documentation.
86 *
87 * Supported marker commands are:
88 * \arg \c %{time} - timestamp. You may specify your custom timestamp format using the second {} brackets after the marker,
89 * timestamp format here will be similiar to those used in QDateTime::toString() function. For example,
90 * "%{time}{dd-MM-yyyy, HH:mm}" may be replaced with "17-12-2010, 20:17" depending on current date and time.
91 * The default format used here is "HH:mm:ss.zzz".
92 * \arg \c %{type} - Log level. Possible log levels are shown in the Logger::LogLevel enumerator.
93 * \arg \c %{Type} - Uppercased log level.
94 * \arg \c %{typeOne} - One letter log level.
95 * \arg \c %{TypeOne} - One uppercase letter log level.
96 * \arg \c %{File} - Full source file name (with path) of the file that requested log recording. Uses the \c __FILE__
97 * preprocessor macro.
98 * \arg \c %{file} - Short file name (with stripped path).
99 * \arg \c %{line} - Line number in the source file. Uses the \c __LINE__ preprocessor macro.
100 * \arg \c %{Function} - Name of function that called on of the LOG_* macros. Uses the \c Q_FUNC_INFO macro provided with
101 * Qt.
102 * \arg \c %{function} - Similiar to the %{Function}, but the function name is stripped using stripFunctionName
103 * \arg \c %{message} - The log message sent by the caller.
104 * \arg \c %{category} - The log category.
105 * \arg \c %{appname} - Application name (returned by QCoreApplication::applicationName() function).
106 * \arg \c %{pid} - Application pid (returned by QCoreApplication::applicationPid() function).
107 * \arg \c %{threadid} - ID of current thread.
108 * \arg \c %% - Convinient marker that is replaced with the single \c % mark.
109 *
110 * \note Format doesn't add \c '\\n' to the end of the format line. Please consider adding it manually.
111 *
112 * \sa format()
113 * \sa stripFunctionName()
114 * \sa Logger::LogLevel
115 */
116void AbstractStringAppender::setFormat(const QString& format)
117{
118 QWriteLocker locker(&m_formatLock);
119 m_format = format;
120}
121
122
123//! Strips the long function signature (as added by Q_FUNC_INFO macro)
124/**
125 * The string processing drops the returning type, arguments and template parameters of function. It is definitely
126 * useful for enchancing the log output readability.
127 * \return stripped function name
128 */
129QString AbstractStringAppender::stripFunctionName(const char* name)
130{
131 return QString::fromLatin1(qCleanupFuncinfo(name));
132}
133
134
135// The function was backported from Qt5 sources (qlogging.h)
136QByteArray AbstractStringAppender::qCleanupFuncinfo(const char* name)
137{
138 QByteArray info(name);
139
140 // Strip the function info down to the base function name
141 // note that this throws away the template definitions,
142 // the parameter types (overloads) and any const/volatile qualifiers.
143 if (info.isEmpty())
144 return info;
145
146 int pos;
147
148 // skip trailing [with XXX] for templates (gcc)
149 pos = info.size() - 1;
150 if (info.endsWith(']')) {
151 while (--pos) {
152 if (info.at(pos) == '[')
153 info.truncate(pos);
154 }
155 }
156
157 bool hasLambda = false;
158 QRegularExpression lambdaRegex("::<lambda\\(.*?\\)>");
159 QRegularExpressionMatch match = lambdaRegex.match(QString::fromLatin1(info));
160 int lambdaIndex = match.capturedStart();
161 if (lambdaIndex != -1)
162 {
163 hasLambda = true;
164 info.remove(lambdaIndex, match.capturedLength());
165 }
166
167 // operator names with '(', ')', '<', '>' in it
168 static const char operator_call[] = "operator()";
169 static const char operator_lessThan[] = "operator<";
170 static const char operator_greaterThan[] = "operator>";
171 static const char operator_lessThanEqual[] = "operator<=";
172 static const char operator_greaterThanEqual[] = "operator>=";
173
174 // canonize operator names
175 info.replace("operator ", "operator");
176
177 // remove argument list
178 forever {
179 int parencount = 0;
180 pos = info.lastIndexOf(')');
181 if (pos == -1) {
182 // Don't know how to parse this function name
183 return info;
184 }
185
186 // find the beginning of the argument list
187 --pos;
188 ++parencount;
189 while (pos && parencount) {
190 if (info.at(pos) == ')')
191 ++parencount;
192 else if (info.at(pos) == '(')
193 --parencount;
194 --pos;
195 }
196 if (parencount != 0)
197 return info;
198
199 info.truncate(++pos);
200
201 if (info.at(pos - 1) == ')') {
202 if (info.indexOf(operator_call) == pos - (int)strlen(operator_call))
203 break;
204
205 // this function returns a pointer to a function
206 // and we matched the arguments of the return type's parameter list
207 // try again
208 info.remove(0, info.indexOf('('));
209 info.chop(1);
210 continue;
211 } else {
212 break;
213 }
214 }
215
216 if (hasLambda)
217 info.append("::lambda");
218
219 // find the beginning of the function name
220 int parencount = 0;
221 int templatecount = 0;
222 --pos;
223
224 // make sure special characters in operator names are kept
225 if (pos > -1) {
226 switch (info.at(pos)) {
227 case ')':
228 if (info.indexOf(operator_call) == pos - (int)strlen(operator_call) + 1)
229 pos -= 2;
230 break;
231 case '<':
232 if (info.indexOf(operator_lessThan) == pos - (int)strlen(operator_lessThan) + 1)
233 --pos;
234 break;
235 case '>':
236 if (info.indexOf(operator_greaterThan) == pos - (int)strlen(operator_greaterThan) + 1)
237 --pos;
238 break;
239 case '=': {
240 int operatorLength = (int)strlen(operator_lessThanEqual);
241 if (info.indexOf(operator_lessThanEqual) == pos - operatorLength + 1)
242 pos -= 2;
243 else if (info.indexOf(operator_greaterThanEqual) == pos - operatorLength + 1)
244 pos -= 2;
245 break;
246 }
247 default:
248 break;
249 }
250 }
251
252 while (pos > -1) {
253 if (parencount < 0 || templatecount < 0)
254 return info;
255
256 char c = info.at(pos);
257 if (c == ')')
258 ++parencount;
259 else if (c == '(')
260 --parencount;
261 else if (c == '>')
262 ++templatecount;
263 else if (c == '<')
264 --templatecount;
265 else if (c == ' ' && templatecount == 0 && parencount == 0)
266 break;
267
268 --pos;
269 }
270 info = info.mid(pos + 1);
271
272 // remove trailing '*', '&' that are part of the return argument
273 while ((info.at(0) == '*')
274 || (info.at(0) == '&'))
275 info = info.mid(1);
276
277 // we have the full function name now.
278 // clean up the templates
279 while ((pos = info.lastIndexOf('>')) != -1) {
280 if (!info.contains('<'))
281 break;
282
283 // find the matching close
284 int end = pos;
285 templatecount = 1;
286 --pos;
287 while (pos && templatecount) {
288 char c = info.at(pos);
289 if (c == '>')
290 ++templatecount;
291 else if (c == '<')
292 --templatecount;
293 --pos;
294 }
295 ++pos;
296 info.remove(pos, end - pos + 1);
297 }
298
299 return info;
300}
301
302
303//! Returns the string to record to the logging target, formatted according to the format().
304/**
305 * \sa format()
306 * \sa setFormat(const QString&)
307 */
308QString AbstractStringAppender::formattedString(const QDateTime& timeStamp, Logger::LogLevel logLevel, const char* file,
309 int line, const char* function, const QString& category, const QString& message) const
310{
311 QString f = format();
312 const int size = f.size();
313
314 QString result;
315
316 int i = 0;
317 while (i < f.size())
318 {
319 QChar c = f.at(i);
320
321 // We will silently ignore the broken % marker at the end of string
322 if (c != QLatin1Char(formattingMarker) || (i + 2) >= size)
323 {
324 result.append(c);
325 }
326 else
327 {
328 i += 2;
329 QChar currentChar = f.at(i);
330 QString command;
331 int fieldWidth = 0;
332
333 if (currentChar.isLetter())
334 {
335 command.append(currentChar);
336 int j = 1;
337 while ((i + j) < size && f.at(i + j).isLetter())
338 {
339 command.append(f.at(i+j));
340 j++;
341 }
342
343 i+=j;
344 currentChar = f.at(i);
345
346 // Check for the padding instruction
347 if (currentChar == QLatin1Char(':'))
348 {
349 currentChar = f.at(++i);
350 if (currentChar.isDigit() || currentChar.category() == QChar::Punctuation_Dash)
351 {
352 int j = 1;
353 while ((i + j) < size && f.at(i + j).isDigit())
354 j++;
355 fieldWidth = f.mid(i, j).toInt();
356
357 i += j;
358 }
359 }
360 }
361
362 // Log record chunk to insert instead of formatting instruction
363 QString chunk;
364
365 // Time stamp
366 if (command == QLatin1String("time"))
367 {
368 if (f.at(i + 1) == QLatin1Char('{'))
369 {
370 int j = 1;
371 while ((i + 2 + j) < size && f.at(i + 2 + j) != QLatin1Char('}'))
372 j++;
373
374 if ((i + 2 + j) < size)
375 {
376 chunk = timeStamp.toString(f.mid(i + 2, j));
377
378 i += j;
379 i += 2;
380 }
381 }
382
383 if (chunk.isNull())
384 chunk = timeStamp.toString(QLatin1String("HH:mm:ss.zzz"));
385 }
386
387 // Log level
388 else if (command == QLatin1String("type"))
389 chunk = Logger::levelToString(logLevel);
390
391 // Uppercased log level
392 else if (command == QLatin1String("Type"))
393 chunk = Logger::levelToString(logLevel).toUpper();
394
395 // One letter log level
396 else if (command == QLatin1String("typeOne"))
397 chunk = Logger::levelToString(logLevel).left(1).toLower();
398
399 // One uppercase letter log level
400 else if (command == QLatin1String("TypeOne"))
401 chunk = Logger::levelToString(logLevel).left(1).toUpper();
402
403 // Filename
404 else if (command == QLatin1String("File"))
405 chunk = QLatin1String(file);
406
407 // Filename without a path
408 else if (command == QLatin1String("file"))
409 chunk = QString(QLatin1String(file)).section(QRegularExpression("[/\\\\]"), -1);
410
411 // Source line number
412 else if (command == QLatin1String("line"))
413 chunk = QString::number(line);
414
415 // Function name, as returned by Q_FUNC_INFO
416 else if (command == QLatin1String("Function"))
417 chunk = QString::fromLatin1(function);
418
419 // Stripped function name
420 else if (command == QLatin1String("function"))
421 chunk = stripFunctionName(function);
422
423 // Log message
424 else if (command == QLatin1String("message"))
425 chunk = message;
426
427 else if (command == QLatin1String("category"))
428 chunk = category;
429
430 // Application pid
431 else if (command == QLatin1String("pid"))
432 chunk = QString::number(QCoreApplication::applicationPid());
433
434 // Appplication name
435 else if (command == QLatin1String("appname"))
436 chunk = QCoreApplication::applicationName();
437
438 // Thread ID (duplicates Qt5 threadid debbuging way)
439 else if (command == QLatin1String("threadid"))
440 chunk = QLatin1String("0x") + QString::number(qlonglong(QThread::currentThread()->currentThread()), 16);
441
442 // We simply replace the double formatting marker (%) with one
443 else if (command == QString(formattingMarker))
444 chunk = QLatin1Char(formattingMarker);
445
446 // Do not process any unknown commands
447 else
448 {
449 chunk = QString(formattingMarker);
450 chunk.append(command);
451 }
452
453 result.append(QString(QLatin1String("%1")).arg(chunk, fieldWidth));
454 }
455
456 ++i;
457 }
458
459 return result;
460}