diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index 4a68ec3b40..02c512f8bc 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -932,8 +932,8 @@ analyze threshold = analyze base threshold + analyze scale factor * number of tu
program if you have one that you are already using with other
server software. For example, the rotatelogs
tool included in the Apache distribution
- can be used with PostgreSQL. To do this,
- just pipe the server's
+ can be used with PostgreSQL. One way to
+ do this is to pipe the server's
stderr output to the desired program.
If you start the server with
pg_ctl, then stderr
@@ -945,6 +945,36 @@ pg_ctl start | rotatelogs /var/log/pgsql_log 86400
+
+ You can combine these approaches by setting up logrotate
+ to collect log files produced by PostgreSQL built-in
+ logging collector. In this case, the logging collector defines the names and
+ location of the log files, while logrotate
+ periodically archives these files. When initiating log rotation,
+ logrotate must ensure that the application
+ sends further output to the new file. This is commonly done with a
+ postrotate script that sends a SIGHUP
+ signal to the application, which then reopens the log file.
+ In PostgreSQL, you can run pg_ctl
+ with the logrotate 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 ).
+
+
+
+
+ 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 logrotate 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 prerotate script to ignore open log files.
+
+
+
Another production-grade approach to managing log output is to
send it to syslog and let
diff --git a/doc/src/sgml/ref/pg_ctl-ref.sgml b/doc/src/sgml/ref/pg_ctl-ref.sgml
index 304a64d6a6..e31275a04e 100644
--- a/doc/src/sgml/ref/pg_ctl-ref.sgml
+++ b/doc/src/sgml/ref/pg_ctl-ref.sgml
@@ -97,6 +97,13 @@ PostgreSQL documentation
+
+ pg_ctl
+
+ datadir
+
+
+
pg_ctl
@@ -226,6 +233,12 @@ PostgreSQL documentation
and begin read-write operations.
+
+ mode rotates the server log file.
+ For details on how to use this mode with external log rotation tools, see
+ .
+
+
mode sends a signal to a specified process.
This is primarily valuable on Microsoft Windows
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 2215ebbb5a..7fb4296b7a 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -1268,6 +1268,9 @@ PostmasterMain(int argc, char *argv[])
*/
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,
@@ -5100,11 +5103,18 @@ sigusr1_handler(SIGNAL_ARGS)
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) &&
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index 2959d1374e..29bdcec895 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -57,6 +57,9 @@
*/
#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
@@ -405,7 +408,7 @@ SysLoggerMain(int argc, char *argv[])
{
/*
* 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;
@@ -1506,6 +1509,30 @@ update_metainfo_datafile(void)
* --------------------------------
*/
+/*
+ * 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)
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index ed2396aa6c..1d0b056dde 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -61,6 +61,7 @@ typedef enum
RELOAD_COMMAND,
STATUS_COMMAND,
PROMOTE_COMMAND,
+ LOGROTATE_COMMAND,
KILL_COMMAND,
REGISTER_COMMAND,
UNREGISTER_COMMAND,
@@ -100,6 +101,7 @@ static char version_file[MAXPGPATH];
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;
@@ -125,6 +127,7 @@ static void do_restart(void);
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);
@@ -1171,6 +1174,62 @@ do_promote(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
@@ -1912,19 +1971,20 @@ do_help(void)
{
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
@@ -2337,6 +2397,8 @@ main(int argc, char **argv)
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)
@@ -2443,6 +2505,9 @@ main(int argc, char **argv)
case PROMOTE_COMMAND:
do_promote();
break;
+ case LOGROTATE_COMMAND:
+ do_logrotate();
+ break;
case KILL_COMMAND:
do_kill(killproc);
break;
diff --git a/src/bin/pg_ctl/t/004_logrotate.pl b/src/bin/pg_ctl/t/004_logrotate.pl
new file mode 100644
index 0000000000..fa5ab74817
--- /dev/null
+++ b/src/bin/pg_ctl/t/004_logrotate.pl
@@ -0,0 +1,42 @@
+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();
diff --git a/src/include/postmaster/syslogger.h b/src/include/postmaster/syslogger.h
index b35fadc1bd..3fcb26cdb8 100644
--- a/src/include/postmaster/syslogger.h
+++ b/src/include/postmaster/syslogger.h
@@ -87,6 +87,9 @@ extern void write_syslogger_file(const char *buffer, int count, int dest);
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
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 79fb457075..ae3d8ee10c 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -804,6 +804,27 @@ sub promote
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
{