feisty meow concerns codebase  2.140
file_logger.cpp
Go to the documentation of this file.
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>
23 #include <filesystem/byte_filer.h>
24 #include <filesystem/directory.h>
25 #include <filesystem/filename.h>
26 #include <mathematics/chaos.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 
81 {
82  close_file();
83  WHACK(_filename);
84  WHACK(_flock);
85 }
86 
88 {
89  filename prog = application_configuration::application_name();
90  return application_configuration::make_logfile_name(prog.rootname() + ".log");
91 }
92 
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 
130 {
131  auto_synchronizer l(*_flock);
132  return *_filename;
133 }
134 
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 
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.
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.
370  name(*_filename); // re-open the regular file with append semantics.
371 }
372 
373 } //namespace.
374 
#define unlink
Definition: Xos2defs.h:45
const contents * observe() const
Returns a pointer to the underlying C array of data.
Definition: array.h:172
int length() const
Returns the current reported length of the allocated C array.
Definition: array.h:115
Provides a dynamically resizable ASCII character string.
Definition: astring.h:35
const char * s() const
synonym for observe. the 's' stands for "string", if that helps.
Definition: astring.h:113
int length() const
Returns the current length of the string.
Definition: astring.cpp:132
auto_synchronizer simplifies concurrent code by automatically unlocking.
Definition: mutex.h:113
Defines the base class for all string processing objects in hoople.
Definition: base_string.h:28
virtual const char * observe() const =0
observes the underlying pointer to the zero-terminated string.
virtual int length() const =0
Returns the current length of the string.
A very common template for a dynamic array of bytes.
Definition: byte_array.h:36
Outcomes describe the state of completion for an operation.
Definition: outcome.h:31
Provides file managment services using the standard I/O support.
Definition: byte_filer.h:32
int write(const basis::abyte *buffer, int buffer_size)
writes "buffer_size" bytes into the file from "buffer".
Definition: byte_filer.cpp:126
bool seek(int where, origins origin=FROM_START)
places the cursor in the file at "where", based on the "origin".
Definition: byte_filer.cpp:187
int read(basis::abyte *buffer, int buffer_size)
reads "buffer_size" bytes from the file into "buffer".
Definition: byte_filer.cpp:123
bool eof()
returns true if the cursor is at (or after) the end of the file.
Definition: byte_filer.cpp:121
size_t tell()
returns the current position within the file, in terms of bytes.
Definition: byte_filer.cpp:105
void flush()
forces any pending writes to actually be saved to the file.
Definition: byte_filer.cpp:165
bool good()
returns true if the file seems to be in the appropriate desired state.
Definition: byte_filer.cpp:103
Provides operations commonly needed on file names.
Definition: filename.h:64
basis::astring rootname() const
returns the root part of the basename without an extension.
Definition: filename.cpp:308
bool reopen()
closes the current file and attempts to reopen it.
Definition: file_logger.cpp:93
bool good() const
returns true if the logger appears correctly hooked up to a file.
basis::astring name() const
observes the filename where logged information is written.
void flush()
causes any pending writes to be sent to the output file.
static basis::astring log_file_for_app_name()
returns a log file name for file_logger based on the program name.
Definition: file_logger.cpp:87
void truncate(size_t new_size)
chops the file to ensure it doesn't go much over the file size limit.
file_logger()
creates a logger without a log file and with the default size limit.
Definition: file_logger.cpp:60
basis::outcome log(const basis::base_string &info, int filter=basis::ALWAYS_PRINT)
writes information to the log file (if the filename is valid).
basis::outcome format_bytes(const basis::byte_array &to_log, int filter=basis::ALWAYS_PRINT)
fancifully formats a stream of bytes "to_log" and sends them into log.
basis::outcome log_bytes(const basis::byte_array &to_log, int filter=basis::ALWAYS_PRINT)
sends a stream of bytes "to_log" without interpretation into the log.
virtual bool member(int filter_to_check)
Returns true if the "filter_to_check" is a member of the filter set.
Definition: filter_set.h:57
a platform-independent way to acquire random numbers in a specific range.
Definition: chaos.h:51
int inclusive(int low, int high) const
< Returns a pseudo-random number r, such that "low" <= r <= "high".
Definition: chaos.h:88
#define NULL_POINTER
The value representing a pointer to nothing.
Definition: definitions.h:32
The guards collection helps in testing preconditions and reporting errors.
Definition: array.h:30
void WHACK(contents *&ptr)
deletion with clearing of the pointer.
Definition: functions.h:121
unsigned char abyte
A fairly important unit which is seldom defined...
Definition: definitions.h:51
A platform independent way to obtain the timestamp of a file.
Definition: byte_filer.cpp:37
A logger that sends to the console screen using the standard output device.
const int REDUCE_FACTOR
Definition: file_logger.cpp:46
const int MAXIMUM_BUFFER_SIZE
Definition: file_logger.cpp:51
int static_chaos()
Definition: file_logger.cpp:55
An extension to floating point primitives providing approximate equality.
Definition: averager.h:21
A dynamic container class that holds any kind of object via pointers.
Definition: amorph.h:55