WvStreams
wvlog.cc
1 /*
2  * Worldvisions Weaver Software:
3  * Copyright (C) 1997-2002 Net Integration Technologies, Inc.
4  *
5  * Functions needed to implement general WvLog class.
6  *
7  * See wvlog.h for more information.
8  */
9 #include "wvlogrcv.h"
10 #include "wvstringlist.h"
11 #include "strutils.h"
12 #include "wvfork.h"
13 
14 #include <ctype.h>
15 
16 #ifdef _WIN32
17 #define snprintf _snprintf
18 #endif
19 
20 WvLogRcvBaseList *WvLog::receivers;
21 int WvLog::num_receivers = 0, WvLog::num_logs = 0;
22 WvLogRcvBase *WvLog::default_receiver = NULL;
23 
24 const char *WvLogRcv::loglevels[WvLog::NUM_LOGLEVELS] = {
25  "Crit",
26  "Err",
27  "Warn",
28  "Notice",
29  "Info",
30  "*1",
31  "*2",
32  "*3",
33  "*4",
34  "*5",
35 };
36 
37 
38 
40 
41 
42 
43 WvLog::WvLog(WvStringParm _app, LogLevel _loglevel, WvLogFilter* _filter)
44  : app(_app), loglevel(_loglevel), filter(_filter)
45 {
46 // printf("log: %s create\n", app.cstr());
47  num_logs++;
48  set_wsname(app);
49 }
50 
51 
52 WvLog::WvLog(const WvLog &l)
53  : app(l.app), loglevel(l.loglevel), filter(l.filter)
54 {
55 // printf("log: %s create\n", app.cstr());
56  num_logs++;
57  set_wsname(app);
58 }
59 
60 
61 WvLog::~WvLog()
62 {
63  num_logs--;
64  if (!num_logs && default_receiver)
65  {
66  num_receivers++; // deleting default does not really reduce
67  delete default_receiver;
68  default_receiver = NULL;
69  }
70 // printf("log: %s delete\n", app.cstr());
71 // printf("num_logs is now %d\n", num_logs);
72 }
73 
74 
75 bool WvLog::isok() const
76 {
77  return true;
78 }
79 
80 
82 {
83  // a wvlog is always writable...
84  if (si.wants.writable)
85  si.msec_timeout = 0;
86  else
88 }
89 
90 
92 {
93  // a wvlog is always writable...
94  if (si.wants.writable)
95  return true;
96  else
97  return WvStream::post_select(si);
98 }
99 
100 
101 size_t WvLog::uwrite(const void *_buf, size_t len)
102 {
103  // Writing the log message to a stream might cause it to emit its own log
104  // messages, causing recursion. Don't let it get out of hand.
105  static const int recursion_max = 8;
106  static int recursion_count = 0;
107  static WvString recursion_msg("Too many extra log messages written while "
108  "writing to the log. Suppressing additional messages.\n");
109 
110  ++recursion_count;
111 
112  if (!num_receivers)
113  {
114  if (!default_receiver)
115  {
116  // nobody's listening -- create a receiver on the console
117  int xfd = dup(2);
118  default_receiver = new WvLogConsole(xfd);
119  num_receivers--; // default does not qualify!
120  }
121 
122  if (recursion_count < recursion_max)
123  default_receiver->log(app, loglevel, (const char *)_buf, len);
124  else if (recursion_count == recursion_max)
125  default_receiver->log(app, WvLog::Warning, recursion_msg.cstr(),
126  recursion_msg.len());
127 
128  --recursion_count;
129  return len;
130  }
131  else if (default_receiver)
132  {
133  // no longer empty list -- delete our default to stderr
134  num_receivers++; // deleting default does not really reduce
135  delete default_receiver;
136  default_receiver = NULL;
137  }
138 
139  assert(receivers);
140  WvLogRcvBaseList::Iter i(*receivers);
141  for (i.rewind(); i.next(); )
142  {
143  WvLogRcvBase &rc = *i;
144 
145  if (recursion_count < recursion_max)
146  rc.log(app, loglevel, (const char *)_buf, len);
147  else if (recursion_count == recursion_max)
148  rc.log(app, WvLog::Warning, recursion_msg.cstr(),
149  recursion_msg.len());
150  }
151 
152  --recursion_count;
153  return len;
154 }
155 
156 
157 
159 
160 
161 
162 WvLogRcvBase::WvLogRcvBase()
163 {
164  static_init();
165  WvLogRcvBase::force_new_line = false;
166  if (!WvLog::receivers)
167  WvLog::receivers = new WvLogRcvBaseList;
168  WvLog::receivers->append(this, false);
169  WvLog::num_receivers++;
170 }
171 
172 
173 WvLogRcvBase::~WvLogRcvBase()
174 {
175  assert(WvLog::receivers);
176  WvLog::receivers->unlink(this);
177  if (WvLog::receivers->isempty())
178  {
179  delete WvLog::receivers;
180  WvLog::receivers = NULL;
181  }
182  WvLog::num_receivers--;
183 }
184 
185 
186 const char *WvLogRcvBase::appname(WvStringParm log) const
187 {
188  if (log)
189  return log;
190  else
191  return "unknown";
192 }
193 
194 
195 void WvLogRcvBase::static_init()
196 {
197  static bool init = false;
198  if (!init)
199  {
200 #ifndef _WIN32
201  add_wvfork_callback(WvLogRcvBase::cleanup_on_fork);
202 #endif
203  init = true;
204  }
205 }
206 
207 
208 void WvLogRcvBase::cleanup_on_fork(pid_t p)
209 {
210  if (p) return; // parent: do nothing
211 
212  if (WvLog::receivers)
213  WvLog::receivers->zap();
214  delete WvLog::default_receiver;
215  WvLog::default_receiver = NULL;
216  WvLog::num_receivers = 0;
217 }
218 
219 
220 
222 
223 
224 
225 WvLogRcv::WvLogRcv(WvLog::LogLevel _max_level) : custom_levels(5)
226 {
227  last_source = WvString();
228  last_level = WvLog::NUM_LOGLEVELS;
229  last_time = 0;
230  max_level = _max_level;
231  at_newline = true;
232 }
233 
234 
235 WvLogRcv::~WvLogRcv()
236 {
237 }
238 
239 
240 void WvLogRcv::_make_prefix(time_t now)
241 {
242  prefix = WvString("%s<%s>: ",
243  last_source, loglevels[last_level]);
244  prelen = prefix.len();
245 }
246 
247 
249 {
250  mid_line(prefix, prelen);
251 }
252 
253 
255 {
256  // do nothing
257 }
258 
259 
260 // like isprint(), but always treats chars >128 as printable, because they
261 // always are (even if they're meaningless)
262 static bool my_isprint(char _c)
263 {
264  unsigned char c = _c;
265  if (isprint(c) || c >= 128)
266  return true;
267  else
268  return false;
269 }
270 
271 
272 void WvLogRcv::log(WvStringParm source, int _loglevel,
273  const char *_buf, size_t len)
274 {
275  WvLog::LogLevel loglevel = (WvLog::LogLevel)_loglevel;
276  char hex[5];
277  WvLog::LogLevel threshold = max_level;
278  WvString srcname(source);
279  strlwr(srcname.edit());
280 
281  Src_LvlDict::Iter i(custom_levels);
282  i.rewind();
283 
284  // Check if the debug level for the source has been overridden
285  while (i.next())
286  {
287  if (strstr(srcname, i->src))
288  {
289  threshold = i->lvl;
290  break;
291  }
292  }
293 
294  if (loglevel > threshold)
295  return;
296 
297  // only need to start a new line with new headers if they headers have
298  // changed. if the source and level are the same as before, just continue
299  // the previous log entry.
300  time_t now = wvtime().tv_sec;
301  if (source != last_source
302  || loglevel != last_level
303  || WvLogRcvBase::force_new_line)
304  {
305  end_line();
306  last_source = source;
307  last_level = loglevel;
308  last_time = now;
309  _make_prefix(now);
310  }
311  else if (last_time == 0 || now != last_time)
312  {
313  // ensure that even with the same source and level, logs will
314  // properly get the right time associated with them. however,
315  // don't split up log messages that should appear in a single
316  // log line.
317  last_time = now;
318  if (at_newline)
319  _make_prefix(now);
320  }
321 
322  const char *buf = (const char *)_buf, *bufend = buf + len, *cptr;
323 
324  // loop through the buffer, printing each character or its [hex] equivalent
325  // if it is unprintable. Also eat newlines unless they are appropriate.
326  while (buf < bufend)
327  {
328  if (buf[0] == '\n' || buf[0] == '\r')
329  {
330  end_line();
331  buf++;
332  continue;
333  }
334 
335  begin_line();
336 
337  if (buf[0] == '\t')
338  {
339  mid_line(" ", 1);
340  buf++;
341  continue;
342  }
343  else if (!my_isprint(buf[0]))
344  {
345  snprintf(hex, 5, "[%02x]", buf[0]);
346  mid_line(hex, 4);
347  buf++;
348  continue;
349  }
350 
351  // like strchr, but size-limited instead of null-terminated
352  for (cptr = buf; cptr < bufend; cptr++)
353  {
354  if (*cptr == '\n' || !my_isprint(*cptr))
355  break;
356  }
357 
358  if (cptr >= bufend) // end of buffer
359  {
360  mid_line(buf, bufend - buf);
361  buf = bufend;
362  }
363  else if (*cptr == '\n') // end of line
364  {
365  mid_line((const char *)buf, cptr - buf);
366  buf = cptr;
367  }
368  else // therefore (!my_isprint(*cptr))
369  {
370  mid_line(buf, cptr - buf);
371  buf = cptr;
372  }
373  }
374 }
375 
376 // input format: name=number, name=number, name=number, etc.
377 // 'name' is the name of a log service
378 // 'number' is the number of the log level to use.
379 bool WvLogRcv::set_custom_levels(WvString descr)
380 {
381  custom_levels.zap();
382 
383  // Parse the filter line into individual rules
384  WvStringList lst;
385  WvStringList::Iter i(lst);
386  lst.split(descr, ",= ");
387  if (lst.isempty())
388  return true;
389  WvString src("");
390 
391  for (i.rewind(); i.next(); )
392  {
393  if (src != "")
394  {
395  if (atoi(*i) > 0 && atoi(*i) <= WvLog::NUM_LOGLEVELS)
396  {
397  custom_levels.add(new Src_Lvl(src, atoi(*i)), true);
398  src = "";
399  }
400  else
401  return false;
402  }
403  else
404  {
405  src = *i;
406  strlwr(trim_string(src.edit()));
407  }
408  }
409  if (src != "")
410  return false;
411 
412  return true;
413 }
414 
415 
417 
418 
419 
420 WvLogConsole::WvLogConsole(int _fd, WvLog::LogLevel _max_level) :
421  WvFDStream(_fd), WvLogRcv(_max_level)
422 {
423 }
424 
425 
426 WvLogConsole::~WvLogConsole()
427 {
428  end_line();
429 }
430 
431 
432 void WvLogConsole::_mid_line(const char *str, size_t len)
433 {
434  uwrite(str, len);
435 }
WvLogConsole
Captures formatted log messages and outputs them to the specified file descriptor.
Definition: wvlogrcv.h:107
WvLogConsole::_mid_line
virtual void _mid_line(const char *str, size_t len)
add text to the current log line.
Definition: wvlog.cc:432
add_wvfork_callback
void add_wvfork_callback(WvForkCallback cb)
Register a callback to be called during wvfork.
Definition: wvfork.cc:51
WvFdStream::uwrite
virtual size_t uwrite(const void *buf, size_t count)
unbuffered I/O functions; these ignore the buffer, which is handled by write().
Definition: wvfdstream.cc:162
WvLog::isok
virtual bool isok() const
fd==-1, but this stream is always ok
Definition: wvlog.cc:75
WvLog::post_select
virtual bool post_select(SelectInfo &si)
post_select() is called after ::select(), and returns true if this object is now ready.
Definition: wvlog.cc:91
trim_string
char * trim_string(char *string)
Trims whitespace from the beginning and end of the character string, including carriage return / line...
Definition: strutils.cc:59
WvString
WvString is an implementation of a simple and efficient printable-string class.
Definition: wvstring.h:329
WvLog
A WvLog stream accepts log messages from applications and forwards them to all registered WvLogRcv's.
Definition: wvlog.h:56
WvLogRcvBase
Definition: wvlog.h:29
IWvStream::SelectInfo
the data structure used by pre_select()/post_select() and internally by select().
Definition: iwvstream.h:50
WvFastString::cstr
const char * cstr() const
return a (const char *) for this string.
Definition: wvstring.h:267
WvLogRcv::_end_line
virtual void _end_line()
End this (Guaranteed NonEmpty) log line.
Definition: wvlog.cc:254
WvStream::post_select
virtual bool post_select(SelectInfo &si)
post_select() is called after ::select(), and returns true if this object is now ready.
Definition: wvstream.cc:875
WvLogRcv
WvLogRcv adds some intelligence to WvLogRcvBase, to keep track of line-prefix-printing and other form...
Definition: wvlogrcv.h:28
wvfork.h
strlwr
char * strlwr(char *string)
In-place modify a character string so that all contained letters are in lower case.
Definition: strutils.cc:201
WvLog::pre_select
virtual void pre_select(SelectInfo &si)
pre_select() sets up for eventually calling ::select().
Definition: wvlog.cc:81
WvLogRcv::_make_prefix
virtual void _make_prefix(time_t now)
Set the Prefix and Prefix Length (size_t prelen)
Definition: wvlog.cc:240
WvFdStream
Base class for streams built on Unix file descriptors.
Definition: wvfdstream.h:20
WvStringList
This is a WvList of WvStrings, and is a really handy way to parse strings.
Definition: wvstringlist.h:27
WvLogRcv::_begin_line
virtual void _begin_line()
Start a new log line (print prefix)
Definition: wvlog.cc:248
WvStringList::split
void split(WvStringParm s, const char *splitchars=" \t\r\n", int limit=0)
split s and form a list ignoring splitchars (except at beginning and end) ie.
Definition: wvstringlist.cc:19
WvLog::uwrite
virtual size_t uwrite(const void *buf, size_t len)
we override the unbuffered write function, so lines also include the application and log level.
Definition: wvlog.cc:101
WvStream::pre_select
virtual void pre_select(SelectInfo &si)
pre_select() sets up for eventually calling ::select().
Definition: wvstream.cc:844