source: trunk/MagicSoft/Mars/mbase/MLog.cc@ 7979

Last change on this file since 7979 was 7941, checked in by tbretz, 18 years ago
*** empty log message ***
File size: 20.8 KB
Line 
1/* ======================================================================== *\
2!
3! *
4! * This file is part of MARS, the MAGIC Analysis and Reconstruction
5! * Software. It is distributed to you in the hope that it can be a useful
6! * and timesaving tool in analysing Data of imaging Cerenkov telescopes.
7! * It is distributed WITHOUT ANY WARRANTY.
8! *
9! * Permission to use, copy, modify and distribute this software and its
10! * documentation for any purpose is hereby granted without fee,
11! * provided that the above copyright notice appear in all copies and
12! * that both that copyright notice and this permission notice appear
13! * in supporting documentation. It is provided "as is" without express
14! * or implied warranty.
15! *
16!
17!
18! Author(s): Thomas Bretz, 12/2000 <mailto:tbretz@astro.uni-wuerzburg.de>
19!
20! Copyright: MAGIC Software Development, 2000-2005
21!
22!
23\* ======================================================================== */
24
25
26//////////////////////////////////////////////////////////////////////////////
27//
28// MLog
29//
30// This is what we call the logging-system.
31//
32// It is derived from the C++ streaming classes and can handle our
33// logging. The log output can be redirected to stdout, stderr, any other
34// stream or a root window.
35//
36// There is a global log-instance which you can use like cout, id is gLog.
37// A log-instance of your choice (gLog by default) is destributed to all
38// Task which are used in an eventloop, so that you can redirect the output
39// of one eventloop to where you want..
40//
41// The MLog stream has the advantage, that it can be used like the common
42// C++ streams (for example cout). It can redirect the stream to different
43// outputs (console, file, GUI) if necessary at the same time.
44//
45// It supports different debug levels. The debug level of the current
46// stream contents is set by SetDebugLevel, the output level of the
47// current stream can be set by SetOutputLevel.
48//
49// The header file MLogManip.h contains so called manipulators (like flush
50// or setw from iomanip.h) which can manipulate these levels from within
51// stream, for example:
52// gLog << debug(3) << "Hallo World " << endl;
53// sets the debug level of the following stream to 3
54//
55// edev(), ddev() can be used to enable/disable an output device from
56// within the stream. The enumerations are defined in MLog::_flags
57//
58// Commonly used abbreviations are also defined:
59// dbginf Prints source file name and line number. Used for output
60// which people may like to look up in the code
61// all Is streamed to the output in any case. Used for outputs
62// which are requested by the user (eg TObject::Print)
63// err Should be used for fatal errors which stops the current
64// processing, eg:
65// gLog << err << "ERROR: TObject::Copy - Stopped" << endl;
66// warn Warning means an error occured, but it is not clear whether
67// this results further procesing or not.
68// inf Informs the user about what's going on. Mostly usefull for
69// debugging, but in general not necessary at all.
70// dbg Use this for your private purpose to mark something as debug
71// output. This is _not_ ment to be persistent!
72//
73// If your console is capable of ANSI colors the stream is displayed
74// in several colors:
75// all: default
76// err: red
77// warn: yellow/brown
78// inf: green
79// dbg: blue (and all other levels)
80//
81// If you have a dark background on your console you might want to set
82// an environment variable, eg:
83// export MARSDEFINES=-DHAVE_DARKBACKGROUND
84// and recompile MLog.
85//
86// If your console can display it also 'underline' can be used. This
87// underlines a text till the next 'endl', eg:
88// gLog << underline << "This is important!" << endl;
89//
90// To switch off ANSI support call: SetNoColors()
91//
92// gLog is a global stream defined like cout or cerr
93//
94//////////////////////////////////////////////////////////////////////////////
95#include "MLog.h"
96
97#include <stdlib.h> // mkstemp
98
99#include <fstream>
100#include <iomanip>
101
102#include <TROOT.h> // gROOT->GetListOfCleanups()
103#include <TSystem.h>
104
105#ifdef _REENTRANT
106#include <TMutex.h>
107#endif
108#include <TGTextView.h>
109
110#include "MArgs.h"
111#include "MTime.h"
112#include "MParContainer.h"
113
114#include "MLogHtml.h"
115
116ClassImp(MLog);
117
118using namespace std;
119
120#undef DEBUG
121//#define DEBUG
122
123
124// root 3.02:
125// check for TObjectWarning, TObject::Info, gErrorIgnoreLevel
126
127const char MLog::kESC = '\033'; // (char)27
128const char *const MLog::kEsc = "\033[";
129const char *const MLog::kReset = "\033[0m";
130const char *const MLog::kRed = "\033[31m";
131const char *const MLog::kGreen = "\033[32m";
132#ifdef HAVE_DARKBACKGROUND
133const char *const MLog::kYellow = "\033[33m\033[1m";
134#else
135const char *const MLog::kYellow = "\033[33m";
136#endif
137const char *const MLog::kBlue = "\033[34m";
138const char *const MLog::kUnderline = "\033[4m";
139const char *const MLog::kBlink = "\033[5m";
140const char *const MLog::kBright = "\033[1m";
141const char *const MLog::kDark = "\033[2m";
142
143//
144// This is the definition of the global log facility
145//
146MLog gLog;
147
148// --------------------------------------------------------------------------
149//
150// this strange usage of an unbufferd buffer is a workaround
151// to make it work on Alpha and Linux!
152//
153void MLog::Init()
154{
155
156 //
157 // Creat drawing semaphore
158 //
159#ifdef _REENTRANT
160 fMuxGui = new TMutex;
161 fMuxStream = new TMutex;
162#endif
163
164 fPlugins = new TList;
165 gROOT->GetListOfCleanups()->Add(fPlugins);
166 fPlugins->SetBit(kMustCleanup);
167
168 setp(&fBuffer, &fBuffer+1);
169 *this << '\0';
170}
171
172// --------------------------------------------------------------------------
173//
174// default constructor which initializes the streamer and sets the device
175// which is used for the output (i)
176//
177MLog::MLog(int i) : ostream(this), fPPtr(fBase), fEPtr(fBase+fgBufferSize), fOutputLevel(0), fDebugLevel((unsigned)-1), fDevice(i), fIsNull(kFALSE), fOut(NULL), fOutAllocated(kFALSE), fGui(NULL), fNumLines(0)
178{
179 Init();
180}
181
182// --------------------------------------------------------------------------
183//
184// default constructor which initializes the streamer and sets the given
185// ofstream as the default output device
186//
187MLog::MLog(ofstream &sout) : ostream(this), fPPtr(fBase), fEPtr(fBase+fgBufferSize), fOutputLevel(0), fDebugLevel((unsigned)-1), fDevice(eFile), fIsNull(kFALSE), fOut(&sout), fOutAllocated(kFALSE), fGui(NULL), fNumLines(0)
188{
189 Init();
190}
191
192// --------------------------------------------------------------------------
193//
194// default constructor which initializes the streamer and sets the given
195// TGTextView as the default output device
196//
197MLog::MLog(TGTextView &sout) : ostream(this), fPPtr(fBase), fEPtr(fBase+fgBufferSize), fOutputLevel(0), fDebugLevel((unsigned)-1), fDevice(eGui), fOut(NULL), fOutAllocated(kFALSE), fGui(&sout), fNumLines(0)
198{
199 Init();
200}
201
202// --------------------------------------------------------------------------
203//
204// default constructor which initializes the streamer and opens a file with
205// the given name. Dependend on the flag the file is set as output device
206// or not.
207//
208MLog::MLog(const char *fname, int flag) : ostream(this), fPPtr(fBase), fEPtr(fBase+fgBufferSize), fOutputLevel(0), fDebugLevel((unsigned)-1), fDevice(eFile), fIsNull(kFALSE), fGui(NULL), fNumLines(0)
209{
210 Init();
211
212 AllocateFile(fname);
213 CheckFlag(eFile, flag);
214}
215
216// --------------------------------------------------------------------------
217//
218// Destructor, destroying the gui mutex.
219//
220MLog::~MLog()
221{
222 DeallocateFile();
223
224#ifdef DEBUG
225 TIter Next(fPlugins);
226 TObject *o=0;
227 while ((o=Next()))
228 {
229 cout << "Delete: " << o->GetName() << std::flush;
230 cout << " [" << o->ClassName() << "]" << endl;
231 delete o;
232 }
233
234 cout << "Delete: fPlugins " << fPlugins << "..." << std::flush;
235#endif
236
237 delete fPlugins;
238#ifdef DEBUG
239 cout << "done." << endl;
240#endif
241
242#ifdef _REENTRANT
243 delete fMuxStream;
244 delete fMuxGui;
245#endif
246}
247
248// --------------------------------------------------------------------------
249//
250// copyt constructor
251//
252/*
253MLog::MLog(MLog const& log)
254{
255// fOutputLevel = log.fOutputLevel;
256// fDebugLevel = log.fDebugLevel;
257// fDevice = log.fDevice;
258}
259*/
260
261void MLog::Underline()
262{
263 if (fIsNull)
264 return;
265
266 SetBit(kIsUnderlined);
267
268 fPlugins->R__FOR_EACH(MLogPlugin, Underline)();
269
270 if (TestBit(eNoColors))
271 return;
272
273 if (fDevice&eStdout)
274 cout << kUnderline;
275
276 if (fDevice&eStderr)
277 cerr << kUnderline;
278}
279
280void MLog::Output(ostream &sout, int len)
281{
282 if (!TestBit(eNoColors))
283 switch (fOutputLevel)
284 {
285 // do not output reset. Otherwise we reset underline in 0-mode
286 // case 1: out << MLog::kReset; break; // all
287 case 0: break; // all = background color
288 case 1: sout << MLog::kRed; break; // err
289 case 2: sout << MLog::kYellow; break; // warn
290 case 3: sout << MLog::kGreen; break; // inf
291 default: sout << MLog::kBlue; break; // all others (dbg)
292 }
293
294 if (len>0)
295 {
296 // Check for EOL
297 const Int_t endline = fBase[len-1]=='\n' ? 1 : 0;
298 // output text to screen (without trailing '\n')
299 sout << TString(fBase, len-endline);
300 // reset colors if working with colors
301 if (!TestBit(eNoColors))
302 sout << kReset;
303 // output EOL of check found EOL
304 if (endline)
305 {
306 sout << '\n';
307 // Check whether text was underlined
308 if (TestBit(kIsUnderlined) && TestBit(eNoColors))
309 {
310 sout << setw(len-1) << setfill('-') << "" << "\n";
311 ResetBit(kIsUnderlined);
312 }
313 }
314 }
315 sout.flush();
316}
317
318void MLog::AddGuiLine(const TString &line)
319{
320 // add a new TString* to the array of gui lines
321 TString **newstr = new TString*[fNumLines+1];
322 memcpy(newstr, fGuiLines, fNumLines*sizeof(TString*));
323 if (fNumLines>0)
324 delete fGuiLines;
325 fGuiLines = newstr;
326
327 // add Gui line as last line of array
328 fGuiLines[fNumLines++] = new TString(line);
329}
330
331// --------------------------------------------------------------------------
332//
333// This is the function which writes the stream physically to a device.
334// If you want to add a new device this must be done here.
335//
336void MLog::WriteBuffer()
337{
338 //
339 // restart writing to the buffer at its first char
340 //
341 const int len = fPPtr - fBase;
342
343 fPPtr = fBase;
344
345 if (fIsNull)
346 return;
347
348 if (fDevice&eStdout)
349 Output(cout, len);
350
351 if (fDevice&eStderr)
352 Output(cerr, len);
353
354 if (fDevice&eFile && fOut)
355 fOut->write(fBase, len);
356
357 fPlugins->R__FOR_EACH(MLogPlugin, SetColor)(fOutputLevel);
358 fPlugins->R__FOR_EACH(MLogPlugin, WriteBuffer)(fBase, len);
359
360 if (fDevice&eGui && fGui)
361 {
362 // check whether the current text was flushed or endl'ed
363 const Int_t endline = fBase[len-1]=='\n' ? 1 : 0;
364
365 // for the gui remove trailing characters ('\n' or '\0')
366 fBase[len-endline]='\0';
367
368 // add new text to line storage
369 fGuiLine += fBase;
370
371 if (endline)
372 {
373 AddGuiLine(fGuiLine);
374 fGuiLine = "";
375
376 // Check whether text should be underlined
377 if (endline && TestBit(kIsUnderlined))
378 {
379 AddGuiLine("");
380 fGuiLines[fNumLines-1]->Append('-', fGuiLines[fNumLines-2]->Length());
381 ResetBit(kIsUnderlined);
382 }
383 }
384 }
385}
386
387void MLog::UpdateGui()
388{
389 if (fNumLines==0)
390 return;
391
392 // lock mutex
393 if (!LockUpdate("UpdateGui"))
394 {
395 Warning("UpdateGui", "Execution skipped");
396 return;
397 }
398
399 TGText &txt=*fGui->GetText();
400
401 // copy lines to TGListBox
402 for (int i=0; i<fNumLines; i++)
403 {
404 // Replace all tabs by 7 white spaces
405 fGuiLines[i]->ReplaceAll("\t", " ");
406 txt.InsText(TGLongPosition(0, txt.RowCount()), *fGuiLines[i]);
407 delete fGuiLines[i];
408 }
409 delete fGuiLines;
410
411 fNumLines=0;
412
413 // cut text box top 1000 lines
414 // while (txt.RowCount()>1000)
415 // txt.DelLine(1);
416
417 // show last entry
418 fGui->Layout();
419 fGui->SetVsbPosition(txt.RowCount()-1);
420
421 // tell a main loop, that list box contents have changed
422 fGui->SetBit(kHasChanged);
423
424 // release mutex
425 UnLockUpdate("UpdateGui");
426}
427
428bool MLog::LockUpdate(const char *msg)
429{
430#ifdef _REENTRANT
431 if (fMuxGui->Lock()==13)
432 {
433 Info("LockUpdate", "%s - mutex is already locked by this thread\n", msg);
434 return false;
435 }
436#endif
437 return true;
438}
439
440bool MLog::UnLockUpdate(const char *msg)
441{
442#ifdef _REENTRANT
443 if (fMuxGui->UnLock()==13)
444 {
445 Info("UnLockUpdate", "%s - tried to unlock mutex locked by other thread\n", msg);
446 return false;
447 }
448#endif
449 return true;
450}
451
452bool MLog::Lock(const char *msg)
453{
454#ifdef _REENTRANT
455 if (fMuxStream->Lock()==13)
456 {
457 Error("Lock", "%s - mutex is already locked by this thread\n", msg);
458 return false;
459 }
460// while (fMuxStream->Lock()==13)
461// usleep(1);
462// {
463// Error("Lock", "%s - mutex is already locked by this thread\n", msg);
464// return false;
465// }
466#endif
467 return true;
468}
469
470bool MLog::UnLock(const char *msg)
471{
472#ifdef _REENTRANT
473 if (fMuxStream->UnLock()==13)
474 {
475 Error("UnLock", "%s - tried to unlock mutex locked by other thread\n", msg);
476 return false;
477 }
478#endif
479 return true;
480}
481
482// --------------------------------------------------------------------------
483//
484// This is called to flush the buffer of the streaming devices
485//
486int MLog::sync()
487{
488 if (!LockUpdate("sync"))
489 usleep(1);
490 WriteBuffer();
491 UnLockUpdate("sync");
492
493 if (fDevice&eStdout)
494 {
495 if (!fIsNull && !TestBit(eNoColors))
496 cout << kReset;
497 cout.flush();
498 }
499
500 if (fDevice&eStderr)
501 cerr.flush();
502
503 if (fDevice&eFile && fOut)
504 fOut->flush();
505
506 return 0;
507}
508
509// --------------------------------------------------------------------------
510//
511// This function comes from streambuf and should
512// output the buffer to the device (flush, endl)
513// or handle a buffer overflow (too many chars)
514// If a real overflow happens i contains the next
515// chars which doesn't fit into the buffer anymore.
516// If the buffer is not really filled i is EOF(-1).
517//
518int MLog::overflow(int i) // i=EOF means not a real overflow
519{
520 //
521 // no output if
522 //
523 if (fOutputLevel <= fDebugLevel)
524 {
525 if (!LockUpdate("overflow"))
526 usleep(1);
527
528 *fPPtr++ = (char)i;
529
530 if (fPPtr == fEPtr)
531 WriteBuffer();
532
533 UnLockUpdate("overflow");
534 }
535
536 return 0;
537}
538
539// --------------------------------------------------------------------------
540//
541// Print usage information setup in Setup()
542//
543void MLog::Usage()
544{
545 // 1 2 3 4 5 6 7 8
546 // 12345678901234567890123456789012345678901234567890123456789012345678901234567890
547 *this << " -v# Verbosity level # [default=2]" << endl;
548 *this << " -a, --no-colors Do not use Ansii color codes" << endl;
549 *this << " --log[=file] Write log-out to ascii-file [default: prgname.log]" << endl;
550 *this << " --html[=file] Write log-out to html-file [default: prgname.html]" << endl;
551 *this << " --debug[=n] Enable root debugging [default: gDebug=1]" << endl;
552 *this << " --null Null output (supresses all output)" << endl;
553}
554
555// --------------------------------------------------------------------------
556//
557// Setup MLog and global debug output from command line arguments.
558//
559void MLog::Setup(MArgs &arg)
560{
561 // FXIME: This is not really at a place where it belongs to!
562 gDebug = arg.HasOption("--debug=") ? arg.GetIntAndRemove("--debug=") : 0;
563 if (gDebug==0 && arg.HasOnlyAndRemove("--debug"))
564 gDebug=1;
565
566 TString f1 = arg.GetStringAndRemove("--log=", "");
567 if (f1.IsNull() && arg.HasOnlyAndRemove("--log"))
568 f1 = Form("%s.log", arg.GetName());
569 if (!f1.IsNull())
570 {
571 SetOutputFile(f1);
572 EnableOutputDevice(eFile);
573 }
574
575 TString f2 = arg.GetStringAndRemove("--html=", "");
576 if (f2.IsNull() && arg.HasOnlyAndRemove("--html"))
577 f2 = Form("%s.html", arg.GetName());
578 if (!f2.IsNull())
579 {
580 MLogHtml *html = new MLogHtml(f2);
581 html->SetBit(kCanDelete);
582 AddPlugin(html);
583 }
584
585 const Bool_t null = arg.HasOnlyAndRemove("--null");
586 if (null)
587 SetNullOutput();
588
589 if (arg.HasOnlyAndRemove("--no-colors") || arg.HasOnlyAndRemove("-a"))
590 SetNoColors();
591
592 SetDebugLevel(arg.GetIntAndRemove("-v", 2));
593}
594
595// --------------------------------------------------------------------------
596//
597// Read the setup from a TEnv:
598// MLog.VerbosityLevel: 0, 1, 2, 3, 4
599// MLog.DebugLevel: 0, 1, 2, 3, 4
600// MLog.NoColors
601//
602// Depending on your setup it might be correct to use something like:
603// Job1.MLog.VerbosityLevel: 1
604// Job1.DebugLevel: 2
605// Job1.MLog.NoColors
606//
607void MLog::ReadEnv(const TEnv &env, TString prefix, Bool_t print)
608{
609 MParContainer mlog("MLog");
610
611 if (mlog.IsEnvDefined(env, prefix+"MLog", "VerbosityLevel", print))
612 SetDebugLevel(mlog.GetEnvValue(env, prefix+"MLog", "VerbosityLevel", 2));
613 else
614 if (mlog.IsEnvDefined(env, "MLog", "VerbosityLevel", print))
615 SetDebugLevel(mlog.GetEnvValue(env, "MLog", "VerbosityLevel", 2));
616
617 if (mlog.IsEnvDefined(env, prefix+"MLog", "DebugLevel", print))
618 gDebug = mlog.GetEnvValue(env, prefix+"MLog", "DebugLevel", 0);
619 else
620 if (mlog.IsEnvDefined(env, "MLog", "DebugLevel", print))
621 gDebug = mlog.GetEnvValue(env, "MLog", "DebugLevel", 0);
622
623 if (mlog.IsEnvDefined(env, prefix+"MLog", "NoColors", print))
624 SetNoColors(mlog.GetEnvValue(env, prefix+"MLog", "NoColors", kFALSE));
625 else
626 if (mlog.IsEnvDefined(env, "MLog", "NoColors", print))
627 SetNoColors(mlog.GetEnvValue(env, "MLog", "NoColors", kFALSE));
628}
629
630// --------------------------------------------------------------------------
631//
632// Read the setup from a TEnv:
633// MLog.VerbosityLevel: 0, 1, 2, 3, 4
634// MLog.DebugLevel: 0, 1, 2, 3, 4
635// MLog.NoColors
636//
637// Depending on your setup it might be correct to use something like:
638// Job1.MLog.VerbosityLevel: 1
639// Job1.DebugLevel: 2
640// Job1.MLog.NoColors
641//
642void MLog::WriteEnv(TEnv &, TString prefix, Bool_t) const
643{
644 if (!prefix.IsNull())
645 prefix += ".";
646 prefix += "MLog";
647
648 cout << "MLog::WriteEnv: not yet implemented!" << endl;
649}
650
651// --------------------------------------------------------------------------
652//
653// Create a new instance of an file output stream
654// an set the corresponding flag
655//
656void MLog::AllocateFile(const char *fname)
657{
658 // gcc 3.2:
659 char *txt = (char*)"logXXXXXX";
660
661 TString n(fname ? fname : txt);
662 gSystem->ExpandPathName(n);
663
664 fOut = new ofstream(n.Data());
665
666 // switch off buffering
667 fOut->rdbuf()->pubsetbuf(0,0);
668
669 fOutAllocated = kTRUE;
670}
671
672// --------------------------------------------------------------------------
673//
674// if fout was allocated by this instance of MLooging
675// delete it.
676//
677void MLog::DeallocateFile()
678{
679 if (fOutAllocated)
680 delete fOut;
681}
682
683// --------------------------------------------------------------------------
684//
685// if necessary delete the old in stance of the file
686// output stream and create a new one
687//
688void MLog::ReallocateFile(const char *fname)
689{
690 DeallocateFile();
691 AllocateFile(fname);
692}
693
694// --------------------------------------------------------------------------
695//
696// This function checks if a device should get enabled or disabled.
697//
698void MLog::CheckFlag(Flags_t chk, int flag)
699{
700 if (flag==-1)
701 return;
702
703 flag ? EnableOutputDevice(chk) : DisableOutputDevice(chk);
704}
705
706// --------------------------------------------------------------------------
707//
708// Add a plugin to which the output should be redirected, eg. MLogHtml
709// The user has to take care of its deletion. If the plugin is deleted
710// (and the kMustCleanup bit was not reset accidentaly) the plugin
711// is automatically removed from the list of active plugins.
712//
713// If MLog should take the ownership call plug->SetBit(kCanDelete);
714//
715void MLog::AddPlugin(MLogPlugin *plug)
716{
717 fPlugins->Add(plug);
718
719 // Make sure that it is recursively deleted from all objects in ListOfCleanups
720 plug->SetBit(kMustCleanup);
721}
722
723// --------------------------------------------------------------------------
724//
725// Returns "yyyy-mm-dd user@host gROOT->GetName()[pid]"
726//
727TString MLog::Intro()
728{
729 UserGroup_t *user = gSystem->GetUserInfo();
730
731 TString rc;
732 rc += MTime(-1).GetSqlDateTime();
733 rc += " ";
734 rc += user->fUser;
735 rc += "@";
736 rc += gSystem->HostName();
737 rc += " ";
738 rc += gROOT->GetName();
739 rc += "[";
740 rc += gSystem->GetPid();
741 rc += "] ";
742
743 delete user;
744
745 return rc;
746}
Note: See TracBrowser for help on using the repository browser.