file_logger.cpp

Go to the documentation of this file.
00001 #ifndef FILE_LOGGER_IMPLEMENTATION_FILE
00002 #define FILE_LOGGER_IMPLEMENTATION_FILE
00003 
00004 /*****************************************************************************\
00005 *                                                                             *
00006 *  Name   : file_logger                                                       *
00007 *  Author : Chris Koeritz                                                     *
00008 *                                                                             *
00009 *******************************************************************************
00010 * Copyright (c) 2000-$now By Author.  This program is free software; you can  *
00011 * redistribute it and/or modify it under the terms of the GNU General Public  *
00012 * License as published by the Free Software Foundation; either version 2 of   *
00013 * the License or (at your option) any later version.  This is online at:      *
00014 *     http://www.fsf.org/copyleft/gpl.html                                    *
00015 * Please send any updates to: fred@gruntose.com                               *
00016 \*****************************************************************************/
00017 
00018 #include "console_logger.h"
00019 #include "file_logger.h"
00020 
00021 #include <basis/chaos.h>
00022 #include <basis/function.h>
00023 #include <basis/istring.h>
00024 #include <basis/mutex.h>
00025 #include <basis/portable.h>
00026 #include <data_struct/static_memory_gremlin.h>
00027 #include <opsystem/byte_filer.h>
00028 #include <opsystem/critical_events.h>
00029 #include <opsystem/directory.h>
00030 #include <opsystem/filename.h>
00031 #include <opsystem/path_configuration.h>
00032 #include <opsystem/rendezvous.h>
00033 #include <textual/byte_format.h>
00034 
00035 #include <stdio.h>
00036 #ifdef __WIN32__
00037   #include <io.h>
00038 #endif
00039 
00040 const int REDUCE_FACTOR = 5;
00041   // we whack this portion of the file every time we truncate.  if it's set
00042   // to 14, for example, then a 14th of the file is whacked every time whacking
00043   // is needed.
00044 
00045 const int MAXIMUM_BUFFER_SIZE = 140000;
00046   // the maximum allowed chunk that can be copied from the old logfile
00047   // to the current one.
00048 
00049 int static_chaos() { return chaos().inclusive(0, 1280004); }
00050 
00052 
00053 istring app_name_for_file_logger()
00054 {
00055   filename prog = path_configuration::application_name();
00056   return path_configuration::make_logfile_name(prog.rootname() + ".log");
00057 }
00058 
00059 #ifndef OMIT_PROGRAM_WIDE_LOGGER
00060 log_base *set_PW_logger_for_file(const istring &file_name, int limit)
00061 {
00062   file_logger *new_pw = new file_logger(file_name, limit);
00063   return retask_program_wide_logger(new_pw);
00064 }
00065 #endif
00066 
00068 
00069 file_logger::file_logger()
00070 : _filename(new istring()),
00071   _file_limit(LOG_FILE_SIZE),
00072   _outfile(NIL),
00073   _flock(new mutex)
00074 {
00075   name("");
00076 }
00077 
00078 file_logger::file_logger(const istring &initial_filename, int limit)
00079 : _filename(new istring()),
00080   _file_limit(limit),
00081   _outfile(NIL),
00082   _flock(new mutex)
00083 {
00084   name(initial_filename); 
00085   // we don't open the file right away because we don't know they'll ever
00086   // use the thing.
00087 }
00088 
00089 file_logger::~file_logger()
00090 {
00091   close_file();
00092   WHACK(_filename);
00093   WHACK(_flock);
00094 }
00095 
00096 bool file_logger::reopen()
00097 {
00098   auto_synchronizer l(*_flock);
00099   name(*_filename);
00100   return open_file();
00101 }
00102 
00103 void file_logger::close_file()
00104 {
00105   auto_synchronizer l(*_flock);
00106   if (_outfile) _outfile->flush();
00107     // dump anything that hasn't gone out yet.
00108   WHACK(_outfile);
00109 }
00110 
00111 void file_logger::name(const istring &new_name)
00112 {
00113   auto_synchronizer l(*_flock);
00114   close_file();
00115   *_filename = new_name; 
00116 }
00117 
00118 int file_logger::size_reduction() const
00119 {
00120   auto_synchronizer l(*_flock);
00121   return int(_file_limit / REDUCE_FACTOR);
00122 }
00123 
00124 bool file_logger::good() const
00125 {
00126   auto_synchronizer l(*_flock);
00127   if (!_outfile && !_file_limit) return true;
00128   if (!_outfile) return false;
00129   return _outfile->good();
00130 }
00131 
00132 istring file_logger::name() const
00133 {
00134   auto_synchronizer l(*_flock);
00135   return *_filename;
00136 }
00137 
00138 void file_logger::flush()
00139 {
00140   auto_synchronizer l(*_flock);
00141   if (!_outfile) open_file();
00142   if (_outfile) _outfile->flush();
00143 }
00144 
00145 bool file_logger::open_file()
00146 {
00147   auto_synchronizer l(*_flock);
00148   close_file();  // close any existing log file.
00149 
00150   if (!_file_limit) {
00151     // if there's a limit of zero, we'll never open the file.
00152     return true;
00153   }
00154 
00155   // make sure we've got a name.
00156   if (!*_filename) {
00157     // if the name is empty, they don't want to save to a normal log file.
00158     _outfile = new byte_filer;
00159     return true;
00160   }
00161 
00162   // canonicalize the name so that we use the same tag for synchronization.
00163   // this might still fail if there are some jokers using different relative
00164   // paths to the file.  but if it's an absolute name, it should work.
00165   for (int i = 0; i < _filename->length(); i++)
00166     if ((*_filename)[i] == '\\') (*_filename)[i] = '/';
00167 
00168   // make sure the directory containing the log file exists, if we can.
00169   filename temp_file(*_filename);
00170   filename temp_dir(temp_file.dirname());
00171   if (!temp_dir.good() || !temp_dir.is_directory()) {
00172     directory::recursive_create(temp_dir);
00173   }
00174 
00175   // if this opening doesn't work, then we just can't log.
00176   _outfile = new byte_filer(*_filename, "a+b");
00177   return _outfile->good();
00178 }
00179 
00180 outcome file_logger::log(const istring &to_show, int filter)
00181 {
00182   if (!_file_limit) return common::OKAY;
00183 
00184   size_t current_size = 0;
00185   {
00186     auto_synchronizer l(*_flock);
00187     if (!member(filter)) return common::OKAY;
00188     if (!_outfile) open_file();
00189     if (!_outfile) return common::BAD_INPUT;  // file opening failed.
00190     if (!_outfile->good()) return common::BAD_INPUT;
00191       // there is no log file currently.
00192   
00193     // dump the string out.
00194     if (to_show.length())
00195       _outfile->write((byte *)to_show.s(), to_show.length());
00196     if (eol() != NO_ENDING) {
00197       istring end = get_ending();
00198       _outfile->write((byte *)end.s(), end.length());
00199     }
00200     current_size = _outfile->tell();
00201     flush();
00202   }
00203 
00204   // check if we need to truncate yet.
00205   if (current_size > _file_limit) truncate(_file_limit - size_reduction());
00206   return common::OKAY;
00207 }
00208 
00209 outcome file_logger::log_bytes(const byte_array &to_log, int filter)
00210 {
00211   if (!_file_limit) return common::OKAY;
00212 
00213   size_t current_size = 0;
00214   {
00215     auto_synchronizer l(*_flock);
00216     if (!member(filter)) return common::OKAY;
00217     if (!_outfile) open_file();
00218     if (!_outfile) return common::BAD_INPUT;  // file opening failed.
00219     if (!_outfile->good()) return common::BAD_INPUT;
00220       // there is no log file currently.
00221   
00222     // dump the contents out.
00223     if (to_log.length())
00224       _outfile->write(to_log.observe(), to_log.length());
00225     current_size = _outfile->tell();
00226     flush();
00227   }
00228 
00229   // check if we need to truncate yet.
00230   if (current_size > _file_limit)
00231     truncate(_file_limit - size_reduction());
00232   return common::OKAY;
00233 }
00234 
00235 outcome file_logger::format_bytes(const byte_array &to_log, int filter)
00236 {
00237   if (!_file_limit) return common::OKAY;
00238 
00239   {
00240     auto_synchronizer l(*_flock);
00241     if (!member(filter)) return common::OKAY;
00242     if (!_outfile) open_file();
00243     if (!_outfile) return common::BAD_INPUT;  // file opening failed.
00244     if (!_outfile->good()) return common::BAD_INPUT;
00245       // there is no log file currently.
00246   }
00247 
00248   // dump the contents out.
00249   if (to_log.length()) {
00250     istring dumped_form;
00251     byte_format::text_dump(dumped_form, to_log);
00252     log(dumped_form);
00253   }
00254 
00255   // check if we need to truncate yet.
00256 //  int current_size = _outfile->tell();
00257 //  flush();
00258 //  if (current_size > _file_limit) truncate(_file_limit - size_reduction());
00259 
00260   return common::OKAY;
00261 }
00262 
00263 //hmmm: should move the truncation functionality into a function on
00264 //      the file object.
00265 
00266 void file_logger::truncate(size_t new_size)
00267 {
00268   auto_synchronizer l(*_flock);
00269   if (!_outfile) open_file();
00270   if (!_outfile) return;  // file opening failed.
00271   if (!_outfile->good()) return;
00272     // there is no log file currently.
00273 
00274   size_t current_size = 0;
00275 
00276   // our synchronization scheme allows us to use this inter-application
00277   // lock; the logger's own lock is always acquired first.  no one else can
00278   // grab the "file_lock", so no deadlocks.
00279 
00280   rendezvous file_lock(*_filename + "_trunclock");
00281   if (!file_lock.healthy()) {
00282     critical_events::write_to_critical_events((istring("could not create "
00283         "lock for ") + *_filename).s());
00284     return;
00285   }
00286   // waiting forever until the file lock succeeds.  as long as there are
00287   // no deadlocks permitted, then this shouldn't be too dangerous...
00288   bool got_lock = file_lock.lock(rendezvous::ENDLESS_WAIT);
00289   if (!got_lock) {
00290     critical_events::write_to_critical_events((istring("could not acquire "
00291         "lock for ") + *_filename).s());
00292     return;
00293   }
00294 
00295   // make sure we weren't second in line to clean the file.  if someone else
00296   // already did this, we don't need to do it again.
00297   _outfile->seek(0, byte_filer::FROM_END);
00298   current_size = _outfile->tell();
00299   if (current_size <= new_size) {
00300     // release the lock and leave since we don't need to change the file.
00301     file_lock.unlock();
00302     return;
00303   }
00304   // create a bogus temporary name.
00305   istring new_file(istring::SPRINTF, "%s.tmp.%d", name().s(),
00306       static_chaos());
00307 
00308   // unlink the temp file, if it exists.
00309   unlink(new_file.s());
00310 
00311   // grab the current size before we close our file.
00312   current_size = _outfile->tell();
00313 
00314   // redo the main stream for reading also.
00315   WHACK(_outfile);
00316   _outfile = new byte_filer(*_filename, "rb");
00317 
00318   // open the temp file as blank for writing.
00319   byte_filer *hold_stream = new byte_filer(new_file, "w+b");
00320 
00321   int start_of_keep = int(current_size - new_size);
00322 
00323   // position the old file where it will be about the right size.
00324   _outfile->seek(start_of_keep, byte_filer::FROM_START);
00325 
00326   istring buff(' ', MAXIMUM_BUFFER_SIZE + 1);
00327 
00328   // we only read as long as the file end isn't hit and we're not past the
00329   // original end of the file.  if the file got bigger during the truncation,
00330   // that's definitely not our doing and should not be coddled.  we've seen
00331   // a situation where we never thought we'd hit the end of the file yet before
00332   // adding this size check.
00333   size_t bytes_written = 0;  // how many bytes have gone out already.
00334 //hmmm: loop could be extracted to some kind of dump from file one into file
00335 //      two operation that starts at a particular address and has a particular
00336 //      size or range.
00337   while (!_outfile->eof() && (bytes_written <= new_size)) {
00338     // grab a line from the input file.
00339     buff[0] = '\0';  // set it to be an empty string.
00340     int bytes_read = _outfile->read((byte *)buff.s(), MAXIMUM_BUFFER_SIZE);
00341     if (!bytes_read)
00342       break;
00343     bytes_written += bytes_read;
00344     // write the line and a CR to the output file.
00345     if (!_outfile->eof() || bytes_read)
00346       hold_stream->write((byte *)buff.s(), bytes_read);
00347   }
00348   WHACK(_outfile);
00349   _outfile = new byte_filer(*_filename, "w+b");
00350 
00351   // we think the new stream is ready for writing.
00352   size_t hold_size = hold_stream->tell();
00353     // get the current length of the clipped chunk.
00354   bytes_written = 0;  // reset our counter.
00355 
00356   // jump back to the beginning of the temp file.
00357   hold_stream->seek(0, byte_filer::FROM_START);
00358   while (!hold_stream->eof() && (bytes_written <= hold_size) ) {
00359     // scoot all the old data back into our file.
00360     int bytes_read = hold_stream->read((byte *)buff.s(), MAXIMUM_BUFFER_SIZE);
00361     if (!bytes_read)
00362       break;
00363         // something funky happened; we shouldn't be at the end of the file yet.
00364     bytes_written += bytes_read;
00365     if (!hold_stream->eof() || bytes_read)
00366       _outfile->write((byte *)buff.s(), bytes_read);
00367   }
00368   WHACK(hold_stream);
00369   unlink(new_file.s());  // trash the temp file.
00370   file_lock.unlock();  // repeal the process-wide lock.
00371   name(*_filename);  // re-open the regular file with append semantics.
00372 }
00373 
00375 
00376 combo_logger::combo_logger(const istring &filename, int limit,
00377     bool standard_error)
00378 : file_logger(filename, limit),
00379   _out(new console_logger(standard_error))
00380 {
00381 }
00382 
00383 combo_logger::~combo_logger() { WHACK(_out); }
00384 
00385 void combo_logger::add_filter(int new_filter)
00386 {
00387   file_logger::add_filter(new_filter);
00388   _out->add_filter(new_filter);
00389 }
00390 
00391 void combo_logger::remove_filter(int old_filter)
00392 {
00393   file_logger::remove_filter(old_filter);
00394   _out->remove_filter(old_filter);
00395 }
00396 
00397 void combo_logger::clear_filters()
00398 {
00399   file_logger::clear_filters();
00400   _out->clear_filters();
00401 }
00402 
00403 void combo_logger::eol(line_ending to_set)
00404 {
00405   file_logger::eol(to_set);
00406   _out->eol(to_set);
00407 }
00408 
00409 outcome combo_logger::log(const istring &info, int filter)
00410 {
00411   _out->log(info, filter);
00412   return file_logger::log(info, filter);
00413 }
00414 
00415 #ifndef OMIT_PROGRAM_WIDE_LOGGER
00416 log_base *set_PW_logger_for_combo(const istring &file_name, int limit,
00417     bool standard_error)
00418 {
00419   combo_logger *new_pw = new combo_logger(file_name, limit, standard_error);
00420   return retask_program_wide_logger(new_pw);
00421 }
00422 #endif
00423 
00424 
00425 #endif //FILE_LOGGER_IMPLEMENTATION_FILE
00426 

Generated on Tue Aug 19 04:29:43 2008 for HOOPLE Libraries by  doxygen 1.5.1