contrib/mul/mbl/mbl_log.h
Go to the documentation of this file.
00001 #ifndef mbl_log_h
00002 #define mbl_log_h
00003 //:
00004 // \file
00005 // \author Ian Scott
00006 // \date 16-Jan-2005
00007 // \brief Flexible and efficient status and error logging.
00008 //
00009 // Good log statements are left in the code, and switched on or off at runtime.
00010 // To make this feasible, the decision about whether to send a log message
00011 // should be as efficient as possible.
00012 // The runtime cost of a switched off mbl_log statement will be no more than an
00013 // integer comparison, if you have a decent optimising compiler.
00014 //
00015 // These classes are patterned after the log4j logging library,
00016 // although without all of the sophistication. (We have not copied any
00017 // code from log4j (or other logging libraries) - just the ideas.)
00018 //
00019 // To send a message to the log you need to do two things - create a logger object, and
00020 // send a message to it.
00021 // By creating a logger, you identify a category of logging events. You should create
00022 // a separate category for each module of your program. However, creating loggers may not
00023 // be cheap - so it is best not to do it too often.
00024 // \code
00025 // mbl_logger my_log("imorphics.easy_stuff.does_halt");
00026 // \endcode
00027 // The easiest way to send a log message looks like:
00028 // \code
00029 // MBL_LOG(WARN, my_log, "Infinite loop: Time wasted so far " << time() );
00030 // \endcode
00031 // The system will have been setup to assign a log level to a particular category.
00032 // The log levels are EMERG < ALERT < CRIT < ERR < WARN < NOTICE < INFO < DEBUG. The
00033 // above log message will be sent if the level for category "imorphics.easy_stuff.does_halt"
00034 // is set to WARN or higher.
00035 // You can avoid using macros by calling the logger directly
00036 // \code
00037 // my_log.log(mbl_logger::WARN) << "Infinite loop: Time wasted so far " << time() << vcl_endl;
00038 // \endcode
00039 // The vcl_endl (or a vcl_flush) is necessary to terminate the log line. The problem with this
00040 // explicit version is that the stream insertion and call to the time() function take place,
00041 // even if the logging level is set below WARN. Ideally, we would like unsent log messages
00042 // to take as little time as possible - so that there is no overhead in leaving the
00043 // log message in your code. To achieve this we can test the log level first
00044 // \code
00045 // if (my_log.level() >= mbl_logger::WARN)
00046 //   my_log.log(mbl_logger::WARN) << "Infinite loop: Time wasted so far " << time() << vcl_endl;
00047 // \endcode
00048 // Of course, you should just use MBL_LOG which handles this for you. Additionally
00049 // MBL_LOG sorts out termination of the log message without using vcl_endl, allowing you to send
00050 // multi-line messages in a single log output.
00051 //
00052 // You can also use the logger to control the dumping of data files straight to the filesystem.
00053 // \code
00054 // if (my_log.level() >= mbl_logger::INFO && my_log.dump())
00055 // {
00056 //   vcl_string filename=my_log.dump_prefix()+"my_image.png";
00057 //   if (vil_save(my_image, filename.c_str()))
00058 //     MBL_LOG(INFO, my_log, "Saved my_image to " << filename);
00059 //   else
00060 //     MBL_LOG(ERR, my_log, "Failed to save my_image to " << filename);
00061 // }
00062 // \endcode
00063 //
00064 // You can find all logger names in all source code in some subtree using the following command:
00065 // \code
00066 //  find . -type f | fgrep -v /.svn/ | xargs perl -ne 'print if s/.*\bmbl_logger(\s+\w+)?\s*\(\"(.*)\"\).*/$2\n/;' | sort -u
00067 // \endcode
00068 
00069 
00070 #include <vcl_fstream.h>
00071 #include <vcl_streambuf.h>
00072 #include <vcl_string.h>
00073 #include <vcl_ostream.h>
00074 #include <vcl_set.h>
00075 #include <vcl_map.h>
00076 #include <vcl_ios.h>
00077 
00078 
00079 // define MBL_LOG_DISABLE_ALL_LOGGING to stop all logging.
00080 
00081 class mbl_logger_root;
00082 class mbl_logger;
00083 
00084 //: Allows stream-like syntax on logger.
00085 class mbl_log_streambuf: public vcl_streambuf
00086 {
00087   mbl_logger* logger_;
00088  public:
00089   mbl_log_streambuf(mbl_logger* logger): logger_(logger) {}
00090   virtual int sync ();
00091   virtual int overflow (int ch);
00092   virtual vcl_streamsize xsputn(const char *ptr, vcl_streamsize count);
00093 };
00094 
00095 //: A null streambuf ignores all input.
00096 class mbl_log_null_streambuf: public vcl_streambuf
00097 {
00098 };
00099 
00100 
00101 //: Base class for destinations of a logging event.
00102 class mbl_log_output_base
00103 {
00104  public:
00105   virtual ~mbl_log_output_base() {}
00106   //: Start a new log entry, with id info.
00107   // Future calls to terminate_flush will be ignored.
00108   virtual void start_with_manual_termination(int level, const char *srcfile, int srcline)=0;
00109   //: Start a new log entry, with id info.
00110   // Future calls to terminate_flush will be honoured.
00111   virtual void start_with_flush_termination(int level, const char *srcfile, int srcline)=0;
00112   //: Add contents to the existing log entry.
00113   virtual void append(const char * contents, vcl_streamsize n_chars)=0;
00114   //: Finish the log entry, sent from a stream flush.
00115   virtual void terminate_manual()=0;
00116   //: Finish the log entry, sent from explicit function call, e.g. by MBL_LOG.
00117   virtual void terminate_flush()=0;
00118   //: Which logger id are we using.
00119   // char * for efficiency, since logger name is in executable's static_data.
00120   virtual const char *id()=0;
00121 };
00122 
00123 #if !defined MBL_LOG_DISABLE_ALL_LOGGING
00124 //: Outputs log messages to an existing stream (e.g. vcl_cerr).
00125 class mbl_log_output_stream: public mbl_log_output_base
00126 {
00127   //: A pointer to the stream where logging finally gets sent.
00128   vcl_ostream* real_stream_;
00129   //: logger identity
00130   const char * id_;
00131   //: true if a log entry is in progress.
00132   bool has_started_;
00133   // Start a new log entry. Pass id info in via append()
00134   void start();
00135   //: Ignore calls to terminate_flush.
00136   // The current log message should be manually terminated.
00137   bool ignore_flush_;
00138  public:
00139   mbl_log_output_stream(vcl_ostream& real_stream, const char *id);
00140   //: Start a new log entry, with id info.
00141   // Future calls to terminate_flush will be ignored.
00142   virtual void start_with_manual_termination(int level, const char *srcfile, int srcline);
00143   //: Start a new log entry, with id info.
00144   // Future calls to terminate_flush will be honoured.
00145   virtual void start_with_flush_termination(int level, const char *srcfile, int srcline);
00146   //: Add contents to the existing log entry.
00147   virtual void append(const char * contents, vcl_streamsize n_chars);
00148   //: Finish the log entry, sent from a stream flush.
00149   virtual void terminate_manual();
00150   //: Finish the log entry, sent from explicit function call, e.g. by MBL_LOG.
00151   virtual void terminate_flush();
00152   //: Which logger id are we using.
00153   virtual const char *id() {return id_;}
00154 };
00155 
00156 //: Outputs log messages to a named file.
00157 class mbl_log_output_file: public mbl_log_output_base
00158 {
00159   //: The file where logging finally gets sent.
00160   vcl_ofstream file_;
00161   //: logger identity
00162   const char * id_;
00163   //: true if a log entry is in progress.
00164   bool has_started_;
00165   //; Start a new log entry. Pass id info in via append()
00166   void start();
00167   //: Ignore calls to terminate_flush.
00168   // The current log message should be manually terminated.
00169   bool ignore_flush_;
00170  public:
00171   mbl_log_output_file(const vcl_string &filename, const char *id);
00172   //: Start a new log entry, with id info.
00173   // Future calls to terminate_flush will be ignored.
00174   virtual void start_with_manual_termination(int level, const char *srcfile, int srcline);
00175   //: Start a new log entry, with id info.
00176   // Future calls to terminate_flush will be honoured.
00177   virtual void start_with_flush_termination(int level, const char *srcfile, int srcline);
00178   //: Add contents to the existing log entry.
00179   virtual void append(const char * contents, vcl_streamsize n_chars);
00180   //: Finish the log entry, sent from a stream flush.
00181   virtual void terminate_manual();
00182   //: Finish the log entry, sent from explicit function call, e.g. by MBL_LOG.
00183   virtual void terminate_flush();
00184   //: Which logger id are we using.
00185   virtual const char *id() {return id_;}
00186 };
00187 
00188 #endif
00189 
00190 //: Main user logging class - represents a category.
00191 class mbl_logger
00192 {
00193 #ifdef MBL_LOG_DISABLE_ALL_LOGGING
00194   mbl_logger(): nullstream_(&nullbuf_) {}
00195   mbl_log_null_streambuf nullbuf_;
00196   vcl_ostream nullstream_;
00197  public:  mbl_logger(const char *id): nullstream_(&nullbuf_) {}
00198   int level() const { return -1000; }
00199   vcl_ostream &log(int level) { return nullstream_; }
00200   vcl_ostream &mtlog() {return nullstream_;}
00201   bool dump() const { return false; }
00202   const vcl_string& dump_prefix() const { return ""; }
00203   const vcl_string& timestamp() const { return ""; }
00204 #else
00205   int level_;
00206   mbl_log_output_base *output_;
00207   mbl_log_streambuf streambuf_;
00208   vcl_ostream logstream_;
00209   vcl_ostream *mt_logstream_;
00210   //: File location to dump files.
00211   // If empty - don't dump files.
00212   vcl_string dump_prefix_;
00213   //: Time stamp format.
00214   // If empty - don't print time stamp.
00215   vcl_string timestamp_;
00216   //: Default constructor only available to root's default logger.
00217   mbl_logger();
00218 
00219   mbl_logger(const mbl_logger&); // Hide copy constructor.
00220 
00221   //: Update settings in light of changes to the root / configuration.
00222   void reinitialise();
00223 
00224  public:
00225   mbl_logger(const char *id);
00226   ~mbl_logger();
00227 
00228   //: logger will take ownership of output
00229   void set(int level, mbl_log_output_base* output);
00230   //: Higher means more output.
00231   int level() const { return level_; }
00232   vcl_ostream &log(int level, const char * srcfile="", int srcline=0);
00233 
00234   //: A log with manual event start and stop requirements.
00235   vcl_ostream &mtlog() {return *mt_logstream_;}
00236   void mtstart(int level, const char * srcfile="", int srcline=0);
00237   void mtstop();
00238 
00239   //: Is this logger allowed to dump data files directly to the filestore?
00240   bool dump() const {return !dump_prefix_.empty();}
00241 
00242   //: A filepath prefix that this logger should use when creating dump files.
00243   // Normal behaviour is to treat this as a prefix rather than a dir, so
00244   // "./my_dump_area/foo_" indicates the program should create files
00245   // that begin with "foo_" in sub-directory my_dump_area of the cwd.
00246   const vcl_string& dump_prefix() const {return dump_prefix_;}
00247 
00248   //: Time stamp format. Don't save timestamp if empty. Not Yet Implemented
00249   const vcl_string& timestamp() const { return timestamp_; }
00250 
00251 #endif // MBL_LOG_DISABLE_ALL_LOGGING
00252 
00253   static mbl_logger_root &root();
00254 
00255   //: Log priority levels.
00256   // Based on POSIX syslog API.
00257   // Default level is NOTICE.
00258   enum levels
00259   {
00260     NONE=-100, EMERG=0, ALERT=100, CRIT=200, ERR=300, WARN=400,
00261     NOTICE=500, INFO=600, DEBUG=700, ALL=800
00262   };
00263   friend class mbl_log_streambuf;
00264   friend class mbl_logger_root;
00265 };
00266 
00267 
00268 //: This class handles category lists.
00269 class mbl_log_categories
00270 {
00271  public:
00272   struct cat_spec
00273   {
00274     int level;
00275     enum output_type {FILE_OUT, NAMED_STREAM} output;
00276     vcl_string name;
00277     vcl_string dump_prefix;
00278     vcl_string timestamp;
00279     vcl_ostream *stream;
00280   };
00281 
00282   mbl_log_categories();
00283 
00284   //: Configure whole category list from a file.
00285   // New entries are added to any existing category details.
00286   void config(vcl_istream&, const vcl_map<vcl_string, vcl_ostream *>& stream_names);
00287 
00288   //: Make the category list empty;
00289   // An "empty" list still contains a root entry.
00290   void clear();
00291 
00292   //: Get the closest matching entry;
00293   const cat_spec& get(const vcl_string& category) const;
00294 
00295   void print(vcl_ostream& ss) const;
00296 
00297  private:
00298   vcl_map<vcl_string, cat_spec> cat_list_;
00299 };
00300 
00301 
00302 //: Singleton, keeps records of logging state.
00303 class mbl_logger_root
00304 {
00305   friend class mbl_logger;
00306   mbl_log_categories categories_;
00307 
00308 #ifndef MBL_LOG_DISABLE_ALL_LOGGING
00309   mbl_log_null_streambuf null_streambuf_;
00310   vcl_ostream null_stream_;
00311 
00312   vcl_set<mbl_logger*> all_loggers_;
00313 
00314   mbl_logger_root():
00315     null_stream_(&null_streambuf_) {}
00316 #endif
00317  public:
00318 
00319   //: List of category entries.
00320   const mbl_log_categories &categories() {return categories_;}
00321 
00322   typedef vcl_map<vcl_string, vcl_ostream *> stream_names_t;
00323 
00324   //:Load a default configuration file
00325   // This function will look for a configuration file called
00326   // "mbl_log.properties" in the current directory, or if no there,
00327   // in the user's home directory. Unix users can call it
00328   // ".mbl_log.properties" in their home directory.
00329   // See load_log_config() for description of config file format, and \p stream_names.
00330   void load_log_config_file(const stream_names_t &stream_names = stream_names_t());
00331 
00332   //: Load a configuration from a stream.
00333   // Each section of the text should begin with a category.
00334   // A logger named "A.B.C" will be controlled by a section labelled A.B.C
00335   // The categories labels have a hierarchical structure, so if A.B.C
00336   // doesn't exist, the code will look for a section labelled A.B,
00337   // or A. If none of these exist it will use the "root" category.
00338   // Each category has a level. If a log message has the same or higher
00339   // priority than the given level, the message will be output.
00340   // Finally the category destination must be specified as a
00341   // "file_output" and the filename or "stream_output" and the stream name.
00342   // The known stream names are "vcl_cout" and "vcl_cerr", and additional
00343   // names can be provided through \p stream_names.
00344   // The 
00345   //\verbatim
00346   // root: { level: INFO stream_output: test }
00347   // obj3: { level: INFO stream_output: vcl_cout }\n
00348   // obj3.obj6: { level: INFO file_output: results.txt }\n
00349   // obj3.obj7.images: { level: INFO stream_output: vcl_cout dump_prefix: ./logging_dir/ }\n
00350   //\endverbatim
00351   // where LEVEL is an integer - setting the logging level.
00352   // see mbl_logger:levels for useful values.
00353   void load_log_config(vcl_istream &config, const stream_names_t &stream_names = stream_names_t());
00354 
00355   //: Force all loggers to update themselves in light of changes to the root and configuration.
00356   // This is already called automatically by load_log_config_file().
00357   void update_all_loggers();
00358 };
00359 
00360 #ifdef MBL_LOG_DISABLE_ALL_LOGGING
00361 # define MBL_LOG(my_level, logger, message) do {} while (false)
00362 #else
00363 //: Log a message.
00364 // This macro wraps up normal uses of the logger efficiently in source code-length and run-time.
00365 // \code
00366 // MBL_LOG(DEBUG, my_logger, "time: " << time() << "\nstatus: " << my_data);
00367 // \endcode
00368 // No function evaluations (e.g. of time() ) will take place unless the logger is enabled.
00369 // The logger will also work correctly even if \c operator<<(my_data_t&) flushes the stream.
00370 # define MBL_LOG(my_level, logger, message) \
00371   do { mbl_logger &rlogger = logger; \
00372     if (rlogger.level() >= mbl_logger:: my_level) {\
00373       rlogger.mtstart(mbl_logger:: my_level, __FILE__, __LINE__); \
00374       vcl_ios_fmtflags flags=rlogger.mtlog().flags(); \
00375       vcl_streamsize precision=rlogger.mtlog().precision(); \
00376       vcl_streamsize width=rlogger.mtlog().width(); \
00377       rlogger.mtlog() << message << vcl_endl; \
00378       rlogger.mtlog().width(width); \
00379       rlogger.mtlog().precision(precision); \
00380       rlogger.mtlog().flags(flags); \
00381       rlogger.mtstop(); } \
00382   } while (false)
00383 #endif
00384 
00385 #endif // mbl_log_h