1 | /* This file is part of the KDE libraries |
2 | Copyright (C) 1997 Matthias Kalle Dalheimer (kalle@kde.org) |
3 | 2002 Holger Freyther (freyther@kde.org) |
4 | |
5 | This library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Library General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2 of the License, or (at your option) any later version. |
9 | |
10 | This library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Library General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Library General Public License |
16 | along with this library; see the file COPYING.LIB. If not, write to |
17 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
18 | Boston, MA 02110-1301, USA. |
19 | */ |
20 | |
21 | #define KDE_EXTENDED_DEBUG_OUTPUT |
22 | |
23 | #ifndef QT_NO_CAST_FROM_ASCII |
24 | #define QT_NO_CAST_FROM_ASCII |
25 | #endif |
26 | #ifndef QT_NO_CAST_TO_ASCII |
27 | #define QT_NO_CAST_TO_ASCII |
28 | #endif |
29 | #ifndef KDE3_SUPPORT |
30 | #define KDE3_SUPPORT |
31 | #endif |
32 | |
33 | #include "kdebug.h" |
34 | #include <QThreadStorage> |
35 | |
36 | #ifdef Q_WS_WIN |
37 | #include <fcntl.h> |
38 | #include <windows.h> |
39 | #ifndef _WIN32_WCE |
40 | #include <wincon.h> |
41 | #endif |
42 | #else |
43 | #include <unistd.h> |
44 | #include <stdio.h> |
45 | #endif |
46 | |
47 | #ifdef NDEBUG |
48 | #undef kDebug |
49 | #undef kBacktrace |
50 | #endif |
51 | |
52 | #include <config.h> |
53 | |
54 | #ifdef HAVE_SYS_TIME_H |
55 | #include <sys/time.h> |
56 | #endif |
57 | #ifdef HAVE_TIME_H |
58 | #include <time.h> |
59 | #endif |
60 | |
61 | #include "kglobal.h" |
62 | #include "kstandarddirs.h" |
63 | #include "kdatetime.h" |
64 | #include "kcmdlineargs.h" |
65 | |
66 | #include <kmessage.h> |
67 | #include <klocale.h> |
68 | #include <kconfiggroup.h> |
69 | #include <kurl.h> |
70 | |
71 | #include <QtCore/QFile> |
72 | #include <QtCore/QHash> |
73 | #include <QtCore/QObject> |
74 | #include <QtCore/QChar> |
75 | #include <QtCore/QCoreApplication> |
76 | |
77 | #include <stdlib.h> // abort |
78 | #include <unistd.h> // getpid |
79 | #include <stdarg.h> // vararg stuff |
80 | #include <ctype.h> // isprint |
81 | #include <syslog.h> |
82 | #include <errno.h> |
83 | #include <string.h> |
84 | #include <kconfig.h> |
85 | #include "kcomponentdata.h" |
86 | |
87 | #ifdef Q_OS_SOLARIS |
88 | // For the purposes of KDebug Solaris has a GNU-libc-compatible |
89 | // backtrace() function. This isn't detected by the CMake checks |
90 | // normally (check_function_exists fails), but we know it's there. |
91 | // For better results, we would use walk_context(), but that's |
92 | // a little more code -- see also the crash handler in kcrash.cpp . |
93 | #define HAVE_BACKTRACE (1) |
94 | #endif |
95 | |
96 | #ifdef HAVE_BACKTRACE |
97 | #include <execinfo.h> |
98 | #ifdef __GNUC__ |
99 | #define HAVE_BACKTRACE_DEMANGLE |
100 | #include <cxxabi.h> |
101 | #endif |
102 | #endif |
103 | |
104 | #include "kdebugdbusiface_p.h" |
105 | #include <QMutex> |
106 | |
107 | |
108 | |
109 | KDECORE_EXPORT bool kde_kdebug_enable_dbus_interface = false; |
110 | |
111 | class KNoDebugStream: public QIODevice |
112 | { |
113 | // Q_OBJECT |
114 | public: |
115 | KNoDebugStream() { open(WriteOnly); } |
116 | bool isSequential() const { return true; } |
117 | qint64 readData(char *, qint64) { return 0; /* eof */ } |
118 | qint64 readLineData(char *, qint64) { return 0; /* eof */ } |
119 | qint64 writeData(const char *, qint64 len) { return len; } |
120 | }; |
121 | |
122 | class KSyslogDebugStream: public KNoDebugStream |
123 | { |
124 | // Q_OBJECT |
125 | public: |
126 | qint64 writeData(const char *data, qint64 len) |
127 | { |
128 | if (len) { |
129 | // not using fromRawData because we need a terminating NUL |
130 | const QByteArray buf(data, len); |
131 | syslog(m_priority, "%s" , buf.constData()); |
132 | } |
133 | return len; |
134 | } |
135 | void setPriority(int priority) { m_priority = priority; } |
136 | private: |
137 | int m_priority; |
138 | }; |
139 | |
140 | class KFileDebugStream: public KNoDebugStream |
141 | { |
142 | // Q_OBJECT |
143 | public: |
144 | qint64 writeData(const char *data, qint64 len) |
145 | { |
146 | if (len) { |
147 | QFile aOutputFile(m_fileName); |
148 | if (aOutputFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Unbuffered)) { |
149 | QByteArray buf = QByteArray::fromRawData(data, len); |
150 | aOutputFile.write(buf.trimmed()); |
151 | aOutputFile.putChar('\n'); |
152 | } |
153 | } |
154 | return len; |
155 | } |
156 | void setFileName(const QString& fn) { m_fileName = fn; } |
157 | private: |
158 | QString m_fileName; |
159 | }; |
160 | |
161 | class KMessageBoxDebugStream: public KNoDebugStream |
162 | { |
163 | // Q_OBJECT |
164 | public: |
165 | qint64 writeData(const char *data, qint64 len) |
166 | { |
167 | if (len) { |
168 | // Since we are in kdecore here, we cannot use KMsgBox |
169 | QString msg = QString::fromLatin1(data, len); |
170 | KMessage::message(KMessage::Information, msg, m_caption); |
171 | } |
172 | return len; |
173 | } |
174 | void setCaption(const QString& h) { m_caption = h; } |
175 | private: |
176 | QString m_caption; |
177 | }; |
178 | |
179 | class KLineEndStrippingDebugStream: public KNoDebugStream |
180 | { |
181 | // Q_OBJECT |
182 | public: |
183 | qint64 writeData(const char *data, qint64 len) |
184 | { |
185 | QByteArray buf = QByteArray::fromRawData(data, len); |
186 | qt_message_output(QtDebugMsg, buf.trimmed().constData()); |
187 | return len; |
188 | } |
189 | }; |
190 | |
191 | struct KDebugPrivate |
192 | { |
193 | enum OutputMode { |
194 | FileOutput = 0, |
195 | MessageBoxOutput = 1, |
196 | QtOutput = 2, |
197 | SyslogOutput = 3, |
198 | NoOutput = 4, |
199 | DefaultOutput = QtOutput, // if you change DefaultOutput, also change the defaults in kdebugdialog! |
200 | Unknown = 5 |
201 | }; |
202 | |
203 | struct Area { |
204 | inline Area() { clear(); } |
205 | void clear(OutputMode set = Unknown) |
206 | { |
207 | for (int i = 0; i < 4; ++i) { |
208 | logFileName[i].clear(); |
209 | mode[i] = set; |
210 | } |
211 | } |
212 | |
213 | QByteArray name; |
214 | QString logFileName[4]; |
215 | OutputMode mode[4]; |
216 | }; |
217 | typedef QHash<unsigned int, Area> Cache; |
218 | |
219 | KDebugPrivate() |
220 | : config(0), kDebugDBusIface(0), m_disableAll(false), m_seenMainComponent(false) |
221 | { |
222 | Q_ASSERT(int(QtDebugMsg) == 0); |
223 | Q_ASSERT(int(QtFatalMsg) == 3); |
224 | |
225 | // Create the D-Bus interface if it has not been created yet |
226 | // But only register to D-Bus if we are in a process with a D-Bus event loop, |
227 | // otherwise introspection will just hang. |
228 | // Examples of processes without a D-Bus event loop: kioslaves and the main kdeinit process. |
229 | // |
230 | // How to know that we have a real event loop? That's tricky. |
231 | // We could delay registration in kDebugDBusIface with a QTimer, but |
232 | // it would still get triggered by kioslaves that use enterLoop/exitLoop |
233 | // to run kio jobs synchronously. |
234 | // |
235 | // Solution: we have a bool that is set by KApplication |
236 | // (kioslaves should use QCoreApplication but not KApplication). |
237 | if (kde_kdebug_enable_dbus_interface) { |
238 | kDebugDBusIface = new KDebugDBusIface; |
239 | } |
240 | |
241 | for (int i = 0; i < 8; i++) { |
242 | m_nullOutputYesNoCache[i] = -1; |
243 | } |
244 | |
245 | } |
246 | |
247 | ~KDebugPrivate() |
248 | { |
249 | delete config; |
250 | delete kDebugDBusIface; |
251 | } |
252 | |
253 | void loadAreaNames() |
254 | { |
255 | // Don't clear the cache here, that would lose previously registered dynamic areas |
256 | //cache.clear(); |
257 | |
258 | Area &areaData = cache[0]; |
259 | areaData.clear(); |
260 | |
261 | if (KGlobal::hasMainComponent()) { |
262 | areaData.name = KGlobal::mainComponent().componentName().toUtf8(); |
263 | m_seenMainComponent = true; |
264 | } else { |
265 | areaData.name = qApp ? qAppName().toUtf8() : QByteArray("unnamed app" ); |
266 | m_seenMainComponent = false; |
267 | } |
268 | //qDebug() << "loadAreaNames: area 0 has name" << areaData.name; |
269 | |
270 | for (int i = 0; i < 8; i++) { |
271 | m_nullOutputYesNoCache[i] = -1; |
272 | } |
273 | |
274 | QString filename(KStandardDirs::locate("config" , QLatin1String("kdebug.areas" ))); |
275 | if (filename.isEmpty()) { |
276 | return; |
277 | } |
278 | QFile file(filename); |
279 | if (!file.open(QIODevice::ReadOnly)) { |
280 | qWarning("Couldn't open %s" , filename.toLocal8Bit().constData()); |
281 | file.close(); |
282 | return; |
283 | } |
284 | |
285 | uint lineNumber=0; |
286 | |
287 | while (!file.atEnd()) { |
288 | const QByteArray line = file.readLine().trimmed(); |
289 | ++lineNumber; |
290 | if (line.isEmpty()) |
291 | continue; |
292 | |
293 | int i=0; |
294 | unsigned char ch=line[i]; |
295 | |
296 | if (ch =='#') |
297 | continue; // We have an eof, a comment or an empty line |
298 | |
299 | if (ch < '0' || ch > '9') { |
300 | qWarning("Syntax error parsing '%s': no number (line %u)" , qPrintable(filename), lineNumber); |
301 | continue; |
302 | } |
303 | |
304 | do { |
305 | ch=line[++i]; |
306 | } while (ch >= '0' && ch <= '9' && i < line.length()); |
307 | |
308 | unsigned int number = line.left(i).toUInt(); |
309 | |
310 | while (i < line.length() && line[i] <= ' ') |
311 | i++; |
312 | |
313 | Area areaData; |
314 | areaData.name = line.mid(i); |
315 | cache.insert(number, areaData); |
316 | } |
317 | file.close(); |
318 | } |
319 | |
320 | inline int level(QtMsgType type) |
321 | { return int(type) - int(QtDebugMsg); } |
322 | |
323 | QString groupNameForArea(unsigned int area) const |
324 | { |
325 | QString groupName = QString::number(area); |
326 | if (area == 0 || !config->hasGroup(groupName)) { |
327 | groupName = QString::fromLocal8Bit(cache.value(area).name); |
328 | } |
329 | return groupName; |
330 | } |
331 | |
332 | OutputMode areaOutputMode(QtMsgType type, unsigned int area, bool enableByDefault) |
333 | { |
334 | if (!configObject()) |
335 | return QtOutput; |
336 | |
337 | QString key; |
338 | switch (type) { |
339 | case QtDebugMsg: |
340 | key = QLatin1String( "InfoOutput" ); |
341 | if (m_disableAll) |
342 | return NoOutput; |
343 | break; |
344 | case QtWarningMsg: |
345 | key = QLatin1String( "WarnOutput" ); |
346 | break; |
347 | case QtFatalMsg: |
348 | key = QLatin1String( "FatalOutput" ); |
349 | break; |
350 | case QtCriticalMsg: |
351 | default: |
352 | /* Programmer error, use "Error" as default */ |
353 | key = QLatin1String( "ErrorOutput" ); |
354 | break; |
355 | } |
356 | |
357 | const KConfigGroup cg(config, groupNameForArea(area)); |
358 | const int mode = cg.readEntry(key, int(enableByDefault ? DefaultOutput : NoOutput)); |
359 | return OutputMode(mode); |
360 | } |
361 | |
362 | QString logFileName(QtMsgType type, unsigned int area) |
363 | { |
364 | if (!configObject()) |
365 | return QString(); |
366 | |
367 | const char* aKey; |
368 | switch (type) |
369 | { |
370 | case QtDebugMsg: |
371 | aKey = "InfoFilename" ; |
372 | break; |
373 | case QtWarningMsg: |
374 | aKey = "WarnFilename" ; |
375 | break; |
376 | case QtFatalMsg: |
377 | aKey = "FatalFilename" ; |
378 | break; |
379 | case QtCriticalMsg: |
380 | default: |
381 | aKey = "ErrorFilename" ; |
382 | break; |
383 | } |
384 | |
385 | KConfigGroup cg(config, groupNameForArea(area)); |
386 | return cg.readPathEntry(aKey, QLatin1String("kdebug.dbg" )); |
387 | } |
388 | |
389 | KConfig* configObject() |
390 | { |
391 | if (!config) { |
392 | config = new KConfig(QLatin1String("kdebugrc" ), KConfig::NoGlobals); |
393 | m_disableAll = config->group(QString()).readEntry("DisableAll" , false); |
394 | } |
395 | return config; |
396 | } |
397 | |
398 | Cache::Iterator areaData(QtMsgType type, unsigned int num, bool enableByDefault = true) |
399 | { |
400 | if (!cache.contains(0)) { |
401 | //qDebug() << "cache size=" << cache.count() << "loading area names"; |
402 | loadAreaNames(); // fills 'cache' |
403 | Q_ASSERT(cache.contains(0)); |
404 | } else if (!m_seenMainComponent && KGlobal::hasMainComponent()) { |
405 | // Update the name for area 0 once a main component exists |
406 | cache[0].name = KGlobal::mainComponent().componentName().toUtf8(); |
407 | m_seenMainComponent = true; |
408 | } |
409 | |
410 | Cache::Iterator it = cache.find(num); |
411 | if (it == cache.end()) { |
412 | // unknown area |
413 | Q_ASSERT(cache.contains(0)); |
414 | it = cache.find(0); |
415 | num = 0; |
416 | } |
417 | |
418 | if (num == 0) { // area 0 is special, it becomes the named area "appname" |
419 | static bool s_firstDebugFromApplication = true; |
420 | if (s_firstDebugFromApplication && !m_disableAll) { |
421 | s_firstDebugFromApplication = false; |
422 | //qDebug() << "First debug output from" << it->name << "writing out with default" << enableByDefault; |
423 | writeGroupForNamedArea(it->name, enableByDefault); |
424 | } |
425 | } |
426 | |
427 | const int lev = level(type); |
428 | //qDebug() << "in cache for" << num << ":" << it->mode[lev]; |
429 | if (it->mode[lev] == Unknown) |
430 | it->mode[lev] = areaOutputMode(type, num, enableByDefault); |
431 | if (it->mode[lev] == FileOutput && it->logFileName[lev].isEmpty()) |
432 | it->logFileName[lev] = logFileName(type, num); |
433 | |
434 | Q_ASSERT(it->mode[lev] != Unknown); |
435 | |
436 | return it; |
437 | } |
438 | |
439 | QDebug setupFileWriter(const QString &fileName) |
440 | { |
441 | if (!filewriter.hasLocalData()) |
442 | filewriter.setLocalData(new KFileDebugStream); |
443 | filewriter.localData()->setFileName(fileName); |
444 | QDebug result(filewriter.localData()); |
445 | return result; |
446 | } |
447 | |
448 | QDebug setupMessageBoxWriter(QtMsgType type, const QByteArray &areaName) |
449 | { |
450 | if (!messageboxwriter.hasLocalData()) |
451 | messageboxwriter.setLocalData(new KMessageBoxDebugStream); |
452 | QDebug result(messageboxwriter.localData()); |
453 | QByteArray ; |
454 | |
455 | switch (type) { |
456 | case QtDebugMsg: |
457 | header = "Info (" ; |
458 | break; |
459 | case QtWarningMsg: |
460 | header = "Warning (" ; |
461 | break; |
462 | case QtFatalMsg: |
463 | header = "Fatal Error (" ; |
464 | break; |
465 | case QtCriticalMsg: |
466 | default: |
467 | header = "Error (" ; |
468 | break; |
469 | } |
470 | |
471 | header += areaName; |
472 | header += ')'; |
473 | messageboxwriter.localData()->setCaption(QString::fromLatin1(header)); |
474 | return result; |
475 | } |
476 | |
477 | QDebug setupSyslogWriter(QtMsgType type) |
478 | { |
479 | if (!syslogwriter.hasLocalData()) |
480 | syslogwriter.setLocalData(new KSyslogDebugStream); |
481 | QDebug result(syslogwriter.localData()); |
482 | int level = 0; |
483 | |
484 | switch (type) { |
485 | case QtDebugMsg: |
486 | level = LOG_INFO; |
487 | break; |
488 | case QtWarningMsg: |
489 | level = LOG_WARNING; |
490 | break; |
491 | case QtFatalMsg: |
492 | level = LOG_CRIT; |
493 | break; |
494 | case QtCriticalMsg: |
495 | default: |
496 | level = LOG_ERR; |
497 | break; |
498 | } |
499 | syslogwriter.localData()->setPriority(level); |
500 | return result; |
501 | } |
502 | |
503 | QDebug setupQtWriter(QtMsgType type) |
504 | { |
505 | if (type != QtDebugMsg) { |
506 | if (type == QtWarningMsg) { |
507 | // KDE warnings are not the same thing as Qt warnings |
508 | // in Qt, warnings indicate bad code, which must be corrected before the release |
509 | // in KDE, it's just something that everyone sees (including users) |
510 | type = QtDebugMsg; |
511 | } |
512 | return QDebug(type); |
513 | } |
514 | return QDebug(&lineendstrippingwriter); |
515 | } |
516 | |
517 | QDebug (QDebug s, const QByteArray &areaName, const char * file, int line, const char *funcinfo, QtMsgType type, bool colored) |
518 | { |
519 | #ifdef KDE_EXTENDED_DEBUG_OUTPUT |
520 | static bool printProcessInfo = (qgetenv("KDE_DEBUG_NOPROCESSINFO" ).isEmpty()); |
521 | static bool printAreaName = (qgetenv("KDE_DEBUG_NOAREANAME" ).isEmpty()); |
522 | static bool printMethodName = (qgetenv("KDE_DEBUG_NOMETHODNAME" ).isEmpty()); |
523 | static bool printFileLine = (!qgetenv("KDE_DEBUG_FILELINE" ).isEmpty()); |
524 | |
525 | static int printTimeStamp = qgetenv("KDE_DEBUG_TIMESTAMP" ).toInt(); |
526 | QByteArray programName; |
527 | s = s.nospace(); |
528 | if (printTimeStamp > 0) { |
529 | if (printTimeStamp >= 2) { |
530 | // the extended print: 17:03:24.123 |
531 | const QString sformat = QString::fromLatin1("hh:mm:ss.zzz" ); |
532 | s << qPrintable(QDateTime::currentDateTime().time().toString(sformat)); |
533 | } else { |
534 | // the default print: 17:03:24 |
535 | s << qPrintable(QDateTime::currentDateTime().time().toString()); |
536 | } |
537 | s << ' '; |
538 | } |
539 | |
540 | if (printProcessInfo) { |
541 | programName = cache.value(0).name; |
542 | if (programName.isEmpty()) { |
543 | if (QCoreApplication::instance()) |
544 | programName = QCoreApplication::instance()->applicationName().toLocal8Bit(); |
545 | else |
546 | programName = "<unknown program name>" ; |
547 | } |
548 | s << programName.constData() << "(" << unsigned(getpid()) << ")" ; |
549 | } |
550 | if (printAreaName && (!printProcessInfo || areaName != programName)) { |
551 | if (printProcessInfo) |
552 | s << "/" ; |
553 | s << areaName.constData(); |
554 | } |
555 | |
556 | if (m_indentString.hasLocalData()) { |
557 | s << m_indentString.localData()->toLatin1().constData(); |
558 | } |
559 | |
560 | if (printFileLine) { |
561 | s << ' ' << file << ':' << line << ' '; |
562 | } |
563 | |
564 | if (funcinfo && printMethodName) { |
565 | if (colored) { |
566 | if (type <= QtDebugMsg) |
567 | s << "\033[0;34m" ; //blue |
568 | else |
569 | s << "\033[0;31m" ; //red |
570 | } |
571 | # ifdef Q_CC_GNU |
572 | // strip the function info down to the base function name |
573 | // note that this throws away the template definitions, |
574 | // the parameter types (overloads) and any const/volatile qualifiers |
575 | QByteArray info = funcinfo; |
576 | int pos = info.indexOf('('); |
577 | Q_ASSERT_X(pos != -1, "kDebug" , |
578 | "Bug in kDebug(): I don't know how to parse this function name" ); |
579 | while (info.at(pos - 1) == ' ') |
580 | // that '(' we matched was actually the opening of a function-pointer |
581 | pos = info.indexOf('(', pos + 1); |
582 | |
583 | info.truncate(pos); |
584 | // gcc 4.1.2 don't put a space between the return type and |
585 | // the function name if the function is in an anonymous namespace |
586 | int index = 1; |
587 | forever { |
588 | index = info.indexOf("<unnamed>::" , index); |
589 | if ( index == -1 ) |
590 | break; |
591 | |
592 | if ( info.at(index-1) != ':' ) |
593 | info.insert(index, ' '); |
594 | |
595 | index += strlen("<unnamed>::" ); |
596 | } |
597 | pos = info.lastIndexOf(' '); |
598 | if (pos != -1) { |
599 | int startoftemplate = info.lastIndexOf('<'); |
600 | if (startoftemplate != -1 && pos > startoftemplate && |
601 | pos < info.lastIndexOf(">::" )) |
602 | // we matched a space inside this function's template definition |
603 | pos = info.lastIndexOf(' ', startoftemplate); |
604 | } |
605 | |
606 | if (pos + 1 == info.length()) |
607 | // something went wrong, so gracefully bail out |
608 | s << " " << funcinfo; |
609 | else |
610 | s << " " << info.constData() + pos + 1; |
611 | # else |
612 | s << " " << funcinfo; |
613 | # endif |
614 | if(colored) |
615 | s << "\033[0m" ; |
616 | } |
617 | |
618 | s << ":" ; |
619 | #else |
620 | Q_UNUSED(funcinfo); |
621 | if (!areaName.isEmpty()) |
622 | s << areaName.constData() << ':'; |
623 | #endif |
624 | return s.space(); |
625 | } |
626 | |
627 | QDebug stream(QtMsgType type, unsigned int area, const char *debugFile, int line, |
628 | const char *funcinfo) |
629 | { |
630 | static bool env_colored = (!qgetenv("KDE_COLOR_DEBUG" ).isEmpty()); |
631 | static bool env_colors_on_any_fd = (!qgetenv("KDE_COLOR_DEBUG_ALWAYS" ).isEmpty()); |
632 | Cache::Iterator it = areaData(type, area); |
633 | OutputMode mode = it->mode[level(type)]; |
634 | Q_ASSERT(mode != Unknown); |
635 | QString file = it->logFileName[level(type)]; |
636 | QByteArray areaName = it->name; |
637 | |
638 | if (areaName.isEmpty()) |
639 | areaName = cache.value(0).name; |
640 | |
641 | bool colored=false; |
642 | |
643 | QDebug s(&devnull); |
644 | switch (mode) { |
645 | case FileOutput: |
646 | s = setupFileWriter(file); |
647 | break; |
648 | case MessageBoxOutput: |
649 | s = setupMessageBoxWriter(type, areaName); |
650 | break; |
651 | case SyslogOutput: |
652 | s = setupSyslogWriter(type); |
653 | break; |
654 | case NoOutput: |
655 | s = QDebug(&devnull); |
656 | return s; //no need to take the time to "print header" if we don't want to output anyway |
657 | break; |
658 | case Unknown: // should not happen |
659 | default: // QtOutput |
660 | s = setupQtWriter(type); |
661 | #ifndef Q_OS_WIN |
662 | //only color if the debug goes to a tty, unless env_colors_on_any_fd is set too. |
663 | colored = env_colored && (env_colors_on_any_fd || isatty(fileno(stderr))); |
664 | #endif |
665 | break; |
666 | } |
667 | |
668 | return printHeader(s, areaName, debugFile, line, funcinfo, type, colored); |
669 | } |
670 | |
671 | void writeGroupForNamedArea(const QByteArray& areaName, bool enabled) |
672 | { |
673 | // Ensure that this area name appears in kdebugrc, so that users (via kdebugdialog) |
674 | // can turn it off. |
675 | KConfig* cfgObj = configObject(); |
676 | if (cfgObj) { |
677 | KConfigGroup cg(cfgObj, QString::fromUtf8(areaName)); |
678 | const QString key = QString::fromLatin1("InfoOutput" ); |
679 | if (!cg.hasKey(key)) { |
680 | cg.writeEntry(key, int(enabled ? KDebugPrivate::QtOutput : KDebugPrivate::NoOutput)); |
681 | cg.sync(); |
682 | } |
683 | } |
684 | } |
685 | |
686 | QMutex mutex; |
687 | KConfig *config; |
688 | KDebugDBusIface *kDebugDBusIface; |
689 | Cache cache; |
690 | bool m_disableAll; |
691 | bool m_seenMainComponent; // false: area zero still contains qAppName |
692 | int m_nullOutputYesNoCache[8]; |
693 | |
694 | KNoDebugStream devnull; |
695 | QThreadStorage<QString*> m_indentString; |
696 | QThreadStorage<KSyslogDebugStream*> syslogwriter; |
697 | QThreadStorage<KFileDebugStream*> filewriter; |
698 | QThreadStorage<KMessageBoxDebugStream*> messageboxwriter; |
699 | KLineEndStrippingDebugStream lineendstrippingwriter; |
700 | }; |
701 | |
702 | K_GLOBAL_STATIC(KDebugPrivate, kDebug_data) |
703 | |
704 | #ifdef HAVE_BACKTRACE |
705 | static QString maybeDemangledName(char *name) |
706 | { |
707 | #ifdef HAVE_BACKTRACE_DEMANGLE |
708 | const int len = strlen(name); |
709 | QByteArray in = QByteArray::fromRawData(name, len); |
710 | const int mangledNameStart = in.indexOf("(_" ); |
711 | if (mangledNameStart >= 0) { |
712 | const int mangledNameEnd = in.indexOf('+', mangledNameStart + 2); |
713 | if (mangledNameEnd >= 0) { |
714 | int status; |
715 | // if we forget about this line and the one that undoes its effect we don't change the |
716 | // internal data of the QByteArray::fromRawData() ;) |
717 | name[mangledNameEnd] = 0; |
718 | char *demangled = abi::__cxa_demangle(name + mangledNameStart + 1, 0, 0, &status); |
719 | name[mangledNameEnd] = '+'; |
720 | if (demangled) { |
721 | QString ret = QString::fromLatin1(name, mangledNameStart + 1) + |
722 | QString::fromLatin1(demangled) + |
723 | QString::fromLatin1(name + mangledNameEnd, len - mangledNameEnd); |
724 | free(demangled); |
725 | return ret; |
726 | } |
727 | } |
728 | } |
729 | #endif |
730 | return QString::fromLatin1(name); |
731 | } |
732 | #endif |
733 | |
734 | QString kRealBacktrace(int levels) |
735 | { |
736 | QString s; |
737 | #ifdef HAVE_BACKTRACE |
738 | void* trace[256]; |
739 | int n = backtrace(trace, 256); |
740 | if (!n) |
741 | return s; |
742 | char** strings = backtrace_symbols (trace, n); |
743 | |
744 | if ( levels != -1 ) |
745 | n = qMin( n, levels ); |
746 | s = QLatin1String("[\n" ); |
747 | |
748 | for (int i = 0; i < n; ++i) |
749 | s += QString::number(i) + QLatin1String(": " ) + |
750 | maybeDemangledName(strings[i]) + QLatin1Char('\n'); |
751 | s += QLatin1String("]\n" ); |
752 | if (strings) |
753 | free (strings); |
754 | #endif |
755 | return s; |
756 | } |
757 | |
758 | QDebug kDebugDevNull() |
759 | { |
760 | return QDebug(&kDebug_data->devnull); |
761 | } |
762 | |
763 | QDebug kDebugStream(QtMsgType level, int area, const char *file, int line, const char *funcinfo) |
764 | { |
765 | if (kDebug_data.isDestroyed()) { |
766 | // we don't know what to return now... |
767 | qCritical().nospace() << "kDebugStream called after destruction (from " |
768 | << (funcinfo ? funcinfo : "" ) |
769 | << (file ? " file " : " unknown file" ) |
770 | << (file ? file :"" ) |
771 | << " line " << line << ")" ; |
772 | return QDebug(level); |
773 | } |
774 | |
775 | QMutexLocker locker(&kDebug_data->mutex); |
776 | return kDebug_data->stream(level, area, file, line, funcinfo); |
777 | } |
778 | |
779 | QDebug perror(QDebug s, KDebugTag) |
780 | { |
781 | return s << QString::fromLocal8Bit(strerror(errno)); |
782 | } |
783 | |
784 | QDebug operator<<(QDebug s, const KDateTime &time) |
785 | { |
786 | if ( time.isDateOnly() ) |
787 | s.nospace() << "KDateTime(" << qPrintable(time.toString(KDateTime::QtTextDate)) << ")" ; |
788 | else |
789 | s.nospace() << "KDateTime(" << qPrintable(time.toString(KDateTime::ISODate)) << ")" ; |
790 | return s.space(); |
791 | } |
792 | |
793 | QDebug operator<<(QDebug s, const KUrl &url) |
794 | { |
795 | s.nospace() << "KUrl(" << url.prettyUrl() << ")" ; |
796 | return s.space(); |
797 | } |
798 | |
799 | void kClearDebugConfig() |
800 | { |
801 | if (!kDebug_data) return; |
802 | KDebugPrivate* d = kDebug_data; |
803 | QMutexLocker locker(&d->mutex); |
804 | delete d->config; |
805 | d->config = 0; |
806 | |
807 | KDebugPrivate::Cache::Iterator it = d->cache.begin(), |
808 | end = d->cache.end(); |
809 | for ( ; it != end; ++it) |
810 | it->clear(); |
811 | |
812 | for (int i = 0; i < 8; i++) { |
813 | d->m_nullOutputYesNoCache[i] = -1; |
814 | } |
815 | } |
816 | |
817 | // static |
818 | bool KDebug::hasNullOutput(QtMsgType type, |
819 | bool condition, |
820 | int area, |
821 | bool enableByDefault) |
822 | { |
823 | if (!condition) { |
824 | return true; |
825 | } |
826 | if (kDebug_data.isDestroyed()) { |
827 | // kDebugStream() will generate a warning anyway, so we don't. |
828 | return false; |
829 | } |
830 | KDebugPrivate *const d = kDebug_data; |
831 | QMutexLocker locker(&d->mutex); |
832 | |
833 | if (type == QtDebugMsg) { |
834 | int *entries = d->m_nullOutputYesNoCache; |
835 | for (int i = 0; i < 8; i += 2) { |
836 | if (entries[i] == area) { |
837 | return entries[i + 1]; |
838 | } |
839 | } |
840 | } |
841 | |
842 | KDebugPrivate::Cache::Iterator it = d->areaData(type, area, enableByDefault); |
843 | const bool ret = it->mode[d->level(type)] == KDebugPrivate::NoOutput; |
844 | |
845 | // cache result for next time... |
846 | if (type == QtDebugMsg) { |
847 | int *entries = d->m_nullOutputYesNoCache; |
848 | int idx = (qrand() % 4) * 2; |
849 | entries[idx] = area; |
850 | entries[idx + 1] = ret; |
851 | } |
852 | |
853 | return ret; |
854 | } |
855 | |
856 | int KDebug::registerArea(const QByteArray& areaName, bool enabled) |
857 | { |
858 | // TODO for optimization: static int s_lastAreaNumber = 1; |
859 | KDebugPrivate* d = kDebug_data; |
860 | QMutexLocker locker(&d->mutex); |
861 | int areaNumber = 1; |
862 | while (d->cache.contains(areaNumber)) { |
863 | ++areaNumber; |
864 | } |
865 | KDebugPrivate::Area areaData; |
866 | areaData.name = areaName; |
867 | //qDebug() << "Assigning area number" << areaNumber << "for name" << areaName; |
868 | d->cache.insert(areaNumber, areaData); |
869 | d->writeGroupForNamedArea(areaName, enabled); |
870 | return areaNumber; |
871 | } |
872 | |
873 | #ifndef KDE_NO_DEBUG_OUTPUT |
874 | |
875 | class KDebug::Block::Private |
876 | { |
877 | public: |
878 | QByteArray m_label; |
879 | }; |
880 | |
881 | KDebug::Block::Block(const char* label, int area) |
882 | : m_label(0), m_area(area), d(0) |
883 | { |
884 | if (hasNullOutputQtDebugMsg(area)) { |
885 | d = 0; // remember, for the dtor |
886 | } else { |
887 | d = new Private; |
888 | d->m_label = label; |
889 | m_startTime.start(); |
890 | kDebug(area) << "BEGIN:" << label; |
891 | |
892 | // The indent string is per thread |
893 | QThreadStorage<QString*> & indentString = kDebug_data->m_indentString; |
894 | if (!indentString.hasLocalData()) { |
895 | indentString.setLocalData(new QString); |
896 | } |
897 | *(indentString.localData()) += QLatin1String(" " ); |
898 | } |
899 | } |
900 | |
901 | KDebug::Block::~Block() |
902 | { |
903 | if (d) { |
904 | const double duration = m_startTime.elapsed() / 1000.0; |
905 | QThreadStorage<QString*> & indentString = kDebug_data->m_indentString; |
906 | indentString.localData()->chop(2); |
907 | |
908 | // Print timing information, and a special message (DELAY) if the method took longer than 5s |
909 | if (duration < 5.0) { |
910 | kDebug(m_area) |
911 | << "END__:" |
912 | << d->m_label.constData() |
913 | << qPrintable(QString::fromLatin1("[Took: %3s]" ).arg(QString::number(duration, 'g', 2))); |
914 | } else { |
915 | kDebug(m_area) |
916 | << "END__:" |
917 | << d->m_label.constData() |
918 | << qPrintable(QString::fromLatin1("[DELAY Took (quite long) %3s]" ).arg(QString::number(duration, 'g', 2))); |
919 | } |
920 | delete d; |
921 | } |
922 | } |
923 | |
924 | #endif |
925 | |