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