1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-05 07:21:24 +03:00

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

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