Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Simple syslog client

0.00/5 (No votes)
3 Sep 2003 2  
A BSD-compatible syslog client

Introduction

This article describes a simple BSD-compatible syslog client. The client will log one-line messages to a .log-file and/or logging daemon. There are probably several other similar clients around, but I decided to make my own. This client will work fine in a Unix environment where typically a server collects status information from several machines.

I decided not to make this into a static library or a .DLL. Using it in your own application is as easy as including 4 files into our project (syslog.[ch] and printk.[ch])

The syslog()facility (as defined by the syslog.h header-file) is a rather difficult and clumsy API. E.g. the authors defined the higher log-priorities as lower numbered values. This implementation is not fully POSIX compatible; E.g. it does not honour the LOG_ODELAY or LOG_NOWAIT options. But then again these seems to be deprecated in current BSD OS'es. See the POSIX syslog-spec for a comparision.

Using the code

The client is initialised by calling the openlog() function. This will cause all syslog() messages to be written to a .log-file and optionally to be sent to a local syslog-daemon (at localhost/127.0.0.1). The default .log-file is deducted from the application using the client; E.g. c:\temp\foo.exe will open c:\temp\foo.log and append to that file. A line written to the .log-file could look like this:

<150>2003-09-03 21:00:39 demo[1604]: syslog client at 10.0.0.6 started.
(1)  (2)                 (3) (4)     (5)
  1. This is the message priority (LOG_EMERG - LOG_DEBUG) and facility value (LOG_KERN - LOG_LOCAL7) OR'ed together. This field cannot be supressed. The priority is in the lower 4 bits and facility value in the rest. Use the LOG_PRI() macro to extract the priority. And use the LOG_FAC() macro to extract the facility.
  2. The local date on YYYY-MM-DD (ISO-9601) format and time on 24h format.
  3. The log-tag or identifier from openlog() or setlogtag().
  4. The process-id (pid) of the running process. Only shown if LOG_PID was given in openlog().
  5. The actual message format given to syslog() or vsyslog()
     

The UDP port used is 514 unless specified differently in %SystemRoot%\system32\drivers\etc\services. A line like this should be used:

  syslog   514/udp

Note that lines written to syslog log-file at the daemon side is different; The date/time is
first. Then the IP-address from where the message was received, then fields (3) to (5).

Functions Reference

int openlog (const char * ident, int options, int logfac);

This function must be called before syslog(). The ident determines the 3rd (demo) parameter on the syslog line. options is a combination of OR'ed values;

  • LOG_PID - The process identifier should be included in log-line.
  • LOG_CONS - Log to console (stdout) for LOG_ERR messages.
  • LOG_PERROR - Log to console (stderr) as well.
  • LOG_NDELAY - log file/connection should be opened immediately.
  • LOG_ODELAY, LOG_NOWAIT - These are ignored. 

logfac is the facility to log to (LOG_KERN - LOG_LOCAL7).

Returns -1 if failed to open .log-file for writing or failed to connect to the syslog-daemon.

int closelog (void);

Close the log-file and/or UDP connection to the syslog-daemon. It's not necessary to call closelog() prior to exit as it is registered as an atexit() function.

int setlogmask (int mask);

If mask is non-zero, sets a new priority logmask and returns the previous value. Default is to log everything (logMask = 0xFF).

int syslog (int pri, const char * fmt, ...);

Sets up the var-arg list and calls vsyslog().

int vsyslog (int pri, va_list ap);

The main logging function. Handle the message if pri is above current priority. See <syslog.h> for the priority codes. Prints message to stderr if LOG_PERROR was specified in openlog(). Or if sendto() fails and LOG_CONS was specified in openlog().

Returns 0 if message is not handled or is written okay. Otherwise use syslog_strerror() to get the error-text of last error.

Extensions in this implementation

const char * syslog_loghost (const char * host);

Specifies the host to send messages to. A hostname or dotted IPv4-address is accepted. Use 0.0.0.0 to disable sending to syslog-daemon. Or use 255.255.255.255 to send to any daemon that's setup to receive broadcast messages. It must be on your local network as broadcast doesn't reach beyond a router. This function should be called prior to openlog() if the default 127.0.0.1 destination is not wanted.

Returns NULL if unable to resolve the host to an IP-address.

const char * syslog_logfilename (void);

Returns the name of current .log-file.

const char * syslog_strerror (void);

Returns an error-string for last failed operation.

Message formats

syslog() and vsyslog() supports a limited sub-set of formats compared to the printf() family of functions. E.g. floating-point formats and long modifier (%lu) are not supported at this time. These special formats are also supported:
  • %I - Prints the corresponding argument as an IP-address on network order.
  • %t - Prints the local date and time (ISO-9601).
  • %m - Prints the error-string for current errno.
  • %M - Prints the error-string for current GetLastError().
  • %S - Prints the name of a signal in the argument list.

As you see the %S is reserved for printing a signal-name. So printing wide-character strings are not possible using syslog().

Implementation Details

Since all most syslog-clients uses connection-less UDP messages, it's a bit difficult to know if the syslogd is reachable or even listening on port 514. Because Winsock's sendto() on UDP messages gives rather limited error messages compared to BSD or Linux, my implementation uses some tricks;

  • It obtains the local IP-address and netmask and does an ARP-request. If there's no ARP-reply, the socket is closed.
  • If sending to a (directed) broadcast address, setsockopt() with SO_BROADCAST is called.
  • Sending the message is done by sendto(). The only accepted error is WSAEMSGSIZE, otherwise the socket is closed. Truncating a message is IMHO acceptable.
Studying the MSDN, gave me the impression that sending to a closed port would give WSAECONNRESET (ICMP port unreachable) on a subsequent call to sendto(). This doesn't seem to be true in my case (Win-XP). And even worse; sending to a non-existant host (on the LAN) does not give a WSAEHOSTUNREACH either. Sigh. So this client will transmit and generate an ICMP error for every message sent unless there is a syslog-daemon at the destination address (ignoring ICMP rate limiting of the destination host). But syslog messages should be used sparingly. Don't write all kind of debug-messages to syslogd. It's better to use some local file for that.

Points of Interest

To receive syslog messages over the network (or the loopback interface), you off-course need a syslog daemon (server). I highly recommend Herbert Hanewinkel's syslog-daemon for Windows. Look at http://www.hanewin.de/syslog-e.htm. This is a light-weight, no frills and stable little syslog-daemon. A minor drawback is that it doesn't handle fragmented messages; I.e. will only log messages that fits in one MTU.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here