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