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
