diff --git a/docs/man/rotatelogs.8 b/docs/man/rotatelogs.8 index 5a5dada..1490cfc 100644 --- a/docs/man/rotatelogs.8 +++ b/docs/man/rotatelogs.8 @@ -27,7 +27,7 @@ rotatelogs \- Piped logging program to rotate Apache logs .SH "SYNOPSIS" .PP -\fBrotatelogs\fR [ -\fBl\fR ] [ -\fBL\fR \fIlinkname\fR ] [ -\fBf\fR ] [ -\fBv\fR ] [ -\fBe\fR ] \fIlogfile\fR \fIrotationtime\fR|\fIfilesize\fR(B|K|M|G) [ \fIoffset\fR ] +\fBrotatelogs\fR [ -\fBl\fR ] [ -\fBL\fR \fIlinkname\fR ] [ -\fBp\fR \fIprogram\fR ] [ -\fBf\fR ] [ -\fBv\fR ] [ -\fBe\fR ] \fIlogfile\fR \fIrotationtime\fR|\fIfilesize\fR(B|K|M|G) [ \fIoffset\fR ] .SH "SUMMARY" @@ -46,6 +46,9 @@ Causes the use of local time rather than GMT as the base for the interval or for -L \fIlinkname\fR Causes a hard link to be made from the current logfile to the specified link name\&. This can be used to watch the log continuously across rotations using a command like tail -F linkname\&. .TP +-p \fIprogram\fR +Causes the specified program to be executed after each rotation\&. Two arguments are supplied upon execution: the newly opened file and the previous file, respectively. The environment variables ROTATELOGS_PATH_CUR and ROTATELOGS_PATH_PREV are also available.\&. +.TP -f Causes the logfile to be opened immediately, as soon as rotatelogs starts, instead of waiting for the first logfile entry to be read (for non-busy sites, there may be a substantial delay between when the server is started and when the first request is handled, meaning that the associated logfile does not "exist" until then, which causes problems from some automated logging tools) .TP diff --git a/docs/manual/programs/rotatelogs.xml b/docs/manual/programs/rotatelogs.xml index 637b105..c78b5b4 100644 --- a/docs/manual/programs/rotatelogs.xml +++ b/docs/manual/programs/rotatelogs.xml @@ -36,6 +36,7 @@

rotatelogs [ -l ] [ -L linkname ] + [ -p program ] [ -f ] [ -v ] [ -e ] @@ -59,6 +60,13 @@ to the specified link name. This can be used to watch the log continuously across rotations using a command like tail -F linkname. +

-p program
+
Causes the specified program to be executed after each rotation. +Two arguments are supplied upon execution: the newly opened file and +the previous file, respectively. The environment variables +ROTATELOGS_PATH_CUR and ROTATELOGS_PATH_PREV +are also available.
+
-f
Causes the logfile to be opened immediately, as soon as rotatelogs starts, instead of waiting for the diff --git a/support/rotatelogs.c b/support/rotatelogs.c index cb85458..cfe9029 100644 --- a/support/rotatelogs.c +++ b/support/rotatelogs.c @@ -36,6 +36,8 @@ * data. * * -v option added Feb, 2008. Verbose output of command line parsing. + * + * -p option added May, 2011. Run arbitrary post-rotate program. */ @@ -48,6 +50,7 @@ #include "apr_general.h" #include "apr_time.h" #include "apr_getopt.h" +#include "apr_thread_proc.h" #if APR_HAVE_STDLIB_H #include @@ -91,6 +94,7 @@ struct rotate_config { const char *szLogRoot; int truncate; const char *linkfile; + const char *postrotate_prog; }; typedef struct rotate_status rotate_status_t; @@ -102,12 +106,20 @@ struct rotate_status { apr_file_t *nLogFD; apr_file_t *nLogFDprev; char filename[MAX_PATH]; + char filenameprev[MAX_PATH]; char errbuf[ERRMSGSZ]; int rotateReason; int tLogEnd; int nMessCount; }; +typedef struct postrotate_info postrotate_info_t; + +struct postrotate_info { + rotate_config_t *config; + rotate_status_t *status; +}; + static rotate_config_t config; static rotate_status_t status; @@ -117,34 +129,50 @@ static void usage(const char *argv0, const char *reason) fprintf(stderr, "%s\n", reason); } fprintf(stderr, - "Usage: %s [-v] [-l] [-L linkname] [-f] [-t] [-e] " + "Usage: %s [-v] [-l] [-L linkname] [-p prog] [-f] [-t] [-e] " "{|(B|K|M|G)} " "[offset minutes from UTC]\n\n", argv0); #ifdef OS2 fprintf(stderr, - "Add this:\n\nTransferLog \"|%s.exe /some/where 86400\"\n\n", + "Add this:\n TransferLog \"|%s.exe /some/where 86400\"\n\n", argv0); #else fprintf(stderr, - "Add this:\n\nTransferLog \"|%s /some/where 86400\"\n\n", + "Add this:\n TransferLog \"|%s /some/where 86400\"\n\n", argv0); fprintf(stderr, - "or \n\nTransferLog \"|%s /some/where 5M\"\n\n", argv0); + "or\n TransferLog \"|%s /some/where 5M\"\n\n", argv0); #endif fprintf(stderr, - "to httpd.conf. The generated name will be /some/where.nnnn " - "where nnnn is the\nsystem time at which the log nominally " - "starts (N.B. if using a rotation time,\nthe time will always " - "be a multiple of the rotation time, so you can synchronize\n" - "cron scripts with it). At the end of each rotation time or " - "when the file size\nis reached a new log is started. If the " - "-t option is specified, the specified\nfile will be truncated " - "instead of rotated, and is useful where tail is used to\n" - "process logs in real time. If the -L option is specified, " - "a hard link will be\nmade from the current log file to the " - "specified filename. In the case of the -e option, the log " - "will be echoed through to stdout for further processing.\n"); + "to httpd.conf. By default, the generated name will be\n" + ".nnnn where nnnn is the system time at which the log\n" + "nominally starts (N.B. if using a rotation time, the time will\n" + "always be a multiple of the rotation time, so you can synchronize\n" + "cron scripts with it). If contains strftime conversion\n" + "specifications, those will be used instead. At the end of each\n" + "rotation time or when the file size is reached a new log is\n" + "started.\n" + "\n" + "Options:\n" + " -v Verbose operation. Messages are written to stderr.\n" + " -l Base rotation on local time instead of UTC.\n" + " -L path Create hard link from current log to specified path.\n" + " -p prog Run specified program on log rotation. See below.\n" + " -f Force opening of log on program start.\n" + " -t Truncate logfile instead of rotating, tail friendly.\n" + " -e Echo log to stdout for further processing.\n" + "\n" + "The post-rotation program must be an executable program or script.\n" + "Scripts are supported as long as the shebang line uses a working\n" + "interpreter. The program is run with equivalent arguments and\n" + "environment variables for convenience:\n" + "\n" + " Argument 1: Full path to current file\n" + " Argument 2: Full path to previous file\n" + " Env var ROTATELOGS_PATH_CUR: Full path to current file\n" + " Env var ROTATELOGS_PATH_PREV: Full path to previous file\n" + "\n"); exit(1); } @@ -206,6 +234,7 @@ static void dumpConfig (rotate_config_t *config) fprintf(stderr, "Rotation file forced open: %12s\n", config->force_open ? "yes" : "no"); fprintf(stderr, "Rotation verbose: %12s\n", config->verbose ? "yes" : "no"); fprintf(stderr, "Rotation file name: %21s\n", config->szLogRoot); + fprintf(stderr, "Post-rotation prog: %21s\n", config->postrotate_prog); } /* @@ -261,6 +290,148 @@ static void checkRotate(rotate_config_t *config, rotate_status_t *status) } /* + * Run post-rotate program. It is run with: + * - Arg 1: Full path to current file + * - Arg 2: Full path to previous file + * - Env var ROTATELOGS_PATH_CUR: Full path to current file + * - Env var ROTATELOGS_PATH_PREV: Full path to previous file + * + * FIXME: Support non-threading too. + */ +static void * APR_THREAD_FUNC post_worker(apr_thread_t *thd, void *data) +{ + int i = 0; + char buf[BUFSIZE]; + char error[120]; + apr_status_t rv; + apr_pool_t *pool; + postrotate_info_t *info; + apr_proc_t *proc; + char **proc_args; + char *proc_name; + char *proc_env[32]; + apr_procattr_t *proc_attr; + int proc_exitcode; + apr_exit_why_e proc_exitwhy; + + info = (postrotate_info_t *)data; + pool = apr_thread_pool_get(thd); + + /* Let users choose between environment variables .. */ + if (strlen(info->status->filename)) + proc_env[i++] = apr_psprintf(pool, "ROTATELOGS_PATH_CUR=%s", + info->status->filename); + if (strlen(info->status->filenameprev)) + proc_env[i++] = apr_psprintf(pool, "ROTATELOGS_PATH_PREV=%s", + info->status->filenameprev); + proc_env[i] = NULL; + + /* .. and command line parameters. */ + snprintf(buf, BUFSIZE, "%s %s %s", + info->config->postrotate_prog, + info->status->filename, + info->status->filenameprev); + apr_tokenize_to_argv(buf, &proc_args, pool); + proc_name = apr_pstrdup(pool, proc_args[0]); + + if ((rv = apr_procattr_create(&proc_attr, pool)) != APR_SUCCESS) + { + fprintf(stderr, + "apr_procattr_create failed for '%s': %s\n", + info->config->postrotate_prog, + apr_strerror(rv, error, sizeof(error))); + goto cleanexit; + } + + if ((rv = apr_procattr_error_check_set(proc_attr, 1)) != APR_SUCCESS) { + fprintf(stderr, + "apr_procattr_error_check_set failed for '%s': %s\n", + info->config->postrotate_prog, + apr_strerror(rv, error, sizeof(error))); + goto cleanexit; + } + + if (info->config->verbose) + fprintf(stderr, "Calling post-rotate program: %s\n", buf); + + proc = (apr_proc_t *)apr_pcalloc(pool, sizeof(*proc)); + if ((rv = apr_proc_create(proc, proc_name, (const char * const *)proc_args, + (const char * const *)proc_env, proc_attr, pool)) != APR_SUCCESS) + { + fprintf(stderr, "Post-rotate spawn error '%s': %s\n", + info->config->postrotate_prog, + apr_strerror(rv, error, sizeof(error))); + goto cleanexit; + } + + if ((apr_proc_wait(proc, &proc_exitcode, &proc_exitwhy, + APR_WAIT)) != APR_CHILD_DONE) + { + fprintf(stderr, "apr_proc_wait not done. This is a bug!\n"); + goto cleanexit; + } + + if (proc_exitcode) + fprintf(stderr, "Post-rotate program returned non-zero: %d\n", + proc_exitcode); + + if (proc_exitwhy != APR_PROC_EXIT) + fprintf(stderr, "Post-rotate program terminated by signal.\n"); + +cleanexit: + apr_thread_exit(thd, APR_SUCCESS); + return NULL; +} + +/* + * Prepare and spawn post-rotate thread. + */ +static int doPostrotate(rotate_config_t *config, rotate_status_t *status) +{ + apr_status_t rv = APR_SUCCESS; + char error[120]; + apr_pool_t *pool; + apr_threadattr_t *threadattr; + apr_thread_t *thread; + postrotate_info_t *info; + + if ((rv = apr_pool_create(&pool, status->pool)) != APR_SUCCESS) + { + fprintf(stderr, "apr_pool_create failed: %s\n", + apr_strerror(rv, error, sizeof(error))); + goto cleanexit; + } + + if ((rv = apr_threadattr_create(&threadattr, pool)) != APR_SUCCESS) + { + fprintf(stderr, "apr_threadattr_create failed: %s\n", + apr_strerror(rv, error, sizeof(error))); + goto cleanexit; + } + + if ((rv = apr_threadattr_detach_set(threadattr, 1)) != APR_SUCCESS) + { + fprintf(stderr, "apr_threadattr_detach_set failed: %s\n", + apr_strerror(rv, error, sizeof(error))); + goto cleanexit; + } + + thread = (apr_thread_t *)apr_pcalloc(pool, sizeof(apr_thread_t *)); + info = (postrotate_info_t *)apr_pcalloc(pool, sizeof(*info)); + info->config = config; + info->status = status; + + if ((rv = apr_thread_create(&thread, threadattr, + post_worker, info, status->pool)) != APR_SUCCESS) + fprintf(stderr, "apr_thread_create failed: %s\n", + apr_strerror(rv, error, sizeof(error))); + +cleanexit: + apr_pool_destroy(pool); + return rv; +} + +/* * Open a new log file, and if successful * also close the old one. * @@ -301,6 +472,8 @@ static void doRotate(rotate_config_t *config, rotate_status_t *status) tLogStart = now; } + apr_cpystrn(status->filenameprev, status->filename, MAX_PATH); + if (config->use_strftime) { apr_time_t tNow = apr_time_from_sec(tLogStart); apr_time_exp_t e; @@ -375,6 +548,17 @@ static void doRotate(rotate_config_t *config, rotate_status_t *status) exit(2); } } + + if (config->postrotate_prog) + { + if ((rv = doPostrotate(config, status)) != APR_SUCCESS) + { + char error[120]; + fprintf(stderr, + "doPostrotate failed: %s\n", + apr_strerror(rv, error, sizeof(error))); + } + } } /* @@ -466,7 +650,7 @@ int main (int argc, const char * const argv[]) apr_pool_create(&status.pool, NULL); apr_getopt_init(&opt, status.pool, argc, argv); - while ((rv = apr_getopt(opt, "lL:ftve", &c, &opt_arg)) == APR_SUCCESS) { + while ((rv = apr_getopt(opt, "lL:p:ftve", &c, &opt_arg)) == APR_SUCCESS) { switch (c) { case 'l': config.use_localtime = 1; @@ -474,6 +658,9 @@ int main (int argc, const char * const argv[]) case 'L': config.linkfile = opt_arg; break; + case 'p': + config.postrotate_prog = opt_arg; + break; case 'f': config.force_open = 1; break;