]> git.ipfire.org Git - thirdparty/openldap.git/commitdiff
ITS#6949 add support for logfile rotation
authorHoward Chu <hyc@openldap.org>
Thu, 15 Jul 2021 15:27:51 +0000 (16:27 +0100)
committerQuanah Gibson-Mount <quanah@openldap.org>
Thu, 5 Aug 2021 15:45:19 +0000 (15:45 +0000)
Uses debuglevel, not sysloglevel.

doc/man/man5/slapd-config.5
doc/man/man5/slapd.conf.5
servers/slapd/bconfig.c
servers/slapd/main.c
servers/slapd/proto-slap.h

index 5686c08253658021503088beff03c93cbfe746aa..ac87c3c68c481ea74cee4fa04d081454e7c10f0d 100644 (file)
@@ -567,10 +567,23 @@ option description.  The default is 71.
 Specify a file for recording slapd debug messages. By default these messages
 only go to stderr, are not recorded anywhere else, and are unrelated to
 messages exposed by the
-.B loglevel
+.B olcLogLevel
 configuration parameter. Specifying a logfile copies messages to both stderr
 and the logfile.
 .TP
+.B olcLogFileOnly: TRUE | FALSE
+Specify that debug messages should only go to the configured logfile, and
+not to stderr.
+.TP
+.B olcLogFileRotate: <max> <Mbytes> <hours>
+Specify automatic rotation for the configured logfile as the maximum
+number of old logfiles to retain, a maximum size in megabytes to allow a
+logfile to grow before rotation, and a maximum age in hours for a logfile
+to be used before rotation. The maximum number must be in the range 1-99.
+Setting Mbytes or hours to zero disables the size or age check, respectively.
+At least one of Mbytes or hours must be non-zero. By default no automatic
+rotation will be performed.
+.TP
 .B olcLogLevel: <integer> [...]
 Specify the level at which debugging statements and operation 
 statistics should be syslogged (currently logged to the
index d948d1bbe87a499e61e45380d1e9b339c78c64ca..f0b43796b6a3e9bf19775d35544501e9305b087b 100644 (file)
@@ -625,6 +625,19 @@ messages exposed by the
 configuration parameter. Specifying a logfile copies messages to both stderr
 and the logfile.
 .TP
+.B logfile-only on | off
+Specify that debug messages should only go to the configured logfile, and
+not to stderr.
+.TP
+.B logfile-rotate <max> <Mbytes> <hours>
+Specify automatic rotation for the configured logfile as the maximum
+number of old logfiles to retain, a maximum size in megabytes to allow a
+logfile to grow before rotation, and a maximum age in hours for a logfile
+to be used before rotation. The maximum number must be in the range 1-99.
+Setting Mbytes or hours to zero disables the size or age check, respectively.
+At least one of Mbytes or hours must be non-zero. By default no automatic
+rotation will be performed.
+.TP
 .B loglevel <integer> [...]
 Specify the level at which debugging statements and operation 
 statistics should be syslogged (currently logged to the
index 09d184daafec0d63f8084a6201b964bf7d736fc2..364b4de31e7c4ecc1992b26d14de7282ec2f9e5b 100644 (file)
@@ -80,8 +80,6 @@ typedef struct {
 static CfBackInfo cfBackInfo;
 
 static char    *passwd_salt;
-static FILE *logfile;
-static char    *logfileName;
 static AccessControl *defacl_parsed = NULL;
 
 static struct berval cfdir;
@@ -203,6 +201,7 @@ enum {
        CFG_TLS_CACERT,
        CFG_TLS_CERT,
        CFG_TLS_KEY,
+       CFG_LOGFILE_ROTATE,
 
        CFG_LAST
 };
@@ -486,6 +485,14 @@ static ConfigTable config_back_cf_table[] = {
                &config_generic, "( OLcfgGlAt:27 NAME 'olcLogFile' "
                        "EQUALITY caseExactMatch "
                        "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+       { "logfile-only", "on|off", 2, 2, 0, ARG_ON_OFF,
+               &logfile_only, "( OLcfgGlAt:102 NAME 'olcLogFileOnly' "
+                       "EQUALITY booleanMatch "
+                       "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+       { "logfile-rotate", "max> <Mbyte> <hours", 4, 4, 0, ARG_MAGIC|CFG_LOGFILE_ROTATE,
+               &config_generic, "( OLcfgGlAt:103 NAME 'olcLogFileRotate' "
+                       "EQUALITY caseIgnoreMatch "
+                       "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
        { "loglevel", "level", 2, 0, 0, ARG_MAGIC,
                &config_loglevel, "( OLcfgGlAt:28 NAME 'olcLogLevel' "
                        "EQUALITY caseIgnoreMatch "
@@ -980,7 +987,7 @@ static ConfigOCs cf_ocs[] = {
                 "olcIndexSubstrAnyLen $ olcIndexSubstrAnyStep $ olcIndexHash64 $ "
                 "olcIndexIntLen $ "
                 "olcListenerThreads $ olcLocalSSF $ olcLogFile $ olcLogLevel $ "
-                "olcMaxFilterDepth $ "
+                "olcLogFileOnly $ olcLogFileRotate $ olcMaxFilterDepth $ "
                 "olcPasswordCryptSaltFormat $ olcPasswordHash $ olcPidFile $ "
                 "olcPluginLogFile $ olcReadOnly $ olcReferral $ "
                 "olcReplogFile $ olcRequires $ olcRestrict $ olcReverseLookup $ "
@@ -1373,11 +1380,27 @@ config_generic(ConfigArgs *c) {
                                rc = 1;
                        }
                        break;
-               case CFG_LOGFILE:
-                       if ( logfileName )
+               case CFG_LOGFILE: {
+                       const char *logfileName = logfile_name();
+                       if ( logfileName && *logfileName )
                                c->value_string = ch_strdup( logfileName );
                        else
                                rc = 1;
+                       }
+                       break;
+               case CFG_LOGFILE_ROTATE:
+                       rc = 1;
+                       if ( logfile_max ) {
+                               char buf[64];
+                               struct berval bv;
+                               bv.bv_len = snprintf( buf, sizeof(buf), "%d %ld %ld", logfile_max,
+                                       (long) logfile_fslimit / 1048576, (long) logfile_age / 3600 );
+                               if ( bv.bv_len > 0 && bv.bv_len < sizeof(buf) ) {
+                                       bv.bv_val = buf;
+                                       value_add_one( &c->rvalue_vals, &bv );
+                                       rc = 0;
+                               }
+                       }
                        break;
                case CFG_LASTMOD:
                        c->value_int = (SLAP_NOLASTMOD(c->be) == 0);
@@ -1610,12 +1633,11 @@ config_generic(ConfigArgs *c) {
                        break;
 
                case CFG_LOGFILE:
-                       ch_free( logfileName );
-                       logfileName = NULL;
-                       if ( logfile ) {
-                               fclose( logfile );
-                               logfile = NULL;
-                       }
+                       logfile_close();
+                       break;
+
+               case CFG_LOGFILE_ROTATE:
+                       logfile_max = logfile_fslimit = logfile_age = 0;
                        break;
 
                case CFG_SERVERID: {
@@ -2392,11 +2414,44 @@ sortval_reject:
                        }
                        break;
                case CFG_LOGFILE: {
-                               if ( logfileName ) ch_free( logfileName );
-                               logfileName = c->value_string;
-                               logfile = fopen(logfileName, "w");
-                               if(logfile) lutil_debug_file(logfile);
-                       } break;
+                               int rc = logfile_open( c->value_string );
+                               ch_free( c->value_string );
+                               return rc;
+                       }
+                       break;
+
+               case CFG_LOGFILE_ROTATE: {
+                       unsigned lf_max, lf_mbyte, lf_hour;
+                       if ( lutil_atoux( &lf_max, c->argv[1], 0 ) != 0 ) {
+                               snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> "
+                                       "invalid max value \"%s\"", c->argv[0], c->argv[1] );
+                               return 1;
+                       }
+                       if ( !lf_max || lf_max > 99 ) {
+                               snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> "
+                                       "invalid max value \"%s\" must be 1-99", c->argv[0], c->argv[1] );
+                               return 1;
+                       }
+                       if ( lutil_atoux( &lf_mbyte, c->argv[2], 0 ) != 0 ) {
+                               snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> "
+                                       "invalid Mbyte value \"%s\"", c->argv[0], c->argv[1] );
+                               return 1;
+                       }
+                       if ( lutil_atoux( &lf_hour, c->argv[3], 0 ) != 0 ) {
+                               snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> "
+                                       "invalid hours value \"%s\"", c->argv[0], c->argv[2] );
+                               return 1;
+                       }
+                       if ( !lf_mbyte && !lf_hour ) {
+                               snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> "
+                                       "Mbyte and hours cannot both be zero", c->argv[0] );
+                               return 1;
+                       }
+                       logfile_max = lf_max;
+                       logfile_fslimit = lf_mbyte * 1048576;   /* Megabytes to bytes */
+                       logfile_age = lf_hour * 3600;                   /* hours to seconds */
+                       }
+                       break;
 
                case CFG_LASTMOD:
                        if(SLAP_NOLASTMODCMD(c->be)) {
index d92f978dd088711aa70742e8abbef68bf1fe45f4..6ca1b00a794901aa80b58cbf79bb4e502cdf0f12 100644 (file)
 #include <ac/wait.h>
 #include <ac/errno.h>
 
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <fcntl.h>
+
 #include "slap.h"
 #include "lutil.h"
 #include "ldif.h"
@@ -377,27 +381,121 @@ usage( char *name )
     );
 }
 
+static char logfile_suffix[sizeof(".xx.gz")];
+char logfile_path[MAXPATHLEN - sizeof(logfile_suffix) -1];
+long logfile_fslimit;
+int logfile_age, logfile_only, logfile_max;
+
+static ldap_pvt_thread_mutex_t logfile_mutex;
+static off_t logfile_fsize;
+static time_t logfile_fcreated;
+static int logfile_fd;
+static char logpaths[2][MAXPATHLEN];
+static int logpathlen;
+
 typedef void (BER_logger)(const char *buf);
 static BER_logger *ber_logger;
 static void debug_print( const char *data )
 {
-       char buf[4136]; /* 4096 + 40 */
+       char prefix[sizeof("ssssssssssssssss.ffffffff 0xtttttttttttttttt ")];
+       struct iovec iov[2];
+       int rotate = 0;
 #ifdef HAVE_CLOCK_GETTIME
        struct timespec tv;
 #define        TS      "%08x"
 #define        Tfrac   tv.tv_nsec
-       clock_gettime( CLOCK_REALTIME, &tv );
+#define gettime(tv)    clock_gettime( CLOCK_REALTIME, tv )
 #else
        struct timeval tv;
 #define        TS      "%05x"
 #define        Tfrac   tv.tv_usec
-       gettimeofday( &tv, NULL );
+#define        gettime(tv)     gettimeofday( tv, NULL )
 #endif
 
-       buf[sizeof(buf)-1] = '\0';
-       snprintf( buf, sizeof(buf)-1, "%lx." TS " %p %s",
-               (long)tv.tv_sec, Tfrac, (void *)ldap_pvt_thread_self(), data );
-       ber_logger( buf );
+       gettime( &tv );
+       iov[0].iov_base = prefix;
+       iov[0].iov_len = sprintf( prefix, "%lx." TS " %p ",
+               (long)tv.tv_sec, (unsigned int)Tfrac, (void *)ldap_pvt_thread_self() );
+       iov[1].iov_base = (void *)data;
+       iov[1].iov_len = strlen( data );
+       if ( !logfile_only )
+               writev( 2, iov, 2 );
+       if ( logfile_fd ) {
+               int len = iov[0].iov_len + iov[1].iov_len;
+               if ( logfile_fslimit || logfile_age ) {
+                       ldap_pvt_thread_mutex_lock( &logfile_mutex );
+                       if ( logfile_fslimit && logfile_fsize + len > logfile_fslimit )
+                               rotate = 1;
+                       if ( logfile_age && tv.tv_sec - logfile_fcreated >= logfile_age )
+                               rotate |= 2;
+                       if ( rotate ) {
+                               close( logfile_fd );
+                               strcpy( logpaths[0]+logpathlen, ".tmp" );
+                               rename( logfile_path, logpaths[0] );
+                               logfile_open( logfile_path );
+                       }
+               }
+               len = writev( logfile_fd, iov, 2 );
+               if ( len > 0 )
+                       logfile_fsize += len;
+               if ( logfile_fslimit || logfile_age )
+                       ldap_pvt_thread_mutex_unlock( &logfile_mutex );
+       }
+       if ( rotate ) {
+               int i;
+               for (i=logfile_max; i > 1; i--) {
+                       sprintf( logpaths[0]+logpathlen, ".%02d", i );
+                       sprintf( logpaths[1]+logpathlen, ".%02d", i-1 );
+                       rename( logpaths[1], logpaths[0] );
+               }
+               sprintf( logpaths[0]+logpathlen, ".tmp" );
+               rename( logpaths[0], logpaths[1] );
+       }
+}
+
+void logfile_close()
+{
+       if ( logfile_fd ) {
+               close( logfile_fd );
+               logfile_fd = 0;
+       }
+       logfile_path[0] = '\0';
+}
+
+int logfile_open( const char *path )
+{
+       struct stat st;
+       int fd;
+
+       fd = open( path, O_CREAT|O_WRONLY, 0640 );
+       if ( fd < 0 )
+               return errno;
+
+       if ( fstat( fd, &st )) {
+               close( fd );
+               return errno;
+       }
+
+       if ( !logfile_path[0] ) {
+               logpathlen = strlen( path );
+               if ( logpathlen >= sizeof(logfile_path) )
+                       return ENAMETOOLONG;
+               strcpy( logfile_path, path );
+               strcpy( logpaths[0], path );
+               strcpy( logpaths[1], path );
+       }
+
+       logfile_fsize = st.st_size;
+       logfile_fcreated = st.st_ctime; /* not strictly true but close enough */
+       logfile_fd = fd;
+       lseek( fd, 0, SEEK_END );
+
+       return 0;
+}
+
+const char *logfile_name()
+{
+       return logfile_path[0] ? logfile_path : NULL;
 }
 
 #ifdef HAVE_NT_SERVICE_MANAGER
@@ -449,8 +547,8 @@ int main( int argc, char **argv )
 
        slap_sl_mem_init();
 
-
        (void) ldap_pvt_thread_initialize();
+       ldap_pvt_thread_mutex_init( &logfile_mutex );
 
        serverName = lutil_progname( "slapd", argc, argv );
 
@@ -1164,6 +1262,7 @@ stop:
        mal_dumpleaktrace( leakfile );
 #endif
 
+       ldap_pvt_thread_mutex_destroy( &logfile_mutex );
        MAIN_RETURN(rc);
 }
 
index d2ff1adae9c010cff3801c87a0c077ec7c6a8a32..c4b6ccdb0754f0b594a0ec320d6fa0d0120eb62d 100644 (file)
@@ -1254,6 +1254,16 @@ LDAP_SLAPD_F (int)
 parse_debug_unknowns LDAP_P(( char **unknowns, int *levelp ));
 LDAP_SLAPD_F (void)
 slap_check_unknown_level LDAP_P(( char *levelstr, int level ));
+LDAP_SLAPD_F (int)
+logfile_open LDAP_P(( const char *path ));
+LDAP_SLAPD_F (void)
+logfile_close LDAP_P(( void ));
+LDAP_SLAPD_F (const char *)
+logfile_name LDAP_P(( void ));
+LDAP_SLAPD_V(int)  logfile_age;
+LDAP_SLAPD_V(int)  logfile_only;
+LDAP_SLAPD_V(int)  logfile_max;
+LDAP_SLAPD_V(long)     logfile_fslimit;
 
 /*
  * matchedValues.c