Antonin Houska <a...@cybertec.at> wrote:

> Magnus Hagander <mag...@hagander.net> wrote:
> 
> > > On Fri, Aug 25, 2017 at 12:12 AM, Antonin Houska <a...@cybertec.at> wrote:
> 
> > 
> > I like this idea in general.
> > 
> >  Then it's supposed to change some of its attributes
> > 
> > >  adjust_log_stream_attr(&stream->filename, "my_extension.log");
> > 
> > This, however, seems to be wrong.
> > 
> > The logfile name does not belong in the extension, it belongs in the
> > configuration file. I think the extension should set it's "stream id" or
> > whatever you want to call it, and then it should be possible to control in
> > postgresql.conf where that log is sent.
> 
> Doesn't the last paragraph of
> 
> https://www.postgresql.org/message-id/11412.1503912190%40localhost
> 
> address your concerns?

Besides a new version of the patch, an example extension is attached that uses
the feature.

> > Also, what if this extension is loaded on demand in a session and not via
> > shared_preload_libraries? It looks like the syslogger only gets the list of
> > configured streams when it starts?
> 
> Yes, the syslogger gets the list of streams only when it starts, so the
> extension that wants to use this feature needs to provide the file information
> via shared_preload_libraries. I consider it sufficient because various
> existing logging-related GUCs also can't be changed on-the-fly.
> 
> > In short, I think the solution should be more generic, and not "just for 
> > extensions".

statement_log.diff demonstrates how the feature can be used by various
subsystems of PG core. Please consider it an example rather than part of the
"separate log patch". Even if there were no other design questions, there's
too much copy & paste in guc.c. I have no good idea right now how to improve
this part.

> o.k. Any idea about dividing the streams into categories? Should they for
> example correspond somehow to categories of GUC variables?
> 
> > I completely missed this thread when I did my quick-wip at
> > https://www.postgresql.org/message-id/flat/cabuevexztl0gorywm9s4tr_ft3fmjbraxqdxj+bqzjpvmru...@mail.gmail.com#cabuevexztl0gorywm9s4tr_ft3fmjbraxqdxj+bqzjpvmru...@mail.gmail.com
> > -- some of the changes made were close enough that I got the two confused :)
> > Based on the feedback of that one, have you done any performance checks?
> 
> I don't expect mere routing of messages into multiple files to bring any
> overhead. I'll run some tests, just out of curiosity.

After having read the thread on your patch I think that the reason you were
asked to evaluate performance was that your patch can possibly make syslogger
a bottleneck. In contrast, my patch does not prevent user from disabling the
syslogger if it (the syslogger) seems to cause performance issues.

-- 
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 a3d4917..e3a0f48
*** a/src/backend/postmaster/postmaster.c
--- b/src/backend/postmaster/postmaster.c
*************** typedef struct
*** 465,470 ****
--- 465,471 ----
  
  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;
*** 483,489 ****
--- 484,496 ----
   */
  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
*** 529,534 ****
--- 536,543 ----
  	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);
*************** internal_forkexec(int argc, char *argv[]
*** 4476,4486 ****
  	static unsigned long tmpBackendFileNum = 0;
  	pid_t		pid;
  	char		tmpfilename[MAXPGPATH];
! 	BackendParameters param;
  	FILE	   *fp;
  
! 	if (!save_backend_variables(&param, port))
  		return -1;				/* log made by save_backend_variables */
  
  	/* Calculate name for temp file */
  	snprintf(tmpfilename, MAXPGPATH, "%s/%s.backend_var.%d.%lu",
--- 4485,4502 ----
  	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[]
*** 4504,4521 ****
  					(errcode_for_file_access(),
  					 errmsg("could not create file \"%s\": %m",
  							tmpfilename)));
  			return -1;
  		}
  	}
  
! 	if (fwrite(&param, 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))
--- 4520,4540 ----
  					(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:
*** 4603,4609 ****
  		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",
--- 4622,4629 ----
  		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:
*** 4758,4763 ****
--- 4778,4807 ----
  }
  #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;
*** 5999,6004 ****
--- 6043,6050 ----
  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
*** 6020,6025 ****
--- 6066,6076 ----
  					   HANDLE childProcess, pid_t childPid)
  #endif
  {
+ 	int			i;
+ 	LogStreamFlat *flat = NULL;
+ 	Size		flat_size_max = 0;
+ 	char	   *cur;
+ 
  	memcpy(&param->port, port, sizeof(Port));
  	if (!write_inheritable_socket(&param->portsocket, port->sock, childPid))
  		return false;
*************** save_backend_variables(BackendParameters
*** 6082,6087 ****
--- 6133,6216 ----
  
  	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")));
+ 
+ 			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
*** 6182,6188 ****
  static void
  read_backend_variables(char *id, Port *port)
  {
! 	BackendParameters param;
  
  #ifndef WIN32
  	/* Non-win32 implementation reads from file */
--- 6311,6319 ----
  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
*** 6197,6203 ****
  		exit(1);
  	}
  
! 	if (fread(&param, sizeof(param), 1, fp) != 1)
  	{
  		write_stderr("could not read from backend variables file \"%s\": %s\n",
  					 id, strerror(errno));
--- 6328,6354 ----
  		exit(1);
  	}
  
! 	/*
! 	 * First, read only the size word.
! 	 */
! 	if (fread(&param_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
*** 6230,6236 ****
  		exit(1);
  	}
  
! 	memcpy(&param, paramp, sizeof(BackendParameters));
  
  	if (!UnmapViewOfFile(paramp))
  	{
--- 6381,6388 ----
  		exit(1);
  	}
  
! 	param = (BackendParameters *) palloc(paramp->size);
! 	memcpy(&param, paramp, paramp->size);
  
  	if (!UnmapViewOfFile(paramp))
  	{
*************** read_backend_variables(char *id, Port *p
*** 6247,6259 ****
  	}
  #endif
  
! 	restore_backend_variables(&param, port);
  }
  
  /* Restore critical backend variables from the BackendParameters struct */
  static void
  restore_backend_variables(BackendParameters *param, Port *port)
  {
  	memcpy(port, &param->port, sizeof(Port));
  	read_inheritable_socket(&port->sock, &param->portsocket);
  
--- 6399,6418 ----
  	}
  #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, &param->port, sizeof(Port));
  	read_inheritable_socket(&port->sock, &param->portsocket);
  
*************** restore_backend_variables(BackendParamet
*** 6310,6315 ****
--- 6469,6550 ----
  	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->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 aeb1177..186063c
*** 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];
! 
! /* The non-extension logs stream are always active. */
! int			log_streams_active = LOG_STREAM_FIRST_EXTENSION;
  
  /*
   * 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[LOG_STREAM_CORE].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. At least "filename" must be supplied, to make the log path
+  * distinct. Therefore it's not initialized here.
+  *
+  * The preferred way to set the LogStream fields is passing their pointers to
+  * GUC using DefineCustomStringVariable(), DefineCustomIntVariable(), etc.
+  *
+  * 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 reloaded during child process startup due to EXEC_BACKEND
+  * technique. Once we have the identifier, we can use it to make error
+  * messages more convenient.
+  */
+ 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 >= LOG_STREAM_FIRST_EXTENSION);
+ 		for (i = LOG_STREAM_FIRST_EXTENSION; 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 = LOG_STREAM_FIRST_EXTENSION; i < log_streams_active; i++)
+ 	{
+ 		LogStream  *stream = &log_streams[i];
+ 
+ 		if (strcmp(id, stream->id))
+ 			ereport(ERROR,
+ 					(errmsg("log stream with id \"%s\" already exists", id)));
+ 	}
+ 
+ 	result = log_streams_active++;
+ 	stream = &log_streams[result];
+ 	memset(stream, 0, sizeof(LogStream));
+ 
+ 	/*
+ 	 * Set the default values according to the core stream.
+ 	 *
+ 	 * Duplicate the strings so that GUC does not break anything if it frees
+ 	 * the core values.
+ 	 */
+ 	stream_core = &log_streams[LOG_STREAM_CORE];
+ 	init_log_stream_attr(&stream->id, id);
+ 	init_log_stream_attr(&stream->directory, stream_core->directory);
+ 	init_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;
+ 
+ 	/*
+ 	 * Filename must be set by caller, so set it to NULL to recognize easily
+ 	 * that he forgot to do so.
+ 	 */
+ 	stream->filename = NULL;
+ 
+ 	*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(&timestamp, 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(&timestamp, 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,1366 ****
--- 1587,1604 ----
   * 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[LOG_STREAM_CORE];
+ 	char	   *last_file_name = stream_core->last_file_name;
+ 	char	   *last_csv_file_name = stream_core->last_csv_file_name;
  
  	if (!(Log_destination & LOG_DESTINATION_STDERR) &&
  		!(Log_destination & LOG_DESTINATION_CSVLOG))
*************** update_metainfo_datafile(void)
*** 1373,1379 ****
  		return;
  	}
  
! 	if ((fh = logfile_open(LOG_METAINFO_DATAFILE_TMP, "w", true)) == NULL)
  	{
  		ereport(LOG,
  				(errcode_for_file_access(),
--- 1611,1620 ----
  		return;
  	}
  
! 	/*
! 	 * The "core stream" should control the file mode, see log_file_mode GUC.
! 	 */
! 	if ((fh = logfile_open(LOG_METAINFO_DATAFILE_TMP, "w", true, LOG_STREAM_CORE)) == NULL)
  	{
  		ereport(LOG,
  				(errcode_for_file_access(),
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
new file mode 100644
index b3b9fc5..037172f
*** 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[LOG_STREAM_CORE];
! 
! 	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 9cdc07f..a869894
*** a/src/backend/utils/error/elog.c
--- b/src/backend/utils/error/elog.c
*************** emit_log_hook_type emit_log_hook = NULL;
*** 102,110 ****
  
  /* 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;
  
--- 102,110 ----
  
  /* GUC parameters */
  int			Log_error_verbosity = PGERROR_VERBOSE;
  int			Log_destination = LOG_DESTINATION_STDERR;
  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);
--- 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, 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 ****
--- 468,479 ----
  	}
  
  	/*
+ 	 * 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 ****
--- 1227,1252 ----
  }
  
  /*
+  * 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
*** 2324,2329 ****
--- 2350,2356 ----
  	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
*** 2339,2348 ****
  	}
  	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 != '%')
  		{
--- 2366,2375 ----
  	}
  	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)
*** 2834,2842 ****
  
  	/* 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);
  }
--- 2861,2870 ----
  
  	/* 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);
  }
*************** send_message_to_server_log(ErrorData *ed
*** 3021,3027 ****
  		 * 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
  
  		/*
--- 3049,3056 ----
  		 * 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
*** 3040,3046 ****
  
  	/* 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)
--- 3069,3075 ----
  
  	/* 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)
*************** send_message_to_server_log(ErrorData *ed
*** 3093,3099 ****
   * 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);
--- 3122,3128 ----
   * 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
*** 3101,3108 ****
--- 3130,3140 ----
  
  	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 6dcd738..8f7fea8
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
*************** static bool check_bonjour(bool *newval,
*** 168,174 ****
  static bool check_ssl(bool *newval, void **extra, GucSource source);
  static bool check_stage_log_stats(bool *newval, void **extra, GucSource source);
  static bool check_log_stats(bool *newval, void **extra, GucSource source);
- static bool check_canonical_path(char **newval, void **extra, GucSource source);
  static bool check_timezone_abbreviations(char **newval, void **extra, GucSource source);
  static void assign_timezone_abbreviations(const char *newval, void *extra);
  static void pg_timezone_abbrev_initialize(void);
--- 168,173 ----
*************** static bool check_application_name(char
*** 190,196 ****
  static void assign_application_name(const char *newval, void *extra);
  static bool check_cluster_name(char **newval, void **extra, GucSource source);
  static const char *show_unix_socket_permissions(void);
- static const char *show_log_file_mode(void);
  
  /* Private functions in guc-file.l that need to be called from guc.c */
  static ConfigVariable *ProcessConfigFileInternal(GucContext context,
--- 189,194 ----
*************** static struct config_bool ConfigureNames
*** 1459,1465 ****
  			gettext_noop("Truncate existing log files of same name during log rotation."),
  			NULL
  		},
! 		&Log_truncate_on_rotation,
  		false,
  		NULL, NULL, NULL
  	},
--- 1457,1463 ----
  			gettext_noop("Truncate existing log files of same name during log rotation."),
  			NULL
  		},
! 		&log_streams[LOG_STREAM_CORE].truncate_on_rotation,
  		false,
  		NULL, NULL, NULL
  	},
*************** static struct config_int ConfigureNamesI
*** 1923,1931 ****
  						 "(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
  	},
  
  	{
--- 1921,1929 ----
  						 "(To use the customary octal format the number must "
  						 "start with a 0 (zero).)")
  		},
! 		&log_streams[LOG_STREAM_CORE].file_mode,
  		0600, 0000, 0777,
! 		NULL, NULL, guc_show_log_file_mode
  	},
  
  	{
*************** static struct config_int ConfigureNamesI
*** 2548,2554 ****
  			NULL,
  			GUC_UNIT_MIN
  		},
! 		&Log_RotationAge,
  		HOURS_PER_DAY * MINS_PER_HOUR, 0, INT_MAX / SECS_PER_MINUTE,
  		NULL, NULL, NULL
  	},
--- 2546,2552 ----
  			NULL,
  			GUC_UNIT_MIN
  		},
! 		&log_streams[LOG_STREAM_CORE].rotation_age,
  		HOURS_PER_DAY * MINS_PER_HOUR, 0, INT_MAX / SECS_PER_MINUTE,
  		NULL, NULL, NULL
  	},
*************** static struct config_int ConfigureNamesI
*** 2559,2565 ****
  			NULL,
  			GUC_UNIT_KB
  		},
! 		&Log_RotationSize,
  		10 * 1024, 0, INT_MAX / 1024,
  		NULL, NULL, NULL
  	},
--- 2557,2563 ----
  			NULL,
  			GUC_UNIT_KB
  		},
! 		&log_streams[LOG_STREAM_CORE].rotation_size,
  		10 * 1024, 0, INT_MAX / 1024,
  		NULL, NULL, NULL
  	},
*************** static struct config_string ConfigureNam
*** 3089,3095 ****
  			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
  	},
--- 3087,3093 ----
  			gettext_noop("Controls information prefixed to each log line."),
  			gettext_noop("If blank, no prefix is used.")
  		},
! 		&log_streams[LOG_STREAM_CORE].line_prefix,
  		"%m [%p] ",
  		NULL, NULL, NULL
  	},
*************** static struct config_string ConfigureNam
*** 3348,3356 ****
  						 "or as absolute path."),
  			GUC_SUPERUSER_ONLY
  		},
! 		&Log_directory,
  		"log",
! 		check_canonical_path, NULL, NULL
  	},
  	{
  		{"log_filename", PGC_SIGHUP, LOGGING_WHERE,
--- 3346,3354 ----
  						 "or as absolute path."),
  			GUC_SUPERUSER_ONLY
  		},
! 		&log_streams[LOG_STREAM_CORE].directory,
  		"log",
! 		guc_check_canonical_path, NULL, NULL
  	},
  	{
  		{"log_filename", PGC_SIGHUP, LOGGING_WHERE,
*************** static struct config_string ConfigureNam
*** 3358,3364 ****
  			NULL,
  			GUC_SUPERUSER_ONLY
  		},
! 		&Log_filename,
  		"postgresql-%Y-%m-%d_%H%M%S.log",
  		NULL, NULL, NULL
  	},
--- 3356,3362 ----
  			NULL,
  			GUC_SUPERUSER_ONLY
  		},
! 		&log_streams[LOG_STREAM_CORE].filename,
  		"postgresql-%Y-%m-%d_%H%M%S.log",
  		NULL, NULL, NULL
  	},
*************** static struct config_string ConfigureNam
*** 3509,3515 ****
  		},
  		&external_pid_file,
  		NULL,
! 		check_canonical_path, NULL, NULL
  	},
  
  	{
--- 3507,3513 ----
  		},
  		&external_pid_file,
  		NULL,
! 		guc_check_canonical_path, NULL, NULL
  	},
  
  	{
*************** static struct config_string ConfigureNam
*** 3560,3566 ****
  		},
  		&pgstat_temp_directory,
  		PG_STAT_TMP_DIR,
! 		check_canonical_path, assign_pgstat_temp_directory, NULL
  	},
  
  	{
--- 3558,3564 ----
  		},
  		&pgstat_temp_directory,
  		PG_STAT_TMP_DIR,
! 		guc_check_canonical_path, assign_pgstat_temp_directory, NULL
  	},
  
  	{
*************** check_log_stats(bool *newval, void **ext
*** 10212,10230 ****
  }
  
  static bool
- check_canonical_path(char **newval, void **extra, GucSource source)
- {
- 	/*
- 	 * Since canonicalize_path never enlarges the string, we can just modify
- 	 * newval in-place.  But watch out for NULL, which is the default value
- 	 * for external_pid_file.
- 	 */
- 	if (*newval)
- 		canonicalize_path(*newval);
- 	return true;
- }
- 
- static bool
  check_timezone_abbreviations(char **newval, void **extra, GucSource source)
  {
  	/*
--- 10210,10215 ----
*************** show_unix_socket_permissions(void)
*** 10504,10515 ****
  	return buf;
  }
  
! static const char *
! show_log_file_mode(void)
  {
  	static char buf[8];
  
! 	snprintf(buf, sizeof(buf), "%04o", Log_file_mode);
  	return buf;
  }
  
--- 10489,10518 ----
  	return buf;
  }
  
! /*
!  * The following hooks are available to extensions so they can use separate
!  * log configuration.
!  */
! bool
! guc_check_canonical_path(char **newval, void **extra, GucSource source)
! {
! 	/*
! 	 * Since canonicalize_path never enlarges the string, we can just modify
! 	 * newval in-place.  But watch out for NULL, which is the default value
! 	 * for external_pid_file.
! 	 */
! 	if (*newval)
! 		canonicalize_path(*newval);
! 	return true;
! }
! 
! const char *
! guc_show_log_file_mode(void)
  {
  	static char buf[8];
  
! 	snprintf(buf, sizeof(buf), "%04o",
! 			 log_streams[LOG_STREAM_CORE].file_mode);
  	return buf;
  }
  
diff --git a/src/include/postmaster/syslogger.h b/src/include/postmaster/syslogger.h
new file mode 100644
index f4248ef..59f8841
*** a/src/include/postmaster/syslogger.h
--- b/src/include/postmaster/syslogger.h
***************
*** 14,19 ****
--- 14,20 ----
  
  #include <limits.h>				/* for PIPE_BUF */
  
+ #include "utils/elog_stream.h"
  
  /*
   * Primitive protocol structure for writing to syslogger pipe(s).  The idea
*************** typedef struct
*** 46,51 ****
--- 47,53 ----
  	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;
  
--- 62,166 ----
  #define PIPE_HEADER_SIZE  offsetof(PipeProtoHeader, data)
  #define PIPE_MAX_PAYLOAD  ((int) (PIPE_CHUNK_SIZE - PIPE_HEADER_SIZE))
  
  /* 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.
! 	 */
! 	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 */
! 
! /*
!  * The maximum number of log streams available for extensions.
!  */
! #define	MAXLOGSTREAMS_EXT	8
! 
! /*
!  * 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 (LOG_STREAM_FIRST_EXTENSION + MAXLOGSTREAMS_EXT)
! 
! /*
!  * The actual log streams.
!  */
! 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();
--- 173,192 ----
  
  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 init_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..bd29a21
*** a/src/include/utils/elog.h
--- b/src/include/utils/elog.h
***************
*** 16,21 ****
--- 16,23 ----
  
  #include <setjmp.h>
  
+ #include "utils/elog_stream.h"
+ 
  /* Error level codes */
  #define DEBUG5		10			/* Debugging messages, in categories of
  								 * decreasing detail. */
*************** extern int	internalerrposition(int curso
*** 177,182 ****
--- 179,185 ----
  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 ****
--- 333,339 ----
  {
  	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
*** 385,393 ****
  }			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;
  
--- 389,397 ----
  }			PGErrorVerbosity;
  
  extern int	Log_error_verbosity;
  extern int	Log_destination;
  extern char *Log_destination_string;
+ 
  extern bool syslog_sequence_numbers;
  extern bool syslog_split_messages;
  
diff --git a/src/include/utils/elog_stream.h b/src/include/utils/elog_stream.h
new file mode 100644
index ...c975be6
*** a/src/include/utils/elog_stream.h
--- b/src/include/utils/elog_stream.h
***************
*** 0 ****
--- 1,31 ----
+ /*-------------------------------------------------------------------------
+  *
+  * elog_stream.h
+  *	  Definitions needed by both elog.c and syslogger.c.
+  *
+  * Copyright (c) 2017, PostgreSQL Global Development Group
+  *
+  * IDENTIFICATION
+  *    src/include/utils/elog_stream.h
+  *
+  * NOTES
+  *    Neither elog.h nor syslogger.h seems appropriate for the contents of
+  *    this file. If it was added to one of them, the other one would have to
+  *    include it, along with many other useless definitions.
+  *
+  *-------------------------------------------------------------------------
+  */
+ #ifndef ELOG_STREAM_H
+ #define ELOG_STREAM_H
+ 
+ /*
+  * LogStreamsId should be used as index in the log_streams array when
+  * accessing non-extension streams.
+  */
+ typedef enum LogStreamId
+ {
+ 	LOG_STREAM_CORE = 0,
+ 	LOG_STREAM_FIRST_EXTENSION,
+ } LogStreamId;
+ 
+ #endif							/* ELOG_STREAM_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
new file mode 100644
index aa130cd..cf51457
*** a/src/include/utils/guc.h
--- b/src/include/utils/guc.h
*************** extern void GUC_check_errcode(int sqlerr
*** 414,419 ****
--- 414,425 ----
  	pre_format_elog_string(errno, TEXTDOMAIN), \
  	GUC_check_errhint_string = format_elog_string
  
+ /*
+  * Hooks available to extensions.
+  */
+ extern bool guc_check_canonical_path(char **newval, void **extra,
+ 									 GucSource source);
+ extern const char *guc_show_log_file_mode(void);
  
  /*
   * The following functions are not in guc.c, but are declared here to avoid

Attachment: log_gen.tgz
Description: GNU Zip compressed data

diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
new file mode 100644
index 05c5c19..bf247ea
*** a/src/backend/tcop/postgres.c
--- b/src/backend/tcop/postgres.c
*************** exec_simple_query(const char *query_stri
*** 947,953 ****
  		ereport(LOG,
  				(errmsg("statement: %s", query_string),
  				 errhidestmt(true),
! 				 errdetail_execute(parsetree_list)));
  		was_logged = true;
  	}
  
--- 947,954 ----
  		ereport(LOG,
  				(errmsg("statement: %s", query_string),
  				 errhidestmt(true),
! 				 errdetail_execute(parsetree_list),
! 				 errstream(LOG_STREAM_STATEMENT)));
  		was_logged = true;
  	}
  
*************** exec_execute_message(const char *portal_
*** 1982,1988 ****
  						*portal_name ? portal_name : "",
  						sourceText),
  				 errhidestmt(true),
! 				 errdetail_params(portalParams)));
  		was_logged = true;
  	}
  
--- 1983,1990 ----
  						*portal_name ? portal_name : "",
  						sourceText),
  				 errhidestmt(true),
! 				 errdetail_params(portalParams),
! 				 errstream(LOG_STREAM_STATEMENT)));
  		was_logged = true;
  	}
  
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
new file mode 100644
index 8f7fea8..569cdb7
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
*************** static struct config_bool ConfigureNames
*** 1461,1466 ****
--- 1461,1475 ----
  		false,
  		NULL, NULL, NULL
  	},
+ 	{
+ 		{"statement_log_truncate_on_rotation", PGC_SIGHUP, LOGGING_WHERE,
+ 			gettext_noop("Truncate existing statement log file of same name during log rotation."),
+ 			NULL
+ 		},
+ 		&log_streams[LOG_STREAM_STATEMENT].truncate_on_rotation,
+ 		false,
+ 		NULL, NULL, NULL
+ 	},
  
  #ifdef TRACE_SORT
  	{
*************** static struct config_int ConfigureNamesI
*** 1927,1932 ****
--- 1936,1955 ----
  	},
  
  	{
+ 		{"statement_log_file_mode", PGC_SIGHUP, LOGGING_WHERE,
+ 			gettext_noop("Sets the file permissions for statement log file."),
+ 			gettext_noop("The parameter value is expected "
+ 						 "to be a numeric mode specification in the form "
+ 						 "accepted by the chmod and umask system calls. "
+ 						 "(To use the customary octal format the number must "
+ 						 "start with a 0 (zero).)")
+ 		},
+ 		&log_streams[LOG_STREAM_STATEMENT].file_mode,
+ 		0600, 0000, 0777,
+ 		NULL, NULL, guc_show_log_file_mode
+ 	},
+ 
+ 	{
  		{"work_mem", PGC_USERSET, RESOURCES_MEM,
  			gettext_noop("Sets the maximum memory to be used for query workspaces."),
  			gettext_noop("This much memory can be used by each internal "
*************** static struct config_int ConfigureNamesI
*** 2563,2568 ****
--- 2586,2612 ----
  	},
  
  	{
+ 		{"statement_log_rotation_age", PGC_SIGHUP, LOGGING_WHERE,
+ 			gettext_noop("Automatic statement log file rotation will occur after N minutes."),
+ 			NULL,
+ 			GUC_UNIT_MIN
+ 		},
+ 		&log_streams[LOG_STREAM_STATEMENT].rotation_age,
+ 		HOURS_PER_DAY * MINS_PER_HOUR, 0, INT_MAX / SECS_PER_MINUTE,
+ 		NULL, NULL, NULL
+ 	},
+ 	{
+ 		{"statement_log_rotation_size", PGC_SIGHUP, LOGGING_WHERE,
+ 			gettext_noop("Automatic statement log file rotation will occur after N kilobytes."),
+ 			NULL,
+ 			GUC_UNIT_KB
+ 		},
+ 		&log_streams[LOG_STREAM_STATEMENT].rotation_size,
+ 		10 * 1024, 0, INT_MAX / 1024,
+ 		NULL, NULL, NULL
+ 	},
+ 
+ 	{
  		{"max_function_args", PGC_INTERNAL, PRESET_OPTIONS,
  			gettext_noop("Shows the maximum number of function arguments."),
  			NULL,
*************** static struct config_string ConfigureNam
*** 3093,3098 ****
--- 3137,3152 ----
  	},
  
  	{
+ 		{"statement_log_line_prefix", PGC_SIGHUP, LOGGING_WHAT,
+ 			gettext_noop("Controls information prefixed to each statement log line."),
+ 			gettext_noop("If blank, no prefix is used.")
+ 		},
+ 		&log_streams[LOG_STREAM_STATEMENT].line_prefix,
+ 		"%m [%p] ",
+ 		NULL, NULL, NULL
+ 	},
+ 
+ 	{
  		{"log_timezone", PGC_SIGHUP, LOGGING_WHAT,
  			gettext_noop("Sets the time zone to use in log messages."),
  			NULL
*************** static struct config_string ConfigureNam
*** 3360,3365 ****
--- 3414,3440 ----
  		"postgresql-%Y-%m-%d_%H%M%S.log",
  		NULL, NULL, NULL
  	},
+ 	{
+ 		{"statement_log_directory", PGC_SIGHUP, LOGGING_WHERE,
+ 			gettext_noop("Sets the destination directory for statement log file."),
+ 			gettext_noop("Can be specified as relative to the data directory "
+ 						 "or as absolute path."),
+ 			GUC_SUPERUSER_ONLY
+ 		},
+ 		&log_streams[LOG_STREAM_STATEMENT].directory,
+ 		"log",
+ 		guc_check_canonical_path, NULL, NULL
+ 	},
+ 	{
+ 		{"statement_log_filename", PGC_SIGHUP, LOGGING_WHERE,
+ 			gettext_noop("Sets the file name pattern for statement log."),
+ 			NULL,
+ 			GUC_SUPERUSER_ONLY
+ 		},
+ 		&log_streams[LOG_STREAM_STATEMENT].filename,
+ 		"statement-%Y-%m-%d_%H%M%S.log",
+ 		NULL, NULL, NULL
+ 	},
  
  	{
  		{"syslog_ident", PGC_SIGHUP, LOGGING_WHERE,
diff --git a/src/include/utils/elog_stream.h b/src/include/utils/elog_stream.h
new file mode 100644
index c975be6..5ada12e
*** a/src/include/utils/elog_stream.h
--- b/src/include/utils/elog_stream.h
***************
*** 25,30 ****
--- 25,31 ----
  typedef enum LogStreamId
  {
  	LOG_STREAM_CORE = 0,
+ 	LOG_STREAM_STATEMENT,
  	LOG_STREAM_FIRST_EXTENSION,
  } LogStreamId;
  

Reply via email to