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

Last change on this file since 4983 was 4732, checked in by tbretz, 20 years ago
*** empty log message ***
File size: 19.6 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-2004
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
104#ifdef _REENTRANT
105#include <TMutex.h>
106#endif
107#include <TGTextView.h>
108
109#include "MArgs.h"
110#include "MParContainer.h"
111
112#include "MLogHtml.h"
113
114ClassImp(MLog);
115
116using namespace std;
117
118// root 3.02:
119// check for TObjectWarning, TObject::Info, gErrorIgnoreLevel
120
121const char MLog::kESC = '\033'; // (char)27
122const char *const MLog::kEsc = "\033[";
123const char *const MLog::kReset = "\033[0m";
124const char *const MLog::kRed = "\033[31m";
125const char *const MLog::kGreen = "\033[32m";
126#ifdef HAVE_DARKBACKGROUND
127const char *const MLog::kYellow = "\033[33m\033[1m";
128#else
129const char *const MLog::kYellow = "\033[33m";
130#endif
131const char *const MLog::kBlue = "\033[34m";
132const char *const MLog::kUnderline = "\033[4m";;
133const char *const MLog::kBlink = "\033[5m";;
134const char *const MLog::kBright = "\033[1m";;
135const char *const MLog::kDark = "\033[2m";;
136
137//
138// This is the definition of the global log facility
139//
140MLog gLog;
141
142// --------------------------------------------------------------------------
143//
144// this strange usage of an unbufferd buffer is a workaround
145// to make it work on Alpha and Linux!
146//
147void MLog::Init()
148{
149
150 //
151 // Creat drawing semaphore
152 //
153#ifdef _REENTRANT
154 fMuxGui = new TMutex;
155 fMuxStream = new TMutex;
156#endif
157
158 fPlugins = new TList;
159 gROOT->GetListOfCleanups()->Add(fPlugins);
160 fPlugins->SetBit(kMustCleanup);
161
162 setp(&fBuffer, &fBuffer+1);
163 *this << '\0';
164}
165
166// --------------------------------------------------------------------------
167//
168// default constructor which initializes the streamer and sets the device
169// which is used for the output (i)
170//
171MLog::MLog(int i) : ostream(this), fPPtr(fBase), fEPtr(fBase+bsz), fOutputLevel(0), fDebugLevel((unsigned)-1), fDevice(i), fIsNull(kFALSE), fOut(NULL), fOutAllocated(kFALSE), fGui(NULL), fNumLines(0)
172{
173 Init();
174}
175
176// --------------------------------------------------------------------------
177//
178// default constructor which initializes the streamer and sets the given
179// ofstream as the default output device
180//
181MLog::MLog(ofstream &out) : ostream(this), fPPtr(fBase), fEPtr(fBase+bsz), fOutputLevel(0), fDebugLevel((unsigned)-1), fDevice(eFile), fIsNull(kFALSE), fOut(&out), fOutAllocated(kFALSE), fGui(NULL), fNumLines(0)
182{
183 Init();
184}
185
186// --------------------------------------------------------------------------
187//
188// default constructor which initializes the streamer and sets the given
189// TGTextView as the default output device
190//
191MLog::MLog(TGTextView &out) : ostream(this), fPPtr(fBase), fEPtr(fBase+bsz), fOutputLevel(0), fDebugLevel((unsigned)-1), fDevice(eGui), fOut(NULL), fOutAllocated(kFALSE), fGui(&out), fNumLines(0)
192{
193 Init();
194}
195
196// --------------------------------------------------------------------------
197//
198// default constructor which initializes the streamer and opens a file with
199// the given name. Dependend on the flag the file is set as output device
200// or not.
201//
202MLog::MLog(const char *fname, int flag) : ostream(this), fPPtr(fBase), fEPtr(fBase+bsz), fOutputLevel(0), fDebugLevel((unsigned)-1), fDevice(eFile), fIsNull(kFALSE), fGui(NULL), fNumLines(0)
203{
204 Init();
205
206 AllocateFile(fname);
207 CheckFlag(eFile, flag);
208}
209
210// --------------------------------------------------------------------------
211//
212// Destructor, destroying the gui mutex.
213//
214MLog::~MLog()
215{
216 DeallocateFile();
217
218 delete fPlugins;
219#ifdef _REENTRANT
220 delete fMuxStream;
221 delete fMuxGui;
222#endif
223}
224
225// --------------------------------------------------------------------------
226//
227// copyt constructor
228//
229/*
230MLog::MLog(MLog const& log)
231{
232// fOutputLevel = log.fOutputLevel;
233// fDebugLevel = log.fDebugLevel;
234// fDevice = log.fDevice;
235}
236*/
237
238void MLog::Underline()
239{
240 SetBit(kIsUnderlined);
241
242 fPlugins->ForEach(MLogPlugin, Underline)();
243
244 if (TestBit(eNoColors))
245 return;
246
247 if (fDevice&eStdout)
248 cout << kUnderline;
249
250 if (fDevice&eStderr)
251 cerr << kUnderline;
252}
253
254void MLog::Output(ostream &out, int len)
255{
256 if (!TestBit(eNoColors))
257 switch (fOutputLevel)
258 {
259 // do not output reset. Otherwise we reset underline in 0-mode
260 // case 1: out << MLog::kReset; break; // all
261 case 0: break; // all = background color
262 case 1: out << MLog::kRed; break; // err
263 case 2: out << MLog::kYellow; break; // warn
264 case 3: out << MLog::kGreen; break; // inf
265 default: out << MLog::kBlue; break; // all others (dbg)
266 }
267
268 if (len>0)
269 {
270 // Check for EOL
271 const Int_t endline = fBase[len-1]=='\n' ? 1 : 0;
272 // output text to screen (without trailing '\n')
273 out << TString(fBase, len-endline);
274 // reset colors if working with colors
275 if (!TestBit(eNoColors))
276 out << kReset;
277 // output EOL of check found EOL
278 if (endline)
279 {
280 out << '\n';
281 // Check whether text was underlined
282 if (TestBit(kIsUnderlined) && TestBit(eNoColors))
283 {
284 out << setw(len-1) << setfill('-') << "" << "\n";
285 ResetBit(kIsUnderlined);
286 }
287 }
288 }
289 out.flush();
290}
291
292void MLog::AddGuiLine(const TString &line)
293{
294 // add a new TString* to the array of gui lines
295 TString **newstr = new TString*[fNumLines+1];
296 memcpy(newstr, fGuiLines, fNumLines*sizeof(TString*));
297 if (fNumLines>0)
298 delete fGuiLines;
299 fGuiLines = newstr;
300
301 // add Gui line as last line of array
302 fGuiLines[fNumLines++] = new TString(line);
303}
304
305// --------------------------------------------------------------------------
306//
307// This is the function which writes the stream physically to a device.
308// If you want to add a new device this must be done here.
309//
310void MLog::WriteBuffer()
311{
312 //
313 // restart writing to the buffer at its first char
314 //
315 const int len = fPPtr - fBase;
316
317 fPPtr = fBase;
318
319 if (fIsNull)
320 return;
321
322 if (fDevice&eStdout)
323 Output(cout, len);
324
325 if (fDevice&eStderr)
326 Output(cerr, len);
327
328 if (fDevice&eFile && fOut)
329 fOut->write(fBase, len);
330
331 fPlugins->ForEach(MLogPlugin, SetColor)(fOutputLevel);
332 fPlugins->ForEach(MLogPlugin, WriteBuffer)(fBase, len);
333
334 if (fDevice&eGui && fGui)
335 {
336 // check whether the current text was flushed or endl'ed
337 const Int_t endline = fBase[len-1]=='\n' ? 1 : 0;
338
339 // for the gui remove trailing characters ('\n' or '\0')
340 fBase[len-endline]='\0';
341
342 // add new text to line storage
343 fGuiLine += fBase;
344
345 if (endline)
346 {
347 AddGuiLine(fGuiLine);
348 fGuiLine = "";
349
350 // Check whether text should be underlined
351 if (endline && TestBit(kIsUnderlined))
352 {
353 AddGuiLine("");
354 fGuiLines[fNumLines-1]->Append('-', fGuiLines[fNumLines-2]->Length());
355 ResetBit(kIsUnderlined);
356 }
357 }
358 }
359}
360
361void MLog::UpdateGui()
362{
363 if (fNumLines==0)
364 return;
365
366 // lock mutex
367 if (!LockUpdate("UpdateGui"))
368 {
369 Warning("UpdateGui", "Execution skipped");
370 return;
371 }
372
373 TGText &txt=*fGui->GetText();
374
375 // copy lines to TGListBox
376 for (int i=0; i<fNumLines; i++)
377 {
378 // Replace all tabs by 7 white spaces
379 fGuiLines[i]->ReplaceAll("\t", " ");
380 txt.InsText(TGLongPosition(0, txt.RowCount()), *fGuiLines[i]);
381 delete fGuiLines[i];
382 }
383 delete fGuiLines;
384
385 fNumLines=0;
386
387 // cut text box top 1000 lines
388 // while (txt.RowCount()>1000)
389 // txt.DelLine(1);
390
391 // show last entry
392 fGui->Layout();
393 fGui->SetVsbPosition(txt.RowCount()-1);
394
395 // tell a main loop, that list box contents have changed
396 fGui->SetBit(kHasChanged);
397
398 // release mutex
399 UnLockUpdate("UpdateGui");
400}
401
402bool MLog::LockUpdate(const char *msg)
403{
404#ifdef _REENTRANT
405 if (fMuxGui->Lock()==13)
406 {
407 Info("LockUpdate", "%s - mutex is already locked by this thread\n", msg);
408 return false;
409 }
410 return true;
411#endif
412}
413
414bool MLog::UnLockUpdate(const char *msg)
415{
416#ifdef _REENTRANT
417 if (fMuxGui->UnLock()==13)
418 {
419 Info("UnLockUpdate", "%s - tried to unlock mutex locked by other thread\n", msg);
420 return false;
421 }
422 return true;
423#endif
424}
425
426bool MLog::Lock(const char *msg)
427{
428#ifdef _REENTRANT
429 if (fMuxStream->Lock()==13)
430 {
431 Error("Lock", "%s - mutex is already locked by this thread\n", msg);
432 return false;
433 }
434// while (fMuxStream->Lock()==13)
435// usleep(1);
436// {
437// Error("Lock", "%s - mutex is already locked by this thread\n", msg);
438// return false;
439// }
440 return true;
441#endif
442}
443
444bool MLog::UnLock(const char *msg)
445{
446#ifdef _REENTRANT
447 if (fMuxStream->UnLock()==13)
448 {
449 Error("UnLock", "%s - tried to unlock mutex locked by other thread\n", msg);
450 return false;
451 }
452 return true;
453#endif
454}
455
456// --------------------------------------------------------------------------
457//
458// This is called to flush the buffer of the streaming devices
459//
460int MLog::sync()
461{
462 if (!LockUpdate("sync"))
463 usleep(1);
464 WriteBuffer();
465 UnLockUpdate("sync");
466
467 if (fDevice&eStdout)
468 {
469 if (!TestBit(eNoColors))
470 cout << kReset;
471 cout.flush();
472 }
473
474 if (fDevice&eStderr)
475 cerr.flush();
476
477 if (fDevice&eFile && fOut)
478 fOut->flush();
479
480 return 0;
481}
482
483// --------------------------------------------------------------------------
484//
485// This function comes from streambuf and should
486// output the buffer to the device (flush, endl)
487// or handle a buffer overflow (too many chars)
488// If a real overflow happens i contains the next
489// chars which doesn't fit into the buffer anymore.
490// If the buffer is not really filled i is EOF(-1).
491//
492int MLog::overflow(int i) // i=EOF means not a real overflow
493{
494 //
495 // no output if
496 //
497 if (fOutputLevel <= fDebugLevel)
498 {
499 if (!LockUpdate("overflow"))
500 usleep(1);
501
502 *fPPtr++ = (char)i;
503
504 if (fPPtr == fEPtr)
505 WriteBuffer();
506
507 UnLockUpdate("overflow");
508 }
509
510 return 0;
511}
512
513// --------------------------------------------------------------------------
514//
515// Print usage information setup in Setup()
516//
517void MLog::Usage()
518{
519 // 1 2 3 4 5 6 7 8
520 // 12345678901234567890123456789012345678901234567890123456789012345678901234567890
521 *this << " -v# Verbosity level # [default=2]" << endl;
522 *this << " -a, --no-colors Do not use Ansii color codes" << endl;
523 *this << " --log[=file] Write log-out to ascii-file [default: prgname.log]" << endl;
524 *this << " --html[=file] Write log-out to html-file [default: prgname.html]" << endl;
525 *this << " --debug[=n] Enable root debugging [default: gDebug=1]" << endl;
526 *this << " --null Null output (supresses all output)" << endl;
527}
528
529// --------------------------------------------------------------------------
530//
531// Setup MLog and global debug output from command line arguments.
532//
533void MLog::Setup(MArgs &arg)
534{
535 // FXIME: This is not really at a place where it belongs to!
536 gDebug = arg.HasOption("--debug=") ? arg.GetIntAndRemove("--debug=") : 0;
537 if (gDebug==0 && arg.HasOnlyAndRemove("--debug"))
538 gDebug=1;
539
540 TString f1 = arg.GetStringAndRemove("--log=", "");
541 if (f1.IsNull() && arg.HasOnlyAndRemove("--log"))
542 f1 = Form("%s.log", arg.GetName());
543 if (!f1.IsNull())
544 {
545 SetOutputFile(f1);
546 EnableOutputDevice(eFile);
547 }
548
549 TString f2 = arg.GetStringAndRemove("--html=", "");
550 if (f2.IsNull() && arg.HasOnlyAndRemove("--html"))
551 f2 = Form("%s.html", arg.GetName());
552 if (!f2.IsNull())
553 {
554 MLogHtml *html = new MLogHtml(f2);
555 html->SetBit(kCanDelete);
556 AddPlugin(html);
557 }
558
559 const Bool_t null = arg.HasOnlyAndRemove("--null");
560 if (null)
561 SetNullOutput();
562
563 if (arg.HasOnlyAndRemove("--no-colors") || arg.HasOnlyAndRemove("-a"))
564 SetNoColors();
565
566 SetDebugLevel(arg.GetIntAndRemove("-v", 2));
567}
568
569// --------------------------------------------------------------------------
570//
571// Read the setup from a TEnv:
572// MLog.VerbosityLevel: 0, 1, 2, 3, 4
573// MLog.DebugLevel: 0, 1, 2, 3, 4
574// MLog.NoColors
575//
576// Depending on your setup it might be correct to use something like:
577// Job1.MLog.VerbosityLevel: 1
578// Job1.DebugLevel: 2
579// Job1.MLog.NoColors
580//
581void MLog::ReadEnv(const TEnv &env, TString prefix, Bool_t print)
582{
583 MParContainer mlog("MLog");
584
585 if (mlog.IsEnvDefined(env, prefix+"MLog", "VerbosityLevel", print))
586 SetDebugLevel(mlog.GetEnvValue(env, prefix+"MLog", "VerbosityLevel", 2));
587 else
588 if (mlog.IsEnvDefined(env, "MLog", "VerbosityLevel", print))
589 SetDebugLevel(mlog.GetEnvValue(env, "MLog", "VerbosityLevel", 2));
590
591 if (mlog.IsEnvDefined(env, prefix+"MLog", "DebugLevel", print))
592 gDebug = mlog.GetEnvValue(env, prefix+"MLog", "DebugLevel", 0);
593 else
594 if (mlog.IsEnvDefined(env, "MLog", "DebugLevel", print))
595 gDebug = mlog.GetEnvValue(env, "MLog", "DebugLevel", 0);
596
597 if (mlog.IsEnvDefined(env, prefix+"MLog", "NoColors", print))
598 SetNoColors(mlog.GetEnvValue(env, prefix+"MLog", "NoColors", kFALSE));
599 else
600 if (mlog.IsEnvDefined(env, "MLog", "NoColors", print))
601 SetNoColors(mlog.GetEnvValue(env, "MLog", "NoColors", kFALSE));
602}
603
604// --------------------------------------------------------------------------
605//
606// Read the setup from a TEnv:
607// MLog.VerbosityLevel: 0, 1, 2, 3, 4
608// MLog.DebugLevel: 0, 1, 2, 3, 4
609// MLog.NoColors
610//
611// Depending on your setup it might be correct to use something like:
612// Job1.MLog.VerbosityLevel: 1
613// Job1.DebugLevel: 2
614// Job1.MLog.NoColors
615//
616void MLog::WriteEnv(TEnv &env, TString prefix, Bool_t print) const
617{
618 if (!prefix.IsNull())
619 prefix += ".";
620 prefix += "MLog";
621
622 cout << "MLog::WriteEnv: not yet implemented!" << endl;
623}
624
625// --------------------------------------------------------------------------
626//
627// Create a new instance of an file output stream
628// an set the corresponding flag
629//
630void MLog::AllocateFile(const char *fname)
631{
632 // gcc 3.2:
633 char *txt = (char*)"logXXXXXX";
634 fOut = fname ? new ofstream(fname) : new ofstream(/*mkstemp(*/txt/*)*/);
635 fOutAllocated = kTRUE;
636}
637
638// --------------------------------------------------------------------------
639//
640// if fout was allocated by this instance of MLooging
641// delete it.
642//
643void MLog::DeallocateFile()
644{
645 if (fOutAllocated)
646 delete fOut;
647}
648
649// --------------------------------------------------------------------------
650//
651// if necessary delete the old in stance of the file
652// output stream and create a new one
653//
654void MLog::ReallocateFile(const char *fname)
655{
656 DeallocateFile();
657 AllocateFile(fname);
658}
659
660// --------------------------------------------------------------------------
661//
662// This function checks if a device should get enabled or disabled.
663//
664void MLog::CheckFlag(Flags_t chk, int flag)
665{
666 if (flag==-1)
667 return;
668
669 flag ? EnableOutputDevice(chk) : DisableOutputDevice(chk);
670}
671
672// --------------------------------------------------------------------------
673//
674// Add a plugin to which the output should be redirected, eg. MLogHtml
675// The user has to take care of its deletion. If the plugin is deleted
676// (and the kMustCleanup bit was not reset accidentaly) the plugin
677// is automatically removed from the list of active plugins.
678//
679// If MLog should take the ownership cal plug->SetBit(kCanDelete);
680//
681void MLog::AddPlugin(MLogPlugin *plug)
682{
683 fPlugins->Add(plug);
684}
Note: See TracBrowser for help on using the repository browser.