program if you have one that you are already using with other
server software. For example, the <application>rotatelogs</application>
tool included in the <productname>Apache</productname> distribution
- can be used with <productname>PostgreSQL</productname>. To do this,
- just pipe the server's
+ can be used with <productname>PostgreSQL</productname>. One way to
+ do this is to pipe the server's
<systemitem>stderr</systemitem> output to the desired program.
If you start the server with
<command>pg_ctl</command>, then <systemitem>stderr</systemitem>
</programlisting>
</para>
+ <para>
+ You can combine these approaches by setting up <application>logrotate</application>
+ to collect log files produced by <productname>PostgreSQL</productname> built-in
+ logging collector. In this case, the logging collector defines the names and
+ location of the log files, while <application>logrotate</application>
+ periodically archives these files. When initiating log rotation,
+ <application>logrotate</application> must ensure that the application
+ sends further output to the new file. This is commonly done with a
+ <literal>postrotate</literal> script that sends a <literal>SIGHUP</literal>
+ signal to the application, which then reopens the log file.
+ In <productname>PostgreSQL</productname>, you can run <command>pg_ctl</command>
+ with the <literal>logrotate</literal> option instead. When the server receives
+ this command, the server either switches to a new log file or reopens the
+ existing file, depending on the logging configuration
+ (see <xref linkend="runtime-config-logging-where"/>).
+ </para>
+
+ <note>
+ <para>
+ When using static log file names, the server might fail to reopen the log
+ file if the max open file limit is reached or a file table overflow occurs.
+ In this case, log messages are sent to the old log file until a
+ successful log rotation. If <application>logrotate</application> is
+ configured to compress the log file and delete it, the server may lose
+ the messages logged in this timeframe. To avoid this issue, you can
+ configure the logging collector to dynamically assign log file names
+ and use a <literal>prerotate</literal> script to ignore open log files.
+ </para>
+ </note>
+
<para>
Another production-grade approach to managing log output is to
send it to <application>syslog</application> and let
<arg choice="opt"><option>-s</option></arg>
</cmdsynopsis>
+ <cmdsynopsis>
+ <command>pg_ctl</command>
+ <arg choice="plain"><option>logrotate</option></arg>
+ <arg choice="opt"><option>-D</option> <replaceable>datadir</replaceable></arg>
+ <arg choice="opt"><option>-s</option></arg>
+ </cmdsynopsis>
+
<cmdsynopsis>
<command>pg_ctl</command>
<arg choice="plain"><option>kill</option></arg>
and begin read-write operations.
</para>
+ <para>
+ <option>logrotate</option> mode rotates the server log file.
+ For details on how to use this mode with external log rotation tools, see
+ <xref linkend="logfile-maintenance"/>.
+ </para>
+
<para>
<option>kill</option> mode sends a signal to a specified process.
This is primarily valuable on <productname>Microsoft Windows</productname>
*/
RemovePromoteSignalFiles();
+ /* Do the same for logrotate signal file */
+ RemoveLogrotateSignalFiles();
+
/* Remove any outdated file holding the current log filenames. */
if (unlink(LOG_METAINFO_DATAFILE) < 0 && errno != ENOENT)
ereport(LOG,
signal_child(PgArchPID, SIGUSR1);
}
- if (CheckPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE) &&
- SysLoggerPID != 0)
+ /* Tell syslogger to rotate logfile if requested */
+ if (SysLoggerPID != 0)
{
- /* Tell syslogger to rotate logfile */
- signal_child(SysLoggerPID, SIGUSR1);
+ if (CheckLogrotateSignal())
+ {
+ signal_child(SysLoggerPID, SIGUSR1);
+ RemoveLogrotateSignalFiles();
+ }
+ else if (CheckPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE))
+ {
+ signal_child(SysLoggerPID, SIGUSR1);
+ }
}
if (CheckPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER) &&
*/
#define READ_BUF_SIZE (2 * PIPE_CHUNK_SIZE)
+/* Log rotation signal file path, relative to $PGDATA */
+#define LOGROTATE_SIGNAL_FILE "logrotate"
+
/*
* GUC parameters. Logging_collector cannot be changed after postmaster
{
/*
* Force rotation when both values are zero. It means the request
- * was sent by pg_rotate_logfile.
+ * was sent by pg_rotate_logfile() or "pg_ctl logrotate".
*/
if (!time_based_rotation && size_rotation_for == 0)
size_rotation_for = LOG_DESTINATION_STDERR | LOG_DESTINATION_CSVLOG;
* --------------------------------
*/
+/*
+ * Check to see if a log rotation request has arrived. Should be
+ * called by postmaster after receiving SIGUSR1.
+ */
+bool
+CheckLogrotateSignal(void)
+{
+ struct stat stat_buf;
+
+ if (stat(LOGROTATE_SIGNAL_FILE, &stat_buf) == 0)
+ return true;
+
+ return false;
+}
+
+/*
+ * Remove the file signaling a log rotateion request.
+ */
+void
+RemoveLogrotateSignalFiles(void)
+{
+ unlink(LOGROTATE_SIGNAL_FILE);
+}
+
/* SIGHUP: set flag to reload config file */
static void
sigHupHandler(SIGNAL_ARGS)
RELOAD_COMMAND,
STATUS_COMMAND,
PROMOTE_COMMAND,
+ LOGROTATE_COMMAND,
KILL_COMMAND,
REGISTER_COMMAND,
UNREGISTER_COMMAND,
static char pid_file[MAXPGPATH];
static char backup_file[MAXPGPATH];
static char promote_file[MAXPGPATH];
+static char logrotate_file[MAXPGPATH];
#ifdef WIN32
static DWORD pgctl_start_type = SERVICE_AUTO_START;
static void do_reload(void);
static void do_status(void);
static void do_promote(void);
+static void do_logrotate(void);
static void do_kill(pgpid_t pid);
static void print_msg(const char *msg);
static void adjust_data_dir(void);
print_msg(_("server promoting\n"));
}
+/*
+ * log rotate
+ */
+
+static void
+do_logrotate(void)
+{
+ FILE *logrotatefile;
+ pgpid_t pid;
+
+ pid = get_pgpid(false);
+
+ if (pid == 0) /* no pid file */
+ {
+ write_stderr(_("%s: PID file \"%s\" does not exist\n"), progname, pid_file);
+ write_stderr(_("Is server running?\n"));
+ exit(1);
+ }
+ else if (pid < 0) /* standalone backend, not postmaster */
+ {
+ pid = -pid;
+ write_stderr(_("%s: cannot rotate log file; "
+ "single-user server is running (PID: %ld)\n"),
+ progname, pid);
+ exit(1);
+ }
+
+ snprintf(logrotate_file, MAXPGPATH, "%s/logrotate", pg_data);
+
+ if ((logrotatefile = fopen(logrotate_file, "w")) == NULL)
+ {
+ write_stderr(_("%s: could not create log rotation signal file \"%s\": %s\n"),
+ progname, logrotate_file, strerror(errno));
+ exit(1);
+ }
+ if (fclose(logrotatefile))
+ {
+ write_stderr(_("%s: could not write log rotation signal file \"%s\": %s\n"),
+ progname, logrotate_file, strerror(errno));
+ exit(1);
+ }
+
+ sig = SIGUSR1;
+ if (kill((pid_t) pid, sig) != 0)
+ {
+ write_stderr(_("%s: could not send log rotation signal (PID: %ld): %s\n"),
+ progname, pid, strerror(errno));
+ if (unlink(logrotate_file) != 0)
+ write_stderr(_("%s: could not remove log rotation signal file \"%s\": %s\n"),
+ progname, logrotate_file, strerror(errno));
+ exit(1);
+ }
+
+ print_msg(_("server signaled to rotate log file\n"));
+}
+
/*
* utility routines
{
printf(_("%s is a utility to initialize, start, stop, or control a PostgreSQL server.\n\n"), progname);
printf(_("Usage:\n"));
- printf(_(" %s init[db] [-D DATADIR] [-s] [-o OPTIONS]\n"), progname);
- printf(_(" %s start [-D DATADIR] [-l FILENAME] [-W] [-t SECS] [-s]\n"
- " [-o OPTIONS] [-p PATH] [-c]\n"), progname);
- printf(_(" %s stop [-D DATADIR] [-m SHUTDOWN-MODE] [-W] [-t SECS] [-s]\n"), progname);
- printf(_(" %s restart [-D DATADIR] [-m SHUTDOWN-MODE] [-W] [-t SECS] [-s]\n"
- " [-o OPTIONS] [-c]\n"), progname);
- printf(_(" %s reload [-D DATADIR] [-s]\n"), progname);
- printf(_(" %s status [-D DATADIR]\n"), progname);
- printf(_(" %s promote [-D DATADIR] [-W] [-t SECS] [-s]\n"), progname);
- printf(_(" %s kill SIGNALNAME PID\n"), progname);
+ printf(_(" %s init[db] [-D DATADIR] [-s] [-o OPTIONS]\n"), progname);
+ printf(_(" %s start [-D DATADIR] [-l FILENAME] [-W] [-t SECS] [-s]\n"
+ " [-o OPTIONS] [-p PATH] [-c]\n"), progname);
+ printf(_(" %s stop [-D DATADIR] [-m SHUTDOWN-MODE] [-W] [-t SECS] [-s]\n"), progname);
+ printf(_(" %s restart [-D DATADIR] [-m SHUTDOWN-MODE] [-W] [-t SECS] [-s]\n"
+ " [-o OPTIONS] [-c]\n"), progname);
+ printf(_(" %s reload [-D DATADIR] [-s]\n"), progname);
+ printf(_(" %s status [-D DATADIR]\n"), progname);
+ printf(_(" %s promote [-D DATADIR] [-W] [-t SECS] [-s]\n"), progname);
+ printf(_(" %s logrotate [-D DATADIR] [-s]\n"), progname);
+ printf(_(" %s kill SIGNALNAME PID\n"), progname);
#ifdef WIN32
- printf(_(" %s register [-D DATADIR] [-N SERVICENAME] [-U USERNAME] [-P PASSWORD]\n"
- " [-S START-TYPE] [-e SOURCE] [-W] [-t SECS] [-s] [-o OPTIONS]\n"), progname);
+ printf(_(" %s register [-D DATADIR] [-N SERVICENAME] [-U USERNAME] [-P PASSWORD]\n"
+ " [-S START-TYPE] [-e SOURCE] [-W] [-t SECS] [-s] [-o OPTIONS]\n"), progname);
printf(_(" %s unregister [-N SERVICENAME]\n"), progname);
#endif
ctl_command = STATUS_COMMAND;
else if (strcmp(argv[optind], "promote") == 0)
ctl_command = PROMOTE_COMMAND;
+ else if (strcmp(argv[optind], "logrotate") == 0)
+ ctl_command = LOGROTATE_COMMAND;
else if (strcmp(argv[optind], "kill") == 0)
{
if (argc - optind < 3)
case PROMOTE_COMMAND:
do_promote();
break;
+ case LOGROTATE_COMMAND:
+ do_logrotate();
+ break;
case KILL_COMMAND:
do_kill(killproc);
break;
--- /dev/null
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More tests => 1;
+use Time::HiRes qw(usleep);
+
+my $tempdir = TestLib::tempdir;
+
+my $node = get_new_node('primary');
+$node->init(allows_streaming => 1);
+$node->append_conf(
+ 'postgresql.conf', qq(
+logging_collector = on
+log_directory = 'log'
+log_filename = 'postgresql.log'
+));
+
+$node->start();
+
+# Rename log file and rotate log. Then log file should appear again.
+
+my $logfile = $node->data_dir . '/log/postgresql.log';
+my $old_logfile = $node->data_dir . '/log/postgresql.old';
+rename($logfile, $old_logfile);
+
+$node->logrotate();
+
+# pg_ctl logrotate doesn't wait until rotation request being completed. So
+# we have to wait some time until log file appears.
+my $attempts = 0;
+my $max_attempts = 180 * 10;
+while (not -e $logfile and $attempts < $max_attempts)
+{
+ usleep(100_000);
+ $attempts++;
+}
+
+ok(-e $logfile, "log file exists");
+
+$node->stop();
extern void SysLoggerMain(int argc, char *argv[]) pg_attribute_noreturn();
#endif
+extern bool CheckLogrotateSignal(void);
+extern void RemoveLogrotateSignalFiles(void);
+
/*
* Name of files saving meta-data information about the log
* files currently in use by the syslogger
return;
}
+=pod
+
+=item $node->logrotate()
+
+Wrapper for pg_ctl logrotate
+
+=cut
+
+sub logrotate
+{
+ my ($self) = @_;
+ my $port = $self->port;
+ my $pgdata = $self->data_dir;
+ my $logfile = $self->logfile;
+ my $name = $self->name;
+ print "### Rotating log in node \"$name\"\n";
+ TestLib::system_or_bail('pg_ctl', '-D', $pgdata, '-l', $logfile,
+ 'logrotate');
+ return;
+}
+
# Internal routine to enable streaming replication on a standby node.
sub enable_streaming
{