[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] log <address> <facility> [max level]
Adds a global syslog server. Up to two global servers can be defined. They 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 will receive logs for startups and exits, as well as all logs from proxies
configured with "log global". <address> is an IPv4 address optionally configured with "log global".
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 <address> can be one of:
standard syslog facilities :
- 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 kern user mail daemon auth syslog lpr news
uucp cron auth2 ftp ntp audit alert cron2 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); 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. * converts <str> to a struct sockaddr_in* which is locally allocated.
* The format is "addr:port", where "addr" can be a dotted IPv4 address, * The format is "addr:port", where "addr" can be a dotted IPv4 address,

View File

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

View File

@ -22,6 +22,8 @@
#ifndef _TYPES_LOG_H #ifndef _TYPES_LOG_H
#define _TYPES_LOG_H #define _TYPES_LOG_H
#include <sys/un.h>
#include <netinet/in.h>
#include <common/config.h> #include <common/config.h>
#define MAX_SYSLOG_LEN 1024 #define MAX_SYSLOG_LEN 1024
@ -44,6 +46,15 @@
#define LW_REQHDR 1024 /* request header(s) */ #define LW_REQHDR 1024 /* request header(s) */
#define LW_RSPHDR 2048 /* response 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 */ #endif /* _TYPES_LOG_H */

View File

@ -38,6 +38,7 @@
#include <types/acl.h> #include <types/acl.h>
#include <types/buffers.h> #include <types/buffers.h>
#include <types/httperr.h> #include <types/httperr.h>
#include <types/log.h>
#include <types/protocols.h> #include <types/protocols.h>
#include <types/session.h> #include <types/session.h>
#include <types/server.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() */ struct sockaddr_in tproxy_addr; /* non-local address we want to bind to for connect() */
#endif #endif
struct proxy *next; 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 */ signed char logfac1, logfac2; /* log facility for both servers. -1 = disabled */
int loglev1, loglev2; /* log level for each server, 7 by default */ int loglev1, loglev2; /* log level for each server, 7 by default */
int to_log; /* things to be logged (LW_*) */ 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]); global.pidfile = strdup(args[1]);
} }
else if (!strcmp(args[0], "log")) { /* syslog server address */ else if (!strcmp(args[0], "log")) { /* syslog server address */
struct sockaddr_in *sa; struct logsrv logsrv;
int facility, level; int facility, level;
if (*(args[1]) == 0 || *(args[2]) == 0) { 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 (args[1][0] == '/') {
if (!sa->sin_port) logsrv.u.addr.sa_family = AF_UNIX;
sa->sin_port = htons(SYSLOG_PORT); 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) { if (global.logfac1 == -1) {
global.logsrv1 = *sa; global.logsrv1 = logsrv;
global.logfac1 = facility; global.logfac1 = facility;
global.loglev1 = level; global.loglev1 = level;
} }
else if (global.logfac2 == -1) { else if (global.logfac2 == -1) {
global.logsrv2 = *sa; global.logsrv2 = logsrv;
global.logfac2 = facility; global.logfac2 = facility;
global.loglev2 = level; global.loglev2 = level;
} }
@ -1639,7 +1645,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args)
newsrv->prev_state = newsrv->state; newsrv->prev_state = newsrv->state;
} }
else if (!strcmp(args[0], "log")) { /* syslog server address */ else if (!strcmp(args[0], "log")) { /* syslog server address */
struct sockaddr_in *sa; struct logsrv logsrv;
int facility; int facility;
if (*(args[1]) && *(args[2]) == 0 && !strcmp(args[1], "global")) { 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 (args[1][0] == '/') {
if (!sa->sin_port) logsrv.u.addr.sa_family = AF_UNIX;
sa->sin_port = htons(SYSLOG_PORT); 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) { if (curproxy->logfac1 == -1) {
curproxy->logsrv1 = *sa; curproxy->logsrv1 = logsrv;
curproxy->logfac1 = facility; curproxy->logfac1 = facility;
curproxy->loglev1 = level; curproxy->loglev1 = level;
} }
else if (curproxy->logfac2 == -1) { else if (curproxy->logfac2 == -1) {
curproxy->logsrv2 = *sa; curproxy->logsrv2 = logsrv;
curproxy->logfac2 = facility; curproxy->logfac2 = facility;
curproxy->loglev2 = level; curproxy->loglev2 = level;
} }

110
src/log.c
View File

@ -18,6 +18,7 @@
#include <syslog.h> #include <syslog.h>
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
#include <errno.h>
#include <sys/time.h> #include <sys/time.h>
@ -30,6 +31,9 @@
#include <types/log.h> #include <types/log.h>
#include <types/session.h> #include <types/session.h>
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL (0)
#endif /* !MSG_NOSIGNAL */
const char *log_facilities[NB_LOG_FACILITIES] = { const char *log_facilities[NB_LOG_FACILITIES] = {
"kern", "user", "mail", "daemon", "kern", "user", "mail", "daemon",
@ -140,6 +144,32 @@ int get_log_facility(const char *fac)
return facility; 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, * 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, ...) 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 */ static long tvsec = -1; /* to force the string to be initialized */
va_list argp; va_list argp;
static char logmsg[MAX_SYSLOG_LEN]; static char logmsg[MAX_SYSLOG_LEN];
static char *dataptr = NULL; static char *dataptr = NULL;
int fac_level; int fac_level;
int hdr_len, data_len; int hdr_len, data_len;
struct sockaddr_in *sa[2]; struct logsrv *logsrvs[2];
int facilities[2], loglevel[2]; int facilities[2], loglevel[2];
int nblogger;
int nbloggers = 0; int nbloggers = 0;
char *log_ptr; 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) if (level < 0 || progname == NULL || message == NULL)
return; return;
@ -210,35 +231,70 @@ void send_log(struct proxy *p, int level, const char *message, ...)
if (p == NULL) { if (p == NULL) {
if (global.logfac1 >= 0) { if (global.logfac1 >= 0) {
sa[nbloggers] = &global.logsrv1; logsrvs[nbloggers] = &global.logsrv1;
facilities[nbloggers] = global.logfac1; facilities[nbloggers] = global.logfac1;
loglevel[nbloggers] = global.loglev1; loglevel[nbloggers] = global.loglev1;
nbloggers++; nbloggers++;
} }
if (global.logfac2 >= 0) { if (global.logfac2 >= 0) {
sa[nbloggers] = &global.logsrv2; logsrvs[nbloggers] = &global.logsrv2;
facilities[nbloggers] = global.logfac2; facilities[nbloggers] = global.logfac2;
loglevel[nbloggers] = global.loglev2; loglevel[nbloggers] = global.loglev2;
nbloggers++; nbloggers++;
} }
} else { } else {
if (p->logfac1 >= 0) { if (p->logfac1 >= 0) {
sa[nbloggers] = &p->logsrv1; logsrvs[nbloggers] = &p->logsrv1;
facilities[nbloggers] = p->logfac1; facilities[nbloggers] = p->logfac1;
loglevel[nbloggers] = p->loglev1; loglevel[nbloggers] = p->loglev1;
nbloggers++; nbloggers++;
} }
if (p->logfac2 >= 0) { if (p->logfac2 >= 0) {
sa[nbloggers] = &p->logsrv2; logsrvs[nbloggers] = &p->logsrv2;
facilities[nbloggers] = p->logfac2; facilities[nbloggers] = p->logfac2;
loglevel[nbloggers] = p->loglev2; loglevel[nbloggers] = p->loglev2;
nbloggers++; 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 */ /* we can filter the level of the messages that are sent to each logger */
if (level > loglevel[nbloggers]) if (level > loglevel[nblogger])
continue; continue;
/* For each target, we may have a different facility. /* 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, * time, we only change the facility in the pre-computed header,
* and we change the pointer to the header accordingly. * 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 */ log_ptr = logmsg + 3; /* last digit of the log level */
do { do {
*log_ptr = '0' + fac_level % 10; *log_ptr = '0' + fac_level % 10;
@ -258,14 +314,12 @@ void send_log(struct proxy *p, int level, const char *message, ...)
*log_ptr = '<'; *log_ptr = '<';
/* the total syslog message now starts at logptr, for dataptr+data_len-logptr */ /* the total syslog message now starts at logptr, for dataptr+data_len-logptr */
sent = sendto(*plogfd, log_ptr, dataptr + data_len - log_ptr,
#ifndef MSG_NOSIGNAL MSG_DONTWAIT | MSG_NOSIGNAL, &logsrv->u.addr, logsrv_addrlen(logsrv));
sendto(logfd, log_ptr, dataptr + data_len - log_ptr, MSG_DONTWAIT, if (sent < 0) {
(struct sockaddr *)sa[nbloggers], sizeof(**sa)); Alert("sendto logger #%d failed: %s (errno=%d)\n",
#else nblogger, strerror(errno), errno);
sendto(logfd, log_ptr, dataptr + data_len - log_ptr, MSG_DONTWAIT | MSG_NOSIGNAL, }
(struct sockaddr *)sa[nbloggers], sizeof(**sa));
#endif
} }
} }

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 : ""); 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. * 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 else
sa.sin_addr = *(struct in_addr *) *(he->h_addr_list); 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_port = htons(port);
sa.sin_family = AF_INET; sa.sin_family = AF_INET;