contrib/mul/mbl/mbl_log.cxx
Go to the documentation of this file.
00001 //:
00002 // \file
00003 // \author Ian Scott
00004 // \date 16-Jan-2005
00005 // \brief Flexible Status and Error Logging.
00006 // These classes are patterned after the log4j logging library,
00007 // although without all of the sophistication.
00008 
00009 // n.b. We have not copied any code from log4j (or other logging libraries)
00010 // - just the ideas.
00011 
00012 #include "mbl_log.h"
00013 #include <vcl_cstddef.h>
00014 #include <vcl_fstream.h>
00015 #include <vcl_memory.h>
00016 #include <vcl_algorithm.h>
00017 #include <vcl_cassert.h>
00018 #include <vcl_iostream.h>
00019 #include <vcl_sstream.h>
00020 #include <vcl_utility.h>
00021 #include <vul/vul_string.h>
00022 #include <mbl/mbl_read_props.h>
00023 #include <mbl/mbl_exception.h>
00024 
00025 vcl_ostream& operator<<(vcl_ostream&os, mbl_logger::levels level)
00026 {
00027   switch (level)
00028   {
00029    case mbl_logger::NONE:
00030     os << "NONE";
00031     break;
00032    case mbl_logger::EMERG:
00033     os << "EMERG";
00034     break;
00035    case mbl_logger::ALERT:
00036     os << "ALERT";
00037     break;
00038    case mbl_logger::CRIT:
00039     os << "CRIT";
00040     break;
00041    case mbl_logger::ERR:
00042     os << "ERR";
00043     break;
00044    case mbl_logger::WARN:
00045     os << "WARN";
00046     break;
00047    case mbl_logger::NOTICE:
00048     os << "NOTICE";
00049     break;
00050    case mbl_logger::INFO:
00051     os << "INFO";
00052     break;
00053    case mbl_logger::DEBUG:
00054     os << "DEBUG";
00055     break;
00056    case mbl_logger::ALL:
00057     os << "ALL";
00058     break;
00059    default:
00060     os << "LOG" << level;
00061     break;
00062   }
00063   return os;
00064 }
00065 
00066 
00067 //Notes - use two different stream bufs to hendle the mt_log() and the log().
00068 // one should response to flushes with a terminate - the other not.
00069 
00070 
00071 // Got the a streambuf example from vul_redirector and hacked around with it.
00072 // It passes on all stuff direct to the real_streambuf, whilst calling
00073 // extra functions on the output object to print message headers and footers.
00074 
00075 
00076 int mbl_log_streambuf::sync()
00077 {
00078 #ifndef MBL_LOG_DISABLE_ALL_LOGGING
00079 
00080   int n = static_cast<int>(pptr() - pbase()); // has to be int because pbump only takes int
00081 
00082   if (n)
00083     logger_->output_->append(pbase(), n);
00084   logger_->output_->terminate_flush();
00085 
00086   pbump(-n);  // Reset pptr().
00087 #endif
00088   return 0;
00089 }
00090 
00091 int mbl_log_streambuf::overflow(int ch)
00092 {
00093 #ifndef MBL_LOG_DISABLE_ALL_LOGGING
00094   int n = static_cast<int>(pptr() - pbase()); // has to be int because pbump only takes int
00095 
00096   if (n)
00097     logger_->output_->append(pbase(), n);
00098   pbump(-n);  // Reset pptr().
00099 
00100   if (ch == EOF)
00101     return 0;
00102 
00103   char cbuf = (char)ch;
00104   logger_->output_->append(&cbuf, 1);
00105   return ch;
00106 #else
00107   return EOF;
00108 #endif
00109 }
00110 
00111 vcl_streamsize mbl_log_streambuf::xsputn( const char *ptr, vcl_streamsize nchar)
00112 {
00113 #ifndef MBL_LOG_DISABLE_ALL_LOGGING
00114 
00115   // Output anything already in buffer
00116   int n = static_cast<int>(pptr() - pbase()); // has to be int because pbump only takes int
00117   if (n)
00118     logger_->output_->append(pbase(), n);
00119   pbump(-n);  // Reset pptr().
00120 
00121   logger_->output_->append(ptr, nchar);
00122   return nchar;
00123 #else
00124   return 0;
00125 #endif
00126 }
00127 
00128 #ifndef MBL_LOG_DISABLE_ALL_LOGGING
00129 
00130 //: Default constructor only available to root's default logger.
00131 mbl_logger::mbl_logger():
00132   level_(NOTICE),
00133   output_(new mbl_log_output_stream(vcl_cerr, "")),
00134   streambuf_(this),
00135   logstream_(&streambuf_),
00136   mt_logstream_(&logstream_)
00137 {
00138   // This will have to change to support proper hierarchical control over categories.
00139 //  logstream_.tie(output_.real_stream_);
00140   // Don't insert default root logger - this would cause infinite recursion.
00141   root().all_loggers_.insert(this);
00142 }
00143 
00144 mbl_logger::~mbl_logger()
00145 {
00146   root().all_loggers_.erase(this);
00147   delete output_;
00148 }
00149 
00150 mbl_log_output_stream::mbl_log_output_stream(vcl_ostream& real_stream, const char *id):
00151   real_stream_(&real_stream), id_(id), has_started_(false)
00152 {}
00153 
00154 //: Start a new log entry, with id info.
00155 void mbl_log_output_stream::start()
00156 {
00157   // Deal with unfinished log message
00158   if (has_started_)
00159     (*real_stream_) << "LOG_MESSAGE_TERMINATED_EARLY\n";
00160 
00161   // Avoid interspersed output.
00162   real_stream_->flush();
00163 
00164   has_started_=true;
00165 }
00166 
00167 //: Start a new log entry, with id info.
00168 // Future calls to terminate_flush will be ignored.
00169 void mbl_log_output_stream::start_with_manual_termination(int level, const char *srcfile, int srcline)
00170 {
00171   ignore_flush_=true;
00172   start();
00173   (*real_stream_) << static_cast<mbl_logger::levels>(level) << ": " << id_ << ' ';
00174 }
00175 
00176 //: Start a new log entry, with id info.
00177 // Future calls to terminate_flush will be honoured.
00178 void mbl_log_output_stream::start_with_flush_termination(int level, const char *srcfile, int srcline)
00179 {
00180   ignore_flush_=false;
00181   start();
00182   (*real_stream_) << static_cast<mbl_logger::levels>(level) << ": " << id_ << ' ';
00183 }
00184 //: add contents to the existing log entry.
00185 void mbl_log_output_stream::append(const char * contents, vcl_streamsize n_chars)
00186 {
00187   // Deal with unfinished log message
00188   if (!has_started_)
00189   {
00190     (*real_stream_) << "UNKNOWN_START_LOG: " << id_ << ' ';
00191     has_started_ = true;
00192     ignore_flush_ = true;
00193   }
00194 
00195   real_stream_->rdbuf()->sputn(contents, n_chars);
00196 }
00197 
00198 //: Finish the log entry, sent from a stream flush.
00199 void mbl_log_output_stream::terminate_manual()
00200 {
00201   real_stream_->flush();
00202   has_started_=false;
00203 }
00204 
00205 //: Finish the log entry, sent from explicit function call, e.g. by MBL_LOG.
00206 void mbl_log_output_stream::terminate_flush()
00207 {
00208   if (!ignore_flush_)
00209   {
00210     real_stream_->flush();
00211     has_started_=false;
00212   }
00213 }
00214 
00215 mbl_log_output_file::mbl_log_output_file(const vcl_string &filename, const char *id):
00216   file_(filename.c_str(), vcl_ios_app), id_(id), has_started_(false)
00217 {}
00218 
00219 //: Start a new log entry, with id info.
00220 void mbl_log_output_file::start()
00221 {
00222   // Deal with unfinished log message
00223   if (has_started_)
00224     file_ << "LOG_MESSAGE_TERMINATED_EARLY\n";
00225 
00226   // Avoid interspersed output.
00227   file_.flush();
00228 
00229   has_started_=true;
00230 }
00231 
00232 //: Start a new log entry, with id info.
00233 // Future calls to terminate_flush will be ignored.
00234 void mbl_log_output_file::start_with_manual_termination(int level, const char *srcfile, int srcline)
00235 {
00236   ignore_flush_=true;
00237   start();
00238   file_ << static_cast<mbl_logger::levels>(level) << ": " << id_ << ' ';
00239 }
00240 
00241 //: Start a new log entry, with id info.
00242 // Future calls to terminate_flush will be honoured.
00243 void mbl_log_output_file::start_with_flush_termination(int level, const char *srcfile, int srcline)
00244 {
00245   ignore_flush_=false;
00246   start();
00247   file_ << static_cast<mbl_logger::levels>(level) << ": " << id_ << ' ';
00248 }
00249 
00250 //: add contents to the existing log entry.
00251 void mbl_log_output_file::append(const char * contents, vcl_streamsize n_chars)
00252 {
00253   // Deal with unstarted log message
00254   if (!has_started_)
00255   {
00256     file_ << "UNKNOWN_START_LOG: " << id_ << ' ';
00257     has_started_=true;
00258     ignore_flush_=false;
00259   }
00260 
00261   file_.rdbuf()->sputn(contents, n_chars);
00262 }
00263 
00264 //: Finish the log entry, sent from a stream flush.
00265 void mbl_log_output_file::terminate_manual()
00266 {
00267   file_.flush();
00268   has_started_=false;
00269 }
00270 
00271 //: Finish the log entry, sent from explicit function call, e.g. by MBL_LOG.
00272 void mbl_log_output_file::terminate_flush()
00273 {
00274   if (!ignore_flush_)
00275   {
00276     file_.flush();
00277     has_started_=false;
00278   }
00279 }
00280 
00281 
00282 mbl_logger::mbl_logger(const char *id):
00283   output_(0),
00284   streambuf_(this),
00285   logstream_(&streambuf_),
00286   mt_logstream_(&logstream_)
00287 {
00288   const mbl_log_categories::cat_spec &cat =
00289     mbl_logger::root().categories().get(id);
00290 
00291   level_ = cat.level;
00292 
00293   if (cat.output == mbl_log_categories::cat_spec::NAMED_STREAM)
00294   {
00295     output_ = new mbl_log_output_stream(*cat.stream, id);
00296 //    logstream_.tie(output_.real_stream_);
00297   }
00298   else if (cat.output == mbl_log_categories::cat_spec::FILE_OUT)
00299   {
00300     output_ = new mbl_log_output_file(cat.name, id);
00301 //    logstream_.tie(output_.real_stream_);
00302   }
00303 
00304   root().all_loggers_.insert(this);
00305 }
00306 
00307 void mbl_logger::reinitialise()
00308 {
00309   const char *id = output_->id();
00310   const mbl_log_categories::cat_spec &cat =
00311     mbl_logger::root().categories().get(id);
00312 
00313   level_ = cat.level;
00314 
00315   if (cat.output == mbl_log_categories::cat_spec::NAMED_STREAM)
00316   {
00317     delete output_;
00318     output_ = new mbl_log_output_stream(vcl_cout, id);
00319 //    logstream_.tie(output_.real_stream_);
00320   }
00321   else if (cat.output == mbl_log_categories::cat_spec::FILE_OUT)
00322   {
00323     delete output_;
00324     output_ = new mbl_log_output_file(cat.name, id);
00325 //    logstream_.tie(output_.real_stream_);
00326   }
00327 }
00328 
00329 void mbl_logger::set(int level, mbl_log_output_base* output)
00330 {
00331   level_ = level;
00332   delete output_;
00333   output_ = output;
00334 //  logstream_.tie(output_.real_stream_);
00335 }
00336 
00337 vcl_ostream &mbl_logger::log(int level, const char * srcfile, int srcline)
00338 {
00339   if (level_ < level)
00340     return root().null_stream_;
00341   output_->start_with_flush_termination(level, srcfile, srcline);
00342   return logstream_;
00343 }
00344 
00345 void mbl_logger::mtstart(int level, const char * srcfile, int srcline)
00346 {
00347   if (level_ < level)
00348   {
00349     mt_logstream_ = &root().null_stream_;
00350     return;
00351   }
00352   mt_logstream_ = &logstream_;
00353   output_->start_with_manual_termination(level, srcfile, srcline);
00354 }
00355 
00356 void mbl_logger::mtstop()
00357 {
00358   logstream_.flush();
00359   output_->terminate_manual();
00360 }
00361 #endif
00362 
00363 
00364 mbl_logger_root &mbl_logger::root()
00365 {
00366   static vcl_auto_ptr<mbl_logger_root> root_;
00367 
00368   if (!root_.get())
00369     root_ = vcl_auto_ptr<mbl_logger_root>(new mbl_logger_root());
00370   return *root_;
00371 }
00372 
00373 
00374 //:Load a default configuration file
00375 // Current Format is
00376 // \verbatim
00377 //LEVEL
00378 // \endverbatim
00379 // where LEVEL is an integer - setting the logging level.
00380 // see mbl_logger:levels for useful values.
00381 void mbl_logger_root::load_log_config_file(
00382   const vcl_map<vcl_string, vcl_ostream *> &stream_names)
00383 {
00384 #ifndef MBL_LOG_DISABLE_ALL_LOGGING
00385   // Make sure this list of mbl_log.properties locations code
00386   // stays in sync with mul/contrib/tools/print_mbl_log_properties.cxx
00387   vcl_ifstream config_file("mbl_log.properties");
00388   if (!config_file.is_open())
00389     config_file.open("~/mbl_log.properties");
00390   if (!config_file.is_open())
00391     config_file.open("~/.mbl_log.properties");
00392   if (!config_file.is_open())
00393   {
00394     vcl_string home1("${HOME}/mbl_log.properties");
00395     vcl_string home2("${HOME}/.mbl_log.properties");
00396     vcl_string home3("${HOMESHARE}/mbl_log.properties");
00397     vcl_string home4("${HOMEDRIVE}${HOMEDIR}/mbl_log.properties");
00398     vcl_string home5("${HOMEDRIVE}${HOMEPATH}/mbl_log.properties");
00399     vcl_string home6("${USERPROFILE}/mbl_log.properties");
00400     if (vul_string_expand_var(home1))
00401       config_file.open(home1.c_str());
00402     if (!config_file.is_open() && vul_string_expand_var(home2))
00403       config_file.open(home2.c_str());
00404     if (!config_file.is_open() && vul_string_expand_var(home3))
00405       config_file.open(home3.c_str());
00406     if (!config_file.is_open() && vul_string_expand_var(home4))
00407       config_file.open(home4.c_str());
00408     if (!config_file.is_open() && vul_string_expand_var(home5))
00409       config_file.open(home5.c_str());
00410     if (!config_file.is_open() && vul_string_expand_var(home6))
00411       config_file.open(home6.c_str());
00412   }
00413   if (!config_file.is_open())
00414     config_file.open("C:\\mbl_log.properties");
00415 
00416   if (!config_file.is_open())
00417   {
00418     vcl_cerr << "WARNING: No mbl_log.properties file found.\n";
00419     return;
00420   }
00421 
00422   config_file.clear(); // May have been set to fail on failed open.
00423   load_log_config(config_file, stream_names);
00424 #endif
00425 }
00426 
00427 
00428 //:Load a default configuration file
00429 // Current Format is
00430 // \verbatim
00431 //LEVEL
00432 // \endverbatim
00433 // where LEVEL is an integer - setting the logging level.
00434 // see mbl_logger:levels for useful values.
00435 void mbl_logger_root::load_log_config(vcl_istream& is,
00436                                       const vcl_map<vcl_string, vcl_ostream *> &stream_names)
00437 {
00438 #ifndef MBL_LOG_DISABLE_ALL_LOGGING
00439   categories_.config(is, stream_names);
00440   update_all_loggers();
00441 #endif
00442 }
00443 
00444 // Make sure all known loggers reinitialise themselves.
00445 void mbl_logger_root::update_all_loggers()
00446 {
00447 #ifndef MBL_LOG_DISABLE_ALL_LOGGING
00448   for (vcl_set<mbl_logger *>::iterator it=all_loggers_.begin(),
00449        end=all_loggers_.end(); it!=end; ++it)
00450     (*it)->reinitialise();
00451 #endif
00452 }
00453 
00454 
00455 mbl_log_categories::mbl_log_categories()
00456 {
00457   cat_spec default_spec;
00458   default_spec.level = mbl_logger::NOTICE;
00459   default_spec.output = cat_spec::NAMED_STREAM;
00460   default_spec.name = "vcl_cerr";
00461   default_spec.stream = &vcl_cerr;
00462 
00463   cat_list_[""] = default_spec;
00464 }
00465 
00466 typedef vcl_map<vcl_string, vcl_ostream*> stream_names_t;
00467 
00468 
00469 inline mbl_log_categories::cat_spec parse_cat_spec(const vcl_string &str,
00470                                                    const stream_names_t& stream_names)
00471 {
00472   mbl_log_categories::cat_spec spec;
00473   vcl_istringstream ss(str);
00474   mbl_read_props_type props = mbl_read_props_ws(ss);
00475 
00476   vcl_string s = props.get_required_property("level");
00477   if (s == "NONE")
00478     spec.level = mbl_logger::NONE;
00479   else if (s == "EMERG")
00480     spec.level = mbl_logger::EMERG;
00481   else if (s == "ALERT")
00482     spec.level = mbl_logger::ALERT;
00483   else if (s == "CRIT")
00484     spec.level = mbl_logger::CRIT;
00485   else if (s == "ERR")
00486     spec.level = mbl_logger::ERR;
00487   else if (s == "WARN")
00488     spec.level = mbl_logger::WARN;
00489   else if (s == "NOTICE")
00490     spec.level = mbl_logger::NOTICE;
00491   else if (s == "INFO")
00492     spec.level = mbl_logger::INFO;
00493   else if (s == "DEBUG")
00494     spec.level = mbl_logger::DEBUG;
00495   else if (s == "ALL")
00496     spec.level = mbl_logger::ALL;
00497   else
00498   {
00499     mbl_exception_warning(
00500       mbl_exception_parse_error(
00501         vcl_string("mbl_log_categories.cxx:parse_cat_spec: unknown level: ") + s) );
00502     // Default to NOTICE if no exceptions.
00503     spec.level = mbl_logger::NOTICE;
00504   }
00505 
00506   if (props.find("file_output") != props.end())
00507   {
00508     spec.output = mbl_log_categories::cat_spec::FILE_OUT;
00509     spec.name = props["file_output"];
00510     props.erase("file_output");
00511   }
00512   else if (props.find("stream_output") != props.end())
00513   {
00514     spec.name = "";
00515     vcl_string s = props["stream_output"];
00516     spec.output = mbl_log_categories::cat_spec::NAMED_STREAM;
00517     spec.name = s;
00518     stream_names_t::const_iterator it = stream_names.find(s);
00519 
00520     if (s == "cout" || s == "vcl_cout" || s == "std::cout")
00521       spec.stream = &vcl_cout;
00522     else if (s == "cerr" || s == "vcl_cerr" || s == "std::cerr")
00523       spec.stream = &vcl_cerr;
00524     else if (it != stream_names.end())
00525       spec.stream = it->second;
00526     else
00527     {
00528       mbl_exception_warning(
00529         mbl_exception_parse_error(
00530           vcl_string("mbl_log.cxx:parse_cat_spec: unknown stream output name: ")
00531           + props["stream_output"]) );
00532       // Default to CERR if no exceptions.
00533       spec.stream = &vcl_cerr;
00534       spec.name = "vcl_cerr";
00535     }
00536     props.erase("stream_output");
00537   }
00538   else
00539   {
00540     spec.output = mbl_log_categories::cat_spec::NAMED_STREAM;
00541     spec.stream = &vcl_cerr;
00542     spec.name = "vcl_cerr";
00543   }
00544 
00545   mbl_read_props_look_for_unused_props("mbl_log.cxx::parse_cat_spec", props);
00546 
00547   return spec;
00548 }
00549 
00550 //: Configure whole category list from a file.
00551 // New entries are added to any existing category details.
00552 void mbl_log_categories::config(vcl_istream&s, const stream_names_t& stream_names)
00553 {
00554   mbl_read_props_type props = mbl_read_props_ws(s);
00555 
00556   //Deal with "root" special case.
00557   mbl_read_props_type::iterator it1=props.find("root");
00558   if (it1 == props.end())
00559     it1 = props.find("ROOT");
00560   if (it1 != props.end())
00561   {
00562     cat_spec spec = parse_cat_spec(it1->second, stream_names);
00563     cat_list_[""] = spec;
00564     props.erase(it1);
00565   }
00566 
00567   for (mbl_read_props_type::const_iterator it2=props.begin(), end = props.end();
00568        it2 != end; ++it2)
00569   {
00570     cat_spec spec = parse_cat_spec(it2->second, stream_names);
00571     cat_list_[it2->first] = spec;
00572   }
00573 }
00574 
00575 //: Make the category list empty;
00576 // An "empty" list still contains a root entry.
00577 void mbl_log_categories::clear()
00578 {
00579   cat_list_.clear();
00580   cat_spec default_spec;
00581   default_spec.level = mbl_logger::NOTICE;
00582   default_spec.name = "cerr";
00583   default_spec.stream = &vcl_cerr;
00584   default_spec.output = cat_spec::NAMED_STREAM;
00585   cat_list_[""] = default_spec;
00586 }
00587 
00588 struct mbl_log_prefix_comp
00589 {
00590   vcl_string s2;
00591   mbl_log_prefix_comp(const vcl_string& s): s2(s) {}
00592 
00593   bool operator() (const vcl_pair<vcl_string, mbl_log_categories::cat_spec>& s1)
00594   {
00595 // simple version:     return s1.first == s2.substr(0,s1.first.size());
00596 // However this would allow s1=AA.11 to match against AA.111
00597 
00598     if (s1.first.size() == s2.size())
00599       return s1.first == s2;
00600     else if (s1.first.size() > s2.size())
00601       return false;
00602     else if (s1.first.empty()) // always match against root.
00603       return true;
00604     else
00605       return s1.first == s2.substr(0,s1.first.size()) && s2[s1.first.size()] == '.';
00606   }
00607 };
00608 
00609 
00610 const mbl_log_categories::cat_spec&
00611   mbl_log_categories::get(const vcl_string& category) const
00612 {
00613   typedef vcl_map<vcl_string, cat_spec>::const_reverse_iterator iter;
00614 
00615   iter it = vcl_find_if(cat_list_.rbegin(), cat_list_.rend(),
00616                         mbl_log_prefix_comp(category));
00617   // The search shouldn't get past the first (root) entry.
00618   assert(it != cat_list_.rend());
00619 
00620   // vcl_cerr << "MBL_LOG: Using category \"" << it->first << '\"' << '\n';
00621   return it->second;
00622 }
00623 
00624 
00625 vcl_ostream& operator<<(vcl_ostream&os, const mbl_log_categories::cat_spec& spec)
00626 {
00627   os << "{ level: " << static_cast<mbl_logger::levels>(spec.level);
00628   switch (spec.output)
00629   {
00630    case mbl_log_categories::cat_spec::FILE_OUT:
00631     os << " file_output: " << spec.name;
00632     break;
00633    case mbl_log_categories::cat_spec::NAMED_STREAM:
00634     os << " stream_output: " << spec.name;
00635     break;
00636    default:
00637     assert(!"This should not happen: invalid spec.output");
00638     break;
00639   }
00640   os << " }";
00641   return os;
00642 }
00643 
00644 void mbl_log_categories::print(vcl_ostream& os) const
00645 {
00646   typedef vcl_map<vcl_string, cat_spec>::const_iterator iter;
00647   assert(!cat_list_.empty());
00648 
00649   iter it = cat_list_.begin(), end = cat_list_.end();
00650   assert(it->first.empty());
00651 
00652   os << "root:\n  " << it->second << '\n';
00653 
00654   ++it;
00655   for (; it!=end; ++it)
00656     os << it->first << ":\n  " << it->second << '\n';
00657 
00658   os.flush();
00659 };