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)
- 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.
- The local date on YYYY-MM-DD (ISO-9601) format and time on 24h format.
- The log-tag or identifier from
openlog()
or setlogtag().
- The process-id (pid) of the running process. Only shown if
LOG_PID
was given in openlog()
.
- 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.