A lot of times people do zany things and try and reinvent wheels when it comes to programming. Sometimes this is good: when learning, when trying to improve state of the art, or when trying to simplify when only Two-Ton solutions are available.

For a current daemon project I need good, fast, thread-safe logging. syslog fits the bill to a tee and using anything else would be downright foolish -- akin to implementing my own relational database. There's one caveat. For development and debugging, I'd like to not fork/daemonize and instead output messages to stdout. Some implementations of syslog() define LOG_PERROR, but this is not in POSIX.1-2008 and it also logs to both stderr and wherever the syslog sink is set. That may not be desired.

So, the goals here are: continue to use syslog() for the normal case as it is awesome, but allow console output in a portable way. Non-goals were using something asinine like a reimplementation of Log4Bloat or other large attempt at thread-safe logging from scratch.

Using function pointers, we can get a close approximation of an Interface or Virtual Function of Object Oriented languages:

void (*LOG)(int, const char *, ...);  
int (*LOG_setmask)(int);  

These are the same parameters that POSIX syslog() and setlogmask() take. Now, at runtime, if we desire to use the the "real" syslog:

LOG = &syslog;  
LOG_setmask = &setlogmask;  

If we wish to instead log to console, a little more work is in order. Essentially, we need to define a console logging function "inheriting" the syslog() "method signature" (or arguments for non-OO types).

/* In a header somewhere */  
void log_console(int priority, const char *format, ...);  
int log_console_setlogmask(int mask);  

And finally, a basic console output format:

/* Private storage for the current mask */
static int log_consolemask;

int log_console_setlogmask(int mask)
  int oldmask = log_consolemask;
  if(mask == 0)
    return oldmask; /* POSIX definition for 0 mask */
  log_consolemask = mask;
  return oldmask;

void log_console(int priority, const char *format, ...)
  va_list arglist;
  const char *loglevel;
  va_start(arglist, format);

  /* Return on MASKed log priorities */
  if (LOG_MASK(priority) & log_consolemask)

  case LOG_ALERT:
    loglevel = "ALERT: ";
  case LOG_CRIT:
    loglevel = "CRIT: ";
  case LOG_DEBUG:
    loglevel = "DEBUG: ";
  case LOG_EMERG:
    loglevel = "EMERG: ";
  case LOG_ERR:
    loglevel = "ERR: ";
  case LOG_INFO:
    loglevel = "INFO: ";
  case LOG_NOTICE:
    loglevel = "NOTICE: ";
    loglevel = "WARNING: ";
    loglevel = "UNKNOWN: ";

  printf("%s", loglevel);
  vprintf(format, arglist);

Now, if console output is what you desire at runtime you could use something like this:

LOG = &log_console;  
LOG_setmask = &log_console_setlogmask;  

LOG(LOG_INFO, "Program Started!");  

In about 60 lines of code we got the desired functionality by slightly extending rather than reinventing things or pulling in a large external dependency. If C++ is your cup of tea, it is left as a trivial reimplementation where you can store the console logmask as a private class variable.

Some notes:

  1. You should still call openlog() at the beginning of your program in case syslog() is selected at runtime. Likewise, you should still call closelog() at exit.
  2. It's left as a trivial exercise to the reader to define another function to do logging to both stdout and, using vsyslog(), the syslog. This implements LOG_PERROR in a portable way.
  3. I chose stdout because it is line buffered by default. If you use stderr, you should combine the loglevel, format, and newline with sprintf before calling vprintf on the variable arglist to prevent jumbled messages.
  4. Of course, make sure you are cognizant that the format string is passed in and do not allow any user-supplied format strings as usual.


comments powered by Disqus