be685d2cdc261d988cca98adde162707fc1ff5d2
[platform/upstream/syncevolution.git] / src / synthesis / src / sysync / debuglogger.cpp
1 /*
2  *  File:         debuglogger.cpp
3  *
4  *  Author:       Lukas Zeller (luz@plan44.ch)
5  *
6  *  Global debug mechanisms
7  *
8  *  Copyright (c) 2005-2011 by Synthesis AG + plan44.ch
9  *
10  *  2005-08-04 : luz : created
11  *
12  */
13
14
15 #include "prefix_file.h"
16
17 #ifdef SYDEBUG
18
19 #include "debuglogger.h"
20
21
22 #ifdef MULTI_THREAD_SUPPORT
23 #include "platform_thread.h"
24 #endif
25
26 namespace sysync {
27
28 #ifndef HARDCODED_CONFIG
29
30 // debug format modes
31 cAppCharP const DbgOutFormatNames[numDbgOutFormats] = {
32   "text",       // plain text format (but can be indented)
33   "xml",        // XML format
34   "html"        // HTML format
35 };
36
37
38 // HTML dynamic folding modes
39 cAppCharP const DbgFoldingModeNames[numDbgFoldingModes] = {
40   "none",       // do not include dynamic folding into HTML logs
41   "collapsed",  // include folding - all collapsed by default
42   "expanded",   // include folding - all expanded by default
43   "auto"        // include folding - collapse/expand state predefined on a block-by-block basis
44 };
45
46
47 cAppCharP const DbgSourceModeNames[numDbgSourceModes] = {
48   "none",       // do not include links into source code in HTML logs
49   "hint",       // no links, but info about what file/line number the message comes from
50   "doxygen",    // include link into doxygen prepared HTML version of source code
51   "txmt",       // include txmt:// link (understood by TextMate and BBEdit) into source code
52 };
53
54
55 // debug flush modes
56 cAppCharP const DbgFlushModeNames[numDbgFlushModes] = {
57   "buffered",   // no flush, keep open as long as possible, output buffered (fast, needed for network drives)
58   "flush",      // flush every debug message
59   "openclose"   // open and close debug channel separately for every message (as in 2.x engine)
60 };
61
62 // debug subthread isolation modes
63 cAppCharP const DbgSubthreadModeNames[numDbgSubthreadModes] = {
64   "none",       // do not handle output from subthread specially
65   "suppress",   // suppress output from subthreads
66   "separate",   // create separate output stream (=file) for each subthread
67   "mix",        // mix on a line by line basis
68   "mixblocks"   // buffer thread's output and mix block-wise it into main stream when appropriate
69 };
70
71 #endif
72
73 // file extentsions for debug format modes
74 cAppCharP const DbgOutFormatExtensions[numDbgOutFormats] = {
75   ".log",        // plain text format (but can be indented)
76   ".xml",        // XML format
77   ".html"        // HTML format
78 };
79
80
81 cAppCharP const DbgOutDefaultPrefixes[numDbgOutFormats] = {
82   "*** Start of log",
83   "<?xml version=\"1.0\"?>\n"
84     "<sysync_log version=\"1.0\">",
85   "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n"
86   "<html><head><title>SySync SyncML Engine " SYSYNC_FULL_VERSION_STRING " Log</title>\n"
87     "<meta http-equiv=\"content-type\" content=\"text/html;charset=UTF-8\">\n"
88     "<style type=\"text/css\" media=\"screen\"><!--\n"
89       ".block { color: #0000FF; font-weight: bold; }\n"
90       ".attribute { color: #A5002C; }\n"
91       ".attrval { color: #D80039; font-weight: bold; }\n"
92       ".error { color: red; font-weight: bold; }\n"
93       ".hotalone { color: #000000; font-weight: bold; }\n"
94       ".hot { font-weight: bold; }\n"
95       ".script { color: #996633; }\n" // brownish
96       ".source { color: #3333FF; font-family:courier,monospace; font-size: 90%; font-weight: bold; }\n" // keyword blue
97       ".comment { color: #669933; font-family:courier,monospace; font-size: 90%; font-weight: bold; }\n" // comment green
98       ".skipped { color: #BBBBBB; font-family:courier,monospace; font-size: 90%; font-weight: bold; }\n" // skipped code
99       ".value { color: #FF3300; }\n" // bright orange
100       ".filter { color: #997F66; }\n" // brownish pale
101       ".match { color: #A95E38; }\n" // brownish orange
102       ".dbapi { color: #CC3366; }\n" // dark reddish/pink (pink/violet = database)
103       ".plugin { color: #9151A3; }\n" // dark violet (pink/violet = database)
104       ".incoming { color: #196D00; }\n" // really dark green (green = remote)
105       ".outgoing { color: #002C84; }\n" // really dark blue (blue = local)
106       ".conflict { color: #990000; }\n" // dark red
107       ".remote { color: #709900; }\n" // greenish (green = remote)
108       ".proto { color: #777100; }\n" // dark yellowish/brown
109       ".rest { color: #AAAAAA; }\n" // greyed
110       ".exotic { color: #FF9900; }\n" // mango
111       "a.jump { color: #5D82BA; }\n"
112       "pre { font-size: 90%; }\n"
113       // for folding (always included, as it must be in header)
114       ".exp {\n"
115       " color: #FF0000;\n"
116       " font-weight: bold;\n"
117       " font-size: 90%;\n"
118       " width: 1em;\n"
119       " height: 1em;\n"
120       " display: inline;\n"
121       " border-width: 0.2em;\n"
122       " border-style: solid;\n"
123       " text-align: center;\n"
124       " vertical-align: middle;\n"
125       " padding: 0px 0.2em 0px 0.2em;\n"
126       " margin: 0 4px 2px 0;\n"
127       "}\n"
128       ".coll {\n"
129       " color: #754242;\n"
130       " font-weight: bold;\n"
131       " font-size: 90%;\n"
132       " width: 1em;\n"
133       " height: 1em;\n"
134       " display: inline;\n"
135       " border-width: 0.2em;\n"
136       " border-style: solid;\n"
137       " text-align: center;\n"
138       " vertical-align: middle;\n"
139       " padding: 0px 0.2em 0px 0.2em;\n"
140       " margin: 0 4px 2px 0;\n"
141       "}\n"
142       ".doall { color: #754242; }\n"
143     "--></style>\n"
144     "</head><body><h2>Start of log - SySync SyncML Engine " SYSYNC_FULL_VERSION_STRING "</h2>\n<ul>\n"
145 };
146
147 cAppCharP const DbgOutDefaultSuffixes[numDbgOutFormats] = {
148   "*** End of log",
149   "</sysync_log>",
150   "</ul><h2>End of log</h2></html>"
151 };
152
153
154 cAppCharP FoldingPrefix =
155   "<script language=javascript1.2 type=text/javascript><!--\n"
156   "function div_ref_style (id) {\n"
157   " if      (document.layers)         return document.layers[id];\n"
158   " else if (document.all)            return document.all[id].style;\n"
159   " else if (document.getElementById) return document.getElementById(id).style;\n"
160   " else                              return null;\n"
161   "}\n"
162   "function exp(id) {\n"
163   " if(div_ref_style('B'+id).display!='block') {\n"
164   "  div_ref_style('B'+id).display='block';\n"
165   "  div_ref_style('E'+id).display='none';\n"
166   "  div_ref_style('C'+id).display='inline';\n"
167   " }\n"
168   "}\n"
169   "function coll(id) {\n"
170   " if(div_ref_style('B'+id).display!='none') {\n"
171   "  div_ref_style('B'+id).display='none';\n"
172   "  div_ref_style('E'+id).display='inline';\n"
173   "  div_ref_style('C'+id).display='none';\n"
174   " }\n"
175   "}\n"
176   "function doall(id,collapse) {\n"
177   " // get parent element\n"
178   " if (id=='') {\n"
179   "  mydiv=document;\n"
180   " }\n"
181   " else {\n"
182   "  mydiv=document.getElementById('B'+id); // get div to collapse or expand\n"
183   "  if (collapse) {\n"
184   "   coll(id);\n"
185   "  }\n"
186   "  else {\n"
187   "   exp(id);\n"
188   "  }\n"
189   " }\n"
190   " // get all contained blocks\n"
191   " divs=mydiv.getElementsByTagName('div')  // all divs\n"
192   " for (i=0 ; i<divs.length ; i++) {\n"
193   "  if (divs[i].className=='blk') {\n"
194   "   // this is a foldable block div\n"
195   "   bid = divs[i].id.substring(1);\n"
196   "   if (collapse) {\n"
197   "    coll(bid);\n"
198   "   }\n"
199   "   else {\n"
200   "    exp(bid);\n"
201   "   }\n"
202   "  }\n"
203   " }\n"
204   "}\n"
205   "--></script>\n"
206   "<li><span class=\"doall\" onclick=\"doall('',true)\">[-- collapse all --]</span><span class=\"doall\" onclick=\"doall('',false)\">[++ expand all ++]</span></li>\n";
207
208
209 // externals
210
211 #ifdef CONSOLEINFO
212 // privately redefined here to avoid circular headers (would need syncappbase.h)
213 extern "C" void ConsolePuts(const char *text);
214 #endif
215
216 // TDbgOptions implementation
217 // --------------------------
218
219 TDbgOptions::TDbgOptions()
220 {
221   // set defaults
222   clear();
223 } // TDbgOptions::TDbgOptions
224
225
226 void TDbgOptions::clear(void)
227 {
228   fOutputFormat = dbgfmt_html; // most universally readable and convenient
229   fIndentString = "  "; // two spaces
230   fCustomPrefix.erase(); // no custom prefix
231   fCustomSuffix.erase(); // no custom suffix
232   fSeparateMsgs = true; // separate text message lines (<msg></msg> in xml)
233   fTimestampStructure = true; // timestamps in structure...
234   fTimestampForAll = false; // ..but not for every line
235   fThreadIDForAll = false; // not by default
236   fFlushMode = dbgflush_none; // no special flush or openclose (fast, but might loose info on process abort)
237   fFoldingMode = dbgfold_auto; // dynamic folding enabled, expanded/collapsed defaults automatically set on block-by-block basis
238   #ifdef SYDEBUG_LOCATION
239   fSourceLinkMode = dbgsource_none; // no links into source code
240   fSourceRootPath = SYDEBUG_LOCATION; // use default path from build
241   #endif
242   fAppend = false; // default to overwrite existing logfiles
243   fSubThreadMode = dbgsubthread_suppress; // simply suppress subthread info
244   fSubThreadBufferMax = 1024*1024; // don't buffer more than one meg.
245 } // TDbgOptions::clear
246
247
248
249 // TDbgOut implementation
250 // ----------------------
251
252 TDbgOut::TDbgOut() :
253   fDestructed(false)
254 {
255   // init
256   fIsOpen=false;
257 } // TDbgOut::TDbgOut
258
259
260 TDbgOut::~TDbgOut()
261 {
262   destruct();
263 } // TDbgOut::~TDbgOut
264
265
266 void TDbgOut::destruct(void)
267 {
268   if (!fDestructed) doDestruct();
269   fDestructed=true;
270 } // TDbgOut::destruct
271
272
273 void TDbgOut::doDestruct(void)
274 {
275   // make sure files are closed
276   closeDbg();
277 } // TDbgOut::doDestruct
278
279
280 // TStdFileDbgOut implementation
281 // -----------------------------
282
283 #ifndef NO_C_FILES
284
285 TStdFileDbgOut::TStdFileDbgOut()
286 {
287   // init
288   fFileName.erase();
289   fFile=NULL;
290   mutex=newMutex();
291 } // TStdFileDbgOut::TStdFileDbgOut
292
293
294 TStdFileDbgOut::~TStdFileDbgOut()
295 {
296   destruct();
297   freeMutex(mutex);
298 } // TStdFileDbgOut::~TStdFileDbgOut
299
300
301 // open standard C file based debug output channel
302 bool TStdFileDbgOut::openDbg(cAppCharP aDbgOutputName, cAppCharP aSuggestedExtension, TDbgFlushModes aFlushMode, bool aOverWrite, bool aRawMode)
303 {
304   if (fIsOpen) {
305     // first close
306     closeDbg();
307   }
308   // now apply new flush mode
309   fFlushMode=aFlushMode;
310   // save new file name
311   fFileName=aDbgOutputName;
312   // for C files, use the extension provided
313   fFileName+=aSuggestedExtension;
314   // open
315   fFile=fopen(fFileName.c_str(),aRawMode ? (aOverWrite ? "wb" : "ab") : (aOverWrite ? "w" : "a"));
316   // in case this fails, we'll have a NULL fFile. We can't do anything more here
317   fIsOpen=fFile!=NULL;
318   // For openclose mode, we have opened here only to check for logfile writability - close again
319   if (fIsOpen && fFlushMode==dbgflush_openclose) {
320     fclose(fFile);
321     fFile=NULL;
322   }
323   // return false if we haven't been successful opening the channel
324   return fIsOpen;
325 } // TStdFileDbgOut::openDbg
326
327
328 // return current size of debug file
329 uInt32 TStdFileDbgOut::dbgFileSize(void)
330 {
331   if (!fIsOpen) return 0; // no file, no size
332   uInt32 sz;
333   if (fFlushMode==dbgflush_openclose) {
334     // we need to open the file for append first
335     fFile=fopen(fFileName.c_str(),"a");
336     fseek(fFile,0,SEEK_END); // move to end (needed, otherwise ftell may return 0 despite "a" fopen mode)
337     sz=ftell(fFile);
338     fclose(fFile);
339     fFile=NULL;
340   }
341   else {
342     fseek(fFile,0,SEEK_END); // move to end (needed, otherwise ftell may return 0 despite "a" fopen mode)
343     sz=ftell(fFile); // return size
344   }
345   return sz;
346 } // TStdFileDbgOut::dbgFileSize
347
348
349 // close standard C file based debug channel
350 void TStdFileDbgOut::closeDbg(void)
351 {
352   if (fIsOpen) {
353     if (fFile) {
354       fclose(fFile);
355       fFile=NULL;
356     }
357     fIsOpen=false;
358   }
359 } // TStdFileDbgOut::closeDbg
360
361
362 // write single line to standard file based output channel
363 void TStdFileDbgOut::putLine(cAppCharP aLine, bool aForceFlush)
364 {
365   // if not open, just NOP
366   if (fIsOpen) {
367     if (fFlushMode==dbgflush_openclose) {
368       // we need to open the file for append first
369       lockMutex(mutex);
370       fFile=fopen(fFileName.c_str(),"a");
371       if (!fFile)
372         unlockMutex(mutex);
373     }
374     if (fFile) {
375       // now output
376       fputs(aLine,fFile);
377       fputs("\n",fFile);
378
379       // do required flushing
380       if (fFlushMode==dbgflush_openclose) {
381         // we need to close the file after every line of output
382         fclose(fFile);
383         fFile=NULL;
384         unlockMutex(mutex);
385       }
386       else if (aForceFlush || fFlushMode==dbgflush_flush) {
387         // simply flush
388         fflush(fFile);
389       }
390     }
391   }
392 } // TStdFileDbgOut::putLine
393
394
395 // write raw data to output file
396 void TStdFileDbgOut::putRawData(cAppPointer aData, memSize aSize)
397 {
398   if (fIsOpen) {
399     if (fFlushMode==dbgflush_openclose) {
400       // we need to open the file for append first
401       lockMutex(mutex);
402       fFile=fopen(fFileName.c_str(),"a");
403     }
404     if (fFile) {
405       if (fwrite(aData, 1, aSize, fFile) != 1) {
406         // error ignored
407       }
408     }
409     // do required flushing
410     if (fFlushMode==dbgflush_openclose) {
411       // we need to close the file after every line of output
412       fclose(fFile);
413       fFile=NULL;
414       unlockMutex(mutex);
415     }
416     else if (fFlushMode==dbgflush_flush) {
417       // simply flush
418       fflush(fFile);
419     }
420   }
421 } // TStdFileDbgOut::putRawData
422
423
424 #endif
425
426
427 // TConsoleDbgOut implementation
428 // -----------------------------
429
430 TConsoleDbgOut::TConsoleDbgOut()
431 {
432   // init
433 } // TStdFileDbgOut::TStdFileDbgOut
434
435
436 // open standard C file based debug output channel
437 bool TConsoleDbgOut::openDbg(cAppCharP aDbgOutputName, cAppCharP aSuggestedExtension, TDbgFlushModes aFlushMode, bool aOverWrite, bool aRawMode)
438 {
439   if (fIsOpen) {
440     // first close
441     closeDbg();
442   }
443   // raw mode is not supported
444   if (!aRawMode)
445     fIsOpen=true;
446   // return false if we haven't been successful opening the channel
447   return fIsOpen;
448 } // TConsoleDbgOut::openDbg
449
450
451 // close standard C file based debug channel
452 void TConsoleDbgOut::closeDbg(void)
453 {
454   fIsOpen=false;
455 } // TConsoleDbgOut::closeDbg
456
457
458 // write single line to standard file based output channel
459 void TConsoleDbgOut::putLine(cAppCharP aLine, bool aForceFlush)
460 {
461   // if not open, just NOP
462   if (fIsOpen) {
463     CONSOLEPUTS(aLine);
464   }
465 } // TConsoleDbgOut::putLine
466
467
468
469
470 // TDebugLoggerBase implementation
471 // -------------------------------
472
473 // constructor
474 TDebugLoggerBase::TDebugLoggerBase(GZones *aGZonesP) :
475   fGZonesP(aGZonesP)
476 {
477   fDebugMask=0;
478   fDebugEnabled=true; // enabled by default
479   fNextDebugMask=0;
480   fDbgOutP=NULL;
481   fDbgOptionsP=NULL;
482   fIndent=0;
483   fBlockHistory=NULL; // no Block open yet
484   fOutStarted=false; // not yet started
485   fBlockNo=0;
486   fGZonesP=NULL;
487   fOutputLoggerP=NULL; // no redirected output yet
488 } // TDebugLoggerBase::TDebugLoggerBase
489
490
491 // destructor
492 TDebugLoggerBase::~TDebugLoggerBase()
493 {
494   // make sure debug is finalized
495   DebugFinalizeOutput();
496   // make sure possibly left-over history elements are erased
497   while (fBlockHistory) {
498     TBlockLevel *bl=fBlockHistory;
499     fBlockHistory=bl->fNext;
500     delete bl;
501   }
502   // get rid of output object
503   if (fDbgOutP) delete fDbgOutP;
504   fDbgOutP=NULL;
505 } // TDebugLoggerBase::TDebugLoggerBase
506
507
508 // @brief convenience version for getting time
509 lineartime_t TDebugLoggerBase::getSystemNowAs(timecontext_t aContext)
510 {
511   return sysync::getSystemNowAs(aContext,fGZonesP);
512 } // TDebugLoggerBase::getSystemNowAs
513
514
515 // install outputter
516 void TDebugLoggerBase::installOutput(TDbgOut *aDbgOutP)
517 {
518   // get rid of possibly installed previous outputter
519   if (fDbgOutP) delete fDbgOutP;
520   fDbgOutP=aDbgOutP;
521 } // TDebugLoggerBase::installOutput
522
523
524 /// @brief link this logger to another logger and redirect output to that logger
525 /// @param aDebugLoggerP[in] another logger, that must be alive as long as this logger is alive
526 void TDebugLoggerBase::outputVia(TDebugLoggerBase *aDebugLoggerP)
527 {
528   // save logger and prefix
529   fOutputLoggerP = aDebugLoggerP;
530 } // TDebugLoggerBase::outputVia
531
532 #if defined(CONSOLEINFO) && defined(CONSOLEINFO_LIBC)
533 extern "C" {
534   int (*SySync_ConsolePrintf)(FILE *stream, const char *format, ...) = fprintf;
535 }
536 #endif
537
538 // output formatted text
539 void TDebugLoggerBase::DebugVPrintf(TDBG_LOCATION_PROTO uInt32 aDbgMask, cAppCharP aFormat, va_list aArgs)
540 {
541   // we need a format and debug not completely off
542   if ((getMask() & aDbgMask)==aDbgMask && aFormat) {
543     const sInt16 maxmsglen=1024;
544     char msg[maxmsglen];
545     msg[0]='\0';
546     // assemble the message string
547     vsnprintf(msg, maxmsglen, aFormat, aArgs);
548     // write the string
549     DebugPuts(TDBG_LOCATION_ARG aDbgMask,msg);
550   }
551 } // TDebugLoggerBase::DebugVPrintf
552
553
554 // helper needed for maintaining old DEBUGPRINTFX() macro syntax
555 TDebugLoggerBase &TDebugLoggerBase::setNextMask(uInt32 aDbgMask)
556 {
557   fNextDebugMask=aDbgMask;
558   return *this;
559 } // TDebugLoggerBase::setNextMask
560
561
562 // like DebugPrintf(), but using mask previously set by setNextMask()
563 void TDebugLoggerBase::DebugPrintfLastMask(TDBG_LOCATION_PROTO cAppCharP aFormat, ...)
564 {
565   va_list args;
566   // we need a format and debug not completely off
567   if ((getMask() & fNextDebugMask)==fNextDebugMask && aFormat) {
568     va_start(args, aFormat);
569     DebugVPrintf(TDBG_LOCATION_ARG fNextDebugMask,aFormat,args);
570     va_end(args);
571   }
572   fNextDebugMask=0;
573 } // TDebugLoggerBase::DebugPrintfLastMask
574
575
576 // output formatted text
577 void TDebugLoggerBase::DebugPrintf(TDBG_LOCATION_PROTO uInt32 aDbgMask, cAppCharP aFormat, ...)
578 {
579   va_list args;
580   // we need a format and debug not completely off
581   if ((getMask() & aDbgMask)==aDbgMask && aFormat) {
582     va_start(args, aFormat);
583     DebugVPrintf(TDBG_LOCATION_ARG aDbgMask,aFormat,args);
584     va_end(args);
585   }
586 } // TDebugLoggerBase::DebugVPrintf
587
588
589 // open new Block without attribute list
590 void TDebugLoggerBase::DebugOpenBlock(TDBG_LOCATION_PROTO cAppCharP aBlockName, cAppCharP aBlockTitle, bool aCollapsed)
591 {
592   // we need a format and debug not completely off
593   if (getMask() && aBlockName) {
594 #ifdef __clang__
595     #pragma clang diagnostic push
596     #pragma clang diagnostic ignored "-Wformat-security"
597 #endif
598     DebugOpenBlock(TDBG_LOCATION_ARG aBlockName,aBlockTitle,aCollapsed,NULL);
599 #ifdef __clang__
600     #pragma clang diagnostic pop
601 #endif
602   }
603 } // TDebugLoggerBase::DebugOpenBlock
604
605
606 // open new Block with attribute list, printf style
607 void TDebugLoggerBase::DebugOpenBlock(TDBG_LOCATION_PROTO cAppCharP aBlockName, cAppCharP aBlockTitle, bool aCollapsed, cAppCharP aBlockFmt, ...)
608 {
609   va_list args;
610   // we need a format and debug not completely off
611   if (getMask() && aBlockName) {
612     va_start(args, aBlockFmt);
613     DebugVOpenBlock(TDBG_LOCATION_ARG aBlockName,aBlockTitle,aCollapsed,aBlockFmt,args);
614     va_end(args);
615   }
616 } // TDebugLoggerBase::DebugOpenBlock
617
618
619 // open new Block with attribute list, printf style, expanded by default
620 void TDebugLoggerBase::DebugOpenBlockExpanded(TDBG_LOCATION_PROTO cAppCharP aBlockName, cAppCharP aBlockTitle, cAppCharP aBlockFmt, ...)
621 {
622   va_list args;
623   // we need a format and debug not completely off
624   if (getMask() && aBlockName) {
625     va_start(args, aBlockFmt);
626     DebugVOpenBlock(TDBG_LOCATION_ARG aBlockName,aBlockTitle,false,aBlockFmt,args);
627     va_end(args);
628   }
629 } // TDebugLoggerBase::DebugOpenBlockExpanded
630
631
632 // open new Block with attribute list, printf style, collapsed by default
633 void TDebugLoggerBase::DebugOpenBlockCollapsed(TDBG_LOCATION_PROTO cAppCharP aBlockName, cAppCharP aBlockTitle, cAppCharP aBlockFmt, ...)
634 {
635   va_list args;
636   // we need a format and debug not completely off
637   if (getMask() && aBlockName) {
638     va_start(args, aBlockFmt);
639     DebugVOpenBlock(TDBG_LOCATION_ARG aBlockName,aBlockTitle,true,aBlockFmt,args);
640     va_end(args);
641   }
642 } // TDebugLoggerBase::DebugOpenBlockCollapsed
643
644
645 #ifdef SYDEBUG_LOCATION
646
647 #define MAKEDBGLINK(txt) dbg2Link(TDBG_LOCATION_ARG txt)
648
649 /// turn text into link to source code
650 string TDebugLoggerBase::dbg2Link(const TDbgLocation &aTDbgLoc, const string &aTxt)
651 {
652   if (!aTDbgLoc.fFile || !fDbgOptionsP || fDbgOptionsP->fSourceLinkMode==dbgsource_none || fDbgOptionsP->fOutputFormat!=dbgfmt_html)
653     return aTxt; // disabled, non-html or no information to create source link
654   // create link or hint to source code
655   string line;
656
657   switch(fDbgOptionsP->fSourceLinkMode) {
658     case dbgsource_hint: {
659       // only add name/line number/function as title hint (in a otherwise inactive link)
660       line = "<a href=\"#\" title=";
661       StringObjPrintf(line,"<a href=\"#\" title=\"%s:%d",aTDbgLoc.fFile,aTDbgLoc.fLine);
662       StringObjAppendPrintf(line," in %s",aTDbgLoc.fFunction);
663       line += '"';
664       goto closelink;
665     }
666     case dbgsource_doxygen: {
667       // create link into doxygen
668       line = "<a href=\"";
669       // replace path with path to Doxygen HTML pages,
670       // mangle base name like Doxygen does
671       line += fDbgOptionsP->fSourceRootPath;
672       line += "/";
673       string file = aTDbgLoc.fFile;
674       size_t off = file.rfind('/');
675       if (off != file.npos)
676         file = file.substr(off + 1);
677       for (off = 0; off < file.size(); off++) {
678         switch(file[off]) {
679         case '_':
680           line+="__";
681           break;
682         case '.':
683           line+="_8";
684           break;
685         default:
686           line+=file[off];
687           break;
688         }
689       }
690       StringObjAppendPrintf(line,"-source.html#l%05d",aTDbgLoc.fLine);
691       line+="\"";
692       if (aTDbgLoc.fFunction) {
693         line+=" title=\"";
694         line+=aTDbgLoc.fFunction;
695         line+="\"";
696       }
697       goto closelink;
698     }
699     case dbgsource_txmt: {
700       // create txmt:// URL scheme link, which opens TextMate or BBEdit at the correct line in MacOS X
701       line = "<a href=\"txmt://open/?url=file://";
702       // - create path
703       string path = fDbgOptionsP->fSourceRootPath;
704       path += aTDbgLoc.fFile;
705       // - add path CGI encoded
706       line += encodeForCGI(path.c_str());
707       // - add line number
708       if (aTDbgLoc.fLine>0)
709         StringObjAppendPrintf(line,"&line=%d",aTDbgLoc.fLine);
710       line+="\"";
711       if (aTDbgLoc.fFunction) {
712         line+=" title=\"";
713         line+=aTDbgLoc.fFunction;
714         line+='"';
715       }
716     }
717     closelink: {
718       line+=">";
719       line+=aTxt;
720       line+="</a>";
721       break;
722     }
723     default:
724       line = aTxt;
725   } // switch
726   // return
727   return line;
728 } // TDebugLoggerBase::dbg2Link
729
730 #else
731
732 #define MAKEDBGLINK(txt) (txt)
733
734 #endif // SYDEBUG_LOCATION
735
736
737 // output text to debug channel
738 void TDebugLoggerBase::DebugPuts(TDBG_LOCATION_PROTO uInt32 aDbgMask, cAppCharP aText, stringSize aTextSize, bool aPreFormatted)
739 {
740   // we need a text and debug not completely off
741   if (!((getMask() & aDbgMask)==aDbgMask && aText && fDbgOptionsP)) {
742     // cannot output
743     //#ifdef __MWERKS__
744     //#warning "ugly hack"
745     DebugPutLine(TDBG_LOCATION_NONE "<li><span class=\"error\">Warning: Dbg output system already half shut down (limited formatting)!</span></li><li>");
746     if (aText) DebugPutLine(TDBG_LOCATION_NONE aText);
747     DebugPutLine(TDBG_LOCATION_NONE "</li>");
748     //#endif
749   }
750   else {
751     // make sure output is started
752     if (!fOutStarted) {
753       // try starting output
754       DebugStartOutput();
755       // disable debugging in this logger if starting output failed
756       // (prevents endless re-trying to open debug logs e.g. when log directory does not exist)
757       if (!fOutStarted) {
758         fDebugEnabled = false;
759         return; // stop all efforts here
760       }
761     }
762     // dissect into lines
763     cAppCharP end=aTextSize ? aText+aTextSize : NULL;
764     bool firstLine=true;
765     // check for preformatted message
766     bool pre=strnncmp(aText,"&pre;",5)==0;
767     if (pre) aText+=5;
768     pre = pre || aPreFormatted;
769     // now process text
770     while ((!end || aText<end) && *aText) {
771       // search for line end or end of string
772       cAppCharP p=aText;
773       while ((!end || p<end) && *p && *p!='\n' && *p!='\r') p++;
774       // output this line, properly formatted
775       string line;
776       line.erase();
777       cAppCharP q,s;
778       string ts;
779       switch (fDbgOptionsP->fOutputFormat) {
780         // HTML
781         case dbgfmt_html:
782           // prefix first line with <li>, second and further with <br/>
783           if (firstLine) {
784             line="<li>";
785             // add timestamp if needed for every line
786             if (
787               fDbgOptionsP->fTimestampForAll
788               || fDbgOptionsP->fThreadIDForAll
789               #ifdef SYDEBUG_LOCATION
790               || fDbgOptionsP->fSourceLinkMode!=dbgsource_none
791               #endif
792             ) {
793               string prefix;
794               prefix = "<i>[";
795               #ifdef MULTI_THREAD_SUPPORT
796               if (fDbgOptionsP->fThreadIDForAll) {
797                 StringObjAppendPrintf(prefix,"%09lu",myThreadID());
798                 if (fDbgOptionsP->fTimestampForAll) prefix += ", ";
799               }
800               #endif
801               if (fDbgOptionsP->fTimestampForAll) {
802                 StringObjTimestamp(ts,getSystemNowAs(TCTX_SYSTEM));
803                 prefix += ts;
804               }
805               #ifdef SYDEBUG_LOCATION
806               else if (!fDbgOptionsP->fThreadIDForAll) {
807                 // neither threadID nor timestamp, but source requested -> put small text here
808                 prefix += "src";
809               }
810               #endif
811               prefix+="]</i>&nbsp;";
812               // if we have links into source code, add it here
813               line += MAKEDBGLINK(prefix);
814             }
815             // colorize some messages
816             string cl="";
817             // colors, not mixable, most relevant first
818             if (aDbgMask & DBG_ERROR) {
819               cl="error";
820             }
821             else if (aDbgMask & DBG_EXOTIC) {
822               cl="exotic";
823             }
824             else if (aDbgMask & DBG_SCRIPTS) {
825               cl="script";
826             }
827             else if (aDbgMask & DBG_PLUGIN) {
828               cl="plugin";
829             }
830             else if (aDbgMask & DBG_DBAPI) {
831               cl="dbapi";
832             }
833             else if (aDbgMask & DBG_CONFLICT) {
834               cl="conflict";
835             }
836             else if (aDbgMask & DBG_MATCH) {
837               cl="match";
838             }
839             else if (aDbgMask & DBG_REMOTEINFO) {
840               cl="remote";
841             }
842             else if (aDbgMask & DBG_PROTO) {
843               cl="proto";
844             }
845             else if (aDbgMask & DBG_FILTER) {
846               cl="filter";
847             }
848             else if (aDbgMask & DBG_PARSE) {
849               cl="incoming";
850             }
851             else if (aDbgMask & DBG_GEN) {
852               cl="outgoing";
853             }
854             else if (aDbgMask & DBG_REST) {
855               cl="rest";
856             }
857             // apply basic color style
858             if (!cl.empty()) {
859               line+="<span class=\""; line+=cl; line+="\">";
860             }
861             // aditional style modifiers that can be combined with colors
862             if (aDbgMask & DBG_HOT) {
863               if (cl.empty())
864                 line+="<span class=\"hotalone\">";
865               else
866                 line+="<span class=\"hot\">";
867             }
868             // start preformatted if selected
869             if (pre)
870               line+="<pre>";
871           }
872           else {
873             if (!pre) line="<br/>";
874           }
875           goto xmlize;
876         // XML, just output and replace special chars as needed
877         case dbgfmt_xml:
878           if (firstLine) {
879             #ifdef MULTI_THREAD_SUPPORT
880             if (fDbgOptionsP->fThreadIDForAll) {
881               line+="<thread>";
882               StringObjAppendPrintf(line,"%09lu",myThreadID());
883               line+="</thread>";
884             }
885             #endif
886             if (fDbgOptionsP->fTimestampForAll) {
887               StringObjTimestamp(ts,getSystemNowAs(TCTX_SYSTEM));
888               line+="<time>";
889               line+=ts;
890               line+="</time>";
891             }
892             DebugPutLine(TDBG_LOCATION_NONE line.c_str(),line.size());
893             line.erase();
894           }
895           if (fDbgOptionsP->fSeparateMsgs) {
896             line+="<msg>";
897           }
898         xmlize:
899           q=aText;
900           s=q;
901           while (q<p) {
902             if (*q=='&') {
903               if (strucmp(q,"&html;",6)==0) {
904                 if (q>s) line.append(s,q-s); // flush stuff scanned so far
905                 // everything until next &html; does not need or want escaping, copy it as is
906                 // - search next &html;
907                 s=q=q+6;
908                 while(*q && strucmp(q,"&html;",6)!=0) q++;
909                 // - append everything between if we are in HTML mode
910                 if (fDbgOptionsP->fOutputFormat==dbgfmt_html && q>s)
911                   line.append(s,q-s);
912                 s=q=q+6;
913               }
914               else if (strucmp(q,"&sp;",4)==0) {
915                 if (q>s) line.append(s,q-s); // flush stuff scanned so far
916                 // non-breaking space in HTML, normal space otherwise
917                 if (fDbgOptionsP->fOutputFormat==dbgfmt_html)
918                   line += "&nbsp;";
919                 else
920                   line += ' ';
921                 s=q=q+4; // skip &sp;
922               }
923               else {
924                 if (q>s) line.append(s,q-s);
925                 line+="&amp;";
926                 s=++q;
927               }
928             }
929             else if (*q=='<') {
930               if (q>s) line.append(s,q-s);
931               line+="&lt;";
932               s=++q;
933             }
934             else if (*q=='>') {
935               if (q>s) line.append(s,q-s);
936               line+="&gt;";
937               s=++q;
938             }
939             else {
940               q++;
941             }
942           }
943           if (q>s) line.append(s,q-s);
944           if (fDbgOptionsP->fSeparateMsgs && fDbgOptionsP->fOutputFormat==dbgfmt_xml) {
945             line+="</msg>";
946           }
947           break;
948         // plain text
949         default:
950         case dbgfmt_text:
951           q=aText;
952           s=q;
953           while (q<p) {
954             if (*q=='&') {
955               if (strucmp(q,"&html;",6)==0) {
956                 if (q>s) line.append(s,q-s);
957                 // everything until next &html; must be filtered out
958                 // - search next &html;
959                 s=q=q+6;
960                 while(*q && strucmp(q,"&html;",6)!=0) q++;
961                 s=q=q+6;
962               }
963               else if (strucmp(q,"&sp;",4)==0) {
964                 if (q>s) line.append(s,q-s);
965                 s=q=q+4;
966                 line += ' '; // convert to plain space
967               }
968               else
969                 q++;
970             }
971             else {
972               q++;
973             }
974           }
975           if (q>s) line.append(s,q-s);
976           break;
977       } // switch text output
978       firstLine=false;
979       // skip the lineend, if any
980       while (((!end || p<end) && *p=='\n') || *p=='\r') p++;
981       if (fDbgOptionsP->fOutputFormat==dbgfmt_html && ((end && p>=end) || *p==0)) {
982         // end preformatted
983         if (pre)
984           line+="</pre>";
985         // colors
986         if (aDbgMask & (
987           DBG_ERROR |
988           DBG_SCRIPTS |
989           DBG_REST |
990           DBG_EXOTIC |
991           DBG_DBAPI |
992           DBG_PLUGIN |
993           DBG_PARSE |
994           DBG_GEN |
995           DBG_CONFLICT |
996           DBG_MATCH |
997           DBG_REMOTEINFO |
998           DBG_PROTO |
999           DBG_FILTER
1000         )) {
1001           line+="</span>"; // end special style
1002         }
1003         // HOT modifier
1004         if (aDbgMask & (
1005           DBG_HOT
1006         )) {
1007           line+="</span>"; // end special style
1008         }
1009         line+="</li>"; // we need to close the list entry
1010       }
1011       DebugPutLine(TDBG_LOCATION_NONE line.c_str(),line.size(),pre);
1012       // next line, if any
1013       aText=p;
1014     } // loop until all text done
1015   }
1016 } // TDebugLoggerBase::DebugPuts
1017
1018
1019 // open new Block with attribute list, varargs passed
1020 void TDebugLoggerBase::DebugVOpenBlock(TDBG_LOCATION_PROTO cAppCharP aBlockName, cAppCharP aBlockTitle, bool aCollapsed, cAppCharP aBlockFmt, va_list aArgs)
1021 {
1022   if (!fDbgOptionsP)
1023     return;
1024   if (fDbgOptionsP->fFoldingMode==dbgfold_collapsed)
1025     aCollapsed=true;
1026   else if (fDbgOptionsP->fFoldingMode==dbgfold_expanded)
1027     aCollapsed=false;
1028   if (getMask() && aBlockName && fDbgOptionsP) {
1029     // make sure output is started
1030     if (!fOutStarted) DebugStartOutput();
1031     // create Block line on current indent level
1032     string bl;
1033     string ts;
1034     // - preamble, possibly with timestamp
1035     bool withTime = fDbgOptionsP->fTimestampStructure;
1036     if (withTime)
1037       StringObjTimestamp(ts,getSystemNowAs(TCTX_SYSTEM));
1038     switch (fDbgOptionsP->fOutputFormat) {
1039       // XML
1040       case dbgfmt_xml:
1041         bl="<"; bl+=aBlockName;
1042         if (withTime) {
1043           bl+=" time=\"" + ts + "\"";
1044         }
1045         if (aBlockTitle) {
1046           bl+=" title=\"";
1047           bl+=aBlockTitle;
1048           bl+="\"";
1049         }
1050         break;
1051       // HTML
1052       case dbgfmt_html:
1053         bl="<li><span class=\"block\">";
1054         if (fDbgOptionsP->fFoldingMode!=dbgfold_none) {
1055           StringObjAppendPrintf(bl,
1056             "<div id=\"E%ld\" style=\"display:%s\" class=\"exp\" onclick=\"exp('%ld')\">+</div><div id=\"C%ld\" style=\"display:%s\" class=\"coll\" onclick=\"coll('%ld')\">&ndash;</div>",
1057                                 long(getBlockNo()), aCollapsed ? "inline" : "none", long(getBlockNo()),
1058                                 long(getBlockNo()), aCollapsed ? "none" : "inline", long(getBlockNo())
1059           );
1060         }
1061         StringObjAppendPrintf(bl,"<a name=\"H%ld\">", long(getBlockNo()));
1062         if (withTime) {
1063           bl += MAKEDBGLINK(string("[") + ts + "] ");
1064         }
1065         #ifdef SYDEBUG_LOCATION
1066         else if (fDbgOptionsP->fSourceLinkMode!=dbgsource_none) {
1067           bl += MAKEDBGLINK(string("[src] "));
1068         }
1069         #endif
1070         bl+="'";
1071         bl+=aBlockName;
1072         bl+="'";
1073         if (aBlockTitle) {
1074           bl+=" - ";
1075           bl+=aBlockTitle;
1076         }
1077         bl+="</a></span><span class=\"attribute\">";
1078         break;
1079       // plain text
1080       default:
1081       case dbgfmt_text:
1082         bl.erase();
1083         if (!fDbgOptionsP->fTimestampForAll && withTime) { // avoid timestamp here if all lines get timestamped anyway
1084           bl+="[" + ts + "] ";
1085         }
1086         bl+=aBlockName;
1087         if (aBlockTitle) {
1088           bl+=" - ";
1089           bl+=aBlockTitle;
1090         }
1091         break;
1092     } // switch preamble
1093     // - attributes
1094     if (aBlockFmt) {
1095       // first expand all printf parameters
1096       string attrs;
1097       vStringObjPrintf(attrs,aBlockFmt,true,aArgs);
1098       // isolate |-separated attribute format strings
1099       cAppCharP q,r,s,p=attrs.c_str();
1100       while (*p) {
1101         // search for beginning of value
1102         q=p;
1103         while(*q && *q!='=' && *q!='|') q++;
1104         // search for end of value
1105         r=q;
1106         s=q; // in case we don't have a =
1107         if (*q=='=') {
1108           s=q+1;
1109           r=s;
1110           while (*r && *r!='|') r++;
1111         }
1112         // now: p=start of attrname, q=end of attrname
1113         //      s=start of value, r=end of value
1114         // output an attribute now
1115         if (q>p && r>s) {
1116           switch (fDbgOptionsP->fOutputFormat) {
1117             // XML
1118             case dbgfmt_xml:
1119               bl+=" ";
1120               bl.append(p,q-p);
1121               bl+="=\"";
1122               bl.append(s,r-s);
1123               bl+="\"";
1124               break;
1125             case dbgfmt_html:
1126               bl+=", ";
1127               bl.append(p,q-p);
1128               bl+="=<span class=\"attrval\">";
1129               bl.append(s,r-s);
1130               bl+="</span>";
1131               break;
1132             case dbgfmt_text:
1133             default:
1134               bl+=", ";
1135               bl.append(p,q-p);
1136               bl+="=";
1137               bl.append(s,r-s);
1138               break;
1139           } // switch attribute
1140         } // non-empty attribute
1141         // more attributes to come?
1142         if (*r=='|') r++; // skip separator
1143         p=r;
1144       } // while
1145     } // attributes present
1146     // - finalize Block
1147     switch (fDbgOptionsP->fOutputFormat) {
1148       // XML
1149       case dbgfmt_xml:
1150         bl+=">";
1151         break;
1152       // HTML
1153       case dbgfmt_html:
1154         bl+="</span>"; // end span for attributes
1155         if (fDbgOptionsP->fFoldingMode!=dbgfold_none) {
1156           StringObjAppendPrintf(bl,
1157             "&nbsp;<span class=\"doall\" onclick=\"doall('%ld',true)\">[--]</span><span class=\"doall\" onclick=\"doall('%ld',false)\">[++]</span>",
1158                                 long(getBlockNo()), long(getBlockNo())
1159           );
1160         }
1161         // link to end of block
1162         StringObjAppendPrintf(bl,"&nbsp;<a class=\"jump\" href=\"#F%ld\">[->end]</a>", long(getBlockNo()));
1163         // link to start of enclosing block (if any)
1164         if (fBlockHistory) {
1165           StringObjAppendPrintf(bl,"&nbsp;<a class=\"jump\" href=\"#H%ld\">[->enclosing]</a>", long(fBlockHistory->fBlockNo));
1166         }
1167         // start div for content folding
1168         if (fDbgOptionsP->fFoldingMode!=dbgfold_none) {
1169           StringObjAppendPrintf(bl,
1170             "<div class=\"blk\" id=\"B%ld\" style=\"display:%s\">",
1171             long(getBlockNo()),
1172             aCollapsed ? "none" : "inline"
1173           );
1174         }
1175         bl+="<ul>"; // now start list for block's contents
1176         break;
1177       // plain text
1178       default:
1179       case dbgfmt_text:
1180         break;
1181     } // switch preamble
1182     // now output Block line (on current indent level)
1183     DebugPutLine(TDBG_LOCATION_NONE bl.c_str(), bl.size());
1184     // increase indent level (applies to all Block contents)
1185     fIndent++;
1186     // save Block on stack
1187     TBlockLevel *newLevel = new TBlockLevel;
1188     newLevel->fBlockName=aBlockName;
1189     newLevel->fNext=fBlockHistory;
1190     newLevel->fBlockNo=getBlockNo(); // save block number to reference block in collapse box at end of block
1191     nextBlock(); // increment block number
1192     fBlockHistory=newLevel; // insert new level at start of list
1193   }
1194 } // TDebugLoggerBase::DebugVOpenBlock
1195
1196
1197 // close named Block. If no name given, topmost Block will be closed
1198 void TDebugLoggerBase::DebugCloseBlock(TDBG_LOCATION_PROTO cAppCharP aBlockName)
1199 {
1200   if (fOutStarted && getMask() && fDbgOptionsP && fBlockHistory) {
1201     if (aBlockName==NULL) {
1202       #if SYDEBUG>1
1203       internalCloseBlocks(TDBG_LOCATION_ARG fBlockHistory->fBlockName.c_str(),"Block Nest Warning: Missing Block name at close");
1204       #else
1205       internalCloseBlocks(TDBG_LOCATION_ARG fBlockHistory->fBlockName.c_str(),NULL);
1206       #endif
1207     }
1208     else {
1209       internalCloseBlocks(TDBG_LOCATION_ARG aBlockName,NULL);
1210     }
1211   }
1212 } // TDebugLoggerBase::DebugCloseBlock
1213
1214
1215
1216 // internal helper used to close all or some Blocks
1217 void TDebugLoggerBase::internalCloseBlocks(TDBG_LOCATION_PROTO cAppCharP aBlockName, cAppCharP aCloseComment)
1218 {
1219   if (!fDbgOptionsP) return; // security
1220   bool withTime = fDbgOptionsP->fTimestampStructure;
1221   string comment;
1222   #if SYDEBUG>1
1223   if (!fBlockHistory && aBlockName) {
1224     // no blocks open any more and not close-all-remaining call (log close...)
1225     DebugPrintf(TDBG_LOCATION_ARG DBG_EXOTIC+DBG_ERROR,"Block Nest Warning: Trying to close block '%s', but no block is open",aBlockName);
1226   }
1227   #endif
1228   while (fBlockHistory) {
1229     // prepare comment
1230     comment.erase();
1231     if (aCloseComment) {
1232       comment += " - ";
1233       comment += aCloseComment;
1234     }
1235     // check if closing top-of-stack Block now
1236     bool found=
1237       (aBlockName && strucmp(aBlockName,fBlockHistory->fBlockName.c_str())==0);
1238     if (!found && fBlockHistory->fNext==NULL) {
1239       // last Block always counts as "found"...
1240       found = true;
1241       #if SYDEBUG>1
1242       // ...but issue warning as name is not what we would have expected
1243       StringObjAppendPrintf(comment, " - Block Nest Warning: closing '%s', but expected '%s'",aBlockName ? aBlockName : "<unknown>", fBlockHistory->fBlockName.c_str());
1244       #endif
1245     }
1246     // now close topmost Block
1247     string ts,bl;
1248     // - get time if needed and possibly put it within indented block
1249     if (withTime) {
1250       StringObjTimestamp(ts,getSystemNowAs(TCTX_SYSTEM));
1251       // for XML, the time must be shown before the close tag on a separate line
1252       if (fDbgOptionsP->fOutputFormat == dbgfmt_xml) {
1253         StringObjPrintf(bl,"<endblock time=\"%s\"/>",ts.c_str());
1254         DebugPutLine(TDBG_LOCATION_NONE bl.c_str(), bl.size()); // still within block, indented
1255       }
1256     }
1257     // - now unindent
1258     if (fIndent>0) fIndent--;
1259     // - then create closing Block
1260     #if SYDEBUG>1
1261     if (!found) StringObjAppendPrintf(comment," - Block Nest Warning: implicitly closed (by explicitly closing '%s')",aBlockName ? aBlockName : "<unknown parent>");
1262     #endif
1263     switch (fDbgOptionsP->fOutputFormat) {
1264       // XML
1265       case dbgfmt_xml:
1266         bl="</";
1267         bl+=fBlockHistory->fBlockName;
1268         bl+=">";
1269         if (!comment.empty()) {
1270           bl+=" <!-- ";
1271           bl+=comment;
1272           bl+=" -->";
1273         }
1274         break;
1275       // HTML
1276       case dbgfmt_html:
1277         bl="</ul><span class=\"block\">"; // end of content list
1278         if (fDbgOptionsP->fFoldingMode!=dbgfold_none) {
1279           StringObjAppendPrintf(bl,
1280             "<span class=\"coll\" onclick=\"coll('%ld')\">&ndash;</span>",
1281             long(fBlockHistory->fBlockNo)
1282           );
1283         }
1284         StringObjAppendPrintf(bl,"<a name=\"F%ld\">",long(fBlockHistory->fBlockNo));
1285         if (withTime) {
1286           bl += MAKEDBGLINK(string("[") + ts + "] ");
1287         }
1288         bl += "End of '";
1289         bl+=fBlockHistory->fBlockName;
1290         bl+="'";
1291         bl+=comment;
1292         bl+="</a></span>";
1293         // link to top of block
1294         StringObjAppendPrintf(bl,"&nbsp;<a class=\"jump\" href=\"#H%ld\">[->top]</a>",long(fBlockHistory->fBlockNo));
1295         // link to end of enclosing block (if any)
1296         if (fBlockHistory->fNext) {
1297           StringObjAppendPrintf(bl,"&nbsp;<a class=\"jump\" href=\"#F%ld\">[->enclosing]</a>",long(fBlockHistory->fNext->fBlockNo));
1298         }
1299         if (fDbgOptionsP->fFoldingMode!=dbgfold_none) {
1300           bl+="</div>"; // end of folding division
1301         }
1302         bl+="</li>"; // end of list entry containing entire block
1303         break;
1304       // plain text
1305       default:
1306       case dbgfmt_text:
1307         bl.erase();
1308         if (!fDbgOptionsP->fTimestampForAll && withTime) { // avoid timestamp here if all lines get timestamped anyway
1309           bl+="[" + ts + "] ";
1310         }
1311         bl+="End of '";
1312         bl+=fBlockHistory->fBlockName;
1313         bl+="'";
1314         bl+=comment;
1315         break;
1316     } // switch Block close
1317     // - output closing Block line
1318     DebugPutLine(TDBG_LOCATION_NONE bl.c_str(), bl.size());
1319     // - remove Block level
1320     TBlockLevel *closedLevel = fBlockHistory;
1321     fBlockHistory = closedLevel->fNext;
1322     delete closedLevel;
1323     // if we have found the Block, exit here
1324     if (found) break;
1325   }
1326 } // TDebugLoggerBase::internalCloseBlocks
1327
1328
1329 // start debugging output if needed and sets fOutStarted
1330 bool TDebugLoggerBase::DebugStartOutput(void)
1331 {
1332   if (!fOutStarted) {
1333     if (fOutputLoggerP) {
1334       // using another logger, call it to start output
1335       fOutStarted = fOutputLoggerP->DebugStartOutput();
1336       if (fOutStarted) {
1337         // start with indent level of parent logger
1338         fIndent = fOutputLoggerP->fIndent;
1339         // note: we'll use the parent logger's block number...
1340         fBlockNo = 0; // ...but init to something just in case
1341       }
1342     }
1343     else if (fDbgOptionsP && fDbgOutP && !fDbgPath.empty()) {
1344       // try to open the debug channel (force to openclose if we have multiple threads mixed in one file)
1345       if (fDbgOutP->openDbg(
1346         fDbgPath.c_str(),
1347         DbgOutFormatExtensions[fDbgOptionsP->fOutputFormat],
1348         fDbgOptionsP->fSubThreadMode==dbgsubthread_linemix ? dbgflush_openclose : fDbgOptionsP->fFlushMode,
1349         !fDbgOptionsP->fAppend
1350       )) {
1351         // make sure we don't recurse when we produce some output
1352         fOutStarted = true;
1353         fIndent = 0; // reset to make sure
1354         // create a block number that is unique in the file, even if we append multiple times.
1355         // We assume that a block consumes at least 256 bytes, so size_of_file/256 always gets
1356         // an unused block ID within that file
1357         // 256 is a safe assumption because the "fold" button <divs> alone are around 250 bytes
1358         fBlockNo = 1 + (fDbgOutP->dbgFileSize()/256);
1359         // now create required prefix
1360         DebugPutLine(TDBG_LOCATION_NONE fDbgOptionsP->fCustomPrefix.empty() ? DbgOutDefaultPrefixes[fDbgOptionsP->fOutputFormat] : fDbgOptionsP->fCustomPrefix.c_str());
1361         // add folding javascript if needed
1362         if (fDbgOptionsP->fOutputFormat==dbgfmt_html && fDbgOptionsP->fFoldingMode!=dbgfold_none) {
1363           DebugPutLine(TDBG_LOCATION_NONE FoldingPrefix);
1364         }
1365       } // debug channel opened successfully
1366     } // use own debug channel
1367   } // environment ready to start output
1368   return fOutStarted;
1369 } // TDebugLoggerBase::DebugStartOutput
1370
1371
1372 // @brief finalize debugging output (close Blocks, close output channel)
1373 void TDebugLoggerBase::DebugFinalizeOutput(void)
1374 {
1375   if (fOutputLoggerP) {
1376     // just close my own blocks
1377     internalCloseBlocks(TDBG_LOCATION_NONE NULL,"closed because sub-log ends here");
1378   }
1379   if (fOutStarted && fDbgOptionsP && fDbgOutP) {
1380     // close all left-open open Blocks
1381     internalCloseBlocks(TDBG_LOCATION_NONE NULL,"closed because log ends here");
1382     // now finalize output
1383     // - special stuff before
1384     if (fDbgOptionsP->fOutputFormat == dbgfmt_xml)
1385       fIndent=0; // unindent to zero (document is not a real Block)
1386     // - then suffix
1387     DebugPutLine(TDBG_LOCATION_NONE fDbgOptionsP->fCustomSuffix.empty() ? DbgOutDefaultSuffixes[fDbgOptionsP->fOutputFormat] : fDbgOptionsP->fCustomSuffix.c_str());
1388     // now close the debug channel
1389     fDbgOutP->closeDbg();
1390   }
1391   // whatever happened, we are not started any more
1392   fOutStarted=false;
1393 } // TDebugLoggerBase::DebugFinalizeOutput
1394
1395
1396 // Output single line to debug channel (includes indenting and other prefixing, but no further formatting)
1397 void TDebugLoggerBase::DebugPutLine(TDBG_LOCATION_PROTO cAppCharP aText, stringSize aTextSize, bool aPre)
1398 {
1399   if (!aText || (!fDbgOutP && !fOutputLoggerP)) return;
1400   if (*aText) {
1401     // not an empty line
1402     string msg;
1403     msg.erase();
1404     // prefix with timestamp if selected in text format
1405     if (fDbgOptionsP && fDbgOptionsP->fOutputFormat==dbgfmt_text && fDbgOptionsP->fTimestampForAll) {
1406       // prefix each line (before the indent!) with a timestamp
1407       string ts;
1408       StringObjTimestamp(ts,getSystemNowAs(TCTX_SYSTEM));
1409       msg='[';
1410       msg+=ts;
1411       msg+="] ";
1412     }
1413     // Indent if selected
1414     if (fDbgOptionsP && !fDbgOptionsP->fIndentString.empty() && !(fDbgOptionsP->fOutputFormat==dbgfmt_html && aPre)) {
1415       // with indent
1416       for (uInt16 n=0; n<fIndent; n++) {
1417         msg+=fDbgOptionsP->fIndentString;
1418       }
1419     }
1420     // add message itself
1421     if (aTextSize)
1422       msg.append(aText,aTextSize);
1423     else
1424       msg.append(aText);
1425     // now output
1426     if (fOutputLoggerP) {
1427       // use parent's output
1428       fOutputLoggerP->fDbgOutP->putLine(msg.c_str(),false); // %%% no forceflush for now
1429     }
1430     else {
1431       // use my own output channel
1432       fDbgOutP->putLine(msg.c_str(),false); // %%% no forceflush for now
1433     }
1434   }
1435 } // TDebugLoggerBase::DebugPutLine
1436
1437
1438 // TDebugLogger implementation
1439 // ---------------------------
1440
1441 // constructor
1442 TDebugLogger::TDebugLogger(GZones *aGZonesP) :
1443   inherited(aGZonesP)
1444 {
1445   #ifdef MULTI_THREAD_SUPPORT
1446   fMainThreadID=0;
1447   fSubThreadLogs=NULL;
1448   fSilentLoggerP=NULL;
1449   #endif
1450 } // TDebugLogger::TDebugLogger
1451
1452
1453 // destructor
1454 TDebugLogger::~TDebugLogger()
1455 {
1456   #ifdef MULTI_THREAD_SUPPORT
1457   // remove subthread loggers
1458   TSubThreadLog* subThreadP = fSubThreadLogs;
1459   fSubThreadLogs = NULL;
1460   while (subThreadP) {
1461     // delete logger if any
1462     if (subThreadP->fSubThreadLogger) {
1463       SYSYNC_TRY {
1464         delete subThreadP->fSubThreadLogger;
1465       }
1466       SYSYNC_CATCH(...)
1467         // nop
1468       SYSYNC_ENDCATCH
1469     }
1470     TSubThreadLog* delP = subThreadP;
1471     subThreadP = subThreadP->fNext;
1472     delete delP;
1473   }
1474   //
1475   if (fSilentLoggerP) {
1476     delete fSilentLoggerP;
1477     fSilentLoggerP = NULL;
1478   }
1479   #endif
1480 } // TDebugLogger::~TDebugLogger
1481
1482
1483 #ifdef MULTI_THREAD_SUPPORT
1484
1485 void TDebugLogger::setOptions(const TDbgOptions *aDbgOptionsP)
1486 {
1487   TDebugLoggerBase::setOptions(aDbgOptionsP);
1488   TSubThreadLog* subThreadP = fSubThreadLogs;
1489   while (subThreadP) {
1490     if (subThreadP->fSubThreadLogger) {
1491       subThreadP->fSubThreadLogger->setOptions(aDbgOptionsP);
1492     }
1493     subThreadP = subThreadP->fNext;
1494   }
1495 }
1496
1497 /// @brief find (and possibly delete) subthread record
1498 /// @param aAndRemove[in] if set, the subthread record will be removed in a thread safe way
1499 ///        IF AND ONLY IF aThreadID is the calling thread (i.e. only own thread may be removed from the list)!
1500 ///        Note that the caller must take care of deleting the subthread record
1501 TSubThreadLog *TDebugLogger::findSubThread(uInt32 aThreadID, bool aAndRemove)
1502 {
1503   TSubThreadLog* subThreadP = fSubThreadLogs;
1504   TSubThreadLog** subThreadLinkPP = &fSubThreadLogs;
1505   while (subThreadP) {
1506     if (subThreadP->fThreadID == aThreadID) {
1507       if (aAndRemove) {
1508         // bridge previous with next in one single assignment (i.e. thread safe)
1509         *subThreadLinkPP = subThreadP->fNext;
1510       }
1511       // return found record (note that it MUST BE DELETED by caller if no longer used)
1512       return subThreadP;
1513     }
1514     subThreadLinkPP = &subThreadP->fNext;
1515     subThreadP = *subThreadLinkPP;
1516   }
1517   return NULL; // none found
1518 } // TDebugLogger::findSubThread
1519
1520
1521 /// @brief find or create logger for subthread
1522 TDebugLoggerBase *TDebugLogger::getThreadLogger(bool aCreateNew)
1523 {
1524   if (!fDbgOptionsP || fDbgOptionsP->fSubThreadMode==dbgsubthread_none)
1525     return this; // no options, do not handle subthreads specially
1526   uIntArch threadID = myThreadID();
1527   if (fDbgOptionsP->fSubThreadMode==dbgsubthread_linemix || threadID==fMainThreadID) {
1528     // In line mix and for mainthread - I am the logger for this thread!
1529     return this;
1530   }
1531   TSubThreadLog* subThreadP = findSubThread(threadID);
1532   if (subThreadP) {
1533     // we know this subthread, return its logger
1534     return subThreadP->fSubThreadLogger; // can be NULL if subthread logging is disabled
1535   }
1536   // unknown subthread
1537   if (fMainThreadID==0) {
1538     // no current mainthread, let subthread write to main log
1539     // Note: this makes sure log info possibly trailing the DebugThreadOutputDone()
1540     //       also lands in the main log. This is not critical - the only thing that must be
1541     //       ensured is that starting new threads is made only with DebugDefineMainThread set.
1542     return this;
1543   }
1544   // new subthread, create entry in list
1545   if (aCreateNew) {
1546     string s;
1547     // create new entry
1548     subThreadP = new TSubThreadLog;
1549     subThreadP->fThreadID=threadID;
1550     subThreadP->fNext=fSubThreadLogs; // link current list behind this new entry
1551     // create logger for the thread (or none)
1552     switch (fDbgOptionsP->fSubThreadMode) {
1553       case dbgsubthread_separate:
1554         // separate file for subthread output
1555         // - create new base logger
1556         subThreadP->fSubThreadLogger = new TDebugLoggerBase(fGZonesP);
1557         // - install output (copy)
1558         subThreadP->fSubThreadLogger->installOutput(fDbgOutP ? fDbgOutP->clone() : NULL);
1559         // - same options
1560         subThreadP->fSubThreadLogger->setOptions(getOptions());
1561         // - inherit current mask/enable
1562         subThreadP->fSubThreadLogger->setMask(getMask());
1563         subThreadP->fSubThreadLogger->setEnabled(fDebugEnabled);
1564         // - debug path is same as myself plus Thread ID
1565         subThreadP->fSubThreadLogger->setDebugPath(fDbgPath.c_str());
1566         StringObjPrintf(s,"_%lu",(long unsigned)threadID);
1567         subThreadP->fSubThreadLogger->appendToDebugPath(s.c_str());
1568         break;
1569       case dbgsubthread_suppress:
1570       default:
1571         // no output from subthreads
1572         subThreadP->fSubThreadLogger=NULL; // no logger
1573         break;
1574     }
1575     // now activate by linking it at top of list (this is thread safe)
1576     fSubThreadLogs = subThreadP;
1577     // return the logger
1578     return subThreadP->fSubThreadLogger;
1579   }
1580   return NULL; // no logger for this thread
1581 } // TDebugLogger::getThreadLogger
1582
1583
1584 // helper needed for maintaining old DEBUGPRINTFX() macro syntax
1585 TDebugLoggerBase &TDebugLogger::setNextMask(uInt32 aDbgMask)
1586 {
1587   TDebugLoggerBase *loggerP = getThreadLogger();
1588   if (loggerP) {
1589     // return pointer to loggerbase whose DebugPrintfLastMask() must be called
1590     return loggerP->inherited::setNextMask(aDbgMask);
1591   }
1592   else {
1593     // we have no logger but still need to return something
1594     if (!fSilentLoggerP) {
1595       fSilentLoggerP = new TDebugLoggerBase(fGZonesP);
1596       fSilentLoggerP->setEnabled(false);
1597     }
1598     fSilentLoggerP->setNextMask(DBG_ERROR); // must set non-zero to make sure it is NOT output!
1599     return *fSilentLoggerP;
1600   }
1601 } // TDebugLoggerBase::setNextMask
1602
1603
1604 // output text to debug channel, with checking for subthreads
1605 void TDebugLogger::DebugPuts(TDBG_LOCATION_PROTO uInt32 aDbgMask, cAppCharP aText, stringSize aTextSize, bool aPreFormatted)
1606 {
1607   TDebugLoggerBase *loggerP = getThreadLogger();
1608   if (loggerP) loggerP->inherited::DebugPuts(TDBG_LOCATION_ARG aDbgMask,aText,aTextSize,aPreFormatted);
1609 } // TDebugLogger::DebugPuts
1610
1611
1612 void TDebugLogger::DebugVPrintf(TDBG_LOCATION_PROTO uInt32 aDbgMask, cAppCharP aFormat, va_list aArgs)
1613 {
1614   TDebugLoggerBase *loggerP = getThreadLogger();
1615   if (loggerP) loggerP->inherited::DebugVPrintf(TDBG_LOCATION_ARG aDbgMask,aFormat,aArgs);
1616 } // TDebugLogger::DebugVPrintf
1617
1618
1619 void TDebugLogger::DebugVOpenBlock(TDBG_LOCATION_PROTO cAppCharP aBlockName, cAppCharP aBlockTitle, bool aCollapsed, cAppCharP aBlockFmt, va_list aArgs)
1620 {
1621   TDebugLoggerBase *loggerP = getThreadLogger();
1622   if (loggerP) loggerP->inherited::DebugVOpenBlock(TDBG_LOCATION_ARG aBlockName, aBlockTitle, aCollapsed, aBlockFmt, aArgs);
1623 } // TDebugLogger::DebugVOpenBlock
1624
1625
1626 void TDebugLogger:: DebugCloseBlock(TDBG_LOCATION_PROTO cAppCharP aBlockName)
1627 {
1628   TDebugLoggerBase *loggerP = getThreadLogger();
1629   if (loggerP) loggerP->inherited::DebugCloseBlock(TDBG_LOCATION_ARG aBlockName);
1630 } // TDebugLogger::DebugCloseBlock
1631
1632 #endif
1633
1634
1635 // output all buffered subthread's output in a special subthread Block in the main output
1636 void TDebugLogger::DebugShowSubThreadOutput(void)
1637 {
1638   #ifdef MULTI_THREAD_SUPPORT
1639   // nop as long mixed-block mode is not implemented
1640   #endif
1641 } // TDebugLogger::DebugShowSubThreadOutput
1642
1643
1644 // the calling thread signals that it is done with doing output for now. If the main
1645 // thread is doing this and we have bufferandmix mode, the next subthread will be allowed
1646 // to write into the output channel until a new main thread gains control via
1647 // DebugDefineMainThread();
1648 void TDebugLogger::DebugThreadOutputDone(bool aRemoveIt)
1649 {
1650   #ifdef MULTI_THREAD_SUPPORT
1651   uIntArch threadID = myThreadID();
1652   if (threadID==fMainThreadID) {
1653     // current main thread done
1654     fMainThreadID = 0;
1655   }
1656   // for session logs, subthreads are usually left in the list at this time (aRemoveIt==false)
1657   // (as they will get deleted with the session logger later anyway)
1658   if (aRemoveIt) {
1659     TSubThreadLog* tP = findSubThread(threadID,true);
1660     if (tP) {
1661       if (tP->fSubThreadLogger) {
1662         SYSYNC_TRY {
1663           delete tP->fSubThreadLogger;
1664         }
1665         SYSYNC_CATCH(...)
1666           // nop
1667         SYSYNC_ENDCATCH
1668       }
1669       delete tP;
1670     }
1671   }
1672   #endif
1673 } // TDebugLogger::DebugThreadOutputDone
1674
1675
1676 // Used to regain control as main thread (e.g. for the next request of a session which
1677 // possibly occurs from another thread).
1678 void TDebugLogger::DebugDefineMainThread(void)
1679 {
1680   #ifdef MULTI_THREAD_SUPPORT
1681   uIntArch threadID = myThreadID();
1682   // if this is already the main thread, no op
1683   if (threadID == fMainThreadID)
1684     return; // nop, done
1685   // thread is not the current main thread
1686   // - search if it is a registered subthread
1687   TSubThreadLog *subThreadP = findSubThread(threadID);
1688   if (fMainThreadID==0) {
1689     // no main thread currently registered
1690     if (subThreadP!=NULL) {
1691       // this is not a new thread, but a known subthread, can't get main thread now
1692       return; // no further op
1693     }
1694     else {
1695       // this is not a known subthread, so it can become the main thread
1696       fMainThreadID = threadID;
1697       return; // done
1698     }
1699   }
1700   else {
1701     // cannot become main thread, will be treated as subthread if it generates output
1702     // - no op required
1703   }
1704   #endif
1705 } // TDebugLogger::DebugDefineMainThread
1706
1707
1708 } // namespace sysync
1709
1710 #endif // SYDEBUG
1711
1712 // eof
1713