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
109KDECORE_EXPORT bool kde_kdebug_enable_dbus_interface = false;
110
111class KNoDebugStream: public QIODevice
112{
113 // Q_OBJECT
114public:
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
122class KSyslogDebugStream: public KNoDebugStream
123{
124 // Q_OBJECT
125public:
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; }
136private:
137 int m_priority;
138};
139
140class KFileDebugStream: public KNoDebugStream
141{
142 // Q_OBJECT
143public:
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; }
157private:
158 QString m_fileName;
159};
160
161class KMessageBoxDebugStream: public KNoDebugStream
162{
163 // Q_OBJECT
164public:
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; }
175private:
176 QString m_caption;
177};
178
179class KLineEndStrippingDebugStream: public KNoDebugStream
180{
181 // Q_OBJECT
182public:
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
191struct 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 header;
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 printHeader(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
702K_GLOBAL_STATIC(KDebugPrivate, kDebug_data)
703
704#ifdef HAVE_BACKTRACE
705static 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
734QString 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
758QDebug kDebugDevNull()
759{
760 return QDebug(&kDebug_data->devnull);
761}
762
763QDebug 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
779QDebug perror(QDebug s, KDebugTag)
780{
781 return s << QString::fromLocal8Bit(strerror(errno));
782}
783
784QDebug 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
793QDebug operator<<(QDebug s, const KUrl &url)
794{
795 s.nospace() << "KUrl(" << url.prettyUrl() << ")";
796 return s.space();
797}
798
799void 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
818bool 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
856int 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
875class KDebug::Block::Private
876{
877public:
878 QByteArray m_label;
879};
880
881KDebug::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
901KDebug::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