[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:
parent
ddbb82ff47
commit
81ae1953bf
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -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_*) */
|
||||
|
@ -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
110
src/log.c
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user