From bdf8ef6925de6ea1a9330fa1ce32e1a315d07eb2 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Thu, 5 Aug 2004 23:32:13 +0000 Subject: [PATCH] Create a built-in log rotation program, so that we no longer have to recommend that people go get Apache's rotatelogs program. Additional benefits are that configuration is done through GUC, rather than externally, and that the postmaster can monitor the log rotator and restart it after failure (though we certainly hope that won't happen often). Andreas Pflug, some rework by Tom Lane. --- doc/src/sgml/maintenance.sgml | 72 +- doc/src/sgml/runtime.sgml | 82 +- src/backend/postmaster/Makefile | 4 +- src/backend/postmaster/pgarch.c | 4 +- src/backend/postmaster/pgstat.c | 4 +- src/backend/postmaster/postmaster.c | 90 ++- src/backend/postmaster/syslogger.c | 748 ++++++++++++++++++ src/backend/utils/error/elog.c | 9 +- src/backend/utils/misc/guc.c | 68 +- src/backend/utils/misc/postgresql.conf.sample | 14 + src/include/postmaster/postmaster.h | 4 +- src/include/postmaster/syslogger.h | 39 + src/include/utils/elog.h | 4 +- 13 files changed, 1073 insertions(+), 69 deletions(-) create mode 100644 src/backend/postmaster/syslogger.c create mode 100644 src/include/postmaster/syslogger.h diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml index 91ead3dc16..7d1dafad31 100644 --- a/doc/src/sgml/maintenance.sgml +++ b/doc/src/sgml/maintenance.sgml @@ -1,5 +1,5 @@ @@ -445,22 +445,52 @@ VACUUM - If you simply direct the stderr of the postmaster into a - file, the only way to truncate the log file is to stop and restart + If you simply direct the stderr of the + postmaster into a + file, you will have log output, but + the only way to truncate the log file is to stop and restart the postmaster. This may be OK if you are using PostgreSQL in a development environment, but few production servers would find this behavior acceptable. - The simplest production-grade approach to managing log output is to + A better approach is to send the postmaster's + stderr output to some type of log rotation program. + There is a built-in log rotation program, which you can use by + setting the configuration parameter redirect_stderr to + true in postgresql.conf. The control + parameters for this program are described in . + + + + Alternatively, you might prefer to use an external log rotation + 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 postmaster's + stderr output to the desired program. + If you start the server with + pg_ctl, then stderr + is already redirected to stdout, so you just need a + pipe command: + + +pg_ctl start | rotatelogs /var/log/pgsql_log 86400 + + + + + Another production-grade approach to managing log output is to send it all to syslog and let syslog deal with file rotation. To do this, set the - configuration parameter log_destination to 'syslog' (to log to - syslog only) in postgresql.conf. Then - you can send a SIGHUP signal to the - syslog daemon whenever you want to force it to - start writing a new log file. If you want to automate log + configuration parameter log_destination to syslog + (to log to syslog only) in + postgresql.conf. Then you can send a SIGHUP + signal to the syslog daemon whenever you want to force it + to start writing a new log file. If you want to automate log rotation, the logrotate program can be configured to work with log files from syslog. @@ -471,27 +501,15 @@ VACUUM particularly with large log messages; it may truncate or drop messages just when you need them the most. Also, on linux, syslog will sync each message to disk, yielding poor - performance. Use a - at the start of the file name - in the syslog config file to disable this behavior. + performance. (You can use a - at the start of the file name + in the syslog config file to disable this behavior.) - You may find it more useful to pipe the - stderr of the postmaster to some type of - log rotation program. If you start the server with - pg_ctl, then the stderr of the postmaster - is already redirected to stdout, so you just need a - pipe command: - - -pg_ctl start | rotatelogs /var/log/pgsql_log 86400 - - - The PostgreSQL distribution doesn't include a - suitable log rotation program, but there are many available on the - Internet. For example, the rotatelogs - tool included in the Apache distribution - can be used with PostgreSQL. + Note that all the solutions described above take care of starting new + log files at configurable intervals, but they do not handle deletion + of old, no-longer-interesting log files. You will also want to set + up a batch job to periodically delete old log files. diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index 95a8c23c82..9f569712b0 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -1,5 +1,5 @@ @@ -1800,15 +1800,91 @@ SET ENABLE_SEQSCAN TO OFF; option to a list of desired log destinations separated by commas. The default is to log to stderr only. + This option can only be set at server start or in the + postgresql.conf configuration file. + + redirect_stderr (boolean) + + + This option allows messages sent to stderr to be + captured and redirected into log files. + This option, in combination with logging to stderr, + is often more useful than + logging to syslog, since some types of messages + may not appear in syslog output (a common example + is dynamic-linker failure messages). + This option can only be set at server start. + + + + + + log_directory (string) + + + When redirect_stderr is enabled, this option + determines the directory in which log files will be created. + It may be specified as an absolute path, or relative to the + cluster data directory. + This option can only be set at server start or in the + postgresql.conf configuration file. + + + + + + log_filename_prefix (string) + + + When 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. + This option can only be set at server start or in the + postgresql.conf configuration file. + + + + + + log_rotation_age (integer) + + + When redirect_stderr is enabled, this option + determines the maximum lifetime of an individual log file. + After this many minutes have elapsed, a new log file will + be created. Set to zero to disable time-based creation of + new log files. + This option can only be set at server start or in the + postgresql.conf configuration file. + + + + + + log_rotation_size (integer) + + + When redirect_stderr is enabled, this option + determines the maximum size of an individual log file. + After this many kilobytes have been emitted into a log file, + a new log file will be created. Set to zero to disable size-based + creation of new log files. + This option can only be set at server start or in the + postgresql.conf configuration file. + + + + syslog_facility (string) - If logging to syslog is enabled, this option + When logging to syslog is enabled, this option determines the syslog facility to be used. You may choose from LOCAL0, LOCAL1, @@ -1826,7 +1902,7 @@ SET ENABLE_SEQSCAN TO OFF; syslog_ident (string) - If logging to syslog is enabled, this option + When logging to syslog is enabled, this option determines the program name used to identify PostgreSQL messages in syslog logs. The default is diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index abbac1674f..fdb12febb4 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -4,7 +4,7 @@ # Makefile for src/backend/postmaster # # IDENTIFICATION -# $PostgreSQL: pgsql/src/backend/postmaster/Makefile,v 1.18 2004/07/21 20:34:46 momjian Exp $ +# $PostgreSQL: pgsql/src/backend/postmaster/Makefile,v 1.19 2004/08/05 23:32:10 tgl Exp $ # #------------------------------------------------------------------------- @@ -12,7 +12,7 @@ subdir = src/backend/postmaster top_builddir = ../../.. include $(top_builddir)/src/Makefile.global -OBJS = postmaster.o bgwriter.o pgstat.o pgarch.o +OBJS = postmaster.o bgwriter.o pgstat.o pgarch.o syslogger.o all: SUBSYS.o diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index 960ece7583..1d398ba596 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -19,7 +19,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/postmaster/pgarch.c,v 1.4 2004/08/03 20:32:33 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/postmaster/pgarch.c,v 1.5 2004/08/05 23:32:10 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -172,7 +172,7 @@ pgarch_start(void) beos_backend_startup(); #endif /* Close the postmaster's sockets */ - ClosePostmasterPorts(); + ClosePostmasterPorts(false); /* Drop our connection to postmaster's shared memory, as well */ PGSharedMemoryDetach(); diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index dbd4f15cef..7638dd28a1 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -13,7 +13,7 @@ * * Copyright (c) 2001-2003, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/backend/postmaster/pgstat.c,v 1.77 2004/07/01 00:50:36 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/postmaster/pgstat.c,v 1.78 2004/08/05 23:32:10 tgl Exp $ * ---------- */ #include "postgres.h" @@ -611,7 +611,7 @@ pgstat_start(void) beos_backend_startup(); #endif /* Close the postmaster's sockets */ - ClosePostmasterPorts(); + ClosePostmasterPorts(false); /* Drop our connection to postmaster's shared memory, as well */ PGSharedMemoryDetach(); diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 6c9e87f5f3..870ad318a8 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -37,7 +37,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/postmaster/postmaster.c,v 1.419 2004/08/04 20:09:47 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/postmaster/postmaster.c,v 1.420 2004/08/05 23:32:10 tgl Exp $ * * NOTES * @@ -104,6 +104,7 @@ #include "nodes/nodes.h" #include "postmaster/postmaster.h" #include "postmaster/pgarch.h" +#include "postmaster/syslogger.h" #include "storage/fd.h" #include "storage/ipc.h" #include "storage/pg_shmem.h" @@ -199,7 +200,8 @@ char *preload_libraries_string = NULL; static pid_t StartupPID = 0, BgWriterPID = 0, PgArchPID = 0, - PgStatPID = 0; + PgStatPID = 0, + SysLoggerPID = 0; /* Startup/shutdown state */ #define NoShutdown 0 @@ -828,7 +830,7 @@ PostmasterMain(int argc, char *argv[]) * CAUTION: when changing this list, check for side-effects on the signal * handling setup of child processes. See tcop/postgres.c, * bootstrap/bootstrap.c, postmaster/bgwriter.c, postmaster/pgarch.c, - * and postmaster/pgstat.c. + * postmaster/pgstat.c, and postmaster/syslogger.c. */ pqinitmask(); PG_SETMASK(&BlockSig); @@ -850,6 +852,11 @@ PostmasterMain(int argc, char *argv[]) pqsignal(SIGXFSZ, SIG_IGN); /* ignored */ #endif + /* + * If enabled, start up syslogger collection subprocess + */ + SysLoggerPID = SysLogger_Start(); + /* * Reset whereToSendOutput from Debug (its starting state) to None. * This stops ereport from sending log messages to stderr unless @@ -933,8 +940,8 @@ checkDataDir(const char *checkdir) { write_stderr("%s does not know where to find the database system data.\n" "You must specify the directory that contains the database system\n" - "or configuration files by either specifying the -D invocation option\n" - "or by setting the PGDATA environment variable.\n", + "either by specifying the -D invocation option or by setting the\n" + "PGDATA environment variable.\n", progname); ExitPostmaster(2); } @@ -944,12 +951,12 @@ checkDataDir(const char *checkdir) if (errno == ENOENT) ereport(FATAL, (errcode_for_file_access(), - errmsg("data or configuration location \"%s\" does not exist", + errmsg("data directory \"%s\" does not exist", checkdir))); else ereport(FATAL, (errcode_for_file_access(), - errmsg("could not read permissions of \"%s\": %m", + errmsg("could not read permissions of directory \"%s\": %m", checkdir))); } @@ -1050,7 +1057,7 @@ pmdaemonize(void) ExitPostmaster(1); } #endif - i = open(NULL_DEV, O_RDWR | PG_BINARY); + i = open(NULL_DEV, O_RDWR); dup2(i, 0); dup2(i, 1); dup2(i, 2); @@ -1207,6 +1214,10 @@ ServerLoop(void) } } + /* If we have lost the system logger, try to start a new one */ + if (SysLoggerPID == 0 && Redirect_stderr) + SysLoggerPID = SysLogger_Start(); + /* * If no background writer process is running, and we are not in * a state that prevents it, start one. It doesn't matter if this @@ -1714,9 +1725,12 @@ ConnFree(Port *conn) * This is called during child process startup to release file descriptors * that are not needed by that child process. The postmaster still has * them open, of course. + * + * Note: we pass am_syslogger as a boolean because we don't want to set + * the global variable yet when this is called. */ void -ClosePostmasterPorts(void) +ClosePostmasterPorts(bool am_syslogger) { int i; @@ -1729,6 +1743,20 @@ ClosePostmasterPorts(void) ListenSocket[i] = -1; } } + + /* If using syslogger, close the read side of the pipe */ + if (!am_syslogger) + { +#ifndef WIN32 + if (syslogPipe[0] >= 0) + close(syslogPipe[0]); + syslogPipe[0] = -1; +#else + if (syslogPipe[0]) + CloseHandle(syslogPipe[0]); + syslogPipe[0] = 0; +#endif + } } @@ -1770,6 +1798,8 @@ SIGHUP_handler(SIGNAL_ARGS) kill(BgWriterPID, SIGHUP); if (PgArchPID != 0) kill(PgArchPID, SIGHUP); + if (SysLoggerPID != 0) + kill(SysLoggerPID, SIGHUP); /* PgStatPID does not currently need SIGHUP */ load_hba(); load_ident(); @@ -2063,6 +2093,18 @@ reaper(SIGNAL_ARGS) continue; } + /* Was it the system logger? try to start a new one */ + if (SysLoggerPID != 0 && pid == SysLoggerPID) + { + SysLoggerPID = 0; + /* for safety's sake, launch new logger *first* */ + SysLoggerPID = SysLogger_Start(); + if (exitstatus != 0) + LogChildExit(LOG, gettext("system logger process"), + pid, exitstatus); + continue; + } + /* * Else do standard backend child cleanup. */ @@ -2258,6 +2300,8 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) kill(PgStatPID, SIGQUIT); } + /* We do NOT restart the syslogger */ + FatalError = true; } @@ -2528,7 +2572,7 @@ BackendRun(Port *port) * Let's clean up ourselves as the postmaster child, and close the * postmaster's listen sockets */ - ClosePostmasterPorts(); + ClosePostmasterPorts(false); /* We don't want the postmaster's proc_exit() handlers */ on_exit_reset(); @@ -2921,7 +2965,7 @@ SubPostmasterMain(int argc, char *argv[]) if (strcmp(argv[1], "-forkboot") == 0) { /* Close the postmaster's sockets */ - ClosePostmasterPorts(); + ClosePostmasterPorts(false); /* Attach process to shared segments */ CreateSharedMemoryAndSemaphores(false, MaxBackends, 0); @@ -2932,7 +2976,7 @@ SubPostmasterMain(int argc, char *argv[]) if (strcmp(argv[1], "-forkarch") == 0) { /* Close the postmaster's sockets */ - ClosePostmasterPorts(); + ClosePostmasterPorts(false); /* Do not want to attach to shared memory */ @@ -2942,7 +2986,7 @@ SubPostmasterMain(int argc, char *argv[]) if (strcmp(argv[1], "-forkbuf") == 0) { /* Close the postmaster's sockets */ - ClosePostmasterPorts(); + ClosePostmasterPorts(false); /* Do not want to attach to shared memory */ @@ -2961,6 +3005,16 @@ SubPostmasterMain(int argc, char *argv[]) PgstatCollectorMain(argc, argv); proc_exit(0); } + if (strcmp(argv[1], "-forklog") == 0) + { + /* Close the postmaster's sockets */ + ClosePostmasterPorts(true); + + /* Do not want to attach to shared memory */ + + SysLoggerMain(argc, argv); + proc_exit(0); + } return 1; /* shouldn't get here */ } @@ -3017,7 +3071,7 @@ sigusr1_handler(SIGNAL_ARGS) if (Shutdown <= SmartShutdown) SignalChildren(SIGUSR1); } - + if (PgArchPID != 0 && Shutdown == NoShutdown) { if (CheckPostmasterSignal(PMSIGNAL_WAKEN_ARCHIVER)) @@ -3214,7 +3268,7 @@ StartChildProcess(int xlop) IsUnderPostmaster = true; /* we are a postmaster subprocess now */ /* Close the postmaster's sockets */ - ClosePostmasterPorts(); + ClosePostmasterPorts(false); /* Lose the postmaster's on-exit routines and port connections */ on_exit_reset(); @@ -3400,6 +3454,9 @@ write_backend_variables(char *filename, Port *port) write_var(PostmasterHandle, fp); #endif + write_var(syslogPipe[0], fp); + write_var(syslogPipe[1], fp); + StrNCpy(str_buf, my_exec_path, MAXPGPATH); write_array_var(str_buf, fp); @@ -3471,6 +3528,9 @@ read_backend_variables(char *filename, Port *port) read_var(PostmasterHandle, fp); #endif + read_var(syslogPipe[0], fp); + read_var(syslogPipe[1], fp); + read_array_var(str_buf, fp); StrNCpy(my_exec_path, str_buf, MAXPGPATH); diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c new file mode 100644 index 0000000000..f5bffdb8a2 --- /dev/null +++ b/src/backend/postmaster/syslogger.c @@ -0,0 +1,748 @@ +/*------------------------------------------------------------------------- + * + * syslogger.c + * + * The system logger (syslogger) is new in Postgres 8.0. It catches all + * stderr output from the postmaster, backends, and other subprocesses + * by redirecting to a pipe, and writes it to a set of logfiles. + * It's possible to have size and age limits for the logfile configured + * in postgresql.conf. If these limits are reached or passed, the + * current logfile is closed and a new one is created (rotated). + * The logfiles are stored in a subdirectory (configurable in + * postgresql.conf), using an internal naming scheme that mangles + * creation time and current postmaster pid. + * + * Author: Andreas Pflug + * + * Copyright (c) 2004, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * $PostgreSQL: pgsql/src/backend/postmaster/syslogger.c,v 1.1 2004/08/05 23:32:10 tgl Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include +#include +#include +#include +#include + +#include "libpq/pqsignal.h" +#include "miscadmin.h" +#include "postmaster/postmaster.h" +#include "postmaster/syslogger.h" +#include "pgtime.h" +#include "storage/ipc.h" +#include "storage/pg_shmem.h" +#include "utils/guc.h" +#include "utils/ps_status.h" + + +/* + * GUC parameters. Redirect_stderr cannot be changed after postmaster + * start, but the rest can change at SIGHUP. + */ +bool Redirect_stderr = false; +int Log_RotationAge = 24*60; +int Log_RotationSize = 10*1024; +char * Log_directory = "pg_log"; +char * Log_filename_prefix = "postgresql-"; + +/* + * Globally visible state (used by elog.c) + */ +bool am_syslogger = false; + +/* + * Private state + */ +static pg_time_t last_rotation_time = 0; + +static bool redirection_done = false; + +static bool pipe_eof_seen = false; + +static FILE *syslogFile = NULL; + +/* These must be exported for EXEC_BACKEND case ... annoying */ +#ifndef WIN32 +int syslogPipe[2] = {-1, -1}; +#else +HANDLE syslogPipe[2] = {0, 0}; +#endif + +#ifdef WIN32 +static HANDLE threadHandle=0; +static CRITICAL_SECTION sysfileSection; +#endif + +/* + * Flags set by interrupt handlers for later service in the main loop. + */ +static volatile sig_atomic_t got_SIGHUP = false; + + +/* Local subroutines */ +#ifdef EXEC_BACKEND +static pid_t syslogger_forkexec(void); +static void syslogger_parseArgs(int argc, char *argv[]); +#endif +#ifdef WIN32 +static unsigned int __stdcall pipeThread(void *arg); +#endif +static void logfile_rotate(void); +static char* logfile_getname(pg_time_t timestamp); +static void sigHupHandler(SIGNAL_ARGS); + + +/* + * Main entry point for syslogger process + * argc/argv parameters are valid only in EXEC_BACKEND case. + */ +NON_EXEC_STATIC void +SysLoggerMain(int argc, char *argv[]) +{ + char currentLogDir[MAXPGPATH]; + + IsUnderPostmaster = true; /* we are a postmaster subprocess now */ + + MyProcPid = getpid(); /* reset MyProcPid */ + + /* Lose the postmaster's on-exit routines */ + on_exit_reset(); + +#ifdef EXEC_BACKEND + syslogger_parseArgs(argc, argv); +#endif /* EXEC_BACKEND */ + + am_syslogger = true; + + init_ps_display("logger process", "", ""); + set_ps_display(""); + + /* + * If we restarted, our stderr is already redirected into our own + * input pipe. This is of course pretty useless, not to mention that + * it interferes with detecting pipe EOF. Point stderr to /dev/null. + * This assumes that all interesting messages generated in the syslogger + * will come through elog.c and will be sent to write_syslogger_file. + */ + if (redirection_done) + { + int i = open(NULL_DEV, O_WRONLY); + + dup2(i, fileno(stdout)); + dup2(i, fileno(stderr)); + close(i); + } + + /* + * Also close our copy of the write end of the pipe. This is needed + * to ensure we can detect pipe EOF correctly. (But note that in the + * restart case, the postmaster already did this.) + */ +#ifndef WIN32 + if (syslogPipe[1] >= 0) + close(syslogPipe[1]); + syslogPipe[1] = -1; +#else + if (syslogPipe[1]) + CloseHandle(syslogPipe[1]); + syslogPipe[1] = 0; +#endif + + /* + * Properly accept or ignore signals the postmaster might send us + * + * Note: we ignore all termination signals, and instead exit only when + * all upstream processes are gone, to ensure we don't miss any dying + * gasps of broken backends... + */ + + pqsignal(SIGHUP, sigHupHandler); /* set flag to read config file */ + pqsignal(SIGINT, SIG_IGN); + pqsignal(SIGTERM, SIG_IGN); + pqsignal(SIGQUIT, SIG_IGN); + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, SIG_IGN); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + pqsignal(SIGTTIN, SIG_DFL); + pqsignal(SIGTTOU, SIG_DFL); + pqsignal(SIGCONT, SIG_DFL); + pqsignal(SIGWINCH, SIG_DFL); + + PG_SETMASK(&UnBlockSig); + +#ifdef WIN32 + /* Fire up separate data transfer thread */ + InitializeCriticalSection(&sysfileSection); + + { + unsigned int tid; + + threadHandle = (HANDLE)_beginthreadex(0, 0, pipeThread, 0, 0, &tid); + } +#endif /* WIN32 */ + + /* remember age of initial logfile */ + last_rotation_time = time(NULL); + /* remember active logfile directory */ + strncpy(currentLogDir, Log_directory, MAXPGPATH); + + /* main worker loop */ + for (;;) + { + bool rotation_requested = false; +#ifndef WIN32 + char logbuffer[1024]; + int bytesRead; + int rc; + fd_set rfds; + struct timeval timeout; +#endif + + if (got_SIGHUP) + { + got_SIGHUP = false; + 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? + */ + if (strncmp(Log_directory, currentLogDir, MAXPGPATH) != 0) + { + strncpy(currentLogDir, Log_directory, MAXPGPATH); + rotation_requested = true; + } + } + + if (!rotation_requested && + last_rotation_time != 0 && + Log_RotationAge > 0) + { + /* + * Do a logfile rotation if too much time has elapsed + * since the last one. + */ + pg_time_t now = time(NULL); + int elapsed_secs = now - last_rotation_time; + + if (elapsed_secs >= Log_RotationAge * 60) + rotation_requested = true; + } + + if (!rotation_requested && Log_RotationSize > 0) + { + /* + * Do a rotation if file is too big + */ + if (ftell(syslogFile) >= Log_RotationSize * 1024L) + rotation_requested = true; + } + + if (rotation_requested) + logfile_rotate(); + +#ifndef WIN32 + /* + * Wait for some data, timing out after 1 second + */ + FD_ZERO(&rfds); + FD_SET(syslogPipe[0], &rfds); + timeout.tv_sec=1; + timeout.tv_usec=0; + + rc = select(syslogPipe[0]+1, &rfds, NULL, NULL, &timeout); + + if (rc < 0) + { + if (errno != EINTR) + ereport(LOG, + (errcode_for_socket_access(), + errmsg("select() failed in logger process: %m"))); + } + else if (rc > 0 && FD_ISSET(syslogPipe[0], &rfds)) + { + bytesRead = piperead(syslogPipe[0], + logbuffer, sizeof(logbuffer)); + + if (bytesRead < 0) + { + if (errno != EINTR) + ereport(LOG, + (errcode_for_socket_access(), + errmsg("could not read from logger pipe: %m"))); + } + else if (bytesRead > 0) + { + write_syslogger_file(logbuffer, bytesRead); + continue; + } + else + { + /* + * Zero bytes read when select() is saying read-ready + * means EOF on the pipe: that is, there are no longer + * any processes with the pipe write end open. Therefore, + * the postmaster and all backends are shut down, and we + * are done. + */ + pipe_eof_seen = true; + } + } +#else /* WIN32 */ + /* + * On Windows we leave it to a separate thread to transfer data and + * detect pipe EOF. The main thread just wakes up once a second to + * check for SIGHUP and rotation conditions. + */ + pgwin32_backend_usleep(1000000); +#endif /* WIN32 */ + + if (pipe_eof_seen) + { + ereport(LOG, + (errmsg("logger shutting down"))); + if (syslogFile) + fclose(syslogFile); + /* normal exit from the syslogger is here */ + proc_exit(0); + } + } +} + +/* + * Postmaster subroutine to start a syslogger subprocess. + */ +int +SysLogger_Start(void) +{ + pid_t sysloggerPid; + pg_time_t now; + char *filename; + + if (!Redirect_stderr) + return 0; + + /* + * If first time through, create the pipe which will receive stderr output. + * + * If the syslogger crashes and needs to be restarted, we continue to use + * the same pipe (indeed must do so, since extant backends will be writing + * into that pipe). + * + * This means the postmaster must continue to hold the read end of the + * pipe open, so we can pass it down to the reincarnated syslogger. + * This is a bit klugy but we have little choice. + */ +#ifndef WIN32 + if (syslogPipe[0] < 0) + { + if (pgpipe(syslogPipe) < 0) + ereport(FATAL, + (errcode_for_socket_access(), + (errmsg("could not create pipe for syslogging: %m")))); + } +#else + if (!syslogPipe[0]) + { + SECURITY_ATTRIBUTES sa; + + memset(&sa, 0, sizeof(SECURITY_ATTRIBUTES)); + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = TRUE; + + if (!CreatePipe(&syslogPipe[0], &syslogPipe[1], &sa, 32768)) + ereport(FATAL, + (errcode_for_file_access(), + (errmsg("could not create pipe for syslogging: %m")))); + } +#endif + + /* + * create log directory if not present; ignore errors + */ + if (is_absolute_path(Log_directory)) + mkdir(Log_directory, 0700); + else + { + filename = palloc(MAXPGPATH); + snprintf(filename, MAXPGPATH, "%s/%s", DataDir, Log_directory); + mkdir(filename, 0700); + pfree(filename); + } + + /* + * The initial logfile is created right in the postmaster, + * to verify that the Log_directory is writable. + */ + now = time(NULL); + filename = logfile_getname(now); + + syslogFile = fopen(filename, "a"); + + if (!syslogFile) + ereport(FATAL, + (errcode_for_file_access(), + (errmsg("could not create logfile \"%s\": %m", + filename)))); + + setvbuf(syslogFile, NULL, _IOLBF, 0); + + pfree(filename); + + /* + * Now we can fork off the syslogger subprocess. + */ + fflush(stdout); + fflush(stderr); + +#ifdef __BEOS__ + /* Specific beos actions before backend startup */ + beos_before_backend_startup(); +#endif + +#ifdef EXEC_BACKEND + switch ((sysloggerPid = syslogger_forkexec())) +#else + switch ((sysloggerPid = fork())) +#endif + { + case -1: +#ifdef __BEOS__ + /* Specific beos actions */ + beos_backend_startup_failed(); +#endif + ereport(LOG, + (errmsg("could not fork system logger: %m"))); + return 0; + +#ifndef EXEC_BACKEND + case 0: + /* in postmaster child ... */ +#ifdef __BEOS__ + /* Specific beos actions after backend startup */ + beos_backend_startup(); +#endif + /* Close the postmaster's sockets */ + ClosePostmasterPorts(true); + + /* Drop our connection to postmaster's shared memory, as well */ + PGSharedMemoryDetach(); + + /* do the work */ + SysLoggerMain(0, NULL); + break; +#endif + + default: + /* success, in postmaster */ + + /* now we redirect stderr, if not done already */ + if (!redirection_done) + { +#ifndef WIN32 + fflush(stdout); + if (dup2(syslogPipe[1], fileno(stdout)) < 0) + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not redirect stdout: %m"))); + fflush(stderr); + if (dup2(syslogPipe[1], fileno(stderr)) < 0) + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not redirect stderr: %m"))); + /* Now we are done with the write end of the pipe. */ + close(syslogPipe[1]); + syslogPipe[1] = -1; +#else + fflush(stderr); + if (dup2(_open_osfhandle((long)syslogPipe[1], _O_APPEND), + _fileno(stderr)) < 0) + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not redirect stderr: %m"))); + /* Now we are done with the write end of the pipe. */ + CloseHandle(syslogPipe[1]); + syslogPipe[1] = 0; +#endif + redirection_done = true; + } + + /* postmaster will never write the file; close it */ + fclose(syslogFile); + syslogFile = NULL; + return (int) sysloggerPid; + } + + /* we should never reach here */ + return 0; +} + + +#ifdef EXEC_BACKEND + +/* + * syslogger_forkexec() - + * + * Format up the arglist for, then fork and exec, a syslogger process + */ +static pid_t +syslogger_forkexec(void) +{ + char *av[10]; + int ac = 0, bufc = 0, i; + char numbuf[2][32]; + + av[ac++] = "postgres"; + av[ac++] = "-forklog"; + av[ac++] = NULL; /* filled in by postmaster_forkexec */ + + /* static variables (those not passed by write_backend_variables) */ +#ifndef WIN32 + if (syslogFile != NULL) + snprintf(numbuf[bufc++], 32, "%d", fileno(syslogFile)); + else + strcpy(numbuf[bufc++], "-1"); + snprintf(numbuf[bufc++], 32, "%d", (int) redirection_done); +#else /* WIN32 */ + if (syslogFile != NULL) + snprintf(numbuf[bufc++], 32, "%ld", + _get_osfhandle(_fileno(syslogFile))); + else + strcpy(numbuf[bufc++], "0"); + snprintf(numbuf[bufc++], 32, "%d", (int) redirection_done); +#endif /* WIN32 */ + + /* Add to the arg list */ + Assert(bufc <= lengthof(numbuf)); + for (i = 0; i < bufc; i++) + av[ac++] = numbuf[i]; + + av[ac] = NULL; + Assert(ac < lengthof(av)); + + return postmaster_forkexec(ac, av); +} + +/* + * syslogger_parseArgs() - + * + * Extract data from the arglist for exec'ed syslogger process + */ +static void +syslogger_parseArgs(int argc, char *argv[]) +{ + int fd; + + Assert(argc == 5); + argv += 3; + +#ifndef WIN32 + fd = atoi(*argv++); + if (fd != -1) + { + syslogFile = fdopen(fd, "a"); + setvbuf(syslogFile, NULL, _IOLBF, 0); + } + redirection_done = (bool) atoi(*argv++); +#else /* WIN32 */ + fd = atoi(*argv++); + if (fd != 0) + { + fd = _open_osfhandle(fd, _O_APPEND); + if (fd != 0) + { + syslogFile = fdopen(fd, "a"); + setvbuf(syslogFile, NULL, _IOLBF, 0); + } + } + redirection_done = (bool) atoi(*argv++); +#endif /* WIN32 */ +} + +#endif /* EXEC_BACKEND */ + + +/* -------------------------------- + * logfile routines + * -------------------------------- + */ + +/* + * Write to the currently open logfile + * + * This is exported so that elog.c can call it when am_syslogger is true. + * This allows the syslogger process to record elog messages of its own, + * even though its stderr does not point at the syslog pipe. + */ +void +write_syslogger_file(const char *buffer, int count) +{ + int rc; + +#ifndef WIN32 + rc = fwrite(buffer, 1, count, syslogFile); +#else + EnterCriticalSection(&sysfileSection); + rc = fwrite(buffer, 1, count, syslogFile); + LeaveCriticalSection(&sysfileSection); +#endif + + if (rc != count) + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not write to logfile: %m"))); +} + +#ifdef WIN32 + +/* + * Worker thread to transfer data from the pipe to the current logfile. + * + * We need this because on Windows, WaitForSingleObject does not work on + * unnamed pipes: it always reports "signaled", so the blocking ReadFile won't + * allow for SIGHUP; and select is for sockets only. + */ +static unsigned int __stdcall +pipeThread(void *arg) +{ + DWORD bytesRead; + char logbuffer[1024]; + + for (;;) + { + if (!ReadFile(syslogPipe[0], logbuffer, sizeof(logbuffer), + &bytesRead, 0)) + { + DWORD error = GetLastError(); + + if (error == ERROR_HANDLE_EOF) + break; + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not read from logger pipe: %m"))); + } + else if (bytesRead > 0) + write_syslogger_file(logbuffer, bytesRead); + } + + /* We exit the above loop only upon detecting pipe EOF */ + pipe_eof_seen = true; + _endthread(); + return 0; +} + +#endif /* WIN32 */ + +/* + * perform logfile rotation + */ +static void +logfile_rotate(void) +{ + char *filename; + pg_time_t now; + FILE *fh; + + now = time(NULL); + filename = logfile_getname(now); + + fh = fopen(filename, "a"); + if (!fh) + { + int saveerrno = errno; + + ereport(LOG, + (errcode_for_file_access(), + errmsg("could not open new logfile \"%s\": %m", + filename))); + + /* + * ENFILE/EMFILE are not too surprising on a busy system; just keep + * using the old file till we manage to get a new one. Otherwise, + * assume something's wrong with Log_directory and stop trying to + * create files. + */ + if (saveerrno != ENFILE && saveerrno != EMFILE) + { + ereport(LOG, + (errmsg("disabling auto rotation (use SIGHUP to reenable)"))); + Log_RotationAge = 0; + Log_RotationSize = 0; + } + pfree(filename); + return; + } + + setvbuf(fh, NULL, _IOLBF, 0); + + /* On Windows, need to interlock against data-transfer thread */ +#ifdef WIN32 + EnterCriticalSection(&sysfileSection); +#endif + fclose(syslogFile); + syslogFile = fh; +#ifdef WIN32 + LeaveCriticalSection(&sysfileSection); +#endif + + last_rotation_time = now; + + pfree(filename); +} + + +/* + * construct logfile name using timestamp information + * + * Result is palloc'd. + */ +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(×tamp)); + + 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); + else + snprintf(filename, MAXPGPATH, "%s/%s/%s%05u_%s.log", + DataDir, Log_directory, Log_filename_prefix, + (unsigned int) PostmasterPid, stamptext); + + return filename; +} + +/* -------------------------------- + * signal handler routines + * -------------------------------- + */ + +/* SIGHUP: set flag to reload config file */ +static void +sigHupHandler(SIGNAL_ARGS) +{ + got_SIGHUP = true; +} diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index 340cca03ca..9723cf76f5 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -37,7 +37,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.145 2004/08/04 20:58:46 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.146 2004/08/05 23:32:11 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -57,6 +57,7 @@ #include "mb/pg_wchar.h" #include "miscadmin.h" #include "postmaster/postmaster.h" +#include "postmaster/syslogger.h" #include "storage/ipc.h" #include "tcop/tcopprot.h" #include "utils/memutils.h" @@ -71,7 +72,7 @@ sigjmp_buf *PG_exception_stack = NULL; /* GUC parameters */ PGErrorVerbosity Log_error_verbosity = PGERROR_VERBOSE; char *Log_line_prefix = NULL; /* format for extra log line info */ -unsigned int Log_destination = LOG_DESTINATION_STDERR; +int Log_destination = LOG_DESTINATION_STDERR; #ifdef HAVE_SYSLOG char *Syslog_facility; /* openlog() parameters */ @@ -1589,6 +1590,10 @@ send_message_to_server_log(ErrorData *edata) fprintf(stderr, "%s", buf.data); } + /* If in the syslogger process, try to write messages direct to file */ + if (am_syslogger) + write_syslogger_file(buf.data, buf.len); + pfree(buf.data); } diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 22df3effc3..a827653bec 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -10,7 +10,7 @@ * Written by Peter Eisentraut . * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.225 2004/07/28 14:23:29 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.226 2004/08/05 23:32:12 tgl Exp $ * *-------------------------------------------------------------------- */ @@ -44,6 +44,7 @@ #include "parser/parse_expr.h" #include "parser/parse_relation.h" #include "postmaster/bgwriter.h" +#include "postmaster/syslogger.h" #include "postmaster/postmaster.h" #include "storage/bufmgr.h" #include "storage/fd.h" @@ -801,19 +802,13 @@ static struct config_bool ConfigureNamesBool[] = &default_with_oids, true, NULL, NULL }, - { - {"integer_datetimes", PGC_INTERNAL, COMPILE_OPTIONS, - gettext_noop("Datetimes are integer based"), - NULL, - GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + {"redirect_stderr", PGC_POSTMASTER, LOGGING_WHERE, + gettext_noop("Start a subprocess to capture stderr output into log files"), + NULL }, - &integer_datetimes, -#ifdef HAVE_INT64_TIMESTAMP - true, NULL, NULL -#else + &Redirect_stderr, false, NULL, NULL -#endif }, #ifdef WAL_DEBUG @@ -828,6 +823,20 @@ static struct config_bool ConfigureNamesBool[] = }, #endif + { + {"integer_datetimes", PGC_INTERNAL, COMPILE_OPTIONS, + gettext_noop("Datetimes are integer based"), + NULL, + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &integer_datetimes, +#ifdef HAVE_INT64_TIMESTAMP + true, NULL, NULL +#else + false, NULL, NULL +#endif + }, + /* End-of-list marker */ { {NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL @@ -1245,6 +1254,24 @@ static struct config_int ConfigureNamesInt[] = 100, 1, 1000, NULL, NULL }, + { + {"log_rotation_age", PGC_SIGHUP, LOGGING_WHERE, + gettext_noop("Automatic logfile rotation will occur after N minutes"), + NULL + }, + &Log_RotationAge, + 24*60, 0, INT_MAX, NULL, NULL + }, + + { + {"log_rotation_size", PGC_SIGHUP, LOGGING_WHERE, + gettext_noop("Automatic logfile rotation will occur after N kilobytes"), + NULL + }, + &Log_RotationSize, + 10*1024, 0, INT_MAX, NULL, NULL + }, + { {"max_function_args", PGC_INTERNAL, COMPILE_OPTIONS, gettext_noop("Shows the maximum number of function arguments"), @@ -1634,6 +1661,23 @@ static struct config_string ConfigureNamesString[] = &log_destination_string, "stderr", assign_log_destination, NULL }, + { + {"log_directory", PGC_SIGHUP, LOGGING_WHERE, + gettext_noop("Sets the destination directory for logfiles."), + gettext_noop("May be specified as relative to the cluster directory " + "or as absolute path.") + }, + &Log_directory, + "pg_log", NULL, NULL + }, + { + {"log_filename_prefix", PGC_SIGHUP, LOGGING_WHERE, + gettext_noop("Prefix for file names created in the log_directory."), + NULL + }, + &Log_filename_prefix, + "postgresql-", NULL, NULL + }, #ifdef HAVE_SYSLOG { @@ -5055,7 +5099,7 @@ assign_log_destination(const char *value, bool doit, GucSource source) char *rawstring; List *elemlist; ListCell *l; - unsigned int newlogdest = 0; + int newlogdest = 0; /* Need a modifiable copy of string */ rawstring = pstrdup(value); diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 9dc1ec8d83..633bb5a322 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -170,9 +170,23 @@ #log_destination = 'stderr' # Valid values are combinations of stderr, # syslog and eventlog, depending on # platform. + +# 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. + # May be specified absolute or relative to PGDATA +#log_filename_prefix = 'postgresql_' # Prefix for logfile names. +#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 + # so many kilobytes of log output. 0 to disable. + +# These are relevant when logging to syslog: #syslog_facility = 'LOCAL0' #syslog_ident = 'postgres' + # - When to Log - #client_min_messages = notice # Values, in order of decreasing detail: diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h index 75242d156d..9afc228efd 100644 --- a/src/include/postmaster/postmaster.h +++ b/src/include/postmaster/postmaster.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/postmaster/postmaster.h,v 1.4 2004/07/21 20:34:48 momjian Exp $ + * $PostgreSQL: pgsql/src/include/postmaster/postmaster.h,v 1.5 2004/08/05 23:32:12 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -36,7 +36,7 @@ extern HANDLE PostmasterHandle; extern int PostmasterMain(int argc, char *argv[]); -extern void ClosePostmasterPorts(void); +extern void ClosePostmasterPorts(bool am_syslogger); #ifdef EXEC_BACKEND extern pid_t postmaster_forkexec(int argc, char *argv[]); extern int SubPostmasterMain(int argc, char *argv[]); diff --git a/src/include/postmaster/syslogger.h b/src/include/postmaster/syslogger.h new file mode 100644 index 0000000000..a0f775c9a2 --- /dev/null +++ b/src/include/postmaster/syslogger.h @@ -0,0 +1,39 @@ +/*------------------------------------------------------------------------- + * + * syslogger.h + * Exports from postmaster/syslogger.c. + * + * Copyright (c) 2004, PostgreSQL Global Development Group + * + * $PostgreSQL: pgsql/src/include/postmaster/syslogger.h,v 1.1 2004/08/05 23:32:12 tgl Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef _SYSLOGGER_H +#define _SYSLOGGER_H + +/* GUC options */ +extern bool Redirect_stderr; +extern int Log_RotationAge; +extern int Log_RotationSize; +extern char *Log_directory; +extern char *Log_filename_prefix; + +extern bool am_syslogger; + +#ifndef WIN32 +extern int syslogPipe[2]; +#else +extern HANDLE syslogPipe[2]; +#endif + + +extern int SysLogger_Start(void); + +extern void write_syslogger_file(const char *buffer, int count); + +#ifdef EXEC_BACKEND +extern void SysLoggerMain(int argc, char *argv[]); +#endif + +#endif /* _SYSLOGGER_H */ diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h index 4a2d26ddb9..ac27b79624 100644 --- a/src/include/utils/elog.h +++ b/src/include/utils/elog.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/elog.h,v 1.72 2004/07/31 23:04:55 tgl Exp $ + * $PostgreSQL: pgsql/src/include/utils/elog.h,v 1.73 2004/08/05 23:32:13 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -272,7 +272,7 @@ typedef enum extern PGErrorVerbosity Log_error_verbosity; extern char *Log_line_prefix; -extern unsigned int Log_destination; +extern int Log_destination; /* Log destination bitmap */ #define LOG_DESTINATION_STDERR 1 -- 2.40.0