Attached is a draft patch to allow extension to write log messages to a
separate file. It introduces a concept of a "log stream". The extension's
shared library gets its stream assigned by calling this function from
_PG_init()
my_stream_id = get_log_stream("my_extension", &my_log_stream);
Then it's supposed to change some of its attributes
adjust_log_stream_attr(&stream->filename, "my_extension.log");
and to use the stream id in ereport() calls
ereport(LOG, (errmsg("Hello world"), errstream(my_stream_id)));
The EXEC_BACKEND mechanism makes initialization of the log streams by
postmaster child processes non-trivial. I decided to extend
save_backend_variables() and restore_backend_variables() accordingly. Maybe
someone has better idea.
pgaudit seems to be the most obvious use case for this enhancement, but it
might be useful for many other extensions.
--
Antonin Houska
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt
Web: http://www.postgresql-support.de, http://www.cybertec.at
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
new file mode 100644
index 95180b2..e9b5684
*** a/src/backend/postmaster/postmaster.c
--- b/src/backend/postmaster/postmaster.c
*************** typedef struct
*** 464,469 ****
--- 464,470 ----
static pid_t backend_forkexec(Port *port);
static pid_t internal_forkexec(int argc, char *argv[], Port *port);
+ static Size get_backend_params_size(void);
/* Type for a socket that can be inherited to a client process */
#ifdef WIN32
*************** typedef int InheritableSocket;
*** 482,488 ****
--- 483,495 ----
*/
typedef struct
{
+ /*
+ * read_backend_variables() relies on size to be the first field, followed
+ * by port.
+ */
+ Size size;
Port port;
+
InheritableSocket portsocket;
char DataDir[MAXPGPATH];
pgsocket ListenSocket[MAXLISTEN];
*************** typedef struct
*** 528,533 ****
--- 535,542 ----
char my_exec_path[MAXPGPATH];
char pkglib_path[MAXPGPATH];
char ExtraOptions[MAXPGPATH];
+ int nlogstreams;
+ char log_streams[FLEXIBLE_ARRAY_MEMBER];
} BackendParameters;
static void read_backend_variables(char *id, Port *port);
*************** PostmasterMain(int argc, char *argv[])
*** 578,583 ****
--- 587,593 ----
bool listen_addr_saved = false;
int i;
char *output_config_variable = NULL;
+ LogStream *log = &log_streams[0];
MyProcPid = PostmasterPid = getpid();
*************** PostmasterMain(int argc, char *argv[])
*** 1273,1279 ****
* saying so, to provide a breadcrumb trail for users who may not remember
* that their logging is configured to go somewhere else.
*/
! if (!(Log_destination & LOG_DESTINATION_STDERR))
ereport(LOG,
(errmsg("ending log output to stderr"),
errhint("Future log output will go to log destination \"%s\".",
--- 1283,1289 ----
* saying so, to provide a breadcrumb trail for users who may not remember
* that their logging is configured to go somewhere else.
*/
! if (!(log->destination & LOG_DESTINATION_STDERR))
ereport(LOG,
(errmsg("ending log output to stderr"),
errhint("Future log output will go to log destination \"%s\".",
*************** internal_forkexec(int argc, char *argv[]
*** 4421,4431 ****
static unsigned long tmpBackendFileNum = 0;
pid_t pid;
char tmpfilename[MAXPGPATH];
! BackendParameters param;
FILE *fp;
! if (!save_backend_variables(¶m, port))
return -1; /* log made by save_backend_variables */
/* Calculate name for temp file */
snprintf(tmpfilename, MAXPGPATH, "%s/%s.backend_var.%d.%lu",
--- 4431,4448 ----
static unsigned long tmpBackendFileNum = 0;
pid_t pid;
char tmpfilename[MAXPGPATH];
! Size param_size;
! BackendParameters *param;
FILE *fp;
! param_size = get_backend_params_size();
! param = (BackendParameters *) palloc(param_size);
! if (!save_backend_variables(param, port))
! {
! pfree(param);
return -1; /* log made by save_backend_variables */
+ }
+ Assert(param->size == param_size);
/* Calculate name for temp file */
snprintf(tmpfilename, MAXPGPATH, "%s/%s.backend_var.%d.%lu",
*************** internal_forkexec(int argc, char *argv[]
*** 4449,4466 ****
(errcode_for_file_access(),
errmsg("could not create file \"%s\": %m",
tmpfilename)));
return -1;
}
}
! if (fwrite(¶m, sizeof(param), 1, fp) != 1)
{
ereport(LOG,
(errcode_for_file_access(),
errmsg("could not write to file \"%s\": %m", tmpfilename)));
FreeFile(fp);
return -1;
}
/* Release file */
if (FreeFile(fp))
--- 4466,4486 ----
(errcode_for_file_access(),
errmsg("could not create file \"%s\": %m",
tmpfilename)));
+ pfree(param);
return -1;
}
}
! if (fwrite(param, param_size, 1, fp) != 1)
{
ereport(LOG,
(errcode_for_file_access(),
errmsg("could not write to file \"%s\": %m", tmpfilename)));
FreeFile(fp);
+ pfree(param);
return -1;
}
+ pfree(param);
/* Release file */
if (FreeFile(fp))
*************** retry:
*** 4548,4554 ****
return -1;
}
! param = MapViewOfFile(paramHandle, FILE_MAP_WRITE, 0, 0, sizeof(BackendParameters));
if (!param)
{
elog(LOG, "could not map backend parameter memory: error code %lu",
--- 4568,4575 ----
return -1;
}
! param = MapViewOfFile(paramHandle, FILE_MAP_WRITE, 0, 0,
! get_backend_params_size());
if (!param)
{
elog(LOG, "could not map backend parameter memory: error code %lu",
*************** retry:
*** 4703,4708 ****
--- 4724,4753 ----
}
#endif /* WIN32 */
+ /*
+ * The storage space depends on the log streams. Compute how much we need.
+ */
+ static Size
+ get_backend_params_size(void)
+ {
+ int i;
+ Size result;
+
+ result = offsetof(BackendParameters, log_streams);
+ for (i = 0; i < log_streams_active; i++)
+ {
+ LogStream *stream = &log_streams[i];
+
+ /*
+ * At least the in-core value should be there.
+ */
+ Assert(stream->line_prefix != NULL);
+
+ result += LOG_STREAM_FLAT_SIZE(stream);
+ }
+ return result;
+ }
+
/*
* SubPostmasterMain -- Get the fork/exec'd process into a state equivalent
*************** extern PMSignalData *PMSignalState;
*** 5938,5943 ****
--- 5983,5990 ----
extern pgsocket pgStatSock;
extern pg_time_t first_syslogger_file_time;
+ bool log_streams_initialized = false;
+
#ifndef WIN32
#define write_inheritable_socket(dest, src, childpid) ((*(dest) = (src)), true)
#define read_inheritable_socket(dest, src) (*(dest) = *(src))
*************** save_backend_variables(BackendParameters
*** 5959,5964 ****
--- 6006,6016 ----
HANDLE childProcess, pid_t childPid)
#endif
{
+ int i;
+ LogStreamFlat *flat = NULL;
+ Size flat_size_max = 0;
+ char *cur;
+
memcpy(¶m->port, port, sizeof(Port));
if (!write_inheritable_socket(¶m->portsocket, port->sock, childPid))
return false;
*************** save_backend_variables(BackendParameters
*** 6021,6026 ****
--- 6073,6158 ----
strlcpy(param->ExtraOptions, ExtraOptions, MAXPGPATH);
+ param->nlogstreams = log_streams_active;
+ cur = param->log_streams;
+ for (i = 0; i < param->nlogstreams; i++)
+ {
+ LogStream *stream;
+ Size flat_size;
+
+ stream = &log_streams[i];
+ flat_size = LOG_STREAM_FLAT_SIZE(stream);
+ if (flat_size_max == 0)
+ {
+ /* First time through? */
+ Assert(flat == NULL);
+ flat = (LogStreamFlat *) palloc(flat_size);
+ flat_size_max = flat_size;
+ }
+ else if (flat_size > flat_size_max)
+ {
+ /* New maximum size? */
+ Assert(flat != NULL);
+ flat = (LogStreamFlat *) repalloc(flat, flat_size);
+ flat_size_max = flat_size;
+ }
+
+ /* Serialize the stream info. */
+ memset(flat, 0, flat_size);
+ flat->size = flat_size;
+ flat->syslog_fd = stream->syslog_fd;
+
+ /*
+ * As for the core stream, backend will read the settings as any other
+ * GUCs.
+ */
+ if (i > 0)
+ {
+ /*
+ * Check string arguments.
+ */
+ if (stream->filename == NULL || strlen(stream->filename) == 0 ||
+ stream->directory == NULL || strlen(stream->directory) == 0 ||
+ stream->line_prefix == NULL || stream->id == NULL)
+ ereport(ERROR, (errmsg("log stream is not initialized properly")));
+
+ if (strlen(stream->filename) >= MAXPGPATH ||
+ strlen(stream->directory) >= MAXPGPATH ||
+ strlen(stream->line_prefix) >= MAXPGPATH ||
+ strlen(stream->id) >= MAXPGPATH)
+ ereport(ERROR,
+ (errmsg("Both log director and file name must be shorter than MAXPGPATH")));
+
+ flat->verbosity = stream->verbosity;
+ flat->destination = stream->destination;
+ Assert(stream->filename != NULL && strlen(stream->filename) > 0);
+ strcpy(flat->id, stream->id);
+ strcpy(flat->filename, stream->filename);
+ Assert(stream->directory != NULL);
+ if (strlen(stream->directory) > 0)
+ strcpy(flat->directory, stream->directory);
+ flat->file_mode = stream->file_mode;
+ flat->rotation_age = stream->rotation_age;
+ flat->rotation_size = stream->rotation_size;
+ flat->truncate_on_rotation = stream->truncate_on_rotation;
+ Assert(stream->line_prefix != NULL);
+
+ if (strlen(stream->line_prefix) > 0)
+ strcpy(flat->line_prefix, stream->line_prefix);
+ }
+
+ /* Copy the data. */
+ memcpy(cur, flat, flat_size);
+ cur += flat_size;
+ }
+
+ /* At least one (the core) stream should always exist. */
+ Assert(flat != NULL);
+ pfree(flat);
+
+ /* File space needed. */
+ param->size = cur - (char *) param;
+
return true;
}
*************** read_inheritable_socket(SOCKET *dest, In
*** 6121,6127 ****
static void
read_backend_variables(char *id, Port *port)
{
! BackendParameters param;
#ifndef WIN32
/* Non-win32 implementation reads from file */
--- 6253,6261 ----
static void
read_backend_variables(char *id, Port *port)
{
! Size param_size,
! off;
! BackendParameters *param;
#ifndef WIN32
/* Non-win32 implementation reads from file */
*************** read_backend_variables(char *id, Port *p
*** 6136,6142 ****
exit(1);
}
! if (fread(¶m, sizeof(param), 1, fp) != 1)
{
write_stderr("could not read from backend variables file \"%s\": %s\n",
id, strerror(errno));
--- 6270,6296 ----
exit(1);
}
! /*
! * First, read only the size word.
! */
! if (fread(¶m_size, sizeof(Size), 1, fp) != 1)
! {
! write_stderr("could not read from backend variables file \"%s\": %s\n",
! id, strerror(errno));
! exit(1);
! }
! /* At least one stream should be there. */
! Assert(param_size > offsetof(BackendParameters, log_streams));
!
! param = (BackendParameters *) palloc(param_size);
! /* The size is needed here just for the sake of completeness. */
! param->size = param_size;
!
! /*
! * Now read the rest.
! */
! off = offsetof(BackendParameters, port);
! if (fread((char *) param + off, param_size - off, 1, fp) != 1)
{
write_stderr("could not read from backend variables file \"%s\": %s\n",
id, strerror(errno));
*************** read_backend_variables(char *id, Port *p
*** 6169,6175 ****
exit(1);
}
! memcpy(¶m, paramp, sizeof(BackendParameters));
if (!UnmapViewOfFile(paramp))
{
--- 6323,6330 ----
exit(1);
}
! param = (BackendParameters *) palloc(paramp->size);
! memcpy(¶m, paramp, paramp->size);
if (!UnmapViewOfFile(paramp))
{
*************** read_backend_variables(char *id, Port *p
*** 6186,6198 ****
}
#endif
! restore_backend_variables(¶m, port);
}
/* Restore critical backend variables from the BackendParameters struct */
static void
restore_backend_variables(BackendParameters *param, Port *port)
{
memcpy(port, ¶m->port, sizeof(Port));
read_inheritable_socket(&port->sock, ¶m->portsocket);
--- 6341,6360 ----
}
#endif
! restore_backend_variables(param, port);
!
! pfree(param);
}
/* Restore critical backend variables from the BackendParameters struct */
static void
restore_backend_variables(BackendParameters *param, Port *port)
{
+ int i;
+ LogStreamFlat *flat = NULL;
+ Size flat_size_max = 0;
+ char *cur;
+
memcpy(port, ¶m->port, sizeof(Port));
read_inheritable_socket(&port->sock, ¶m->portsocket);
*************** restore_backend_variables(BackendParamet
*** 6249,6254 ****
--- 6411,6494 ----
strlcpy(pkglib_path, param->pkglib_path, MAXPGPATH);
strlcpy(ExtraOptions, param->ExtraOptions, MAXPGPATH);
+
+ cur = param->log_streams;
+ for (i = 0; i < param->nlogstreams; i++)
+ {
+ Size flat_size;
+ LogStream *stream;
+
+ /* First, read the size word. */
+ memcpy(&flat_size, cur, sizeof(Size));
+
+ /* Make sure we have enough space. */
+ if (flat_size_max == 0)
+ {
+ /* First time through? */
+ Assert(flat == NULL);
+ flat = (LogStreamFlat *) palloc(flat_size);
+ flat_size_max = flat_size;
+ }
+ else if (flat_size > flat_size_max)
+ {
+ /* New maximum size? */
+ Assert(flat != NULL);
+ flat = (LogStreamFlat *) repalloc(flat, flat_size);
+ flat_size_max = flat_size;
+ }
+
+ /* Copy the data into an aligned address so we can access it. */
+ memcpy(flat, cur, flat_size);
+
+ /*
+ * Copy the data into the regular LogStream instance.
+ */
+ stream = &log_streams[i];
+ memset(stream, 0, sizeof(LogStream));
+ stream->syslog_fd = flat->syslog_fd;
+
+ /*
+ * As for the core stream, backend will read the settings as any other
+ * GUCs.
+ */
+ if (i > 0)
+ {
+ stream->verbosity = flat->verbosity;
+ stream->destination = flat->destination;
+ stream->id = pstrdup(flat->id);
+ stream->filename = pstrdup(flat->filename);
+ if (strlen(flat->directory) > 0)
+ stream->directory = pstrdup(flat->directory);
+ stream->file_mode = flat->file_mode;
+ stream->rotation_age = flat->rotation_age;
+ stream->rotation_size = flat->rotation_size;
+ stream->truncate_on_rotation = flat->truncate_on_rotation;
+
+ if (strlen(flat->line_prefix) > 0)
+ stream->line_prefix = pstrdup(flat->line_prefix);
+ }
+
+ cur += flat_size;
+ }
+ log_streams_active = param->nlogstreams;
+
+ /*
+ * SubPostmasterMain() will call process_shared_preload_libraries(). We
+ * don't want get_log_stream to be called again and re-initialize the
+ * existing streams.
+ *
+ * One problem is that there's no guarantee that extensions would receive
+ * the same log stream ids: we should not expect that the same set of
+ * libraries will be loaded as the set loaded earlier by postmaster, not
+ * to mention the loading order. Besides that, the only way to receive
+ * valid syslog_fd of particular LogStream needs is to receive it from
+ * postmaster.
+ */
+ log_streams_initialized = true;
+
+ /* At least one (the core) stream should always exist. */
+ Assert(flat != NULL);
+ pfree(flat);
}
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
new file mode 100644
index 3255b42..93c23df
*** a/src/backend/postmaster/syslogger.c
--- b/src/backend/postmaster/syslogger.c
***************
*** 45,50 ****
--- 45,51 ----
#include "storage/latch.h"
#include "storage/pg_shmem.h"
#include "utils/guc.h"
+ #include "utils/memutils.h"
#include "utils/ps_status.h"
#include "utils/timestamp.h"
***************
*** 55,72 ****
*/
#define READ_BUF_SIZE (2 * PIPE_CHUNK_SIZE)
-
/*
* GUC parameters. Logging_collector cannot be changed after postmaster
* start, but the rest can change at SIGHUP.
*/
bool Logging_collector = false;
- int Log_RotationAge = HOURS_PER_DAY * MINS_PER_HOUR;
- int Log_RotationSize = 10 * 1024;
- char *Log_directory = NULL;
- char *Log_filename = NULL;
- bool Log_truncate_on_rotation = false;
- int Log_file_mode = S_IRUSR | S_IWUSR;
/*
* Globally visible state (used by elog.c)
--- 56,66 ----
*************** extern bool redirection_done;
*** 78,91 ****
/*
* Private state
*/
! static pg_time_t next_rotation_time;
static bool pipe_eof_seen = false;
static bool rotation_disabled = false;
! static FILE *syslogFile = NULL;
! static FILE *csvlogFile = NULL;
! NON_EXEC_STATIC pg_time_t first_syslogger_file_time = 0;
! static char *last_file_name = NULL;
! static char *last_csv_file_name = NULL;
/*
* Buffers for saving partial messages from different backends.
--- 72,86 ----
/*
* Private state
*/
!
static bool pipe_eof_seen = false;
static bool rotation_disabled = false;
! NON_EXEC_STATIC pg_time_t first_syslogger_file_time;
!
! LogStream log_streams[MAXLOGSTREAMS];
!
! /* At least the core log stream should be active. */
! int log_streams_active = 1;
/*
* Buffers for saving partial messages from different backends.
*************** static char *last_csv_file_name = NULL;
*** 95,106 ****
--- 90,104 ----
* the number of entries we have to examine for any one incoming message.
* There must never be more than one entry for the same source pid.
*
+ * stream_id is needed because of flush_pipe_input.
+ *
* An inactive buffer is not removed from its list, just held for re-use.
* An inactive buffer has pid == 0 and undefined contents of data.
*/
typedef struct
{
int32 pid; /* PID of source process */
+ int32 stream_id; /* Stream identifier. */
StringInfoData data; /* accumulated data, as a StringInfo */
} save_buffer;
*************** static CRITICAL_SECTION sysloggerSection
*** 123,151 ****
* Flags set by interrupt handlers for later service in the main loop.
*/
static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t rotation_requested = false;
/* Local subroutines */
#ifdef EXEC_BACKEND
static pid_t syslogger_forkexec(void);
- static void syslogger_parseArgs(int argc, char *argv[]);
#endif
NON_EXEC_STATIC void SysLoggerMain(int argc, char *argv[]) pg_attribute_noreturn();
static void process_pipe_input(char *logbuffer, int *bytes_in_logbuffer);
static void flush_pipe_input(char *logbuffer, int *bytes_in_logbuffer);
! static void open_csvlogfile(void);
static FILE *logfile_open(const char *filename, const char *mode,
! bool allow_errors);
#ifdef WIN32
static unsigned int __stdcall pipeThread(void *arg);
#endif
! static void logfile_rotate(bool time_based_rotation, int size_rotation_for);
! static char *logfile_getname(pg_time_t timestamp, const char *suffix);
! static void set_next_rotation_time(void);
static void sigHupHandler(SIGNAL_ARGS);
static void sigUsr1Handler(SIGNAL_ARGS);
static void update_metainfo_datafile(void);
--- 121,153 ----
* Flags set by interrupt handlers for later service in the main loop.
*/
static volatile sig_atomic_t got_SIGHUP = false;
+
+ /* Rotation of all log requested by pg_rotate_logfile? */
static volatile sig_atomic_t rotation_requested = false;
/* Local subroutines */
#ifdef EXEC_BACKEND
static pid_t syslogger_forkexec(void);
#endif
NON_EXEC_STATIC void SysLoggerMain(int argc, char *argv[]) pg_attribute_noreturn();
static void process_pipe_input(char *logbuffer, int *bytes_in_logbuffer);
static void flush_pipe_input(char *logbuffer, int *bytes_in_logbuffer);
! static void open_csvlogfile(int stream_id);
static FILE *logfile_open(const char *filename, const char *mode,
! bool allow_errors, int stream_id);
#ifdef WIN32
static unsigned int __stdcall pipeThread(void *arg);
#endif
! static void logfile_rotate(bool time_based_rotation, int size_rotation_for,
! int stream_id);
! static char *logfile_getname(pg_time_t timestamp, const char *suffix,
! int stream_id);
! static void set_next_rotation_time(int stream_id);
static void sigHupHandler(SIGNAL_ARGS);
static void sigUsr1Handler(SIGNAL_ARGS);
+
static void update_metainfo_datafile(void);
*************** SysLoggerMain(int argc, char *argv[])
*** 160,174 ****
char logbuffer[READ_BUF_SIZE];
int bytes_in_logbuffer = 0;
#endif
- char *currentLogDir;
- char *currentLogFilename;
- int currentLogRotationAge;
pg_time_t now;
now = MyStartTime;
#ifdef EXEC_BACKEND
! syslogger_parseArgs(argc, argv);
#endif /* EXEC_BACKEND */
am_syslogger = true;
--- 162,213 ----
char logbuffer[READ_BUF_SIZE];
int bytes_in_logbuffer = 0;
#endif
pg_time_t now;
+ int i;
+ bool timeout_valid;
now = MyStartTime;
+ /*
+ * Initialize configuration parameters and status info.
+ *
+ * XXX Should we only do this for log_stream[0]? get_log_stream() does so
+ * for the extension streams.
+ */
+ for (i = 0; i < log_streams_active; i++)
+ {
+ LogStream *stream = &log_streams[i];
+
+ stream->csvlog_file = NULL;
+ stream->rotation_needed = false;
+ stream->last_file_name = NULL;
+ stream->last_csv_file_name = NULL;
+ }
+
#ifdef EXEC_BACKEND
! for (i = 0; i < log_streams_active; i++)
! {
! LogStream *stream = &log_streams[i];
! int fd = stream->syslog_fd;
!
! #ifndef WIN32
! if (fd != -1)
! {
! stream->syslog_file = fdopen(fd, "a");
! setvbuf(stream->syslog_file, NULL, PG_IOLBF, 0);
! }
! #else /* WIN32 */
! if (fd != 0)
! {
! fd = _open_osfhandle(fd, _O_APPEND | _O_TEXT);
! if (fd > 0)
! {
! stream->syslog_file = fdopen(fd, "a");
! setvbuf(stream->syslog_file, NULL, PG_IOLBF, 0);
! }
! }
! #endif /* WIN32 */
! }
#endif /* EXEC_BACKEND */
am_syslogger = true;
*************** SysLoggerMain(int argc, char *argv[])
*** 271,297 ****
#endif /* WIN32 */
/*
! * Remember active logfile's name. We recompute this from the reference
* time because passing down just the pg_time_t is a lot cheaper than
* passing a whole file path in the EXEC_BACKEND case.
*/
! last_file_name = logfile_getname(first_syslogger_file_time, NULL);
! /* remember active logfile parameters */
! currentLogDir = pstrdup(Log_directory);
! currentLogFilename = pstrdup(Log_filename);
! currentLogRotationAge = Log_RotationAge;
! /* set next planned rotation time */
! set_next_rotation_time();
update_metainfo_datafile();
/* main worker loop */
for (;;)
{
- bool time_based_rotation = false;
- int size_rotation_for = 0;
long cur_timeout;
int cur_flags;
#ifndef WIN32
int rc;
--- 310,342 ----
#endif /* WIN32 */
/*
! * Remember active logfile names. We recompute this from the reference
* time because passing down just the pg_time_t is a lot cheaper than
* passing a whole file path in the EXEC_BACKEND case.
*/
! for (i = 0; i < log_streams_active; i++)
! {
! LogStream *stream = &log_streams[i];
! stream->last_file_name = logfile_getname(first_syslogger_file_time,
! NULL, i);
!
! /* remember active logfile parameters */
! stream->current_dir = pstrdup(stream->directory);
! stream->current_filename = pstrdup(stream->filename);
! stream->current_rotation_age = stream->rotation_age;
!
! /* set next planned rotation time */
! set_next_rotation_time(i);
! }
update_metainfo_datafile();
/* main worker loop */
for (;;)
{
long cur_timeout;
int cur_flags;
+ int i;
#ifndef WIN32
int rc;
*************** SysLoggerMain(int argc, char *argv[])
*** 308,344 ****
got_SIGHUP = false;
ProcessConfigFile(PGC_SIGHUP);
! /*
! * Check if the log directory or filename pattern changed in
! * postgresql.conf. If so, force rotation to make sure we're
! * writing the logfiles in the right place.
! */
! if (strcmp(Log_directory, currentLogDir) != 0)
{
! pfree(currentLogDir);
! currentLogDir = pstrdup(Log_directory);
! rotation_requested = true;
/*
! * Also, create new directory if not present; ignore errors
*/
! mkdir(Log_directory, S_IRWXU);
! }
! if (strcmp(Log_filename, currentLogFilename) != 0)
! {
! pfree(currentLogFilename);
! currentLogFilename = pstrdup(Log_filename);
! rotation_requested = true;
! }
! /*
! * If rotation time parameter changed, reset next rotation time,
! * but don't immediately force a rotation.
! */
! if (currentLogRotationAge != Log_RotationAge)
! {
! currentLogRotationAge = Log_RotationAge;
! set_next_rotation_time();
}
/*
--- 353,395 ----
got_SIGHUP = false;
ProcessConfigFile(PGC_SIGHUP);
! for (i = 0; i < log_streams_active; i++)
{
! LogStream *stream = &log_streams[i];
/*
! * Check if the log directory or filename pattern changed in
! * postgresql.conf. If so, force rotation to make sure we're
! * writing the logfiles in the right place.
*/
! if (strcmp(stream->directory, stream->current_dir) != 0)
! {
! pfree(stream->current_dir);
! stream->current_dir = pstrdup(stream->directory);
! stream->rotation_needed = true;
! /*
! * Also, create new directory if not present; ignore
! * errors
! */
! mkdir(stream->directory, S_IRWXU);
! }
! if (strcmp(stream->filename, stream->current_filename) != 0)
! {
! pfree(stream->current_filename);
! stream->current_filename = pstrdup(stream->filename);
! stream->rotation_needed = true;
! }
!
! /*
! * If rotation time parameter changed, reset next rotation
! * time, but don't immediately force a rotation.
! */
! if (stream->current_rotation_age != stream->rotation_age)
! {
! stream->current_rotation_age = stream->rotation_age;
! set_next_rotation_time(i);
! }
}
/*
*************** SysLoggerMain(int argc, char *argv[])
*** 359,397 ****
update_metainfo_datafile();
}
! if (Log_RotationAge > 0 && !rotation_disabled)
{
! /* Do a logfile rotation if it's time */
! now = (pg_time_t) time(NULL);
! if (now >= next_rotation_time)
! rotation_requested = time_based_rotation = true;
! }
! if (!rotation_requested && Log_RotationSize > 0 && !rotation_disabled)
! {
! /* Do a rotation if file is too big */
! if (ftell(syslogFile) >= Log_RotationSize * 1024L)
{
! rotation_requested = true;
! size_rotation_for |= LOG_DESTINATION_STDERR;
}
! if (csvlogFile != NULL &&
! ftell(csvlogFile) >= Log_RotationSize * 1024L)
{
! rotation_requested = true;
! size_rotation_for |= LOG_DESTINATION_CSVLOG;
}
- }
- if (rotation_requested)
- {
/*
! * Force rotation when both values are zero. It means the request
! * was sent by pg_rotate_logfile.
*/
! if (!time_based_rotation && size_rotation_for == 0)
! size_rotation_for = LOG_DESTINATION_STDERR | LOG_DESTINATION_CSVLOG;
! logfile_rotate(time_based_rotation, size_rotation_for);
}
/*
--- 410,461 ----
update_metainfo_datafile();
}
! for (i = 0; i < log_streams_active; i++)
{
! bool time_based_rotation = false;
! int size_rotation_for = 0;
! LogStream *stream = &log_streams[i];
! if (stream->current_rotation_age > 0 && !rotation_disabled)
{
! /* Do a logfile rotation if it's time */
! now = (pg_time_t) time(NULL);
! if (now >= stream->next_rotation_time)
! stream->rotation_needed = time_based_rotation = true;
}
!
! if (!rotation_requested && !stream->rotation_needed &&
! stream->rotation_size > 0 && !rotation_disabled)
{
! /* Do a rotation if file is too big */
! if (ftell(stream->syslog_file) >=
! stream->rotation_size * 1024L)
! {
! stream->rotation_needed = true;
! size_rotation_for |= LOG_DESTINATION_STDERR;
! }
! if (stream->csvlog_file != NULL &&
! ftell(stream->csvlog_file) >=
! stream->rotation_size * 1024L)
! {
! stream->rotation_needed = true;
! size_rotation_for |= LOG_DESTINATION_CSVLOG;
! }
}
/*
! * Consider rotation if the current file needs it or if rotation
! * of all files has been requested explicitly.
*/
! if (stream->rotation_needed || rotation_requested)
! {
! /*
! * Force rotation if it's requested by pg_rotate_logfile.
! */
! if (rotation_requested)
! size_rotation_for = LOG_DESTINATION_STDERR | LOG_DESTINATION_CSVLOG;
! logfile_rotate(time_based_rotation, size_rotation_for, i);
! }
}
/*
*************** SysLoggerMain(int argc, char *argv[])
*** 402,428 ****
* next_rotation_time.
*
* Also note that we need to beware of overflow in calculation of the
! * timeout: with large settings of Log_RotationAge, next_rotation_time
! * could be more than INT_MAX msec in the future. In that case we'll
! * wait no more than INT_MAX msec, and try again.
*/
! if (Log_RotationAge > 0 && !rotation_disabled)
{
! pg_time_t delay;
! delay = next_rotation_time - now;
! if (delay > 0)
{
! if (delay > INT_MAX / 1000)
! delay = INT_MAX / 1000;
! cur_timeout = delay * 1000L; /* msec */
}
! else
! cur_timeout = 0;
! cur_flags = WL_TIMEOUT;
}
else
{
cur_timeout = -1L;
cur_flags = 0;
}
--- 466,516 ----
* next_rotation_time.
*
* Also note that we need to beware of overflow in calculation of the
! * timeout: with large settings of current_rotation_age,
! * next_rotation_time could be more than INT_MAX msec in the future.
! * In that case we'll wait no more than INT_MAX msec, and try again.
*/
! timeout_valid = false;
! for (i = 0; i < log_streams_active; i++)
{
! LogStream *stream = &log_streams[i];
! if (stream->current_rotation_age > 0 && !rotation_disabled)
{
! pg_time_t delay;
! long timeout_tmp;
!
! delay = stream->next_rotation_time - now;
! if (delay > 0)
! {
! if (delay > INT_MAX / 1000)
! delay = INT_MAX / 1000;
! timeout_tmp = delay * 1000L; /* msec */
! }
! else
! timeout_tmp = 0;
!
! /* Looking for the nearest timeout across log files. */
! if (!timeout_valid)
! {
! /* cur_timeout not defined yet. */
! cur_timeout = timeout_tmp;
! timeout_valid = true;
! }
! else
! cur_timeout = Min(cur_timeout, timeout_tmp);
}
!
}
+
+ if (timeout_valid)
+ cur_flags = WL_TIMEOUT;
else
{
+ /*
+ * No file will need rotation, so wait until data can be read from
+ * the pipe.
+ */
cur_timeout = -1L;
cur_flags = 0;
}
*************** SysLoggerMain(int argc, char *argv[])
*** 503,510 ****
/*
* Normal exit from the syslogger is here. Note that we
! * deliberately do not close syslogFile before exiting; this is to
! * allow for the possibility of elog messages being generated
* inside proc_exit. Regular exit() will take care of flushing
* and closing stdio channels.
*/
--- 591,598 ----
/*
* Normal exit from the syslogger is here. Note that we
! * deliberately do not close syslog_file before exiting; this is
! * to allow for the possibility of elog messages being generated
* inside proc_exit. Regular exit() will take care of flushing
* and closing stdio channels.
*/
*************** int
*** 520,526 ****
SysLogger_Start(void)
{
pid_t sysloggerPid;
! char *filename;
if (!Logging_collector)
return 0;
--- 608,615 ----
SysLogger_Start(void)
{
pid_t sysloggerPid;
! int i;
! LogStream *stream;
if (!Logging_collector)
return 0;
*************** SysLogger_Start(void)
*** 562,589 ****
#endif
/*
! * Create log directory if not present; ignore errors
*/
! mkdir(Log_directory, S_IRWXU);
/*
* The initial logfile is created right in the postmaster, to verify that
! * the Log_directory is writable. We save the reference time so that the
* syslogger child process can recompute this file name.
*
* It might look a bit strange to re-do this during a syslogger restart,
! * but we must do so since the postmaster closed syslogFile after the
* previous fork (and remembering that old file wouldn't be right anyway).
* Note we always append here, we won't overwrite any existing file. This
* is consistent with the normal rules, because by definition this is not
* a time-based rotation.
*/
first_syslogger_file_time = time(NULL);
! filename = logfile_getname(first_syslogger_file_time, NULL);
!
! syslogFile = logfile_open(filename, "a", false);
! pfree(filename);
#ifdef EXEC_BACKEND
switch ((sysloggerPid = syslogger_forkexec()))
--- 651,697 ----
#endif
/*
! * Although we can't check here if the streams are initialized in a
! * sensible way, check at least if user (typically extension) messed any
! * setting up.
*/
! for (i = 0; i < log_streams_active; i++)
! {
! stream = &log_streams[i];
! if (stream->directory == NULL || stream->filename == NULL ||
! stream->rotation_age == 0 || stream->rotation_size == 0)
! ereport(FATAL,
! (errmsg("Log stream %d is not properly initialized", i)));
! }
!
! /*
! * Create log directories if not present; ignore errors
! */
! for (i = 0; i < log_streams_active; i++)
! mkdir(log_streams[i].directory, S_IRWXU);
/*
* The initial logfile is created right in the postmaster, to verify that
! * the log directory is writable. We save the reference time so that the
* syslogger child process can recompute this file name.
*
* It might look a bit strange to re-do this during a syslogger restart,
! * but we must do so since the postmaster closed syslog_file after the
* previous fork (and remembering that old file wouldn't be right anyway).
* Note we always append here, we won't overwrite any existing file. This
* is consistent with the normal rules, because by definition this is not
* a time-based rotation.
*/
first_syslogger_file_time = time(NULL);
! for (i = 0; i < log_streams_active; i++)
! {
! char *filename;
! stream = &log_streams[i];
! filename = logfile_getname(first_syslogger_file_time, NULL, i);
! stream->syslog_file = logfile_open(filename, "a", false, i);
! pfree(filename);
! }
#ifdef EXEC_BACKEND
switch ((sysloggerPid = syslogger_forkexec()))
*************** SysLogger_Start(void)
*** 627,637 ****
* Leave a breadcrumb trail when redirecting, in case the user
* forgets that redirection is active and looks only at the
* original stderr target file.
*/
ereport(LOG,
(errmsg("redirecting log output to logging collector process"),
errhint("Future log output will appear in directory \"%s\".",
! Log_directory)));
#ifndef WIN32
fflush(stdout);
--- 735,748 ----
* Leave a breadcrumb trail when redirecting, in case the user
* forgets that redirection is active and looks only at the
* original stderr target file.
+ *
+ * TODO Also list the extension log directories if there are
+ * some?
*/
ereport(LOG,
(errmsg("redirecting log output to logging collector process"),
errhint("Future log output will appear in directory \"%s\".",
! log_streams[0].directory)));
#ifndef WIN32
fflush(stdout);
*************** SysLogger_Start(void)
*** 674,682 ****
redirection_done = true;
}
! /* postmaster will never write the file; close it */
! fclose(syslogFile);
! syslogFile = NULL;
return (int) sysloggerPid;
}
--- 785,798 ----
redirection_done = true;
}
! /* postmaster will never write the files; close them */
! for (i = 0; i < log_streams_active; i++)
! {
! LogStream *stream = &log_streams[i];
!
! fclose(stream->syslog_file);
! stream->syslog_file = NULL;
! }
return (int) sysloggerPid;
}
*************** syslogger_forkexec(void)
*** 697,762 ****
{
char *av[10];
int ac = 0;
! char filenobuf[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(filenobuf, sizeof(filenobuf), "%d",
- fileno(syslogFile));
- else
- strcpy(filenobuf, "-1");
- #else /* WIN32 */
- if (syslogFile != NULL)
- snprintf(filenobuf, sizeof(filenobuf), "%ld",
- (long) _get_osfhandle(_fileno(syslogFile)));
- else
- strcpy(filenobuf, "0");
- #endif /* WIN32 */
- av[ac++] = filenobuf;
-
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 == 4);
! argv += 3;
#ifndef WIN32
! fd = atoi(*argv++);
! if (fd != -1)
! {
! syslogFile = fdopen(fd, "a");
! setvbuf(syslogFile, NULL, PG_IOLBF, 0);
! }
#else /* WIN32 */
! fd = atoi(*argv++);
! if (fd != 0)
! {
! fd = _open_osfhandle(fd, _O_APPEND | _O_TEXT);
! if (fd > 0)
! {
! syslogFile = fdopen(fd, "a");
! setvbuf(syslogFile, NULL, PG_IOLBF, 0);
! }
! }
#endif /* WIN32 */
}
#endif /* EXEC_BACKEND */
--- 813,845 ----
{
char *av[10];
int ac = 0;
! int i;
av[ac++] = "postgres";
av[ac++] = "--forklog";
av[ac++] = NULL; /* filled in by postmaster_forkexec */
av[ac] = NULL;
Assert(ac < lengthof(av));
! for (i = 0; i < log_streams_active; i++)
! {
! LogStream *stream = &log_streams[i];
#ifndef WIN32
! if (stream->syslog_file != NULL)
! stream->syslog_fd = fileno(stream->syslog_file);
! else
! stream->syslog_fd = -1;
#else /* WIN32 */
! if (syslog_file != NULL)
! stream->syslog_fd = (long)
! _get_osfhandle(_fileno(stream->syslog_file));
! else
! stream->syslog_fd = 0;
#endif /* WIN32 */
+ }
+
+ return postmaster_forkexec(ac, av);
}
#endif /* EXEC_BACKEND */
*************** syslogger_parseArgs(int argc, char *argv
*** 773,786 ****
* (hopefully atomic) chunks - such chunks are detected and reassembled here.
*
* The protocol has a header that starts with two nul bytes, then has a 16 bit
! * length, the pid of the sending process, and a flag to indicate if it is
! * the last chunk in a message. Incomplete chunks are saved until we read some
! * more, and non-final chunks are accumulated until we get the final chunk.
*
* All of this is to avoid 2 problems:
* . partial messages being written to logfiles (messes rotation), and
* . messages from different backends being interleaved (messages garbled).
*
* Any non-protocol messages are written out directly. These should only come
* from non-PostgreSQL sources, however (e.g. third party libraries writing to
* stderr).
--- 856,874 ----
* (hopefully atomic) chunks - such chunks are detected and reassembled here.
*
* The protocol has a header that starts with two nul bytes, then has a 16 bit
! * length, the pid of the sending process, stream identifier, and a flag to
! * indicate if it is the last chunk in a message. Incomplete chunks are saved
! * until we read some more, and non-final chunks are accumulated until we get
! * the final chunk.
*
* All of this is to avoid 2 problems:
* . partial messages being written to logfiles (messes rotation), and
* . messages from different backends being interleaved (messages garbled).
*
+ * The stream identifier is in the header to ensure correct routing into log
+ * files, however message chunks of different streams sent by the same backend
+ * are not expected to be interleaved.
+ *
* Any non-protocol messages are written out directly. These should only come
* from non-PostgreSQL sources, however (e.g. third party libraries writing to
* stderr).
*************** process_pipe_input(char *logbuffer, int
*** 807,812 ****
--- 895,901 ----
if (p.nuls[0] == '\0' && p.nuls[1] == '\0' &&
p.len > 0 && p.len <= PIPE_MAX_PAYLOAD &&
p.pid != 0 &&
+ p.stream_id >= 0 && p.stream_id < MAXLOGSTREAMS &&
(p.is_last == 't' || p.is_last == 'f' ||
p.is_last == 'T' || p.is_last == 'F'))
{
*************** process_pipe_input(char *logbuffer, int
*** 867,872 ****
--- 956,962 ----
buffer_lists[p.pid % NBUFFER_LISTS] = buffer_list;
}
free_slot->pid = p.pid;
+ free_slot->stream_id = p.stream_id;
str = &(free_slot->data);
initStringInfo(str);
appendBinaryStringInfo(str,
*************** process_pipe_input(char *logbuffer, int
*** 886,892 ****
appendBinaryStringInfo(str,
cursor + PIPE_HEADER_SIZE,
p.len);
! write_syslogger_file(str->data, str->len, dest);
/* Mark the buffer unused, and reclaim string storage */
existing_slot->pid = 0;
pfree(str->data);
--- 976,983 ----
appendBinaryStringInfo(str,
cursor + PIPE_HEADER_SIZE,
p.len);
! write_syslogger_file(str->data, str->len, dest,
! existing_slot->stream_id);
/* Mark the buffer unused, and reclaim string storage */
existing_slot->pid = 0;
pfree(str->data);
*************** process_pipe_input(char *logbuffer, int
*** 895,901 ****
{
/* The whole message was one chunk, evidently. */
write_syslogger_file(cursor + PIPE_HEADER_SIZE, p.len,
! dest);
}
}
--- 986,992 ----
{
/* The whole message was one chunk, evidently. */
write_syslogger_file(cursor + PIPE_HEADER_SIZE, p.len,
! dest, p.stream_id);
}
}
*************** process_pipe_input(char *logbuffer, int
*** 922,928 ****
break;
}
/* fall back on the stderr log as the destination */
! write_syslogger_file(cursor, chunklen, LOG_DESTINATION_STDERR);
cursor += chunklen;
count -= chunklen;
}
--- 1013,1019 ----
break;
}
/* fall back on the stderr log as the destination */
! write_syslogger_file(cursor, chunklen, LOG_DESTINATION_STDERR, 0);
cursor += chunklen;
count -= chunklen;
}
*************** flush_pipe_input(char *logbuffer, int *b
*** 960,966 ****
StringInfo str = &(buf->data);
write_syslogger_file(str->data, str->len,
! LOG_DESTINATION_STDERR);
/* Mark the buffer unused, and reclaim string storage */
buf->pid = 0;
pfree(str->data);
--- 1051,1057 ----
StringInfo str = &(buf->data);
write_syslogger_file(str->data, str->len,
! LOG_DESTINATION_STDERR, buf->stream_id);
/* Mark the buffer unused, and reclaim string storage */
buf->pid = 0;
pfree(str->data);
*************** flush_pipe_input(char *logbuffer, int *b
*** 974,984 ****
*/
if (*bytes_in_logbuffer > 0)
write_syslogger_file(logbuffer, *bytes_in_logbuffer,
! LOG_DESTINATION_STDERR);
*bytes_in_logbuffer = 0;
}
-
/* --------------------------------
* logfile routines
* --------------------------------
--- 1065,1074 ----
*/
if (*bytes_in_logbuffer > 0)
write_syslogger_file(logbuffer, *bytes_in_logbuffer,
! LOG_DESTINATION_STDERR, 0);
*bytes_in_logbuffer = 0;
}
/* --------------------------------
* logfile routines
* --------------------------------
*************** flush_pipe_input(char *logbuffer, int *b
*** 992,1006 ****
* even though its stderr does not point at the syslog pipe.
*/
void
! write_syslogger_file(const char *buffer, int count, int destination)
{
int rc;
FILE *logfile;
! if (destination == LOG_DESTINATION_CSVLOG && csvlogFile == NULL)
! open_csvlogfile();
! logfile = destination == LOG_DESTINATION_CSVLOG ? csvlogFile : syslogFile;
rc = fwrite(buffer, 1, count, logfile);
/* can't use ereport here because of possible recursion */
--- 1082,1099 ----
* even though its stderr does not point at the syslog pipe.
*/
void
! write_syslogger_file(const char *buffer, int count, int destination,
! int stream_id)
{
int rc;
FILE *logfile;
+ LogStream *stream = &log_streams[stream_id];
! if (destination == LOG_DESTINATION_CSVLOG && stream->csvlog_file == NULL)
! open_csvlogfile(stream_id);
! logfile = destination == LOG_DESTINATION_CSVLOG ? stream->csvlog_file :
! stream->syslog_file;
rc = fwrite(buffer, 1, count, logfile);
/* can't use ereport here because of possible recursion */
*************** write_syslogger_file(const char *buffer,
*** 1008,1013 ****
--- 1101,1223 ----
write_stderr("could not write to log file: %s\n", strerror(errno));
}
+ /*
+ * Extensions can use this function to write their output to separate log
+ * files. The value returned is to be used as an argument in the errstream()
+ * function, for example:
+ *
+ * ereport(ERROR,
+ * (errcode(ERRCODE_UNDEFINED_CURSOR),
+ * errmsg("portal \"%s\" not found", stmt->portalname),
+ * errstream(stream_id),
+ * ... other errxxx() fields as needed ...));
+ *
+ * Caller is expected to pass a pointer to which the function writes a pointer
+ * to LogStream structure, which is pre-initialized according to the core log
+ * stream. Caller is expected to ensure that the log file path is eventually
+ * different from that of the postgres core log.
+ *
+ * CAUTION: Use adjust_log_stream_attr() to set string attributes of the log
+ * stream, as opposed to assigning arbitrary (char *) pointers directly.
+ *
+ * Note: The "id" argument is necessary so that repeated call of the function
+ * from the same library makes no harm. The particular scenario is that shared
+ * library can be re-loaded during child process startup due to EXEC_BACKEND
+ * technique. Once we have the identifier, we can use it to make error
+ * messages more convenient.
+ *
+ * XXX Do we need a function that validates the log stream after changes are
+ * done? Probably not, as shared library developer should know what he is
+ * doing.
+ */
+ extern int
+ get_log_stream(char *id, LogStream **stream_p)
+ {
+ int result = -1;
+ LogStream *stream,
+ *stream_core;
+ int i;
+
+ if (!process_shared_preload_libraries_in_progress)
+ ereport(ERROR,
+ (errmsg("get_log_stream() can only be called during shared "
+ "library preload"),
+ errhint("Please check if your extension library is in "
+ "\"shared_preload_libraries\"")));
+
+ if (log_streams_active >= MAXLOGSTREAMS)
+ ereport(ERROR,
+ (errmsg("The maximum number of log streams exceeded")));
+
+ if (id == NULL || strlen(id) == 0)
+ ereport(ERROR, (errmsg("stream id must be a non-empty string.")));
+
+ /*
+ * The function is called twice in the EXEC_BACKEND case.
+ */
+ #ifdef EXEC_BACKEND
+ if (log_streams_initialized)
+ {
+ /*
+ * If 2nd time here, only find the existing id among the extension
+ * streams.
+ */
+ Assert(log_streams_active >= 1);
+ for (i = 1; i < log_streams_active; i++)
+ {
+ LogStream *stream = &log_streams[i];
+
+ if (strcmp(id, stream->id) == 0)
+ {
+ result = i;
+ break;
+ }
+ }
+ Assert(result >= 0);
+ *stream_p = &log_streams[result];
+ return result;
+ }
+ #endif
+
+ /*
+ * Make sure the id is unique. (The core stream is not supposed to have
+ * id.)
+ */
+ for (i = 1; i < log_streams_active; i++)
+ {
+ LogStream *stream = &log_streams[i];
+
+ if (strcmp(id, stream->id))
+ ereport(ERROR, (errmsg("\"%s\" stream already exists", id)));
+ }
+
+ result = log_streams_active++;
+ stream = &log_streams[result];
+ memset(stream, 0, sizeof(LogStream));
+
+ /*
+ * Set the default values.
+ *
+ * Duplicate the strings so that GUC does not break anything if it frees
+ * the core values.
+ */
+ stream_core = &log_streams[0];
+ stream->verbosity = stream_core->verbosity;
+ stream->destination = stream_core->destination;
+ adjust_log_stream_attr(&stream->id, id);
+ adjust_log_stream_attr(&stream->filename, stream_core->filename);
+ adjust_log_stream_attr(&stream->directory, stream_core->directory);
+ adjust_log_stream_attr(&stream->line_prefix, stream_core->line_prefix);
+ stream->file_mode = stream_core->file_mode;
+ stream->rotation_age = stream_core->rotation_age;
+ stream->rotation_size = stream_core->rotation_size;
+ stream->truncate_on_rotation = stream_core->truncate_on_rotation;
+
+ *stream_p = stream;
+ return result;
+ }
+
+
#ifdef WIN32
/*
*************** pipeThread(void *arg)
*** 1064,1071 ****
*/
if (Log_RotationSize > 0)
{
! if (ftell(syslogFile) >= Log_RotationSize * 1024L ||
! (csvlogFile != NULL && ftell(csvlogFile) >= Log_RotationSize * 1024L))
SetLatch(MyLatch);
}
LeaveCriticalSection(&sysloggerSection);
--- 1274,1282 ----
*/
if (Log_RotationSize > 0)
{
! if (ftell(syslog_file) >= Log_RotationSize * 1024L ||
! (csvlog_file != NULL &&
! ftell(csvlog_file) >= Log_RotationSize * 1024L))
SetLatch(MyLatch);
}
LeaveCriticalSection(&sysloggerSection);
*************** pipeThread(void *arg)
*** 1095,1134 ****
* always append in this situation.
*/
static void
! open_csvlogfile(void)
{
char *filename;
! filename = logfile_getname(time(NULL), ".csv");
! csvlogFile = logfile_open(filename, "a", false);
! if (last_csv_file_name != NULL) /* probably shouldn't happen */
! pfree(last_csv_file_name);
! last_csv_file_name = filename;
! update_metainfo_datafile();
}
/*
* Open a new logfile with proper permissions and buffering options.
*
! * If allow_errors is true, we just log any open failure and return NULL
! * (with errno still correct for the fopen failure).
! * Otherwise, errors are treated as fatal.
*/
static FILE *
! logfile_open(const char *filename, const char *mode, bool allow_errors)
{
FILE *fh;
mode_t oumask;
/*
* Note we do not let Log_file_mode disable IWUSR, since we certainly want
* to be able to write the files ourselves.
*/
! oumask = umask((mode_t) ((~(Log_file_mode | S_IWUSR)) & (S_IRWXU | S_IRWXG | S_IRWXO)));
fh = fopen(filename, mode);
umask(oumask);
--- 1306,1357 ----
* always append in this situation.
*/
static void
! open_csvlogfile(int stream_id)
{
char *filename;
+ LogStream *stream = &log_streams[stream_id];
! filename = logfile_getname(time(NULL), ".csv", stream_id);
! stream->csvlog_file = logfile_open(filename, "a", false, stream_id);
! if (stream->last_csv_file_name != NULL)
! /* probably shouldn't * happen */
! pfree(stream->last_csv_file_name);
! stream->last_csv_file_name = filename;
! if (stream_id == 0)
! update_metainfo_datafile();
}
/*
* Open a new logfile with proper permissions and buffering options.
*
! * If allow_errors is true, we just log any open failure and return NULL (with
! * errno still correct for the fopen failure). Otherwise, errors are treated
! * as fatal.
! *
! * TODO Should we check that no other stream uses the same file? If so,
! * consider the best portable way. (Comparison of the file path is not good
! * because some of the paths may be symlinks.) Can we rely on fileno() to
! * return the same number if the same file is opened by the same process
! * multiple times?
*/
static FILE *
! logfile_open(const char *filename, const char *mode, bool allow_errors,
! int stream_id)
{
FILE *fh;
mode_t oumask;
+ LogStream *stream = &log_streams[stream_id];
+ int file_mode = stream->file_mode;
/*
* Note we do not let Log_file_mode disable IWUSR, since we certainly want
* to be able to write the files ourselves.
*/
! oumask = umask((mode_t) ((~(file_mode | S_IWUSR)) & (S_IRWXU | S_IRWXG | S_IRWXO)));
fh = fopen(filename, mode);
umask(oumask);
*************** logfile_open(const char *filename, const
*** 1159,1172 ****
* perform logfile rotation
*/
static void
! logfile_rotate(bool time_based_rotation, int size_rotation_for)
{
char *filename;
char *csvfilename = NULL;
pg_time_t fntime;
FILE *fh;
! rotation_requested = false;
/*
* When doing a time-based rotation, invent the new logfile name based on
--- 1382,1398 ----
* perform logfile rotation
*/
static void
! logfile_rotate(bool time_based_rotation, int size_rotation_for, int stream_id)
{
char *filename;
char *csvfilename = NULL;
pg_time_t fntime;
FILE *fh;
+ LogStream *stream = &log_streams[stream_id];
! Assert(stream_id < log_streams_active);
!
! stream->rotation_needed = false;
/*
* When doing a time-based rotation, invent the new logfile name based on
*************** logfile_rotate(bool time_based_rotation,
*** 1174,1185 ****
* file name when we don't do the rotation immediately.
*/
if (time_based_rotation)
! fntime = next_rotation_time;
else
fntime = time(NULL);
! filename = logfile_getname(fntime, NULL);
! if (csvlogFile != NULL)
! csvfilename = logfile_getname(fntime, ".csv");
/*
* Decide whether to overwrite or append. We can overwrite if (a)
--- 1400,1411 ----
* file name when we don't do the rotation immediately.
*/
if (time_based_rotation)
! fntime = stream->next_rotation_time;
else
fntime = time(NULL);
! filename = logfile_getname(fntime, NULL, stream_id);
! if (stream->csvlog_file != NULL)
! csvfilename = logfile_getname(fntime, ".csv", stream_id);
/*
* Decide whether to overwrite or append. We can overwrite if (a)
*************** logfile_rotate(bool time_based_rotation,
*** 1191,1209 ****
*/
if (time_based_rotation || (size_rotation_for & LOG_DESTINATION_STDERR))
{
! if (Log_truncate_on_rotation && time_based_rotation &&
! last_file_name != NULL &&
! strcmp(filename, last_file_name) != 0)
! fh = logfile_open(filename, "w", true);
else
! fh = logfile_open(filename, "a", true);
if (!fh)
{
/*
* 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 (errno != ENFILE && errno != EMFILE)
--- 1417,1435 ----
*/
if (time_based_rotation || (size_rotation_for & LOG_DESTINATION_STDERR))
{
! if (stream->truncate_on_rotation && time_based_rotation &&
! stream->last_file_name != NULL &&
! strcmp(filename, stream->last_file_name) != 0)
! fh = logfile_open(filename, "w", true, stream_id);
else
! fh = logfile_open(filename, "a", true, stream_id);
if (!fh)
{
/*
* 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 (errno != ENFILE && errno != EMFILE)
*************** logfile_rotate(bool time_based_rotation,
*** 1220,1253 ****
return;
}
! fclose(syslogFile);
! syslogFile = fh;
/* instead of pfree'ing filename, remember it for next time */
! if (last_file_name != NULL)
! pfree(last_file_name);
! last_file_name = filename;
filename = NULL;
}
/* Same as above, but for csv file. */
! if (csvlogFile != NULL &&
(time_based_rotation || (size_rotation_for & LOG_DESTINATION_CSVLOG)))
{
! if (Log_truncate_on_rotation && time_based_rotation &&
! last_csv_file_name != NULL &&
! strcmp(csvfilename, last_csv_file_name) != 0)
! fh = logfile_open(csvfilename, "w", true);
else
! fh = logfile_open(csvfilename, "a", true);
if (!fh)
{
/*
* 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 (errno != ENFILE && errno != EMFILE)
--- 1446,1479 ----
return;
}
! fclose(stream->syslog_file);
! stream->syslog_file = fh;
/* instead of pfree'ing filename, remember it for next time */
! if (stream->last_file_name != NULL)
! pfree(stream->last_file_name);
! stream->last_file_name = filename;
filename = NULL;
}
/* Same as above, but for csv file. */
! if (stream->csvlog_file != NULL &&
(time_based_rotation || (size_rotation_for & LOG_DESTINATION_CSVLOG)))
{
! if (stream->truncate_on_rotation && time_based_rotation &&
! stream->last_csv_file_name != NULL &&
! strcmp(csvfilename, stream->last_csv_file_name) != 0)
! fh = logfile_open(csvfilename, "w", true, stream_id);
else
! fh = logfile_open(csvfilename, "a", true, stream_id);
if (!fh)
{
/*
* 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 (errno != ENFILE && errno != EMFILE)
*************** logfile_rotate(bool time_based_rotation,
*** 1264,1276 ****
return;
}
! fclose(csvlogFile);
! csvlogFile = fh;
/* instead of pfree'ing filename, remember it for next time */
! if (last_csv_file_name != NULL)
! pfree(last_csv_file_name);
! last_csv_file_name = csvfilename;
csvfilename = NULL;
}
--- 1490,1502 ----
return;
}
! fclose(stream->csvlog_file);
! stream->csvlog_file = fh;
/* instead of pfree'ing filename, remember it for next time */
! if (stream->last_csv_file_name != NULL)
! pfree(stream->last_csv_file_name);
! stream->last_csv_file_name = csvfilename;
csvfilename = NULL;
}
*************** logfile_rotate(bool time_based_rotation,
*** 1279,1287 ****
if (csvfilename)
pfree(csvfilename);
! update_metainfo_datafile();
! set_next_rotation_time();
}
--- 1505,1514 ----
if (csvfilename)
pfree(csvfilename);
! if (stream_id == 0)
! update_metainfo_datafile();
! set_next_rotation_time(stream_id);
}
*************** logfile_rotate(bool time_based_rotation,
*** 1294,1312 ****
* Result is palloc'd.
*/
static char *
! logfile_getname(pg_time_t timestamp, const char *suffix)
{
char *filename;
int len;
filename = palloc(MAXPGPATH);
! snprintf(filename, MAXPGPATH, "%s/", Log_directory);
len = strlen(filename);
! /* treat Log_filename as a strftime pattern */
! pg_strftime(filename + len, MAXPGPATH - len, Log_filename,
pg_localtime(×tamp, log_timezone));
if (suffix != NULL)
--- 1521,1540 ----
* Result is palloc'd.
*/
static char *
! logfile_getname(pg_time_t timestamp, const char *suffix, int stream_id)
{
char *filename;
int len;
+ LogStream *stream = &log_streams[stream_id];
filename = palloc(MAXPGPATH);
! snprintf(filename, MAXPGPATH, "%s/", stream->directory);
len = strlen(filename);
! /* treat log filename as a strftime pattern */
! pg_strftime(filename + len, MAXPGPATH - len, stream->filename,
pg_localtime(×tamp, log_timezone));
if (suffix != NULL)
*************** logfile_getname(pg_time_t timestamp, con
*** 1324,1337 ****
* Determine the next planned rotation time, and store in next_rotation_time.
*/
static void
! set_next_rotation_time(void)
{
pg_time_t now;
struct pg_tm *tm;
int rotinterval;
/* nothing to do if time-based rotation is disabled */
! if (Log_RotationAge <= 0)
return;
/*
--- 1552,1566 ----
* Determine the next planned rotation time, and store in next_rotation_time.
*/
static void
! set_next_rotation_time(int stream_id)
{
pg_time_t now;
struct pg_tm *tm;
int rotinterval;
+ LogStream *stream = &log_streams[stream_id];
/* nothing to do if time-based rotation is disabled */
! if (stream->rotation_age <= 0)
return;
/*
*************** set_next_rotation_time(void)
*** 1340,1353 ****
* fairly loosely. In this version we align to log_timezone rather than
* GMT.
*/
! rotinterval = Log_RotationAge * SECS_PER_MINUTE; /* convert to seconds */
now = (pg_time_t) time(NULL);
tm = pg_localtime(&now, log_timezone);
now += tm->tm_gmtoff;
now -= now % rotinterval;
now += rotinterval;
now -= tm->tm_gmtoff;
! next_rotation_time = now;
}
/*
--- 1569,1584 ----
* fairly loosely. In this version we align to log_timezone rather than
* GMT.
*/
! rotinterval = stream->rotation_age *
! SECS_PER_MINUTE; /* convert to seconds */
now = (pg_time_t) time(NULL);
tm = pg_localtime(&now, log_timezone);
now += tm->tm_gmtoff;
now -= now % rotinterval;
now += rotinterval;
now -= tm->tm_gmtoff;
!
! stream->next_rotation_time = now;
}
/*
*************** set_next_rotation_time(void)
*** 1356,1369 ****
* when there is time-based logfile rotation. Filenames are stored in a
* temporary file and which is renamed into the final destination for
* atomicity.
*/
static void
update_metainfo_datafile(void)
{
FILE *fh;
! if (!(Log_destination & LOG_DESTINATION_STDERR) &&
! !(Log_destination & LOG_DESTINATION_CSVLOG))
{
if (unlink(LOG_METAINFO_DATAFILE) < 0 && errno != ENOENT)
ereport(LOG,
--- 1587,1608 ----
* when there is time-based logfile rotation. Filenames are stored in a
* temporary file and which is renamed into the final destination for
* atomicity.
+ *
+ * TODO Should the extension logs be included? If so, how can we generate a
+ * unique prefix for them? (stream_id is not suitable because an extension can
+ * receive different id after cluster restart).
*/
static void
update_metainfo_datafile(void)
{
FILE *fh;
+ LogStream *stream_core = &log_streams[0];
+ char *last_file_name = stream_core->last_file_name;
+ char *last_csv_file_name = stream_core->last_csv_file_name;
+ LogStream *log = &log_streams[0];
! if (!(log->destination & LOG_DESTINATION_STDERR) &&
! !(log->destination & LOG_DESTINATION_CSVLOG))
{
if (unlink(LOG_METAINFO_DATAFILE) < 0 && errno != ENOENT)
ereport(LOG,
*************** update_metainfo_datafile(void)
*** 1373,1379 ****
return;
}
! if ((fh = logfile_open(LOG_METAINFO_DATAFILE_TMP, "w", true)) == NULL)
{
ereport(LOG,
(errcode_for_file_access(),
--- 1612,1621 ----
return;
}
! /*
! * Pass 0 for stream_id so that log_directory GUC controls file mode.
! */
! if ((fh = logfile_open(LOG_METAINFO_DATAFILE_TMP, "w", true, 0)) == NULL)
{
ereport(LOG,
(errcode_for_file_access(),
*************** update_metainfo_datafile(void)
*** 1382,1388 ****
return;
}
! if (last_file_name && (Log_destination & LOG_DESTINATION_STDERR))
{
if (fprintf(fh, "stderr %s\n", last_file_name) < 0)
{
--- 1624,1630 ----
return;
}
! if (last_file_name && (log->destination & LOG_DESTINATION_STDERR))
{
if (fprintf(fh, "stderr %s\n", last_file_name) < 0)
{
*************** update_metainfo_datafile(void)
*** 1395,1401 ****
}
}
! if (last_csv_file_name && (Log_destination & LOG_DESTINATION_CSVLOG))
{
if (fprintf(fh, "csvlog %s\n", last_csv_file_name) < 0)
{
--- 1637,1643 ----
}
}
! if (last_csv_file_name && (log->destination & LOG_DESTINATION_CSVLOG))
{
if (fprintf(fh, "csvlog %s\n", last_csv_file_name) < 0)
{
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
new file mode 100644
index 5285aa5..96b7256
*** a/src/backend/utils/adt/genfile.c
--- b/src/backend/utils/adt/genfile.c
*************** convert_and_check_filename(text *arg)
*** 66,77 ****
* Allow absolute paths if within DataDir or Log_directory, even
* though Log_directory might be outside DataDir.
*/
! if (!path_is_prefix_of_path(DataDir, filename) &&
! (!is_absolute_path(Log_directory) ||
! !path_is_prefix_of_path(Log_directory, filename)))
! ereport(ERROR,
! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! (errmsg("absolute path not allowed"))));
}
else if (!path_is_relative_and_below_cwd(filename))
ereport(ERROR,
--- 66,95 ----
* Allow absolute paths if within DataDir or Log_directory, even
* though Log_directory might be outside DataDir.
*/
! if (!path_is_prefix_of_path(DataDir, filename))
! {
! int i;
! bool accept = false;
!
! for (i = 0; i < log_streams_active; i++)
! {
! LogStream *stream = &log_streams[i];
!
! if (!is_absolute_path(stream->directory))
! continue;
!
! if (path_is_prefix_of_path(stream->directory, filename))
! {
! accept = true;
! break;
! }
! }
!
! if (!accept)
! ereport(ERROR,
! (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! (errmsg("absolute path not allowed"))));
! }
}
else if (!path_is_relative_and_below_cwd(filename))
ereport(ERROR,
*************** pg_ls_dir_files(FunctionCallInfo fcinfo,
*** 558,564 ****
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
! return pg_ls_dir_files(fcinfo, Log_directory);
}
/* Function to return the list of files in the WAL directory */
--- 576,584 ----
Datum
pg_ls_logdir(PG_FUNCTION_ARGS)
{
! LogStream *stream = &log_streams[0];
!
! return pg_ls_dir_files(fcinfo, stream->directory);
}
/* Function to return the list of files in the WAL directory */
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
new file mode 100644
index 918db0a..ffdd0e8
*** a/src/backend/utils/error/elog.c
--- b/src/backend/utils/error/elog.c
*************** extern bool redirection_done;
*** 100,110 ****
*/
emit_log_hook_type emit_log_hook = NULL;
/* GUC parameters */
- int Log_error_verbosity = PGERROR_VERBOSE;
- char *Log_line_prefix = NULL; /* format for extra log line info */
- int Log_destination = LOG_DESTINATION_STDERR;
char *Log_destination_string = NULL;
bool syslog_sequence_numbers = true;
bool syslog_split_messages = true;
--- 100,109 ----
*/
emit_log_hook_type emit_log_hook = NULL;
+
/* GUC parameters */
char *Log_destination_string = NULL;
+
bool syslog_sequence_numbers = true;
bool syslog_split_messages = true;
*************** static const char *process_log_prefix_pa
*** 175,181 ****
static void log_line_prefix(StringInfo buf, ErrorData *edata);
static void write_csvlog(ErrorData *edata);
static void send_message_to_server_log(ErrorData *edata);
! static void write_pipe_chunks(char *data, int len, int dest);
static void send_message_to_frontend(ErrorData *edata);
static char *expand_fmt_string(const char *fmt, ErrorData *edata);
static const char *useful_strerror(int errnum);
--- 174,180 ----
static void log_line_prefix(StringInfo buf, ErrorData *edata);
static void write_csvlog(ErrorData *edata);
static void send_message_to_server_log(ErrorData *edata);
! static void write_pipe_chunks(char *data, int len, int dest, int stream_id);
static void send_message_to_frontend(ErrorData *edata);
static char *expand_fmt_string(const char *fmt, ErrorData *edata);
static const char *useful_strerror(int errnum);
*************** errfinish(int dummy,...)
*** 468,473 ****
--- 467,478 ----
}
/*
+ * A serious error should find its way to the core log.
+ */
+ if (elevel >= FATAL)
+ edata->syslogger_stream = 0;
+
+ /*
* If we are doing FATAL or PANIC, abort any old-style COPY OUT in
* progress, so that we can report the message before dying. (Without
* this, pq_putmessage will refuse to send the message at all, which is
*************** err_generic_string(int field, const char
*** 1221,1226 ****
--- 1226,1251 ----
}
/*
+ * errstream --- set identifier of the server log file the message should be
+ * written into.
+ */
+ int
+ errstream(const int stream_id)
+ {
+ ErrorData *edata = &errordata[errordata_stack_depth];
+
+ /* we don't bother incrementing recursion_depth */
+ CHECK_STACK_DEPTH();
+
+ if (stream_id < 0 || stream_id >= log_streams_active)
+ elog(ERROR, "invalid syslogger stream: %d", stream_id);
+
+ edata->syslogger_stream = stream_id;
+
+ return 0; /* return value does not matter */
+ }
+
+ /*
* set_errdata_field --- set an ErrorData string field
*/
static void
*************** log_line_prefix(StringInfo buf, ErrorDat
*** 2319,2324 ****
--- 2344,2350 ----
static int log_my_pid = 0;
int padding;
const char *p;
+ char *line_prefix = log_streams[edata->syslogger_stream].line_prefix;
/*
* This is one of the few places where we'd rather not inherit a static
*************** log_line_prefix(StringInfo buf, ErrorDat
*** 2334,2343 ****
}
log_line_number++;
! if (Log_line_prefix == NULL)
return; /* in case guc hasn't run yet */
! for (p = Log_line_prefix; *p != '\0'; p++)
{
if (*p != '%')
{
--- 2360,2369 ----
}
log_line_number++;
! if (line_prefix == NULL)
return; /* in case guc hasn't run yet */
! for (p = line_prefix; *p != '\0'; p++)
{
if (*p != '%')
{
*************** write_csvlog(ErrorData *edata)
*** 2648,2653 ****
--- 2674,2680 ----
{
StringInfoData buf;
bool print_stmt = false;
+ LogStream *log = &log_streams[edata->syslogger_stream];
/* static counter for line numbers */
static long log_line_number = 0;
*************** write_csvlog(ErrorData *edata)
*** 2803,2809 ****
appendStringInfoChar(&buf, ',');
/* file error location */
! if (Log_error_verbosity >= PGERROR_VERBOSE)
{
StringInfoData msgbuf;
--- 2830,2836 ----
appendStringInfoChar(&buf, ',');
/* file error location */
! if (log->verbosity >= PGERROR_VERBOSE)
{
StringInfoData msgbuf;
*************** write_csvlog(ErrorData *edata)
*** 2829,2837 ****
/* If in the syslogger process, try to write messages direct to file */
if (am_syslogger)
! write_syslogger_file(buf.data, buf.len, LOG_DESTINATION_CSVLOG);
else
! write_pipe_chunks(buf.data, buf.len, LOG_DESTINATION_CSVLOG);
pfree(buf.data);
}
--- 2856,2865 ----
/* If in the syslogger process, try to write messages direct to file */
if (am_syslogger)
! write_syslogger_file(buf.data, buf.len, LOG_DESTINATION_CSVLOG, 0);
else
! write_pipe_chunks(buf.data, buf.len, LOG_DESTINATION_CSVLOG,
! edata->syslogger_stream);
pfree(buf.data);
}
*************** static void
*** 2864,2869 ****
--- 2892,2898 ----
send_message_to_server_log(ErrorData *edata)
{
StringInfoData buf;
+ LogStream *log = &log_streams[edata->syslogger_stream];
initStringInfo(&buf);
*************** send_message_to_server_log(ErrorData *ed
*** 2873,2879 ****
log_line_prefix(&buf, edata);
appendStringInfo(&buf, "%s: ", _(error_severity(edata->elevel)));
! if (Log_error_verbosity >= PGERROR_VERBOSE)
appendStringInfo(&buf, "%s: ", unpack_sql_state(edata->sqlerrcode));
if (edata->message)
--- 2902,2908 ----
log_line_prefix(&buf, edata);
appendStringInfo(&buf, "%s: ", _(error_severity(edata->elevel)));
! if (log->verbosity >= PGERROR_VERBOSE)
appendStringInfo(&buf, "%s: ", unpack_sql_state(edata->sqlerrcode));
if (edata->message)
*************** send_message_to_server_log(ErrorData *ed
*** 2890,2896 ****
appendStringInfoChar(&buf, '\n');
! if (Log_error_verbosity >= PGERROR_DEFAULT)
{
if (edata->detail_log)
{
--- 2919,2925 ----
appendStringInfoChar(&buf, '\n');
! if (log->verbosity >= PGERROR_DEFAULT)
{
if (edata->detail_log)
{
*************** send_message_to_server_log(ErrorData *ed
*** 2927,2933 ****
append_with_tabs(&buf, edata->context);
appendStringInfoChar(&buf, '\n');
}
! if (Log_error_verbosity >= PGERROR_VERBOSE)
{
/* assume no newlines in funcname or filename... */
if (edata->funcname && edata->filename)
--- 2956,2962 ----
append_with_tabs(&buf, edata->context);
appendStringInfoChar(&buf, '\n');
}
! if (log->verbosity >= PGERROR_VERBOSE)
{
/* assume no newlines in funcname or filename... */
if (edata->funcname && edata->filename)
*************** send_message_to_server_log(ErrorData *ed
*** 2961,2967 ****
#ifdef HAVE_SYSLOG
/* Write to syslog, if enabled */
! if (Log_destination & LOG_DESTINATION_SYSLOG)
{
int syslog_level;
--- 2990,2996 ----
#ifdef HAVE_SYSLOG
/* Write to syslog, if enabled */
! if (log->destination & LOG_DESTINATION_SYSLOG)
{
int syslog_level;
*************** send_message_to_server_log(ErrorData *ed
*** 3001,3014 ****
#ifdef WIN32
/* Write to eventlog, if enabled */
! if (Log_destination & LOG_DESTINATION_EVENTLOG)
{
write_eventlog(edata->elevel, buf.data, buf.len);
}
#endif /* WIN32 */
/* Write to stderr, if enabled */
! if ((Log_destination & LOG_DESTINATION_STDERR) || whereToSendOutput == DestDebug)
{
/*
* Use the chunking protocol if we know the syslogger should be
--- 3030,3043 ----
#ifdef WIN32
/* Write to eventlog, if enabled */
! if (log->destination & LOG_DESTINATION_EVENTLOG)
{
write_eventlog(edata->elevel, buf.data, buf.len);
}
#endif /* WIN32 */
/* Write to stderr, if enabled */
! if ((log->destination & LOG_DESTINATION_STDERR) || whereToSendOutput == DestDebug)
{
/*
* Use the chunking protocol if we know the syslogger should be
*************** send_message_to_server_log(ErrorData *ed
*** 3016,3022 ****
* Otherwise, just do a vanilla write to stderr.
*/
if (redirection_done && !am_syslogger)
! write_pipe_chunks(buf.data, buf.len, LOG_DESTINATION_STDERR);
#ifdef WIN32
/*
--- 3045,3052 ----
* Otherwise, just do a vanilla write to stderr.
*/
if (redirection_done && !am_syslogger)
! write_pipe_chunks(buf.data, buf.len, LOG_DESTINATION_STDERR,
! edata->syslogger_stream);
#ifdef WIN32
/*
*************** send_message_to_server_log(ErrorData *ed
*** 3035,3044 ****
/* If in the syslogger process, try to write messages direct to file */
if (am_syslogger)
! write_syslogger_file(buf.data, buf.len, LOG_DESTINATION_STDERR);
/* Write to CSV log if enabled */
! if (Log_destination & LOG_DESTINATION_CSVLOG)
{
if (redirection_done || am_syslogger)
{
--- 3065,3074 ----
/* If in the syslogger process, try to write messages direct to file */
if (am_syslogger)
! write_syslogger_file(buf.data, buf.len, LOG_DESTINATION_STDERR, 0);
/* Write to CSV log if enabled */
! if (log->destination & LOG_DESTINATION_CSVLOG)
{
if (redirection_done || am_syslogger)
{
*************** send_message_to_server_log(ErrorData *ed
*** 3055,3061 ****
* syslogger not up (yet), so just dump the message to stderr,
* unless we already did so above.
*/
! if (!(Log_destination & LOG_DESTINATION_STDERR) &&
whereToSendOutput != DestDebug)
write_console(buf.data, buf.len);
pfree(buf.data);
--- 3085,3091 ----
* syslogger not up (yet), so just dump the message to stderr,
* unless we already did so above.
*/
! if (!(log->destination & LOG_DESTINATION_STDERR) &&
whereToSendOutput != DestDebug)
write_console(buf.data, buf.len);
pfree(buf.data);
*************** send_message_to_server_log(ErrorData *ed
*** 3088,3094 ****
* rc to void to shut up the compiler.
*/
static void
! write_pipe_chunks(char *data, int len, int dest)
{
PipeProtoChunk p;
int fd = fileno(stderr);
--- 3118,3124 ----
* rc to void to shut up the compiler.
*/
static void
! write_pipe_chunks(char *data, int len, int dest, int stream_id)
{
PipeProtoChunk p;
int fd = fileno(stderr);
*************** write_pipe_chunks(char *data, int len, i
*** 3096,3103 ****
--- 3126,3136 ----
Assert(len > 0);
+ StaticAssertStmt(PIPE_MAX_PAYLOAD > 0, "PipeProtoHeader is too big");
+
p.proto.nuls[0] = p.proto.nuls[1] = '\0';
p.proto.pid = MyProcPid;
+ p.proto.stream_id = (unsigned char) stream_id;
/* write all but the last chunk */
while (len > PIPE_MAX_PAYLOAD)
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
new file mode 100644
index 246fea8..1b9c404
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
*************** static struct config_bool ConfigureNames
*** 1449,1455 ****
gettext_noop("Truncate existing log files of same name during log rotation."),
NULL
},
! &Log_truncate_on_rotation,
false,
NULL, NULL, NULL
},
--- 1449,1455 ----
gettext_noop("Truncate existing log files of same name during log rotation."),
NULL
},
! &log_streams[0].truncate_on_rotation,
false,
NULL, NULL, NULL
},
*************** static struct config_int ConfigureNamesI
*** 1903,1909 ****
"(To use the customary octal format the number must "
"start with a 0 (zero).)")
},
! &Log_file_mode,
0600, 0000, 0777,
NULL, NULL, show_log_file_mode
},
--- 1903,1909 ----
"(To use the customary octal format the number must "
"start with a 0 (zero).)")
},
! &log_streams[0].file_mode,
0600, 0000, 0777,
NULL, NULL, show_log_file_mode
},
*************** static struct config_int ConfigureNamesI
*** 2536,2542 ****
NULL,
GUC_UNIT_MIN
},
! &Log_RotationAge,
HOURS_PER_DAY * MINS_PER_HOUR, 0, INT_MAX / SECS_PER_MINUTE,
NULL, NULL, NULL
},
--- 2536,2542 ----
NULL,
GUC_UNIT_MIN
},
! &log_streams[0].rotation_age,
HOURS_PER_DAY * MINS_PER_HOUR, 0, INT_MAX / SECS_PER_MINUTE,
NULL, NULL, NULL
},
*************** static struct config_int ConfigureNamesI
*** 2547,2553 ****
NULL,
GUC_UNIT_KB
},
! &Log_RotationSize,
10 * 1024, 0, INT_MAX / 1024,
NULL, NULL, NULL
},
--- 2547,2553 ----
NULL,
GUC_UNIT_KB
},
! &log_streams[0].rotation_size,
10 * 1024, 0, INT_MAX / 1024,
NULL, NULL, NULL
},
*************** static struct config_string ConfigureNam
*** 3081,3087 ****
gettext_noop("Controls information prefixed to each log line."),
gettext_noop("If blank, no prefix is used.")
},
! &Log_line_prefix,
"%m [%p] ",
NULL, NULL, NULL
},
--- 3081,3087 ----
gettext_noop("Controls information prefixed to each log line."),
gettext_noop("If blank, no prefix is used.")
},
! &log_streams[0].line_prefix,
"%m [%p] ",
NULL, NULL, NULL
},
*************** static struct config_string ConfigureNam
*** 3340,3347 ****
"or as absolute path."),
GUC_SUPERUSER_ONLY
},
! &Log_directory,
! "log",
check_canonical_path, NULL, NULL
},
{
--- 3340,3347 ----
"or as absolute path."),
GUC_SUPERUSER_ONLY
},
! &log_streams[0].directory,
! "pg_log",
check_canonical_path, NULL, NULL
},
{
*************** static struct config_string ConfigureNam
*** 3350,3356 ****
NULL,
GUC_SUPERUSER_ONLY
},
! &Log_filename,
"postgresql-%Y-%m-%d_%H%M%S.log",
NULL, NULL, NULL
},
--- 3350,3356 ----
NULL,
GUC_SUPERUSER_ONLY
},
! &log_streams[0].filename,
"postgresql-%Y-%m-%d_%H%M%S.log",
NULL, NULL, NULL
},
*************** static struct config_enum ConfigureNames
*** 3727,3733 ****
gettext_noop("Sets the verbosity of logged messages."),
NULL
},
! &Log_error_verbosity,
PGERROR_DEFAULT, log_error_verbosity_options,
NULL, NULL, NULL
},
--- 3727,3733 ----
gettext_noop("Sets the verbosity of logged messages."),
NULL
},
! &log_streams[0].verbosity,
PGERROR_DEFAULT, log_error_verbosity_options,
NULL, NULL, NULL
},
*************** check_log_destination(char **newval, voi
*** 10109,10115 ****
static void
assign_log_destination(const char *newval, void *extra)
{
! Log_destination = *((int *) extra);
}
static void
--- 10109,10117 ----
static void
assign_log_destination(const char *newval, void *extra)
{
! LogStream *log = &log_streams[0];
!
! log->destination = *((int *) extra);
}
static void
*************** show_log_file_mode(void)
*** 10506,10512 ****
{
static char buf[8];
! snprintf(buf, sizeof(buf), "%04o", Log_file_mode);
return buf;
}
--- 10508,10514 ----
{
static char buf[8];
! snprintf(buf, sizeof(buf), "%04o", log_streams[0].file_mode);
return buf;
}
diff --git a/src/include/postmaster/syslogger.h b/src/include/postmaster/syslogger.h
new file mode 100644
index f4248ef..17df307
*** a/src/include/postmaster/syslogger.h
--- b/src/include/postmaster/syslogger.h
*************** typedef struct
*** 46,51 ****
--- 46,52 ----
char nuls[2]; /* always \0\0 */
uint16 len; /* size of this chunk (counts data only) */
int32 pid; /* writer's pid */
+ unsigned char stream_id; /* 0 for core, > 0 for extensions */
char is_last; /* last chunk of message? 't' or 'f' ('T' or
* 'F' for CSV case) */
char data[FLEXIBLE_ARRAY_MEMBER]; /* data payload starts here */
*************** typedef union
*** 60,74 ****
#define PIPE_HEADER_SIZE offsetof(PipeProtoHeader, data)
#define PIPE_MAX_PAYLOAD ((int) (PIPE_CHUNK_SIZE - PIPE_HEADER_SIZE))
/* GUC options */
extern bool Logging_collector;
! extern int Log_RotationAge;
! extern int Log_RotationSize;
! extern PGDLLIMPORT char *Log_directory;
! extern PGDLLIMPORT char *Log_filename;
! extern bool Log_truncate_on_rotation;
! extern int Log_file_mode;
extern bool am_syslogger;
--- 61,158 ----
#define PIPE_HEADER_SIZE offsetof(PipeProtoHeader, data)
#define PIPE_MAX_PAYLOAD ((int) (PIPE_CHUNK_SIZE - PIPE_HEADER_SIZE))
+ /*
+ * The maximum number of log streams the syslogger can collect data from.
+ *
+ * If increasing this, make sure the new value fits in the stream_id field of
+ * PipeProtoHeader.
+ */
+ #define MAXLOGSTREAMS 8
/* GUC options */
extern bool Logging_collector;
!
! /*
! * ereport() associates each message with particular stream so that messages
! * from various sources can be logged to separate files. Each stream can
! * actually end up in multiple files, as specified by log destination
! * (LOG_DESTINATION_STDERR, LOG_DESTINATION_CSVLOG, ...).
! */
! typedef struct LogStream
! {
! /*
! * The following variables can take their value from the related GUCs.
! */
! int verbosity;
! int destination;
! char *directory;
! char *filename;
! int file_mode;
! int rotation_age;
! int rotation_size;
! bool truncate_on_rotation;
! char *line_prefix;
!
! char *id;
!
! pg_time_t next_rotation_time;
! bool rotation_needed;
! int current_rotation_age;
! FILE *syslog_file;
! #ifdef EXEC_BACKEND
! #ifndef WIN32
! int syslog_fd;
! #else /* WIN32 */
! long syslog_fd;
! #endif /* WIN32 */
! #endif /* EXEC_BACKEND */
! FILE *csvlog_file;
! char *last_file_name;
! char *last_csv_file_name;
! char *current_dir;
! char *current_filename;
! } LogStream;
!
! #ifdef EXEC_BACKEND
! extern bool log_streams_initialized;
!
! /*
! * directory, filename and line_prefix need to be passed in the EXEC_BACKEND
! * case, so store the actual strings, not just pointers. Since there's no size
! * limit on line_prefix, put it at the end of the structure.
! */
! typedef struct LogStreamFlat
! {
! Size size;
! int verbosity;
! int destination;
! char directory[MAXPGPATH];
! char filename[MAXPGPATH];
! char id[MAXPGPATH];
! int file_mode;
! int rotation_age;
! int rotation_size;
! bool truncate_on_rotation;
!
! #ifndef WIN32
! int syslog_fd;
! #else /* WIN32 */
! long syslog_fd;
! #endif /* WIN32 */
!
! char line_prefix[FLEXIBLE_ARRAY_MEMBER];
! } LogStreamFlat;
!
! /*
! * The structures are stored w/o alignment, so the next one can immediately
! * follow the end of line_prefix.
! */
! #define LOG_STREAM_FLAT_SIZE(s) (offsetof(LogStreamFlat, line_prefix) + \
! strlen((s)->line_prefix) + 1)
! #endif /* EXEC_BACKEND */
!
! extern LogStream log_streams[MAXLOGSTREAMS];
! extern int log_streams_active;
extern bool am_syslogger;
*************** extern HANDLE syslogPipe[2];
*** 81,87 ****
extern int SysLogger_Start(void);
! extern void write_syslogger_file(const char *buffer, int count, int dest);
#ifdef EXEC_BACKEND
extern void SysLoggerMain(int argc, char *argv[]) pg_attribute_noreturn();
--- 165,183 ----
extern int SysLogger_Start(void);
! extern void write_syslogger_file(const char *buffer, int count, int dest,
! int stream_id);
! extern int get_log_stream(char *id, LogStream **stream_p);
!
! /*
! * Convenience macro to set string attributes of LogStream.
! *
! * String values that caller sets must be allocated in the TopMemoryContext or
! * malloc'd. (The latter is true if pointers to the stream fields are passed
! * to GUC framework).
! */
! #define adjust_log_stream_attr(oldval_p, newval) \
! (*(oldval_p) = MemoryContextStrdup(TopMemoryContext, (newval)))
#ifdef EXEC_BACKEND
extern void SysLoggerMain(int argc, char *argv[]) pg_attribute_noreturn();
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
new file mode 100644
index 7bfd25a..bd582e5
*** a/src/include/utils/elog.h
--- b/src/include/utils/elog.h
*************** extern int internalerrposition(int curso
*** 177,182 ****
--- 177,183 ----
extern int internalerrquery(const char *query);
extern int err_generic_string(int field, const char *str);
+ extern int errstream(const int stream_id);
extern int geterrcode(void);
extern int geterrposition(void);
*************** typedef struct ErrorData
*** 330,335 ****
--- 331,337 ----
{
int elevel; /* error level */
bool output_to_server; /* will report to server log? */
+ int syslogger_stream; /* stream identifier. > 0 for extension */
bool output_to_client; /* will report to client? */
bool show_funcname; /* true to force funcname inclusion */
bool hide_stmt; /* true to prevent STATEMENT: inclusion */
*************** typedef enum
*** 384,393 ****
PGERROR_VERBOSE /* all the facts, ma'am */
} PGErrorVerbosity;
- extern int Log_error_verbosity;
- extern char *Log_line_prefix;
- extern int Log_destination;
extern char *Log_destination_string;
extern bool syslog_sequence_numbers;
extern bool syslog_split_messages;
--- 386,393 ----
PGERROR_VERBOSE /* all the facts, ma'am */
} PGErrorVerbosity;
extern char *Log_destination_string;
+
extern bool syslog_sequence_numbers;
extern bool syslog_split_messages;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
new file mode 100644
index 17ba2bd..642e1a4
*** a/src/tools/pgindent/typedefs.list
--- b/src/tools/pgindent/typedefs.list
*************** yyscan_t
*** 3209,3211 ****
--- 3209,3213 ----
z_stream
z_streamp
zic_t
+ LogStream
+ LogStreamFlat
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers