From 8c603f2c95e34f8c06994c0454779b76e7f6c012 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Tue, 31 Aug 2004 04:53:44 +0000
Subject: [PATCH] Replace log_filename_prefix with more general log_filename
 parameter, to allow DBA to choose the form in which log filenames reflect the
 current time.  Also allow for truncating instead of appending to pre-existing
 files --- this is convenient when the log filename pattern rewrites the same
 names cyclically.  Per Ed L.

---
 doc/src/sgml/runtime.sgml                     |  39 ++++-
 src/backend/postmaster/syslogger.c            | 157 ++++++++++++------
 src/backend/utils/misc/guc.c                  |  20 ++-
 src/backend/utils/misc/postgresql.conf.sample |  12 +-
 src/include/postmaster/syslogger.h            |   5 +-
 5 files changed, 166 insertions(+), 67 deletions(-)

diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index bdf029fa64..1a2f24500f 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/runtime.sgml,v 1.279 2004/08/24 00:06:50 neilc Exp $
+$PostgreSQL: pgsql/doc/src/sgml/runtime.sgml,v 1.280 2004/08/31 04:53:43 tgl Exp $
 -->
 
 <Chapter Id="runtime">
@@ -1925,14 +1925,21 @@ archive_command = 'copy "%p" /mnt/server/archivedir/"%f"'  # Win32
        </listitem>
      </varlistentry>
 
-     <varlistentry id="guc-log-filename-prefix" xreflabel="log_filename_prefix">
-      <term><varname>log_filename_prefix</varname> (<type>string</type>)</term>
+     <varlistentry id="guc-log-filename" xreflabel="log_filename">
+      <term><varname>log_filename</varname> (<type>string</type>)</term>
        <listitem>
         <para>
           When <varname>redirect_stderr</> is enabled, this option
-          sets the prefix of the file names of the created log files.
-          The postmaster PID and the current time are appended to this
-          prefix to form an exact log file name.
+          sets the file names of the created log files.  The value
+          is treated as a <systemitem>strftime</> pattern,
+          so <literal>%</>-escapes
+          can be used to specify time-varying file names.
+          If no <literal>%</>-escapes are present,
+          <productname>PostgreSQL</productname> will
+          append the epoch of the new log file's open time.  For example,
+          if <varname>log_filename</> were <literal>server_log</>, then the
+          chosen file name would be <literal>server_log.1093827753</>
+          for a log starting at Sun Aug 29 19:02:33 2004 MST.
          This option can only be set at server start or in the
          <filename>postgresql.conf</filename> configuration file.
         </para>
@@ -1969,6 +1976,26 @@ archive_command = 'copy "%p" /mnt/server/archivedir/"%f"'  # Win32
        </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-log-truncate-on-rotation" xreflabel="log_truncate_on_rotation">
+      <term><varname>log_truncate_on_rotation</varname> (<type>boolean</type>)</term>
+       <listitem>
+        <para>
+          When <varname>redirect_stderr</> is enabled, this option will cause
+          <productname>PostgreSQL</productname> to truncate (overwrite),
+          rather than append to, any existing log file of the same name.
+          However, truncation will occur only when a new file is being opened
+          due to time-based rotation, not during server startup or size-based
+          rotation.  When false, pre-existing files will be appended to in
+          all cases.  For example, using this option in combination with
+          a <varname>log_filename</> like <literal>postgresql-%H.log</>
+          would result in generating twenty-four hourly log files and then
+          cyclically overwriting them.
+         This option can only be set at server start or in the
+         <filename>postgresql.conf</filename> configuration file.
+        </para>
+       </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-syslog-facility" xreflabel="syslog_facility">
       <term><varname>syslog_facility</varname> (<type>string</type>)</term>
        <listitem>
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index 56bb381388..a1e8870e92 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -18,7 +18,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/postmaster/syslogger.c,v 1.7 2004/08/29 05:06:46 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/postmaster/syslogger.c,v 1.8 2004/08/31 04:53:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -61,8 +61,9 @@
 bool		Redirect_stderr = false;
 int			Log_RotationAge = 24 * 60;
 int			Log_RotationSize = 10 * 1024;
-char	   *Log_directory = "pg_log";
-char	   *Log_filename_prefix = "postgresql-";
+char	   *Log_directory = NULL;
+char	   *Log_filename = NULL;
+bool		Log_truncate_on_rotation = false;
 
 /*
  * Globally visible state (used by elog.c)
@@ -72,7 +73,7 @@ bool		am_syslogger = false;
 /*
  * Private state
  */
-static pg_time_t last_rotation_time = 0;
+static pg_time_t next_rotation_time;
 
 static bool redirection_done = false;
 
@@ -109,8 +110,9 @@ static void write_syslogger_file_binary(const char *buffer, int count);
 #ifdef WIN32
 static unsigned int __stdcall pipeThread(void *arg);
 #endif
-static void logfile_rotate(void);
+static void logfile_rotate(bool time_based_rotation);
 static char *logfile_getname(pg_time_t timestamp);
+static void set_next_rotation_time(void);
 static void sigHupHandler(SIGNAL_ARGS);
 
 
@@ -121,7 +123,9 @@ static void sigHupHandler(SIGNAL_ARGS);
 NON_EXEC_STATIC void
 SysLoggerMain(int argc, char *argv[])
 {
-	char		currentLogDir[MAXPGPATH];
+	char	   *currentLogDir;
+	char	   *currentLogFilename;
+	int			currentLogRotationAge;
 
 	IsUnderPostmaster = true;	/* we are a postmaster subprocess now */
 
@@ -218,15 +222,18 @@ SysLoggerMain(int argc, char *argv[])
 	}
 #endif   /* WIN32 */
 
-	/* remember age of initial logfile */
-	last_rotation_time = time(NULL);
-	/* remember active logfile directory */
-	strncpy(currentLogDir, Log_directory, MAXPGPATH);
+	/* remember active logfile parameters */
+	currentLogDir = pstrdup(Log_directory);
+	currentLogFilename = pstrdup(Log_filename);
+	currentLogRotationAge = Log_RotationAge;
+	/* set next planned rotation time */
+	set_next_rotation_time();
 
 	/* main worker loop */
 	for (;;)
 	{
 		bool		rotation_requested = false;
+		bool		time_based_rotation = false;
 
 #ifndef WIN32
 		char		logbuffer[1024];
@@ -242,46 +249,51 @@ SysLoggerMain(int argc, char *argv[])
 			ProcessConfigFile(PGC_SIGHUP);
 
 			/*
-			 * Check if the log directory changed in postgresql.conf. If
-			 * so, force rotation to make sure we're writing the logfiles
-			 * in the right place.
-			 *
-			 * XXX is it worth responding similarly to a change of
-			 * Log_filename_prefix?
+			 * Check if the log directory or filename pattern changed in 
+			 * postgresql.conf. If so, force rotation to make sure we're 
+			 * writing the logfiles in the right place.
 			 */
-			if (strncmp(Log_directory, currentLogDir, MAXPGPATH) != 0)
+			if (strcmp(Log_directory, currentLogDir) != 0)
 			{
-				strncpy(currentLogDir, Log_directory, MAXPGPATH);
+				pfree(currentLogDir);
+				currentLogDir = pstrdup(Log_directory);
 				rotation_requested = true;
 			}
+			if (strcmp(Log_filename, currentLogFilename) != 0)
+			{
+				pfree(currentLogFilename);
+				currentLogFilename = pstrdup(Log_filename);
+				rotation_requested = true;
+			}
+			/*
+			 * If rotation time parameter changed, reset next rotation time,
+			 * but don't immediately force a rotation.
+			 */
+			if (currentLogRotationAge != Log_RotationAge)
+			{
+				currentLogRotationAge = Log_RotationAge;
+				set_next_rotation_time();
+			}
 		}
 
-		if (!rotation_requested &&
-			last_rotation_time != 0 &&
-			Log_RotationAge > 0)
+		if (!rotation_requested && Log_RotationAge > 0)
 		{
-			/*
-			 * Do a logfile rotation if too much time has elapsed since
-			 * the last one.
-			 */
+			/* Do a logfile rotation if it's time */
 			pg_time_t	now = time(NULL);
-			int			elapsed_secs = now - last_rotation_time;
 
-			if (elapsed_secs >= Log_RotationAge * 60)
-				rotation_requested = true;
+			if (now >= next_rotation_time)
+				rotation_requested = time_based_rotation = true;
 		}
 
 		if (!rotation_requested && Log_RotationSize > 0)
 		{
-			/*
-			 * Do a rotation if file is too big
-			 */
+			/* Do a rotation if file is too big */
 			if (ftell(syslogFile) >= Log_RotationSize * 1024L)
 				rotation_requested = true;
 		}
 
 		if (rotation_requested)
-			logfile_rotate();
+			logfile_rotate(time_based_rotation);
 
 #ifndef WIN32
 
@@ -365,7 +377,6 @@ int
 SysLogger_Start(void)
 {
 	pid_t		sysloggerPid;
-	pg_time_t	now;
 	char	   *filename;
 
 	if (!Redirect_stderr)
@@ -424,8 +435,7 @@ SysLogger_Start(void)
 	 * The initial logfile is created right in the postmaster, to verify
 	 * that the Log_directory is writable.
 	 */
-	now = time(NULL);
-	filename = logfile_getname(now);
+	filename = logfile_getname(time(NULL));
 
 	syslogFile = fopen(filename, "a");
 
@@ -736,16 +746,26 @@ pipeThread(void *arg)
  * perform logfile rotation
  */
 static void
-logfile_rotate(void)
+logfile_rotate(bool time_based_rotation)
 {
 	char	   *filename;
-	pg_time_t	now;
 	FILE	   *fh;
 
-	now = time(NULL);
-	filename = logfile_getname(now);
+	/*
+	 * When doing a time-based rotation, invent the new logfile name based
+	 * on the planned rotation time, not current time, to avoid "slippage"
+	 * in the file name when we don't do the rotation immediately.
+	 */
+	if (time_based_rotation)
+		filename = logfile_getname(next_rotation_time);
+	else
+		filename = logfile_getname(time(NULL));
+
+	if (Log_truncate_on_rotation && time_based_rotation)
+		fh = fopen(filename, "w");
+	else
+		fh = fopen(filename, "a");
 
-	fh = fopen(filename, "a");
 	if (!fh)
 	{
 		int			saveerrno = errno;
@@ -784,7 +804,7 @@ logfile_rotate(void)
 	LeaveCriticalSection(&sysfileSection);
 #endif
 
-	last_rotation_time = now;
+	set_next_rotation_time();
 
 	pfree(filename);
 }
@@ -799,25 +819,60 @@ static char *
 logfile_getname(pg_time_t timestamp)
 {
 	char	   *filename;
-	char		stamptext[128];
-
-	pg_strftime(stamptext, sizeof(stamptext), "%Y-%m-%d_%H%M%S",
-				pg_localtime(&timestamp));
+	int			len;
+	struct pg_tm *tm;
 
 	filename = palloc(MAXPGPATH);
 
 	if (is_absolute_path(Log_directory))
-		snprintf(filename, MAXPGPATH, "%s/%s%05u_%s.log",
-				 Log_directory, Log_filename_prefix,
-				 (unsigned int) PostmasterPid, stamptext);
+		snprintf(filename, MAXPGPATH, "%s/", Log_directory);
 	else
-		snprintf(filename, MAXPGPATH, "%s/%s/%s%05u_%s.log",
-				 DataDir, Log_directory, Log_filename_prefix,
-				 (unsigned int) PostmasterPid, stamptext);
+		snprintf(filename, MAXPGPATH, "%s/%s/", DataDir, Log_directory);
+
+	len = strlen(filename);
+
+	if (strchr(Log_filename, '%'))
+	{
+		/* treat it as a strftime pattern */
+		tm = pg_localtime(&timestamp);
+		pg_strftime(filename + len, MAXPGPATH - len, Log_filename, tm);
+	}
+	else 
+	{
+		/* no strftime escapes, so append timestamp to new filename */
+		snprintf(filename + len, MAXPGPATH - len, "%s.%lu",
+				 Log_filename, (unsigned long) timestamp);
+	}
 
 	return filename;
 }
 
+/*
+ * Determine the next planned rotation time, and store in next_rotation_time.
+ */
+static void
+set_next_rotation_time(void)
+{
+	pg_time_t	now;
+	int			rotinterval;
+
+	/* nothing to do if time-based rotation is disabled */
+	if (Log_RotationAge <= 0)
+		return;
+
+	/*
+	 * The requirements here are to choose the next time > now that is a
+	 * "multiple" of the log rotation interval.  "Multiple" can be interpreted
+	 * fairly loosely --- in particular, for intervals larger than an hour,
+	 * it might be interesting to align to local time instead of GMT.
+	 */
+	rotinterval = Log_RotationAge * 60; /* convert to seconds */
+	now = time(NULL);
+	now -= now % rotinterval;
+	now += rotinterval;
+	next_rotation_time = now;
+}
+
 /* --------------------------------
  *		signal handler routines
  * --------------------------------
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 252d4a7f9e..c5919c47fe 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -10,7 +10,7 @@
  * Written by Peter Eisentraut <peter_e@gmx.net>.
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.235 2004/08/30 02:54:40 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.236 2004/08/31 04:53:44 tgl Exp $
  *
  *--------------------------------------------------------------------
  */
@@ -812,6 +812,14 @@ static struct config_bool ConfigureNamesBool[] =
 		&Redirect_stderr,
 		false, NULL, NULL
 	},
+	{
+		{"log_truncate_on_rotation", PGC_SIGHUP, LOGGING_WHERE,
+			gettext_noop("Truncate existing log files of same name during log rotation"),
+			NULL
+		},
+		&Log_truncate_on_rotation,
+		false, NULL, NULL
+	},
 
 #ifdef WAL_DEBUG
 	{
@@ -1665,7 +1673,7 @@ static struct config_string ConfigureNamesString[] =
 	},
 	{
 		{"log_directory", PGC_SIGHUP, LOGGING_WHERE,
-			gettext_noop("Sets the destination directory for logfiles."),
+			gettext_noop("Sets the destination directory for log files."),
 			gettext_noop("May be specified as relative to the cluster directory "
 						 "or as absolute path.")
 		},
@@ -1673,12 +1681,12 @@ static struct config_string ConfigureNamesString[] =
 		"pg_log", NULL, NULL
 	},
 	{
-		{"log_filename_prefix", PGC_SIGHUP, LOGGING_WHERE,
-			gettext_noop("Prefix for file names created in the log_directory."),
+		{"log_filename", PGC_SIGHUP, LOGGING_WHERE,
+			gettext_noop("Sets the file name pattern for log files."),
 			NULL
 		},
-		&Log_filename_prefix,
-		"postgresql-", NULL, NULL
+		&Log_filename,
+		"postgresql-%Y-%m-%d_%H%M%S.log", NULL, NULL
 	},
 
 #ifdef HAVE_SYSLOG
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index cdd427f04b..67b9dbadaf 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -171,9 +171,17 @@
 # This is relevant when logging to stderr:
 #redirect_stderr = false    # Enable capturing of stderr into log files.
 # These are only relevant if redirect_stderr is true:
-#log_directory = 'pg_log'   # Directory where logfiles are written.
+#log_directory = 'pg_log'   # Directory where log files are written.
                             # May be specified absolute or relative to PGDATA
-#log_filename_prefix = 'postgresql_' # Prefix for logfile names.
+#log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' # Log file name pattern.
+                            # May include strftime() escapes
+#log_truncate_on_rotation = false  # If true, any existing log file of the 
+                            # same name as the new log file will be truncated
+                            # rather than appended to.  But such truncation
+                            # only occurs on time-driven rotation,
+                            # not on restarts or size-driven rotation.
+                            # Default is false, meaning append to existing 
+                            # files in all cases.
 #log_rotation_age = 1440    # Automatic rotation of logfiles will happen after
                             # so many minutes.  0 to disable.
 #log_rotation_size = 10240  # Automatic rotation of logfiles will happen after
diff --git a/src/include/postmaster/syslogger.h b/src/include/postmaster/syslogger.h
index 2e1adc6900..12f2ffe5cf 100644
--- a/src/include/postmaster/syslogger.h
+++ b/src/include/postmaster/syslogger.h
@@ -5,7 +5,7 @@
  *
  * Copyright (c) 2004, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/include/postmaster/syslogger.h,v 1.2 2004/08/29 05:06:58 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/postmaster/syslogger.h,v 1.3 2004/08/31 04:53:44 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -17,7 +17,8 @@ extern bool Redirect_stderr;
 extern int	Log_RotationAge;
 extern int	Log_RotationSize;
 extern char *Log_directory;
-extern char *Log_filename_prefix;
+extern char *Log_filename;
+extern bool Log_truncate_on_rotation;
 
 extern bool am_syslogger;
 
-- 
2.24.1