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.
This commit is contained in:
Tom Lane 2004-08-05 23:32:13 +00:00
parent b4cd416ab0
commit bdf8ef6925
13 changed files with 1075 additions and 71 deletions

View File

@ -1,5 +1,5 @@
<!--
$PostgreSQL: pgsql/doc/src/sgml/maintenance.sgml,v 1.36 2004/07/24 19:51:22 tgl Exp $
$PostgreSQL: pgsql/doc/src/sgml/maintenance.sgml,v 1.37 2004/08/05 23:32:10 tgl Exp $
-->
<chapter id="maintenance">
@ -445,22 +445,52 @@ VACUUM
</para>
<para>
If you simply direct the <systemitem>stderr</> of the <command>postmaster</command> into a
file, the only way to truncate the log file is to stop and restart
If you simply direct the <systemitem>stderr</> of the
<command>postmaster</command> into a
file, you will have log output, but
the only way to truncate the log file is to stop and restart
the <command>postmaster</command>. This may be OK if you are using
<productname>PostgreSQL</productname> in a development environment,
but few production servers would find this behavior acceptable.
</para>
<para>
The simplest production-grade approach to managing log output is to
A better approach is to send the <command>postmaster</>'s
<systemitem>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 <literal>redirect_stderr</> to
<literal>true</> in <filename>postgresql.conf</>. The control
parameters for this program are described in <xref
linkend="runtime-config-logging-where">.
</para>
<para>
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 <application>rotatelogs</application>
tool included in the <productname>Apache</productname> distribution
can be used with <productname>PostgreSQL</productname>. To do this,
just pipe the <command>postmaster</>'s
<systemitem>stderr</> output to the desired program.
If you start the server with
<command>pg_ctl</>, then <systemitem>stderr</>
is already redirected to <systemitem>stdout</>, so you just need a
pipe command:
<programlisting>
pg_ctl start | rotatelogs /var/log/pgsql_log 86400
</programlisting>
</para>
<para>
Another production-grade approach to managing log output is to
send it all to <application>syslog</> and let
<application>syslog</> deal with file rotation. To do this, set the
configuration parameter <literal>log_destination</> to 'syslog' (to log to
<application>syslog</> only) in <filename>postgresql.conf</>. Then
you can send a <literal>SIGHUP</literal> signal to the
<application>syslog</> daemon whenever you want to force it to
start writing a new log file. If you want to automate log
configuration parameter <literal>log_destination</> to <literal>syslog</>
(to log to <application>syslog</> only) in
<filename>postgresql.conf</>. Then you can send a <literal>SIGHUP</literal>
signal to the <application>syslog</> daemon whenever you want to force it
to start writing a new log file. If you want to automate log
rotation, the <application>logrotate</application> program can be
configured to work with log files from
<application>syslog</application>.
@ -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 <productname>linux</>,
<application>syslog</> will sync each message to disk, yielding poor
performance. Use a <literal>-</> at the start of the file name
in the <application>syslog</> config file to disable this behavior.
performance. (You can use a <literal>-</> at the start of the file name
in the <application>syslog</> config file to disable this behavior.)
</para>
<para>
You may find it more useful to pipe the
<systemitem>stderr</> of the <command>postmaster</> to some type of
log rotation program. If you start the server with
<command>pg_ctl</>, then the <systemitem>stderr</> of the <command>postmaster</command>
is already redirected to <systemitem>stdout</>, so you just need a
pipe command:
<programlisting>
pg_ctl start | rotatelogs /var/log/pgsql_log 86400
</programlisting>
The <productname>PostgreSQL</> distribution doesn't include a
suitable log rotation program, but there are many available on the
Internet. For example, the <application>rotatelogs</application>
tool included in the <productname>Apache</productname> distribution
can be used with <productname>PostgreSQL</productname>.
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.
</para>
</sect1>
</chapter>

View File

@ -1,5 +1,5 @@
<!--
$PostgreSQL: pgsql/doc/src/sgml/runtime.sgml,v 1.271 2004/08/03 23:42:59 tgl Exp $
$PostgreSQL: pgsql/doc/src/sgml/runtime.sgml,v 1.272 2004/08/05 23:32:10 tgl Exp $
-->
<Chapter Id="runtime">
@ -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 <systemitem>stderr</systemitem>
only.
This option can only be set at server start or in the
<filename>postgresql.conf</filename> configuration file.
</para>
</listitem>
</varlistentry>
<varlistentry id="guc-redirect-stderr" xreflabel="redirect_stderr">
<term><varname>redirect_stderr</varname> (<type>boolean</type>)</term>
<listitem>
<para>
This option allows messages sent to <application>stderr</> to be
captured and redirected into log files.
This option, in combination with logging to <application>stderr</>,
is often more useful than
logging to <application>syslog</>, since some types of messages
may not appear in <application>syslog</> output (a common example
is dynamic-linker failure messages).
This option can only be set at server start.
</para>
</listitem>
</varlistentry>
<varlistentry id="guc-log-directory" xreflabel="log_directory">
<term><varname>log_directory</varname> (<type>string</type>)</term>
<listitem>
<para>
When <varname>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
<filename>postgresql.conf</filename> configuration file.
</para>
</listitem>
</varlistentry>
<varlistentry id="guc-log-filename-prefix" xreflabel="log_filename_prefix">
<term><varname>log_filename_prefix</varname> (<type>string</type>)</term>
<listitem>
<para>
When <varname>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
<filename>postgresql.conf</filename> configuration file.
</para>
</listitem>
</varlistentry>
<varlistentry id="guc-log-rotation-age" xreflabel="log_rotation_age">
<term><varname>log_rotation_age</varname> (<type>integer</type>)</term>
<listitem>
<para>
When <varname>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
<filename>postgresql.conf</filename> configuration file.
</para>
</listitem>
</varlistentry>
<varlistentry id="guc-log-rotation-size" xreflabel="log_rotation_size">
<term><varname>log_rotation_size</varname> (<type>integer</type>)</term>
<listitem>
<para>
When <varname>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
<filename>postgresql.conf</filename> configuration file.
</para>
</listitem>
</varlistentry>
<varlistentry id="guc-syslog-facility" xreflabel="syslog_facility">
<term><varname>syslog_facility</varname> (<type>string</type>)</term>
<listitem>
<para>
If logging to <application>syslog</> is enabled, this option
When logging to <application>syslog</> is enabled, this option
determines the <application>syslog</application>
<quote>facility</quote> to be used. You may choose
from <literal>LOCAL0</>, <literal>LOCAL1</>,
@ -1826,7 +1902,7 @@ SET ENABLE_SEQSCAN TO OFF;
<term><varname>syslog_ident</varname> (<type>string</type>)</term>
<listitem>
<para>
If logging to <application>syslog</> is enabled, this option
When logging to <application>syslog</> is enabled, this option
determines the program name used to identify
<productname>PostgreSQL</productname> messages in
<application>syslog</application> logs. The default is

View File

@ -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

View File

@ -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();

View File

@ -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();

View File

@ -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);

View File

@ -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 <pgadmin@pse-consulting.de>
*
* 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 <fcntl.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#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(&timestamp));
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;
}

View File

@ -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);
}

View File

@ -10,7 +10,7 @@
* Written by Peter Eisentraut <peter_e@gmx.net>.
*
* 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,6 +802,26 @@ static struct config_bool ConfigureNamesBool[] =
&default_with_oids,
true, NULL, NULL
},
{
{"redirect_stderr", PGC_POSTMASTER, LOGGING_WHERE,
gettext_noop("Start a subprocess to capture stderr output into log files"),
NULL
},
&Redirect_stderr,
false, NULL, NULL
},
#ifdef WAL_DEBUG
{
{"wal_debug", PGC_SUSET, DEVELOPER_OPTIONS,
gettext_noop("Emit WAL-related debugging output."),
NULL,
GUC_NOT_IN_SAMPLE
},
&XLOG_DEBUG,
false, NULL, NULL
},
#endif
{
{"integer_datetimes", PGC_INTERNAL, COMPILE_OPTIONS,
@ -816,18 +837,6 @@ static struct config_bool ConfigureNamesBool[] =
#endif
},
#ifdef WAL_DEBUG
{
{"wal_debug", PGC_SUSET, DEVELOPER_OPTIONS,
gettext_noop("Emit WAL-related debugging output."),
NULL,
GUC_NOT_IN_SAMPLE
},
&XLOG_DEBUG,
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);

View File

@ -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:

View File

@ -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[]);

View File

@ -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 */

View File

@ -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