1 /*****************************************************************************\
4 * Author : Chris Koeritz *
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 \*****************************************************************************/
15 #include "critical_events.h"
16 #include "file_logger.h"
17 #include "logging_filters.h"
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>
30 #if defined(__UNIX__) || defined(__GNU_WINDOWS__)
37 using namespace basis;
38 using namespace configuration;
39 using namespace filesystem;
40 using namespace mathematics;
41 using namespace structures;
42 using namespace textual;
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
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.
56 static chaos __hidden_chaos;
57 return __hidden_chaos.inclusive(0, 1280004);
60 file_logger::file_logger()
61 : _filename(new astring()),
62 _file_limit(DEFAULT_LOG_FILE_SIZE),
63 _outfile(NULL_POINTER),
69 file_logger::file_logger(const astring &initial_filename, int limit)
70 : _filename(new astring()),
72 _outfile(NULL_POINTER),
75 name(initial_filename);
76 // we don't open the file right away because we don't know they'll ever
80 file_logger::~file_logger()
87 basis::astring file_logger::log_file_for_app_name()
89 filename prog = application_configuration::application_name();
90 return application_configuration::make_logfile_name(prog.rootname() + ".log");
93 bool file_logger::reopen()
95 auto_synchronizer l(*_flock);
100 void file_logger::close_file()
102 auto_synchronizer l(*_flock);
103 if (_outfile) _outfile->flush();
104 // dump anything that hasn't gone out yet.
108 void file_logger::name(const astring &new_name)
110 auto_synchronizer l(*_flock);
112 *_filename = new_name;
115 int file_logger::size_reduction() const
117 auto_synchronizer l(*_flock);
118 return int(_file_limit / REDUCE_FACTOR);
121 bool file_logger::good() const
123 auto_synchronizer l(*_flock);
124 if (!_outfile && !_file_limit) return true;
125 if (!_outfile) return false;
126 return _outfile->good();
129 astring file_logger::name() const
131 auto_synchronizer l(*_flock);
135 void file_logger::flush()
137 auto_synchronizer l(*_flock);
138 if (!_outfile) open_file();
139 if (_outfile) _outfile->flush();
142 bool file_logger::open_file()
144 auto_synchronizer l(*_flock);
145 close_file(); // close any existing log file.
148 // if there's a limit of zero, we'll never open the file.
152 // make sure we've got a name.
154 // if the name is empty, they don't want to save to a normal log file.
155 _outfile = new byte_filer;
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] = '/';
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);
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();
177 outcome file_logger::log(const base_string &to_show, int filter)
179 if (!_file_limit) return common::OKAY;
181 size_t current_size = 0;
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.
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());
199 current_size = _outfile->tell();
203 // check if we need to truncate yet.
204 if (current_size > _file_limit) truncate(_file_limit - size_reduction());
208 outcome file_logger::log_bytes(const byte_array &to_log, int filter)
210 if (!_file_limit) return common::OKAY;
212 size_t current_size = 0;
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.
221 // dump the contents out.
223 _outfile->write(to_log.observe(), to_log.length());
224 current_size = _outfile->tell();
228 // check if we need to truncate yet.
229 if (current_size > _file_limit)
230 truncate(_file_limit - size_reduction());
234 outcome file_logger::format_bytes(const byte_array &to_log, int filter)
236 if (!_file_limit) return common::OKAY;
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.
247 // dump the contents out.
248 if (to_log.length()) {
250 byte_formatter::text_dump(dumped_form, to_log);
254 // check if we need to truncate yet.
255 // int current_size = _outfile->tell();
257 // if (current_size > _file_limit) truncate(_file_limit - size_reduction());
262 //hmmm: should move the truncation functionality into a function on
265 void file_logger::truncate(size_t new_size)
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.
273 size_t current_size = 0;
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.
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());
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);
289 /// critical_events::write_to_critical_events((astring("could not acquire "
290 /// "lock for ") + *_filename).s());
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();
303 // create a bogus temporary name.
304 astring new_file(astring::SPRINTF, "%s.tmp.%d", name().s(),
307 // unlink the temp file, if it exists.
308 unlink(new_file.s());
310 // grab the current size before we close our file.
311 current_size = _outfile->tell();
313 // redo the main stream for reading also.
315 _outfile = new byte_filer(*_filename, "rb");
317 // open the temp file as blank for writing.
318 byte_filer *hold_stream = new byte_filer(new_file, "w+b");
320 int start_of_keep = int(current_size - new_size);
322 // position the old file where it will be about the right size.
323 _outfile->seek(start_of_keep, byte_filer::FROM_START);
325 astring buff(' ', MAXIMUM_BUFFER_SIZE + 1);
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
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);
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);
348 _outfile = new byte_filer(*_filename, "w+b");
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.
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);
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);
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.