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>
25#include <filesystem/filename.h>
26#include <mathematics/chaos.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
37using namespace basis;
38using namespace configuration;
39using namespace filesystem;
40using namespace mathematics;
41using namespace structures;
42using namespace textual;
43
44namespace loggers {
45
46const 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
51const int MAXIMUM_BUFFER_SIZE = 140000;
52 // the maximum allowed chunk that can be copied from the old logfile
53 // to the current one.
54
56 static chaos __hidden_chaos;
57 return __hidden_chaos.inclusive(0, 1280004);
58}
59
61: _filename(new astring()),
62 _file_limit(DEFAULT_LOG_FILE_SIZE),
63 _outfile(NULL_POINTER),
64 _flock(new mutex)
65{
66 name("");
67}
68
69file_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
92
94{
95 auto_synchronizer l(*_flock);
96 name(*_filename);
97 return open_file();
98}
99
100void 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
108void file_logger::name(const astring &new_name)
109{
110 auto_synchronizer l(*_flock);
111 close_file();
112 *_filename = new_name;
113}
114
115int file_logger::size_reduction() const
116{
117 auto_synchronizer l(*_flock);
118 return int(_file_limit / REDUCE_FACTOR);
119}
120
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
142bool 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()) {
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
177outcome 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();
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
208outcome 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
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
265void 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 int length() const =0
Returns the current length of the string.
virtual const char * observe() const =0
observes the underlying pointer to the zero-terminated 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
static basis::astring make_logfile_name(const basis::astring &base_name)
generates an installation appropriate log file name from "base_name".
static basis::astring application_name()
returns the full name of the current application.
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".
@ FROM_START
offset is from the beginning of the file.
Definition byte_filer.h:94
@ FROM_END
offset is from the end of the file.
Definition byte_filer.h:95
bool seek(int where, origins origin=FROM_START)
places the cursor in the file at "where", based on the "origin".
int read(basis::abyte *buffer, int buffer_size)
reads "buffer_size" bytes from the file into "buffer".
bool eof()
returns true if the cursor is at (or after) the end of the file.
size_t tell()
returns the current position within the file, in terms of bytes.
void flush()
forces any pending writes to actually be saved to the file.
bool good()
returns true if the file seems to be in the appropriate desired state.
static bool recursive_create(const basis::astring &directory_name)
returns true if the "directory_name" can be created or already exists.
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.
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.
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.
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
static void text_dump(basis::astring &output, const basis::abyte *location, basis::un_int length, basis::un_int label=0, const char *eol="\n")
prints out a block of memory in a human readable form.
static const char * platform_eol_to_chars()
provides the characters that make up this platform's line ending.
#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.
A logger that sends to the console screen using the standard output device.
const int REDUCE_FACTOR
const int MAXIMUM_BUFFER_SIZE
int static_chaos()
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