diff options
Diffstat (limited to 'utils/rbutilqt/logger/src/AbstractStringAppender.cpp')
-rw-r--r-- | utils/rbutilqt/logger/src/AbstractStringAppender.cpp | 460 |
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 | |||
47 | const char formattingMarker = '%'; | ||
48 | |||
49 | |||
50 | //! Constructs a new string appender object | ||
51 | AbstractStringAppender::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 | */ | ||
63 | QString 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 | */ | ||
116 | void 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 | */ | ||
129 | QString 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) | ||
136 | QByteArray 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 | */ | ||
308 | QString 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 | } | ||