Attached is a draft patch to allow extension to write log messages to a
separate file. It introduces a concept of a "log stream". The extension's
shared library gets its stream assigned by calling this function from
_PG_init()

        my_stream_id = get_log_stream("my_extension", &my_log_stream);

Then it's supposed to change some of its attributes

        adjust_log_stream_attr(&stream->filename, "my_extension.log");

and to use the stream id in ereport() calls

        ereport(LOG, (errmsg("Hello world"), errstream(my_stream_id)));

The EXEC_BACKEND mechanism makes initialization of the log streams by
postmaster child processes non-trivial. I decided to extend
save_backend_variables() and restore_backend_variables() accordingly. Maybe
someone has better idea.

pgaudit seems to be the most obvious use case for this enhancement, but it
might be useful for many other extensions.

-- 
Antonin Houska
Cybertec Schönig & Schönig GmbH
Gröhrmühlgasse 26
A-2700 Wiener Neustadt
Web: http://www.postgresql-support.de, http://www.cybertec.at

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

Reply via email to