]> granicus.if.org Git - postgresql/commitdiff
Implement "pg_ctl logrotate" command
authorAlexander Korotkov <akorotkov@postgresql.org>
Sat, 1 Sep 2018 16:46:49 +0000 (19:46 +0300)
committerAlexander Korotkov <akorotkov@postgresql.org>
Sat, 1 Sep 2018 16:46:49 +0000 (19:46 +0300)
Currently there are two ways to trigger log rotation in logging collector
process: call pg_rotate_logfile() SQL-function or send SIGUSR1 signal directly
to logging collector process.  However, it's nice to have more suitable way
for external tools to do that, which wouldn't require SQL connection or
knowledge of logging collector pid.  This commit implements triggering log
rotation by "pg_ctl logrotate" command.

Discussion: https://postgr.es/m/20180416.115435.28153375.horiguchi.kyotaro%40lab.ntt.co.jp
Author: Kyotaro Horiguchi, Alexander Kuzmenkov, Alexander Korotkov

doc/src/sgml/maintenance.sgml
doc/src/sgml/ref/pg_ctl-ref.sgml
src/backend/postmaster/postmaster.c
src/backend/postmaster/syslogger.c
src/bin/pg_ctl/pg_ctl.c
src/bin/pg_ctl/t/004_logrotate.pl [new file with mode: 0644]
src/include/postmaster/syslogger.h
src/test/perl/PostgresNode.pm

index 4a68ec3b4041f3d904e8e4d3d500b709a305bcf3..02c512f8bcd387c84de3658468389da978b610d8 100644 (file)
@@ -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 <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>
@@ -945,6 +945,36 @@ pg_ctl start | rotatelogs /var/log/pgsql_log 86400
 </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
index 304a64d6a607e92738e4cf9ab924ac8f549f88d6..e31275a04e27f1bbcaf695a01e67db0bf68984fd 100644 (file)
@@ -97,6 +97,13 @@ PostgreSQL documentation
    <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>
@@ -226,6 +233,12 @@ PostgreSQL documentation
    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>
index 2215ebbb5a5618f6f450b87f6b95cafe904c58b0..7fb4296b7a40f1258e8df3cdead9b49e778a4266 100644 (file)
@@ -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) &&
index 2959d1374ee5d21c8be3d65fb4a9a25144d42c9e..29bdcec8958496894d59c1ea0567fe66f1e124f9 100644 (file)
@@ -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)
index ed2396aa6c5a4df2f1620603e5c899f093f231af..1d0b056dde0c92fdab21fa624aabb96e7f4cc06b 100644 (file)
@@ -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 (file)
index 0000000..fa5ab74
--- /dev/null
@@ -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();
index b35fadc1bd0979c93c0f80302c27a8423062db88..3fcb26cdb8310ecd8625677cfc91763ec1e46c82 100644 (file)
@@ -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
index 79fb457075848c17c10751c62e83681aeac4d192..ae3d8ee10cc96923bf5a8d434a981be02c91a366 100644 (file)
@@ -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
 {