diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2007-04-20 09:09:46 +0000 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2007-04-20 09:09:46 +0000 |
commit | f0992a40ee322ea27371ed6b81dce893cb8cd4d8 (patch) | |
tree | 8f216ae0aab3d309178816df14f4a8d31c2caf8f | |
parent | 5d78549a3171c8d058f1485e5cbc6f3d74770c9a (diff) | |
download | mailfromd-f0992a40ee322ea27371ed6b81dce893cb8cd4d8.tar.gz mailfromd-f0992a40ee322ea27371ed6b81dce893cb8cd4d8.tar.bz2 |
Implement asyncronous syslog
git-svn-id: file:///svnroot/mailfromd/trunk@1368 7a8a7f39-df28-0410-adc6-e0d955640f24
-rw-r--r-- | ChangeLog | 12 | ||||
-rw-r--r-- | NEWS | 21 | ||||
-rw-r--r-- | configure.ac | 21 | ||||
-rw-r--r-- | doc/mailfromd.texi | 41 | ||||
-rw-r--r-- | gacopyz/gacopyz.c | 34 | ||||
-rw-r--r-- | src/Makefile.am | 10 | ||||
-rw-r--r-- | src/mailfromd.h | 9 | ||||
-rw-r--r-- | src/main.c | 40 | ||||
-rw-r--r-- | src/syslog_async.c | 409 | ||||
-rw-r--r-- | src/syslog_async.h | 122 |
10 files changed, 692 insertions, 27 deletions
@@ -1,3 +1,15 @@ +2007-04-20 Sergey Poznyakoff <gray@gnu.org.ua> + + * src/syslog_async.c, src/syslog_async.h: Async syslog + implementation by Simon Kelley + * src/mailfromd.h, src/main.c, src/Makefile.am, + configure.ac: Implement async syslog. + + * doc/mailfromd.texi, NEWS: Document syslog-async + + * gacopyz/gacopyz.c (gacopyz_handle_connection): Print connection + info after forking, so the actual PID is displayed. + 2007-04-18 Sergey Poznyakoff <gray@gnu.org.ua> * src/mu_dbm.c: When possible lock BDB databases directly. @@ -1,4 +1,4 @@ -Mailfromd NEWS -- history of user-visible changes. 2007-04-18 +Mailfromd NEWS -- history of user-visible changes. 2007-04-20 Copyright (C) 2005, 2006, 2007 Sergey Poznyakoff See the end of file for copying conditions. @@ -7,6 +7,17 @@ Please send mailfromd bug reports to <bug-mailfromd@gnu.org.ua> Version 3.1.91, SVN +* Non-blocking syslog + +This version is shipped with non-blocking syslog implementation by +Simon Kelley. You may wish to enable it if you noticed that the +number of mailfromd processes grows uncontrollably and the processes +are hung for prolonged amounts of time. Usually this indicates that +the daemon blocks in syslog() calls. Read the description of +`--enable-syslog-async' option in chapter `Building' for the detailed +discussion of this (try `info -f doc/mailfromd.info --index-search +syslog-async'). + * SPF support The function check_host() tests the SPF record for the given @@ -27,7 +38,7 @@ below). For compatibility with the previous versions, its use outside of a loop statement is still allowed, but a warning is issued. You are -enouraged to replace all occurrances of `next' in your confifuration +encouraged to replace all occurrences of `next' in your configuration scripts with `pass'. * Loop @@ -210,7 +221,7 @@ an error message. * DNS functions -DNS functions are reemplemented in two layers: +DNS functions are reimplemented in two layers: 1. Primitive calls: @@ -236,7 +247,7 @@ To use the traditional calls, include file "dns.mf". * Function `match_cidr' -This function has been reemplemented in MFL. To use it, include +This function has been reimplemented in MFL. To use it, include "match_cidr.mf". * Catch arguments @@ -319,7 +330,7 @@ The bug manifested itself with the following log messages: Milter(mailfrom): to error state ** Fix coredumps on printing void returns with --dump-tree -** Fix coredumps on optimising conditionals like +** Fix coredumps on optimizing conditionals like if 0 do_something diff --git a/configure.ac b/configure.ac index 2b19f7cb..0d963010 100644 --- a/configure.ac +++ b/configure.ac @@ -109,6 +109,21 @@ AH_BOTTOM([#ifndef HAVE_ARGCV_UNESCAPE_CHAR #endif ]) +AC_ARG_ENABLE([syslog-async], + AC_HELP_STRING([--enable-syslog-async], + [enable non-blocking version of syslog]), + [case "${enableval}" in + yes) syslog_async=yes ;; + no) syslog_async=no ;; + *) AC_MSG_ERROR([bad value ${enableval} for --enable-syslog-async]) ;; + esac],[syslog_async=no]) + +if test $syslog_async = "yes"; then + AC_SUBST([BUILD_SYSLOG_ASYNC], 'libsyslog_async.a') + AC_SUBST([SYSLOG_LIBS], './libsyslog_async.a') + AC_DEFINE([USE_SYSLOG_ASYNC], [1], [Define if syslog-async is being used]) +fi + # Check for DBM flavor AH_TEMPLATE(BDB2_CURSOR_LASTARG, [Last argument to the cursor member of Berkeley 2 DB structure]) @@ -493,6 +508,7 @@ Socket.................................... $socket Expiration interval....................... $expire Negative DNS answer expiration interval... $negative_dns_expire Rates expire interval..................... $rates_expire +Syslog implementation..................... $syslog_flavor Readline (for mtasim)..................... $usereadline Documentation rendition type.............. $rendition ******************************************************************* @@ -508,6 +524,11 @@ negative_dns_expire=$DEFAULT_DNS_NEGATIVE_EXPIRE_INTERVAL rates_expire=$DEFAULT_EXPIRE_RATES_INTERVAL usereadline=$usereadline rendition=$RENDITION +if test $syslog_async = "yes"; then + syslog_flavor="non-blocking" +else + syslog_flavor="standard" +fi ]) AC_CONFIG_FILES([Makefile diff --git a/doc/mailfromd.texi b/doc/mailfromd.texi index e5d99593..f941975c 100644 --- a/doc/mailfromd.texi +++ b/doc/mailfromd.texi @@ -614,6 +614,7 @@ described in more detail in @ref{Databases}. The default value is 86400 seconds, i.e. 24 hours. It is OK for most sites. If, however, you wish to change it, use @var{DEFAULT_EXPIRE_INTERVAL} environment variable. + @cindex DEFAULT_@/DNS_@/NEGATIVE_@/EXPIRE_@/INTERVAL, @command{configure} variable @cindex DEFAULT_EXPIRE_RATES_INTERVAL, @command{configure} variable There are also two variables that allow to control particular @@ -627,6 +628,38 @@ Expiration settings can be changed at run time using @samp{#pragma database} statement in the filter script file (@pxref{database}). +@cindex --enable-syslog-async, @command{configure} option +@cindex syslog, non-blocking +@item Select @command{syslog} implementation to use. + +@anchor{syslog-async} + @command{Mailfromd} uses @code{syslog} for diagnostics output. The +default @code{syslog} implementation on most systems (most notably, on +GNU/Linux) uses blocking @code{AF_UNIX SOCK_DGRAM} sockets. As a +result, when an application calls @code{syslog()}, and +@command{syslogd} is not responding and the socket buffers get full, +the application will hang. + +@cindex Simon Kelley + In the case of a daemon as @command{mailfromd}, it is more important +that it continue to run, than that it continue to log. For this +purpose, @command{mailfromd} is shipped with a non-blocking +@code{syslog} implementation by Simon Kelley. This implementation, +instead of blocking, buffers log lines in memory. When the buffer log +overflows, some lines are lost, but the daemon continues to run. When +lines are lost, this fact is logged with a message of the form: + +@smallexample + async_syslog overflow: 5 log entries lost +@end smallexample + + To enable non-blocking @command{syslog}, use the +@option{--enable-syslog-async} command line option: + +@smallexample +./configure --enable-syslog-async +@end smallexample + @item Run @command{configure} with all the desired options. For example, the following command: @@ -651,10 +684,12 @@ Mailfromd configured with the following settings: DBM version............................... Berkeley DB v. 3 Default user.............................. mail State directory........................... $(localstatedir)/@/$(PACKAGE) -Socket.................................... unix:$(MAILFROMSTATEDIR)/@/mailfrom +Socket.................................... unix:$(DEFAULT_STATE_DIR)/@/mailfrom Expiration interval....................... 86400 Negative DNS answer expiration interval... 3600 Rates expire interval..................... 300 +Syslog implementation..................... blocking +Readline (for mtasim)..................... yes Documentation rendition type.............. PROOF ******************************************************************* @end group @@ -688,6 +723,10 @@ the corresponding section below. @cindex Upgrading from 3.1.x to 3.2 @UNREVISED{} + Before building this version, please re-read the chapter +@xref{Building}, especially the section @xref{syslog-async, Using +non-blocking syslog}. + Starting from the version 3.2, @acronym{MFL} no longer uses the predefined symbolic names for exception codes (previous versions used the @samp{&} prefix to dereference them). Instead, it relies on diff --git a/gacopyz/gacopyz.c b/gacopyz/gacopyz.c index c9445134..5c9c991c 100644 --- a/gacopyz/gacopyz.c +++ b/gacopyz/gacopyz.c @@ -1340,7 +1340,6 @@ gacopyz_handle_connection(gacopyz_conn_t conn) { milter_sockaddr_t addr; int addrlen = sizeof addr; - char *cliname; int fd; int rc; @@ -1353,21 +1352,6 @@ gacopyz_handle_connection(gacopyz_conn_t conn) return MI_FAILURE; } - switch (addr.sa.sa_family) { - case AF_UNIX: - cliname = "socket"; - break; - - case AF_INET: - cliname = inet_ntoa(addr.sin.sin_addr); - break; - - default: - cliname = "unknown"; - } - - gacopyz_log(conn, SMI_LOG_INFO, "connect from %s", cliname); - if (!conn->foreground) { pid_t pid = fork(); if (pid == -1) { @@ -1390,6 +1374,24 @@ gacopyz_handle_connection(gacopyz_conn_t conn) close(conn->sd); conn->sd = -1; } + + switch (addr.sa.sa_family) { + case AF_UNIX: + gacopyz_log(conn, SMI_LOG_INFO, "connect from socket"); + break; + + case AF_INET: + gacopyz_log(conn, SMI_LOG_INFO, "connect from %s:%u", + inet_ntoa(addr.sin.sin_addr), + (unsigned) ntohs(addr.sin.sin_port)); + break; + + default: + gacopyz_log(conn, SMI_LOG_INFO, + "connect from unsupported family: %d", + addr.sa.sa_family); + } + if (conn->desc.xxfi_start) conn->desc.xxfi_start(); diff --git a/src/Makefile.am b/src/Makefile.am index c090a170..dee8e0a2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -58,15 +58,19 @@ mailfromd_SOURCES = \ rate.c\ $(M4_FILES:.m4=.c) +mailfromd_LDADD = $(LDADD) $(SYSLOG_LIBS) +EXTRA_LIBRARIES=libsyslog_async.a +noinst_LIBRARIES=@BUILD_SYSLOG_ASYNC@ +libsyslog_async_a_SOURCES=syslog_async.c syslog_async.h + mtasim_SOURCES = mtasim.c mtasim_LDADD = $(LDADD) $(READLINE_LIBS) -noinst_HEADERS = mailfromd.h mu_dbm.h builtin.h dns.h spf.h drivers.c -EXTRA_DIST = gram.h snarf.m4 init.m4 $(M4_FILES) builtin.h builtin.def mf-status.mfi status.mfi mfstat.awk status.mfh optab.opc optab.oph opcode.awk opcodes node-type.h node-tab.c drv.awk +noinst_HEADERS = mailfromd.h mu_dbm.h builtin.h dns.h spf.h drivers.c +EXTRA_DIST = gram.h snarf.m4 init.m4 $(M4_FILES) builtin.h builtin.def mf-status.mfi status.mfi mfstat.awk status.mfh optab.opc optab.oph opcode.awk opcodes node-type.h node-tab.c drv.awk $(SYSLOG_ASYNC) BUILT_SOURCES=$(M4_FILES:.m4=.c) builtin.h mf-status.c optab.c optab.h node-type.h node-tab.c AM_CPPFLAGS=-DSYSCONFDIR=\"$(sysconfdir)\"\ - -DDATAROOTDIR=\"$(datarootdir)\"\ -DDEFAULT_STATE_DIR=\"$(DEFAULT_STATE_DIR)\"\ -DDEFAULT_SOCKET=\"$(DEFAULT_SOCKET)\"\ -DDEFAULT_VERSION_INCLUDE_DIR=\"$(incdir)\"\ diff --git a/src/mailfromd.h b/src/mailfromd.h index 23574182..973c94ae 100644 --- a/src/mailfromd.h +++ b/src/mailfromd.h @@ -223,7 +223,14 @@ mf_status getmxip(char *ipstr, mxbuf_t mxbuf); } while (0) #define debug6(lev,fmt,x1,x2,x3,x4,x5,x6) \ do { __DBG(lev) __debug6(fmt,x1,x2,x3,x4,x5,x6); } while(0) - + + +/* Syslog flavor */ +#ifdef USE_SYSLOG_ASYNC +# include <syslog_async.h> +#endif + + void debug_log(char *fmt, ...); void enable_module_trace(char *name); @@ -111,7 +111,9 @@ time_t response_timeout = 30; int syslog_printer (int prio, const char *fmt, va_list ap) { -#ifdef HAVE_VSYSLOG +#ifdef USE_SYSLOG_ASYNC + vsyslog_async (prio, fmt, ap); +#elif HAVE_VSYSLOG vsyslog (prio, fmt, ap); #else char buf[128]; @@ -121,6 +123,32 @@ syslog_printer (int prio, const char *fmt, va_list ap) return 0; } +#ifdef USE_SYSLOG_ASYNC +void +mf_gacopyz_syslog_async_log_printer(int level, char *fmt, va_list ap) +{ + switch (level) { + case SMI_LOG_DEBUG: + level = LOG_DEBUG; + break; + case SMI_LOG_INFO: + level = LOG_INFO; + break; + case SMI_LOG_WARN: + level = LOG_WARNING; + break; + case SMI_LOG_ERR: + level = LOG_ERR; + break; + + case SMI_LOG_FATAL: + default: + level = LOG_EMERG; + } + vsyslog_async(level, fmt, ap); +} +#endif + int syslog_error_printer (const char *fmt, va_list ap) { @@ -1744,6 +1772,11 @@ mailfromd_show_defaults() printf("statedir: %s\n", DEFAULT_STATE_DIR); printf("socket: %s\n", DEFAULT_SOCKET); printf("pidfile: %s\n", DEFAULT_PIDFILE); +#ifdef USE_SYSLOG_ASYNC + printf("syslog: non-blocking\n"); +#else + printf("syslog: blocking\n"); +#endif printf("database format: "); #if defined WITH_GDBM printf("GDBM"); @@ -1822,7 +1855,12 @@ main(int argc, char **argv) /* Set up default values */ if (!log_to_stderr) { +#ifdef USE_SYSLOG_ASYNC + openlog_async(syslog_tag, LOG_PID, log_facility); + gacopyz_set_logger (mf_gacopyz_syslog_async_log_printer); +#else openlog(syslog_tag, LOG_PID, log_facility); +#endif mu_error_set_print(syslog_error_printer); } else gacopyz_set_logger (gacopyz_stderr_log_printer); diff --git a/src/syslog_async.c b/src/syslog_async.c new file mode 100644 index 00000000..431e834f --- /dev/null +++ b/src/syslog_async.c @@ -0,0 +1,409 @@ +/* syslog_async is Copyright (c) 2007 Simon Kelley + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 dated June, 1991. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +*/ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/file.h> +#include <sys/syslog.h> + +#include <sys/uio.h> +#include <sys/wait.h> +#include <netdb.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <errno.h> +#include <stdarg.h> +#include <stdlib.h> +#include <paths.h> +#include <stdio.h> +#include <ctype.h> + +#include "syslog_async.h" + +/* From RFC 3164 */ +#define MAX_MESSAGE 1024 + +#define DEF_BACKLOG 5 +#define DEF_DELAY 1000 /* doesn't come into effect until backlog > 10 */ + +static int log_fac = LOG_USER; +static int log_opts = LOG_ODELAY; +static const char *log_tag = "syslog"; +static int log_mask = 0xff; +static int log_backlog = DEF_BACKLOG; +static int log_delay = DEF_DELAY; + +static int log_fd = -1; +static int entries_alloced = 0; +static int entries_lost = 0; +static int connection_good = 1; + +struct log_entry { + int offset, length; + struct log_entry *next; + char payload[MAX_MESSAGE]; +}; + +static struct log_entry *entries = NULL; +static struct log_entry *free_entries = NULL; + +static int mksock(int type) +{ + int flags; + int fd = socket(AF_UNIX, type, 0); + + if (fd != -1) + { + if ((flags = fcntl(fd, F_GETFL)) == -1 || + fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1 || + (flags = fcntl(fd, F_GETFD)) == -1 || + fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) + { + close(fd); + fd = -1; + } + } + return fd; +} + +void openlog_async(const char *ident, int option, int facility) +{ + if (ident) + log_tag = ident; + + log_opts = option; + + if (facility != 0 && (facility &~ LOG_FACMASK) == 0) + log_fac = facility; + + if (log_opts & LOG_NDELAY) + log_fd = mksock(SOCK_DGRAM); +} + +int setlogmask_async(int mask) +{ + int old = log_mask; + + if (mask != 0) + log_mask = mask; + + return old; +} + +void tunelog_async(int backlog, int delay) +{ + /* we need at least one buffer, and the + delay calculations overflow for more than 99 */ + if (backlog < 1) + backlog = 1; + else if (backlog > 99) + backlog = 99; + + /* don't lose existing buffers */ + if (backlog < entries_alloced) + log_backlog = entries_alloced; + else + log_backlog = backlog; + + if (delay < 0) + log_delay = 0; + else if (delay > 1000) + log_delay = 1000; + else + log_delay = delay; +} + +void closelog_async(void) +{ + /* maybe last chance to flush */ + log_write_async(); + + if (log_fd != -1) + { + close(log_fd); + log_fd = -1; + } + + /* restore defaults */ + log_fac = LOG_USER; + log_opts = LOG_ODELAY; + log_tag = "syslog"; + log_mask = 0xff; + + log_delay = DEF_DELAY; + + if (entries_alloced < DEF_BACKLOG) + log_backlog = entries_alloced; + else + log_backlog = DEF_BACKLOG; +} + +int log_fd_async(void) +{ + if (!entries || !connection_good) + return -1; + + return log_fd; +} + +void log_write_async(void) +{ + ssize_t rc; + int fd, tried_stream = 0; + struct log_entry *tmp; + + while (entries) + { + if (log_fd == -1 && + (log_fd = mksock(SOCK_DGRAM)) == -1) + goto fail; + + connection_good = 1; + + if ((rc = send(log_fd, + entries->payload + entries->offset, + entries->length, + MSG_NOSIGNAL)) != -1) + { + entries->length -= rc; + entries->offset += rc; + connection_good = 1; + + if (entries->length == 0) + goto free; + + continue; + } + + if (errno == EINTR) + continue; + + if (errno == EAGAIN) + return; + + /* *BSD, returns this instead of blocking? */ + if (errno == ENOBUFS) + { + connection_good = 0; + return; + } + + /* A stream socket closed at the other end goes into EPIPE + forever, close and re-open. */ + if (errno == EPIPE) + goto reopen_stream; + + if (errno == ECONNREFUSED || + errno == ENOTCONN || + errno == EDESTADDRREQ || + errno == ECONNRESET) + { + /* socket went (syslogd down?), try and reconnect. If we fail, + stop trying until the next call to my_syslog() + ECONNREFUSED -> connection went down + ENOTCONN -> nobody listening + (ECONNRESET, EDESTADDRREQ are *BSD equivalents) */ + + struct sockaddr_un logaddr; + + logaddr.sun_family = AF_LOCAL; + strncpy(logaddr.sun_path, _PATH_LOG, sizeof(logaddr.sun_path)); + + /* Got connection back? try again. */ + if (connect(log_fd, (struct sockaddr *)&logaddr, sizeof(logaddr)) != -1) + continue; + + /* errors from connect which mean we should keep trying */ + if (errno == ENOENT || + errno == EALREADY || + errno == ECONNREFUSED || + errno == EISCONN || + errno == EINTR || + errno == EAGAIN) + { + /* try again on next syslog() call */ + connection_good = 0; + return; + } + + /* we start with a SOCK_DGRAM socket, but syslog may want SOCK_STREAM */ + if (!tried_stream && errno == EPROTOTYPE) + { + reopen_stream: + tried_stream = 1; + close(log_fd); + if ((log_fd = mksock(SOCK_STREAM)) != -1) + continue; + } + } + + fail: + tried_stream = 0; + + /* give up - try to write to console if we've been asked + take care not to block in open() or write() */ + if ((log_opts & LOG_CONS) && + (fd = open(_PATH_CONSOLE, O_WRONLY | O_NONBLOCK, 0)) != -1) + { + char *start = strchr(entries->payload, '>') + 1; + int flags = fcntl(fd, F_GETFL); + + if (flags != -1) + fcntl(fd, F_SETFL, flags | O_NONBLOCK); + + entries->length -= start - entries->payload; + /* move down to remove the tag, and make room for the \r\n */ + memmove(entries->payload, start, entries->length); + entries->payload[entries->length - 1] = '\r'; + entries->payload[entries->length] = '\n'; + write(fd, entries->payload, entries->length + 1); + close(fd); + } + + free: + tmp = entries; + entries = tmp->next; + tmp->next = free_entries; + free_entries = tmp; + + if (entries_lost != 0) + { + int e = entries_lost; + entries_lost = 0; /* avoid wild recursion */ + syslog_async(LOG_WARNING, "async_syslog overflow: %d log entries lost", e); + } + continue; + } +} + +void syslog_async(int priority, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vsyslog_async(priority, format, ap); + va_end(ap); +} + +void vsyslog_async(int priority, const char *format, va_list ap) +{ + struct log_entry *entry; + time_t time_now; + char *p, *q, *r; + size_t len; + + if (!(log_mask & LOG_MASK(LOG_PRI(priority))) || (priority &~ (LOG_PRIMASK|LOG_FACMASK))) + return; + + if ((entry = free_entries)) + free_entries = entry->next; + else if (entries_alloced < log_backlog && (entry = malloc(sizeof(struct log_entry)))) + entries_alloced++; + + if (!entry) + entries_lost++; + else + { + /* add to end of list, consumed from the start */ + entry->next = NULL; + if (!entries) + entries = entry; + else + { + struct log_entry *tmp; + for (tmp = entries; tmp->next; tmp = tmp->next); + tmp->next = entry; + } + + time(&time_now); + p = entry->payload; + p += sprintf(p, "<%d>", priority | log_fac); + + q = p; + + if (log_opts & LOG_PID) + p += sprintf(p, "%.15s %s[%d]: ", ctime(&time_now) + 4, log_tag, getpid()); + else + p += sprintf(p, "%.15s %s: ", ctime(&time_now) + 4, log_tag); + + len = p - entry->payload; + len += vsnprintf(p, MAX_MESSAGE - len, format, ap) + 1; /* include zero-terminator */ + entry->length = len > MAX_MESSAGE ? MAX_MESSAGE : len; + + /* remove trailing '\n's passed to us. */ + for (r = &entry->payload[entry->length - 2]; r >= entry->payload; r--) + if (*r == '\n') + entry->length--; + else + break; + + entry->offset = 0; + + if (log_opts & LOG_PERROR) + { + ssize_t rc, s = entry->length - (q - entry->payload); + /* replace terminator with \n */ + entry->payload[entry->length - 1] = '\n'; + + while (s != 0) + if ((rc = write(STDERR_FILENO, q, s)) != -1) + { + s -= rc; + q += rc; + continue; + } + else if (errno == EINTR) + continue; + else + break; + } + entry->payload[entry->length - 1] = 0; + } + + /* almost always, logging won't block, so try and write this now, + to save collecting too many log messages during a select loop. */ + log_write_async(); + + /* Since we're doing things asynchronously, we + can now generate log lines very fast. With a small buffer (desirable), + that means it can overflow the log-buffer very quickly. + To avoid this, we delay here, the delay growing exponentially + with queue length. Delay is limited to 1 second, by default + but can be tuned for less if needed. Note that for a responsive + syslog, the log-line we just created will have been writen by the + call the log_write_async() above, so that this doesn't delay at all. */ + if (entries && log_delay != 0) + { + struct timespec waiter; + int d; + + for (d = 1,entry = entries; entry->next; entry = entry->next) + { + d *= 2; + if (d >= log_delay) /* limit to 999ms */ + { + d = log_delay - 1; + break; + } + } + + waiter.tv_sec = 0; + waiter.tv_nsec = d * 1000000; /* 1 ms */ + nanosleep(&waiter, NULL); + + /* try and write again */ + log_write_async(); + } +} + diff --git a/src/syslog_async.h b/src/syslog_async.h new file mode 100644 index 00000000..ba6ae766 --- /dev/null +++ b/src/syslog_async.h @@ -0,0 +1,122 @@ +/* syslog_async is Copyright (c) 2007 Simon Kelley + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 dated June, 1991. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +*/ + +#ifndef _SYSLOG_ASYNC_H +#define _SYSLOG_ASYNC_H 1 + +#include <syslog.h> +#include <stdarg.h> + +/* Syslog_async is a non-blocking replacement for the + POSIX-standard syslog() system call. Instead of blocking, + log-lines are buffered in memory. The buffer size is limited + and if the buffer overflows log lines are lost. When lines are + lost this fact is logged with a message of the form: + + async_syslog overflow: 5 log entries lost + + In order to limit the probability of buffer overflow + short delays are added to syslog_async() calls when the + queue is getting full. The delay added is strictly + bounded and tunable. + + The API is very close to the standard syslog(), with an + additional call the tune buffer parameters and a couple + of calls into the event loop. + + The code has been tested under Linux and BSD, and with both + the syslog and syslog-ng log daemons. +*/ + + +/* + openlog_async(), closelog_async() and setlogmask_async() are + identical to the POSIX equivalents. +*/ + +void openlog_async(const char *ident, int option, int facility); +void closelog_async(void); +int setlogmask_async(int mask); + + + +/* + syslog_async() and vsyslog_async() are identical to syslog() and vsyslog() + except for their blocking behaviour. The formatting is done using printf(), + so the additional format operator %m is available only if the system + printf() provides it. (GNU printf() does.) +*/ + +void syslog_async(int priority, const char *format, ...); +void vsyslog_async(int priority, const char *format, va_list ap); + + + +/* + log_fd_async() and log_write_async() are the interface between the library + and the daemon event loop. + + log_fd_async() returns a file descriptor which the library needs to write, + or -1 if no write is queued. log_write_async() does the write. + + The result of log_fd_async() is only valid until [v]syslog_async() or + log_write_async() is called, so it should be called each time around the + event loop, just before the call to select() or poll(). + + A typical event loop looks like this: + + while (1) + { + int log_fd; + fd_set read_set, write_set; + + FD_ZERO(&read_set); + FD_ZERO(&write_set); + + ...other stuff.. + + if ((log_fd = log_fd_async()) != -1) + FD_SET(log_fd, &write_set); + + select(..., &read_set, &write_set, ...); + + if (log_fd != -1 && FD_ISSET(log_fd, &write)) + log_write_async(); + + + ...other stuff.... + } +*/ + +int log_fd_async(void); +void log_write_async(void); + + + +/* + tunelog_async() tunes the log-line buffer. Backlog is the limit + on the number of queued log-lines. These are stored in malloc'ed memory + and each line is stored in a fixed-size buffer which is just over 1K bytes. + The library maintains a buffer pool to avoid heap fragmentation. Delay + is the upper bound on the time taken to run syslog_async, in milliseconds. + This delay is added when syslog is busy in order to reduce the probability + of buffer overflow. Backlog is constrained between 1 and 99 and delay + between 1 millisecond and 1000 millisconds. The default for backlog + is 5 and for delay 1000. Note that delay is calculated from queue size as + 2^queue_size (in milliseconds) therefore the maximum delay for the default + queue size is 64ms. Setting delay to zero is allowed, and inhibits the delay + completely. +*/ + +void tunelog_async(int backlog, int delay); + +#endif |