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 {