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])
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_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: syslog client at 10.0.0.6 started.
(1) (2) (3) (4) (5)
- This is the message priority (
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.
- The local date on YYYY-MM-DD (ISO-9601) format and time on 24h format.
- The log-tag or identifier from
- The process-id (pid) of the running process. Only shown if
LOG_PID was given in
- The actual message format given to
The UDP port used is 514 unless specified differently in %SystemRoot%\system32\drivers\etc\services. A line like this should be used:
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).
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 (
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
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
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
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.
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.
supports a limited sub-set of formats compared to the
family of functions. E.g. floating-point formats and long modifier (
) 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
%M - Prints the error-string for current
%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
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
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,
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
(ICMP port unreachable
) on a subsequent call to
. 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
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.