first check-in of feisty meow codebase. many things broken still due to recent
[feisty_meow.git] / core / library / loggers / file_logger.cpp
1 /*****************************************************************************\
2 *                                                                             *
3 *  Name   : file_logger                                                       *
4 *  Author : Chris Koeritz                                                     *
5 *                                                                             *
6 *******************************************************************************
7 * Copyright (c) 2000-$now By Author.  This program is free software; you can  *
8 * redistribute it and/or modify it under the terms of the GNU General Public  *
9 * License as published by the Free Software Foundation; either version 2 of   *
10 * the License or (at your option) any later version.  This is online at:      *
11 *     http://www.fsf.org/copyleft/gpl.html                                    *
12 * Please send any updates to: fred@gruntose.com                               *
13 \*****************************************************************************/
14
15 #include "critical_events.h"
16 #include "file_logger.h"
17 #include "logging_filters.h"
18
19 #include <basis/astring.h>
20 #include <basis/functions.h>
21 #include <basis/mutex.h>
22 #include <configuration/application_configuration.h>
23 #include <filesystem/byte_filer.h>
24 #include <filesystem/directory.h>
25 #include <filesystem/filename.h>
26 #include <mathematics/chaos.h>
27 #include <structures/static_memory_gremlin.h>
28 #include <textual/byte_formatter.h>
29
30 #ifdef __WIN32__
31   #include <io.h>
32 #endif
33 #include <stdio.h>
34 #ifdef __UNIX__
35   #include <unistd.h>
36 #endif
37
38 using namespace basis;
39 using namespace configuration;
40 using namespace filesystem;
41 using namespace mathematics;
42 using namespace structures;
43 using namespace textual;
44
45 namespace loggers {
46
47 const int REDUCE_FACTOR = 5;
48   // we whack this portion of the file every time we truncate.  if it's set
49   // to 14, for example, then a 14th of the file is whacked every time whacking
50   // is needed.
51
52 const int MAXIMUM_BUFFER_SIZE = 140000;
53   // the maximum allowed chunk that can be copied from the old logfile
54   // to the current one.
55
56 int static_chaos() {
57   static chaos __hidden_chaos;
58   return __hidden_chaos.inclusive(0, 1280004);
59 }
60
61 file_logger::file_logger()
62 : _filename(new astring()),
63   _file_limit(DEFAULT_LOG_FILE_SIZE),
64   _outfile(NIL),
65   _flock(new mutex)
66 {
67   name("");
68 }
69
70 file_logger::file_logger(const astring &initial_filename, int limit)
71 : _filename(new astring()),
72   _file_limit(limit),
73   _outfile(NIL),
74   _flock(new mutex)
75 {
76   name(initial_filename); 
77   // we don't open the file right away because we don't know they'll ever
78   // use the thing.
79 }
80
81 file_logger::~file_logger()
82 {
83   close_file();
84   WHACK(_filename);
85   WHACK(_flock);
86 }
87
88 basis::astring file_logger::log_file_for_app_name()
89 {
90   filename prog = application_configuration::application_name();
91   return application_configuration::make_logfile_name(prog.rootname() + ".log");
92 }
93
94 bool file_logger::reopen()
95 {
96   auto_synchronizer l(*_flock);
97   name(*_filename);
98   return open_file();
99 }
100
101 void file_logger::close_file()
102 {
103   auto_synchronizer l(*_flock);
104   if (_outfile) _outfile->flush();
105     // dump anything that hasn't gone out yet.
106   WHACK(_outfile);
107 }
108
109 void file_logger::name(const astring &new_name)
110 {
111   auto_synchronizer l(*_flock);
112   close_file();
113   *_filename = new_name; 
114 }
115
116 int file_logger::size_reduction() const
117 {
118   auto_synchronizer l(*_flock);
119   return int(_file_limit / REDUCE_FACTOR);
120 }
121
122 bool file_logger::good() const
123 {
124   auto_synchronizer l(*_flock);
125   if (!_outfile && !_file_limit) return true;
126   if (!_outfile) return false;
127   return _outfile->good();
128 }
129
130 astring file_logger::name() const
131 {
132   auto_synchronizer l(*_flock);
133   return *_filename;
134 }
135
136 void file_logger::flush()
137 {
138   auto_synchronizer l(*_flock);
139   if (!_outfile) open_file();
140   if (_outfile) _outfile->flush();
141 }
142
143 bool file_logger::open_file()
144 {
145   auto_synchronizer l(*_flock);
146   close_file();  // close any existing log file.
147
148   if (!_file_limit) {
149     // if there's a limit of zero, we'll never open the file.
150     return true;
151   }
152
153   // make sure we've got a name.
154   if (!*_filename) {
155     // if the name is empty, they don't want to save to a normal log file.
156     _outfile = new byte_filer;
157     return true;
158   }
159
160   // canonicalize the name so that we use the same tag for synchronization.
161   // this might still fail if there are some jokers using different relative
162   // paths to the file.  but if it's an absolute name, it should work.
163   for (int i = 0; i < _filename->length(); i++)
164     if ((*_filename)[i] == '\\') (*_filename)[i] = '/';
165
166   // make sure the directory containing the log file exists, if we can.
167   filename temp_file(*_filename);
168   filename temp_dir(temp_file.dirname());
169   if (!temp_dir.good() || !temp_dir.is_directory()) {
170     directory::recursive_create(temp_dir);
171   }
172
173   // if this opening doesn't work, then we just can't log.
174   _outfile = new byte_filer(*_filename, "a+b");
175   return _outfile->good();
176 }
177
178 outcome file_logger::log(const base_string &to_show, int filter)
179 {
180   if (!_file_limit) return common::OKAY;
181
182   size_t current_size = 0;
183   {
184     auto_synchronizer l(*_flock);
185     if (!member(filter)) return common::OKAY;
186     if (!_outfile) open_file();
187     if (!_outfile) return common::BAD_INPUT;  // file opening failed.
188     if (!_outfile->good()) return common::BAD_INPUT;
189       // there is no log file currently.
190   
191     // dump the string out.
192     if (to_show.length())
193       _outfile->write((abyte *)to_show.observe(), to_show.length());
194 //hmmm: need eol feature again.
195 //    if (eol() != NO_ENDING) {
196 //      astring end = get_ending();
197 astring end = parser_bits::platform_eol_to_chars();
198       _outfile->write((abyte *)end.s(), end.length());
199 //    }
200     current_size = _outfile->tell();
201     flush();
202   }
203
204   // check if we need to truncate yet.
205   if (current_size > _file_limit) truncate(_file_limit - size_reduction());
206   return common::OKAY;
207 }
208
209 outcome file_logger::log_bytes(const byte_array &to_log, int filter)
210 {
211   if (!_file_limit) return common::OKAY;
212
213   size_t current_size = 0;
214   {
215     auto_synchronizer l(*_flock);
216     if (!member(filter)) return common::OKAY;
217     if (!_outfile) open_file();
218     if (!_outfile) return common::BAD_INPUT;  // file opening failed.
219     if (!_outfile->good()) return common::BAD_INPUT;
220       // there is no log file currently.
221   
222     // dump the contents out.
223     if (to_log.length())
224       _outfile->write(to_log.observe(), to_log.length());
225     current_size = _outfile->tell();
226     flush();
227   }
228
229   // check if we need to truncate yet.
230   if (current_size > _file_limit)
231     truncate(_file_limit - size_reduction());
232   return common::OKAY;
233 }
234
235 outcome file_logger::format_bytes(const byte_array &to_log, int filter)
236 {
237   if (!_file_limit) return common::OKAY;
238
239   {
240     auto_synchronizer l(*_flock);
241     if (!member(filter)) return common::OKAY;
242     if (!_outfile) open_file();
243     if (!_outfile) return common::BAD_INPUT;  // file opening failed.
244     if (!_outfile->good()) return common::BAD_INPUT;
245       // there is no log file currently.
246   }
247
248   // dump the contents out.
249   if (to_log.length()) {
250     astring dumped_form;
251     byte_formatter::text_dump(dumped_form, to_log);
252     log(dumped_form);
253   }
254
255   // check if we need to truncate yet.
256 //  int current_size = _outfile->tell();
257 //  flush();
258 //  if (current_size > _file_limit) truncate(_file_limit - size_reduction());
259
260   return common::OKAY;
261 }
262
263 //hmmm: should move the truncation functionality into a function on
264 //      the file object.
265
266 void file_logger::truncate(size_t new_size)
267 {
268   auto_synchronizer l(*_flock);
269   if (!_outfile) open_file();
270   if (!_outfile) return;  // file opening failed.
271   if (!_outfile->good()) return;
272     // there is no log file currently.
273
274   size_t current_size = 0;
275
276 ///  // our synchronization scheme allows us to use this inter-application
277 ///  // lock; the logger's own lock is always acquired first.  no one else can
278 ///  // grab the "file_lock", so no deadlocks.
279 ///
280 ///  rendezvous file_lock(*_filename + "_trunclock");
281 ///  if (!file_lock.healthy()) {
282 ///    critical_events::write_to_critical_events((astring("could not create "
283 ///       "lock for ") + *_filename).s());
284 ///    return;
285 ///  }
286 ///  // waiting forever until the file lock succeeds.  as long as there are
287 ///  // no deadlocks permitted, then this shouldn't be too dangerous...
288 ///  bool got_lock = file_lock.lock(rendezvous::ENDLESS_WAIT);
289 ///  if (!got_lock) {
290 ///    critical_events::write_to_critical_events((astring("could not acquire "
291 ///        "lock for ") + *_filename).s());
292 ///    return;
293 ///  }
294
295   // make sure we weren't second in line to clean the file.  if someone else
296   // already did this, we don't need to do it again.
297   _outfile->seek(0, byte_filer::FROM_END);
298   current_size = _outfile->tell();
299   if (current_size <= new_size) {
300     // release the lock and leave since we don't need to change the file.
301 ///    file_lock.unlock();
302     return;
303   }
304   // create a bogus temporary name.
305   astring new_file(astring::SPRINTF, "%s.tmp.%d", name().s(),
306       static_chaos());
307
308   // unlink the temp file, if it exists.
309   unlink(new_file.s());
310
311   // grab the current size before we close our file.
312   current_size = _outfile->tell();
313
314   // redo the main stream for reading also.
315   WHACK(_outfile);
316   _outfile = new byte_filer(*_filename, "rb");
317
318   // open the temp file as blank for writing.
319   byte_filer *hold_stream = new byte_filer(new_file, "w+b");
320
321   int start_of_keep = int(current_size - new_size);
322
323   // position the old file where it will be about the right size.
324   _outfile->seek(start_of_keep, byte_filer::FROM_START);
325
326   astring buff(' ', MAXIMUM_BUFFER_SIZE + 1);
327
328   // we only read as long as the file end isn't hit and we're not past the
329   // original end of the file.  if the file got bigger during the truncation,
330   // that's definitely not our doing and should not be coddled.  we've seen
331   // a situation where we never thought we'd hit the end of the file yet before
332   // adding this size check.
333   size_t bytes_written = 0;  // how many bytes have gone out already.
334 //hmmm: loop could be extracted to some kind of dump from file one into file
335 //      two operation that starts at a particular address and has a particular
336 //      size or range.
337   while (!_outfile->eof() && (bytes_written <= new_size)) {
338     // grab a line from the input file.
339     buff[0] = '\0';  // set it to be an empty string.
340     int bytes_read = _outfile->read((abyte *)buff.s(), MAXIMUM_BUFFER_SIZE);
341     if (!bytes_read)
342       break;
343     bytes_written += bytes_read;
344     // write the line and a CR to the output file.
345     if (!_outfile->eof() || bytes_read)
346       hold_stream->write((abyte *)buff.s(), bytes_read);
347   }
348   WHACK(_outfile);
349   _outfile = new byte_filer(*_filename, "w+b");
350
351   // we think the new stream is ready for writing.
352   size_t hold_size = hold_stream->tell();
353     // get the current length of the clipped chunk.
354   bytes_written = 0;  // reset our counter.
355
356   // jump back to the beginning of the temp file.
357   hold_stream->seek(0, byte_filer::FROM_START);
358   while (!hold_stream->eof() && (bytes_written <= hold_size) ) {
359     // scoot all the old data back into our file.
360     int bytes_read = hold_stream->read((abyte *)buff.s(), MAXIMUM_BUFFER_SIZE);
361     if (!bytes_read)
362       break;
363         // something funky happened; we shouldn't be at the end of the file yet.
364     bytes_written += bytes_read;
365     if (!hold_stream->eof() || bytes_read)
366       _outfile->write((abyte *)buff.s(), bytes_read);
367   }
368   WHACK(hold_stream);
369   unlink(new_file.s());  // trash the temp file.
370 ///  file_lock.unlock();  // repeal the process-wide lock.
371   name(*_filename);  // re-open the regular file with append semantics.
372 }
373
374 } //namespace.
375