[MEDIUM] add support for logging via a UNIX socket

The code in haproxy-1.3.13.1 only supports syslogging to an internet
address. The attached patch:

 - Adds support for syslogging to a UNIX domain socket (e.g., /dev/log).
   If the address field begins with '/' (absolute file path), then
   AF_UNIX is used to construct the socket. Otherwise, AF_INET is used.

 - Achieves clean single-source build on both Mac OS X and Linux
   (sockaddr_in.sin_len and sockaddr_un.sun_len field aren't always present).

For handling sendto() failures in send_log(), it appears that the existing
code is fine (no need to close/recreate socket) for both UDP and UNIX-domain
syslog server. So I left things alone (did not close/recreate socket).
Closing/recreating socket after each failure would also work, but would lead
to increased amount of unnecessary socket creation/destruction if syslog is
temporarily unavailable for some reason (especially for verbose loggers).

Please consider this patch for inclusion into the upstream haproxy codebase.
This commit is contained in:
Robert Tsai 2007-12-05 10:47:29 +01:00 committed by Willy Tarreau
parent ddbb82ff47
commit 81ae1953bf
8 changed files with 177 additions and 46 deletions

View File

@ -84,10 +84,20 @@ group <group name>
log <address> <facility> [max level]
Adds a global syslog server. Up to two global servers can be defined. They
will receive logs for startups and exits, as well as all logs from proxies
configured with "log global". <address> is an IPv4 address optionally
followed by a colon and an UDP port. If no port is specified, 514 is used
by default (the standard syslog port). <facility> must be one of the 24
standard syslog facilities :
configured with "log global".
<address> can be one of:
- An IPv4 address optionally followed by a colon and an UDP port. If
no port is specified, 514 is used by default (the standard syslog
port).
- A filesystem path to a UNIX domain socket, keeping in mind
considerations for chroot (be sure the path is accessible inside
the chroot) and uid/gid (be sure the path is appropriately
writeable).
<facility> must be one of the 24 standard syslog facilities :
kern user mail daemon auth syslog lpr news
uucp cron auth2 ftp ntp audit alert cron2

View File

@ -124,6 +124,12 @@ extern int ishex(char s);
*/
extern const char *invalid_char(const char *name);
/*
* converts <str> to a struct sockaddr_un* which is locally allocated.
* The format is "/path", where "/path" is a path to a UNIX domain socket.
*/
struct sockaddr_un *str2sun(char *str);
/*
* converts <str> to a struct sockaddr_in* which is locally allocated.
* The format is "addr:port", where "addr" can be a dotted IPv4 address,

View File

@ -25,6 +25,7 @@
#include <netinet/in.h>
#include <common/config.h>
#include <types/log.h>
#include <types/protocols.h>
#include <types/task.h>
@ -59,7 +60,7 @@ struct global {
char *pidfile;
int logfac1, logfac2;
int loglev1, loglev2;
struct sockaddr_in logsrv1, logsrv2;
struct logsrv logsrv1, logsrv2;
struct {
int maxpollevents; /* max number of poll events at once */
} tune;

View File

@ -22,6 +22,8 @@
#ifndef _TYPES_LOG_H
#define _TYPES_LOG_H
#include <sys/un.h>
#include <netinet/in.h>
#include <common/config.h>
#define MAX_SYSLOG_LEN 1024
@ -44,6 +46,15 @@
#define LW_REQHDR 1024 /* request header(s) */
#define LW_RSPHDR 2048 /* response header(s) */
struct logsrv {
union {
struct sockaddr addr;
struct sockaddr_un un; /* AF_UNIX */
struct sockaddr_in in; /* AF_INET */
} u;
};
int logsrv_addrlen(const struct logsrv *logsrv);
#endif /* _TYPES_LOG_H */

View File

@ -38,6 +38,7 @@
#include <types/acl.h>
#include <types/buffers.h>
#include <types/httperr.h>
#include <types/log.h>
#include <types/protocols.h>
#include <types/session.h>
#include <types/server.h>
@ -206,7 +207,7 @@ struct proxy {
struct sockaddr_in tproxy_addr; /* non-local address we want to bind to for connect() */
#endif
struct proxy *next;
struct sockaddr_in logsrv1, logsrv2; /* 2 syslog servers */
struct logsrv logsrv1, logsrv2; /* 2 syslog servers */
signed char logfac1, logfac2; /* log facility for both servers. -1 = disabled */
int loglev1, loglev2; /* log level for each server, 7 by default */
int to_log; /* things to be logged (LW_*) */

View File

@ -425,7 +425,7 @@ int cfg_parse_global(const char *file, int linenum, char **args)
global.pidfile = strdup(args[1]);
}
else if (!strcmp(args[0], "log")) { /* syslog server address */
struct sockaddr_in *sa;
struct logsrv logsrv;
int facility, level;
if (*(args[1]) == 0 || *(args[2]) == 0) {
@ -448,17 +448,23 @@ int cfg_parse_global(const char *file, int linenum, char **args)
}
}
sa = str2sa(args[1]);
if (!sa->sin_port)
sa->sin_port = htons(SYSLOG_PORT);
if (args[1][0] == '/') {
logsrv.u.addr.sa_family = AF_UNIX;
logsrv.u.un = *str2sun(args[1]);
} else {
logsrv.u.addr.sa_family = AF_INET;
logsrv.u.in = *str2sa(args[1]);
if (!logsrv.u.in.sin_port)
logsrv.u.in.sin_port = htons(SYSLOG_PORT);
}
if (global.logfac1 == -1) {
global.logsrv1 = *sa;
global.logsrv1 = logsrv;
global.logfac1 = facility;
global.loglev1 = level;
}
else if (global.logfac2 == -1) {
global.logsrv2 = *sa;
global.logsrv2 = logsrv;
global.logfac2 = facility;
global.loglev2 = level;
}
@ -1639,7 +1645,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args)
newsrv->prev_state = newsrv->state;
}
else if (!strcmp(args[0], "log")) { /* syslog server address */
struct sockaddr_in *sa;
struct logsrv logsrv;
int facility;
if (*(args[1]) && *(args[2]) == 0 && !strcmp(args[1], "global")) {
@ -1668,17 +1674,25 @@ int cfg_parse_listen(const char *file, int linenum, char **args)
}
}
sa = str2sa(args[1]);
if (!sa->sin_port)
sa->sin_port = htons(SYSLOG_PORT);
if (args[1][0] == '/') {
logsrv.u.addr.sa_family = AF_UNIX;
logsrv.u.un = *str2sun(args[1]);
} else {
logsrv.u.addr.sa_family = AF_INET;
logsrv.u.in = *str2sa(args[1]);
if (!logsrv.u.in.sin_port) {
logsrv.u.in.sin_port =
htons(SYSLOG_PORT);
}
}
if (curproxy->logfac1 == -1) {
curproxy->logsrv1 = *sa;
curproxy->logsrv1 = logsrv;
curproxy->logfac1 = facility;
curproxy->loglev1 = level;
}
else if (curproxy->logfac2 == -1) {
curproxy->logsrv2 = *sa;
curproxy->logsrv2 = logsrv;
curproxy->logfac2 = facility;
curproxy->loglev2 = level;
}

110
src/log.c
View File

@ -18,6 +18,7 @@
#include <syslog.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
@ -30,6 +31,9 @@
#include <types/log.h>
#include <types/session.h>
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL (0)
#endif /* !MSG_NOSIGNAL */
const char *log_facilities[NB_LOG_FACILITIES] = {
"kern", "user", "mail", "daemon",
@ -140,6 +144,32 @@ int get_log_facility(const char *fac)
return facility;
}
/*
* Return the length of the address endpoint, suitable for use with sendto().
*/
int logsrv_addrlen(const struct logsrv *logsrv)
{
#ifdef __SOCKADDR_COMMON
switch (logsrv->u.addr.sa_family) {
case AF_UNIX:
return sizeof(logsrv->u.un);
case AF_INET:
return sizeof(logsrv->u.in);
default:
break;
}
#else /* !__SOCKADDR_COMMON */
switch (logsrv->u.addr.sa_family) {
case AF_UNIX:
return logsrv->u.un.sun_len;
case AF_INET:
return logsrv->u.in.sin_len;
default:
break;
}
#endif /* !__SOCKADDR_COMMON */
return -1;
}
/*
* This function sends a syslog message to both log servers of a proxy,
@ -149,29 +179,20 @@ int get_log_facility(const char *fac)
*/
void send_log(struct proxy *p, int level, const char *message, ...)
{
static int logfd = -1; /* syslog UDP socket */
static int logfdunix = -1; /* syslog to AF_UNIX socket */
static int logfdinet = -1; /* syslog to AF_INET socket */
static long tvsec = -1; /* to force the string to be initialized */
va_list argp;
static char logmsg[MAX_SYSLOG_LEN];
static char *dataptr = NULL;
int fac_level;
int hdr_len, data_len;
struct sockaddr_in *sa[2];
struct logsrv *logsrvs[2];
int facilities[2], loglevel[2];
int nblogger;
int nbloggers = 0;
char *log_ptr;
if (logfd < 0) {
if ((logfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
return;
/* we don't want to receive anything on this socket */
setsockopt(logfd, SOL_SOCKET, SO_RCVBUF, &zero, sizeof(zero));
/* need for AIX which does not know about MSG_DONTWAIT */
if (!MSG_DONTWAIT)
fcntl(logfd, F_SETFL, O_NONBLOCK);
shutdown(logfd, SHUT_RD); /* does nothing under Linux, maybe needed for others */
}
if (level < 0 || progname == NULL || message == NULL)
return;
@ -210,35 +231,70 @@ void send_log(struct proxy *p, int level, const char *message, ...)
if (p == NULL) {
if (global.logfac1 >= 0) {
sa[nbloggers] = &global.logsrv1;
logsrvs[nbloggers] = &global.logsrv1;
facilities[nbloggers] = global.logfac1;
loglevel[nbloggers] = global.loglev1;
nbloggers++;
}
if (global.logfac2 >= 0) {
sa[nbloggers] = &global.logsrv2;
logsrvs[nbloggers] = &global.logsrv2;
facilities[nbloggers] = global.logfac2;
loglevel[nbloggers] = global.loglev2;
nbloggers++;
}
} else {
if (p->logfac1 >= 0) {
sa[nbloggers] = &p->logsrv1;
logsrvs[nbloggers] = &p->logsrv1;
facilities[nbloggers] = p->logfac1;
loglevel[nbloggers] = p->loglev1;
nbloggers++;
}
if (p->logfac2 >= 0) {
sa[nbloggers] = &p->logsrv2;
logsrvs[nbloggers] = &p->logsrv2;
facilities[nbloggers] = p->logfac2;
loglevel[nbloggers] = p->loglev2;
nbloggers++;
}
}
while (nbloggers-- > 0) {
/* Lazily set up syslog sockets for protocol families of configured
* syslog servers. */
for (nblogger = 0; nblogger < nbloggers; nblogger++) {
const struct logsrv *logsrv = logsrvs[nblogger];
int proto, *plogfd;
if (logsrv->u.addr.sa_family == AF_UNIX) {
proto = 0;
plogfd = &logfdunix;
} else {
/* sa_family == AF_INET */
proto = IPPROTO_UDP;
plogfd = &logfdinet;
}
if (*plogfd >= 0) {
/* socket already created. */
continue;
}
if ((*plogfd = socket(logsrv->u.addr.sa_family, SOCK_DGRAM,
proto)) < 0) {
Alert("socket for logger #%d failed: %s (errno=%d)\n",
nblogger + 1, strerror(errno), errno);
return;
}
/* we don't want to receive anything on this socket */
setsockopt(*plogfd, SOL_SOCKET, SO_RCVBUF, &zero, sizeof(zero));
/* does nothing under Linux, maybe needed for others */
shutdown(*plogfd, SHUT_RD);
}
/* Send log messages to syslog server. */
for (nblogger = 0; nblogger < nbloggers; nblogger++) {
const struct logsrv *logsrv = logsrvs[nblogger];
int *plogfd = logsrv->u.addr.sa_family == AF_UNIX ?
&logfdunix : &logfdinet;
int sent;
/* we can filter the level of the messages that are sent to each logger */
if (level > loglevel[nbloggers])
if (level > loglevel[nblogger])
continue;
/* For each target, we may have a different facility.
@ -248,7 +304,7 @@ void send_log(struct proxy *p, int level, const char *message, ...)
* time, we only change the facility in the pre-computed header,
* and we change the pointer to the header accordingly.
*/
fac_level = (facilities[nbloggers] << 3) + level;
fac_level = (facilities[nblogger] << 3) + level;
log_ptr = logmsg + 3; /* last digit of the log level */
do {
*log_ptr = '0' + fac_level % 10;
@ -258,14 +314,12 @@ void send_log(struct proxy *p, int level, const char *message, ...)
*log_ptr = '<';
/* the total syslog message now starts at logptr, for dataptr+data_len-logptr */
#ifndef MSG_NOSIGNAL
sendto(logfd, log_ptr, dataptr + data_len - log_ptr, MSG_DONTWAIT,
(struct sockaddr *)sa[nbloggers], sizeof(**sa));
#else
sendto(logfd, log_ptr, dataptr + data_len - log_ptr, MSG_DONTWAIT | MSG_NOSIGNAL,
(struct sockaddr *)sa[nbloggers], sizeof(**sa));
#endif
sent = sendto(*plogfd, log_ptr, dataptr + data_len - log_ptr,
MSG_DONTWAIT | MSG_NOSIGNAL, &logsrv->u.addr, logsrv_addrlen(logsrv));
if (sent < 0) {
Alert("sendto logger #%d failed: %s (errno=%d)\n",
nblogger, strerror(errno), errno);
}
}
}

View File

@ -77,6 +77,37 @@ const char *limit_r(unsigned long n, char *buffer, int size, const char *alt)
return (n) ? ultoa_r(n, buffer, size) : (alt ? alt : "");
}
/*
* converts <str> to a struct sockaddr_un* which is locally allocated.
* The format is "/path", where "/path" is a path to a UNIX domain socket.
*/
struct sockaddr_un *str2sun(char *str)
{
static struct sockaddr_un sun;
int strsz; /* length included null */
memset(&sun, 0, sizeof(sun));
str = strdup(str);
if (str == NULL)
goto out_nofree;
strsz = strlen(str) + 1;
if (strsz > sizeof(sun.sun_path)) {
Alert("Socket path '%s' too long (max %d)\n",
str, sizeof(sun.sun_path) - 1);
goto out_nofree;
}
#ifndef __SOCKADDR_COMMON
sun.sun_len = sizeof(sun);
#endif /* !__SOCKADDR_COMMON */
sun.sun_family = AF_UNIX;
memcpy(sun.sun_path, str, strsz);
free(str);
out_nofree:
return &sun;
}
/*
* Returns non-zero if character <s> is a hex digit (0-9, a-f, A-F), else zero.
@ -153,6 +184,9 @@ struct sockaddr_in *str2sa(char *str)
else
sa.sin_addr = *(struct in_addr *) *(he->h_addr_list);
}
#ifndef __SOCKADDR_COMMON
sa.sin_len = sizeof(sa);
#endif /* !__SOCKADDR_COMMON */
sa.sin_port = htons(port);
sa.sin_family = AF_INET;