Hi,

attached patch against trunk adds new rotatelogs option "-c" to create logs after tRotation time even if there are no messages to log during tRotation time. This is achieved by calling apr_poll() on stdin with proper timeout before apr_file_read (which blocks until there are some data on stdin).

This feature is useful when logs are processed automatically and missing logs add additional complexity to these activities. It can also help in situation when one doesn't know if missing log means some problem or there just wasn't anything to log.

Thanks for reviewing,
Jan Kaluza
Index: docs/man/rotatelogs.8
===================================================================
--- docs/man/rotatelogs.8	(revision 1181642)
+++ docs/man/rotatelogs.8	(working copy)
@@ -27,7 +27,7 @@
 .SH "SYNOPSIS"
  
 .PP
-\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 ]
+\fBrotatelogs\fR [ -\fBl\fR ] [ -\fBL\fR \fIlinkname\fR ] [ -\fBp\fR \fIprogram\fR ] [ -\fBf\fR ] [ -\fBv\fR ] [ -\fBe\fR ] [ -\fBc\fR ] \fIlogfile\fR \fIrotationtime\fR|\fIfilesize\fR(B|K|M|G) [ \fIoffset\fR ]
  
 
 .SH "SUMMARY"
@@ -61,6 +61,9 @@
 -e
 Echo logs through to stdout\&. Useful when logs need to be further processed in real time by a further tool in the chain\&.  
 .TP
+-c
+Create log file even if it is empty.
+.TP
 \fIlogfile\fR
 .PP The path plus basename of the logfile\&. If \fIlogfile\fR includes any '%' characters, it is treated as a format string for strftime(3)\&. Otherwise, the suffix \fI\&.nnnnnnnnnn\fR is automatically added and is the time in seconds (unless the -t option is used)\&. Both formats compute the start time from the beginning of the current period\&. For example, if a rotation time of 86400 is specified, the hour, minute, and second fields created from the strftime(3) format will all be zero, referring to the beginning of the current 24-hour period (midnight)\&. .PP When using strftime(3) filename formatting, be sure the log file format has enough granularity to produce a different file name each time the logs are rotated\&. Otherwise rotation will overwrite the same file instead of starting a new one\&. For example, if \fIlogfile\fR was /var/logs/errorlog\&.%Y-%m-%d with log rotation at 5 megabytes, but 5 megabytes was reached twice in the same day, the same log file name would be produced and log rotation would keep writing to the same file\&.  
 .TP
Index: support/rotatelogs.c
===================================================================
--- support/rotatelogs.c	(revision 1181642)
+++ support/rotatelogs.c	(working copy)
@@ -49,6 +49,7 @@
 #include "apr_time.h"
 #include "apr_getopt.h"
 #include "apr_thread_proc.h"
+#include "apr_poll.h"
 
 #if APR_HAVE_STDLIB_H
 #include <stdlib.h>
@@ -89,6 +90,7 @@
     int force_open;
     int verbose;
     int echo;
+    int create_empty;
     const char *szLogRoot;
     int truncate;
     const char *linkfile;
@@ -123,7 +125,7 @@
         fprintf(stderr, "%s\n", reason);
     }
     fprintf(stderr,
-            "Usage: %s [-v] [-l] [-L linkname] [-p prog] [-f] [-t] [-e] <logfile> "
+            "Usage: %s [-v] [-l] [-L linkname] [-p prog] [-f] [-t] [-e] [-c] <logfile> "
             "{<rotation time in seconds>|<rotation size>(B|K|M|G)} "
             "[offset minutes from UTC]\n\n",
             argv0);
@@ -156,6 +158,7 @@
             "  -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"
+            "  -c       Create log even when it is empty.\n"
             "\n"
             "The program is invoked as \"[prog] <curfile> [<prevfile>]\"\n"
             "where <curfile> is the filename of the newly opened logfile, and\n"
@@ -208,6 +211,7 @@
     fprintf(stderr, "Rotation file date pattern:  %12s\n", config->use_strftime ? "yes" : "no");
     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 create empty logs:  %12s\n", config->create_empty ? "yes" : "no");
     fprintf(stderr, "Rotation file name: %21s\n", config->szLogRoot);
     fprintf(stderr, "Post-rotation prog: %21s\n", config->postrotate_prog);
 }
@@ -518,6 +522,9 @@
     char c;
     const char *opt_arg;
     const char *err = NULL;
+    apr_pollfd_t pollfd = { 0 };
+    apr_status_t pollret = APR_SUCCESS;
+    int polltimeout;
 
     apr_app_initialize(&argc, &argv, NULL);
     atexit(apr_terminate);
@@ -528,7 +535,7 @@
 
     apr_pool_create(&status.pool, NULL);
     apr_getopt_init(&opt, status.pool, argc, argv);
-    while ((rv = apr_getopt(opt, "lL:p:ftve", &c, &opt_arg)) == APR_SUCCESS) {
+    while ((rv = apr_getopt(opt, "lL:p:ftvec", &c, &opt_arg)) == APR_SUCCESS) {
         switch (c) {
         case 'l':
             config.use_localtime = 1;
@@ -551,6 +558,9 @@
         case 'e':
             config.echo = 1;
             break;
+        case 'c':
+            config.create_empty = 1;
+            break;
         }
     }
 
@@ -595,6 +605,13 @@
     if (config.verbose) {
         dumpConfig(&config);
     }
+    
+    if (config.create_empty && config.tRotation) {
+        pollfd.p = status.pool;
+        pollfd.desc_type = APR_POLL_FILE;
+        pollfd.reqevents = APR_POLLIN;
+        pollfd.desc.f = f_stdin;
+    }
 
     /*
      * Immediately open the logfile as we start, if we were forced
@@ -606,13 +623,32 @@
 
     for (;;) {
         nRead = sizeof(buf);
-        rv = apr_file_read(f_stdin, buf, &nRead);
-        if (APR_STATUS_IS_EOF(rv)) {
-            break;
+        if (config.create_empty && config.tRotation) {
+            polltimeout = status.tLogEnd ? status.tLogEnd - get_now(&config) : config.tRotation;
+            if (polltimeout <= 0) {
+                pollret = APR_TIMEUP;
+            }
+            else {
+                pollret = apr_poll(&pollfd, 1, &pollret, apr_time_from_sec(polltimeout));
+            }
         }
-        else if (rv != APR_SUCCESS) {
-            exit(3);
+        if (pollret == APR_SUCCESS) {
+            rv = apr_file_read(f_stdin, buf, &nRead);
+            if (APR_STATUS_IS_EOF(rv)) {
+                break;
+            }
+            else if (rv != APR_SUCCESS) {
+                exit(3);
+            }
         }
+        else if (pollret == APR_TIMEUP) {
+            *buf = 0;
+            nRead = 0;
+        }
+        else {
+            fprintf(stderr, "Unable to poll stdin\n");
+            exit(5);
+        }
         checkRotate(&config, &status);
         if (status.rotateReason != ROTATE_NONE) {
             doRotate(&config, &status);

Reply via email to