Here's my latest WIP patch for COPYable CSV logs. I think I addresses all (or most :-) ) of the concerns Dave and Tom had. It's a whole lot simpler than before (no extra pipe).

I have not yet tested this on Windows, but given that it now contains almost no Windows-specific code I'm not so worried about it - will test when I get a chance.


Windows tests (make installcheck-parallel with csv logs turned on) seem to work just fine.



updated in attached to fix a couple of small logic bugs in picking the destination file.

I think this is close to ready for documentation and application.

cheers

andrew
Index: src/backend/postmaster/syslogger.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/postmaster/syslogger.c,v
retrieving revision 1.36
diff -c -r1.36 syslogger.c
*** src/backend/postmaster/syslogger.c	4 Aug 2007 01:26:53 -0000	1.36
--- src/backend/postmaster/syslogger.c	8 Aug 2007 16:32:19 -0000
***************
*** 87,93 ****
--- 87,95 ----
  static pg_time_t next_rotation_time;
  static bool pipe_eof_seen = false;
  static FILE *syslogFile = NULL;
+ static FILE *csvlogFile = NULL;
  static char *last_file_name = NULL;
+ static char *last_csvfile_name = NULL;
  
  /* 
   * Buffers for saving partial messages from different backends. We don't expect
***************
*** 132,143 ****
  #endif
  static void process_pipe_input(char *logbuffer, int *bytes_in_logbuffer);
  static void flush_pipe_input(char *logbuffer, int *bytes_in_logbuffer);
  
  #ifdef WIN32
  static unsigned int __stdcall pipeThread(void *arg);
  #endif
! static void logfile_rotate(bool time_based_rotation);
! static char *logfile_getname(pg_time_t timestamp);
  static void set_next_rotation_time(void);
  static void sigHupHandler(SIGNAL_ARGS);
  static void sigUsr1Handler(SIGNAL_ARGS);
--- 134,146 ----
  #endif
  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);
  
  #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, char * suffix);
  static void set_next_rotation_time(void);
  static void sigHupHandler(SIGNAL_ARGS);
  static void sigUsr1Handler(SIGNAL_ARGS);
***************
*** 281,287 ****
  	for (;;)
  	{
  		bool		time_based_rotation = false;
! 
  #ifndef WIN32
  		int			bytesRead;
  		int			rc;
--- 284,290 ----
  	for (;;)
  	{
  		bool		time_based_rotation = false;
! 		int         size_rotation_for = 0;
  #ifndef WIN32
  		int			bytesRead;
  		int			rc;
***************
*** 336,346 ****
  		{
  			/* Do a rotation if file is too big */
  			if (ftell(syslogFile) >= Log_RotationSize * 1024L)
  				rotation_requested = true;
  		}
  
  		if (rotation_requested)
! 			logfile_rotate(time_based_rotation);
  
  #ifndef WIN32
  
--- 339,358 ----
  		{
  			/* 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)
! 			logfile_rotate(time_based_rotation, size_rotation_for);
  
  #ifndef WIN32
  
***************
*** 405,411 ****
  
  		if (pipe_eof_seen)
  		{
! 			ereport(LOG,
  					(errmsg("logger shutting down")));
  
  			/*
--- 417,426 ----
  
  		if (pipe_eof_seen)
  		{
! 			/* seeing this message on the real stderr is annoying - so we
! 			 * make it DEBUG1 to suppress in normal use.
! 			 */
! 			ereport(DEBUG1,
  					(errmsg("logger shutting down")));
  
  			/*
***************
*** 477,483 ****
  	 * The initial logfile is created right in the postmaster, to verify that
  	 * the Log_directory is writable.
  	 */
! 	filename = logfile_getname(time(NULL));
  
  	syslogFile = fopen(filename, "a");
  
--- 492,498 ----
  	 * The initial logfile is created right in the postmaster, to verify that
  	 * the Log_directory is writable.
  	 */
! 	filename = logfile_getname(time(NULL), NULL);
  
  	syslogFile = fopen(filename, "a");
  
***************
*** 688,693 ****
--- 703,709 ----
  {
  	char   *cursor = logbuffer;
  	int		count = *bytes_in_logbuffer;
+ 	int     dest = LOG_DESTINATION_STDERR;
  
  	/* While we have enough for a header, process data... */
  	while (count >= (int) sizeof(PipeProtoHeader))
***************
*** 700,706 ****
  		if (p.nuls[0] == '\0' && p.nuls[1] == '\0' &&
  			p.len > 0 && p.len <= PIPE_MAX_PAYLOAD &&
  			p.pid != 0 &&
! 			(p.is_last == 't' || p.is_last == 'f'))
  		{
  			chunklen = PIPE_HEADER_SIZE + p.len;
  
--- 716,723 ----
  		if (p.nuls[0] == '\0' && p.nuls[1] == '\0' &&
  			p.len > 0 && p.len <= PIPE_MAX_PAYLOAD &&
  			p.pid != 0 &&
! 			(p.is_last == 't' || p.is_last == 'f' || 
! 			 p.is_last == 'T' || p.is_last == 'F' ))
  		{
  			chunklen = PIPE_HEADER_SIZE + p.len;
  
***************
*** 708,714 ****
  			if (count < chunklen)
  				break;
  
! 			if (p.is_last == 'f')
  			{
  				/* 
  				 * Save a complete non-final chunk in the per-pid buffer 
--- 725,734 ----
  			if (count < chunklen)
  				break;
  
! 			dest = (p.is_last == 'T' || p.is_last == 'F' ) ? 
! 				LOG_DESTINATION_CSVLOG : LOG_DESTINATION_STDERR;
! 
! 			if (p.is_last == 'f' || p.is_last == 'F')
  			{
  				/* 
  				 * Save a complete non-final chunk in the per-pid buffer 
***************
*** 751,757 ****
  					 * chances and write out a partial message and hope that
  					 * it's not followed by something from another pid.
  					 */
! 					write_syslogger_file(cursor + PIPE_HEADER_SIZE, p.len);
  				}
  			}
  			else
--- 771,778 ----
  					 * chances and write out a partial message and hope that
  					 * it's not followed by something from another pid.
  					 */
! 					write_syslogger_file(cursor + PIPE_HEADER_SIZE, p.len, 
! 										 dest);
  				}
  			}
  			else
***************
*** 778,791 ****
  					appendBinaryStringInfo(str,
  										   cursor + PIPE_HEADER_SIZE,
  										   p.len);
! 					write_syslogger_file(str->data, str->len);
  					saved_chunks[existing_slot].pid = 0;
  					pfree(str->data);
  				}
  				else
  				{
  					/* The whole message was one chunk, evidently. */
! 					write_syslogger_file(cursor + PIPE_HEADER_SIZE, p.len);
  				}
  			}
  
--- 799,813 ----
  					appendBinaryStringInfo(str,
  										   cursor + PIPE_HEADER_SIZE,
  										   p.len);
! 					write_syslogger_file(str->data, str->len, dest);
  					saved_chunks[existing_slot].pid = 0;
  					pfree(str->data);
  				}
  				else
  				{
  					/* The whole message was one chunk, evidently. */
! 					write_syslogger_file(cursor + PIPE_HEADER_SIZE, p.len,
! 						dest);
  				}
  			}
  
***************
*** 811,817 ****
  				if (cursor[chunklen] == '\0')
  					break;
  			}
! 			write_syslogger_file(cursor, chunklen);
  			cursor += chunklen;
  			count -= chunklen;
  		}
--- 833,840 ----
  				if (cursor[chunklen] == '\0')
  					break;
  			}
! 			/* fall back on the stderr log as the destination */
! 			write_syslogger_file(cursor, chunklen, LOG_DESTINATION_STDERR);
  			cursor += chunklen;
  			count -= chunklen;
  		}
***************
*** 841,847 ****
  		if (saved_chunks[i].pid != 0)
  		{
  			str = &(saved_chunks[i].data);
! 			write_syslogger_file(str->data, str->len);
  			saved_chunks[i].pid = 0;
  			pfree(str->data);
  		}
--- 864,870 ----
  		if (saved_chunks[i].pid != 0)
  		{
  			str = &(saved_chunks[i].data);
! 			write_syslogger_file(str->data, str->len, LOG_DESTINATION_STDERR);
  			saved_chunks[i].pid = 0;
  			pfree(str->data);
  		}
***************
*** 851,857 ****
  	 * remove any protocol headers that may exist in it.
  	 */
  	if (*bytes_in_logbuffer > 0)
! 		write_syslogger_file(logbuffer, *bytes_in_logbuffer);
  	*bytes_in_logbuffer = 0;
  }
  
--- 874,881 ----
  	 * remove any protocol headers that may exist in it.
  	 */
  	if (*bytes_in_logbuffer > 0)
! 		write_syslogger_file(logbuffer, *bytes_in_logbuffer, 
! 							 LOG_DESTINATION_STDERR);
  	*bytes_in_logbuffer = 0;
  }
  
***************
*** 869,883 ****
   * even though its stderr does not point at the syslog pipe.
   */
  void
! write_syslogger_file(const char *buffer, int count)
  {
  	int			rc;
  
  #ifndef WIN32
! 	rc = fwrite(buffer, 1, count, syslogFile);
  #else
  	EnterCriticalSection(&sysfileSection);
! 	rc = fwrite(buffer, 1, count, syslogFile);
  	LeaveCriticalSection(&sysfileSection);
  #endif
  
--- 893,913 ----
   * 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 ;
  
  #ifndef WIN32
! 	rc = fwrite(buffer, 1, count, logfile);
  #else
  	EnterCriticalSection(&sysfileSection);
! 	rc = fwrite(buffer, 1, count, logfile);
  	LeaveCriticalSection(&sysfileSection);
  #endif
  
***************
*** 939,950 ****
  #endif   /* WIN32 */
  
  /*
   * perform logfile rotation
   */
  static void
! logfile_rotate(bool time_based_rotation)
  {
  	char	   *filename;
  	FILE	   *fh;
  
  	rotation_requested = false;
--- 969,1013 ----
  #endif   /* WIN32 */
  
  /*
+  * open the csv log file - we do this opportunistically, because 
+  * we don't know if CSV logging will be wanted.
+  */
+ static void
+ open_csvlogfile(void)
+ {
+ 	char *filename;
+ 	FILE *fh;
+ 
+ 	filename = logfile_getname(time(NULL),".csv");
+ 
+ 	fh = fopen(filename, "a");
+ 
+ 	if (!fh)
+ 		ereport(FATAL,
+ 				(errcode_for_file_access(),
+ 				 (errmsg("could not create log file \"%s\": %m",
+ 						 filename))));
+ 
+ 	setvbuf(fh, NULL, LBF_MODE, 0);
+ 
+ #ifdef WIN32
+ 	_setmode(_fileno(fh), _O_TEXT); /* use CRLF line endings on Windows */
+ #endif
+ 
+ 	csvlogFile = fh;
+ 
+ 	pfree(filename);
+ 
+ }
+ 
+ /*
   * perform logfile rotation
   */
  static void
! logfile_rotate(bool time_based_rotation, int size_rotation_for)
  {
  	char	   *filename;
+ 	char       *csvfilename;
  	FILE	   *fh;
  
  	rotation_requested = false;
***************
*** 955,963 ****
  	 * file name when we don't do the rotation immediately.
  	 */
  	if (time_based_rotation)
! 		filename = logfile_getname(next_rotation_time);
  	else
! 		filename = logfile_getname(time(NULL));
  
  	/*
  	 * Decide whether to overwrite or append.  We can overwrite if (a)
--- 1018,1034 ----
  	 * file name when we don't do the rotation immediately.
  	 */
  	if (time_based_rotation)
! 	{
! 		filename = logfile_getname(next_rotation_time, NULL);
! 		if (csvlogFile != NULL)
! 			csvfilename = logfile_getname(next_rotation_time, ".csv");
! 	}
  	else
! 	{
! 		filename = logfile_getname(time(NULL), NULL);
! 		if (csvlogFile != NULL)
! 			csvfilename = logfile_getname(time(NULL), ".csv");
! 	}
  
  	/*
  	 * Decide whether to overwrite or append.  We can overwrite if (a)
***************
*** 970,1030 ****
  	 * postmaster because it ain't gonna work in the EXEC_BACKEND case.) So we
  	 * will always append in that situation, even though truncating would
  	 * usually be safe.
  	 */
! 	if (Log_truncate_on_rotation && time_based_rotation &&
! 		last_file_name != NULL && strcmp(filename, last_file_name) != 0)
! 		fh = fopen(filename, "w");
! 	else
! 		fh = fopen(filename, "a");
! 
! 	if (!fh)
  	{
! 		int			saveerrno = errno;
! 
! 		ereport(LOG,
! 				(errcode_for_file_access(),
! 				 errmsg("could not open new log file \"%s\": %m",
! 						filename)));
  
! 		/*
! 		 * ENFILE/EMFILE are not too surprising on a busy system; just keep
! 		 * using the old file till we manage to get a new one. Otherwise,
! 		 * assume something's wrong with Log_directory and stop trying to
! 		 * create files.
! 		 */
! 		if (saveerrno != ENFILE && saveerrno != EMFILE)
  		{
  			ereport(LOG,
! 					(errmsg("disabling automatic rotation (use SIGHUP to reenable)")));
! 			Log_RotationAge = 0;
! 			Log_RotationSize = 0;
  		}
! 		pfree(filename);
! 		return;
  	}
  
! 	setvbuf(fh, NULL, LBF_MODE, 0);
  
  #ifdef WIN32
! 	_setmode(_fileno(fh), _O_TEXT); /* use CRLF line endings on Windows */
  #endif
  
! 	/* On Windows, need to interlock against data-transfer thread */
  #ifdef WIN32
! 	EnterCriticalSection(&sysfileSection);
  #endif
! 	fclose(syslogFile);
! 	syslogFile = fh;
  #ifdef WIN32
! 	LeaveCriticalSection(&sysfileSection);
  #endif
  
  	set_next_rotation_time();
  
- 	/* instead of pfree'ing filename, remember it for next time */
- 	if (last_file_name != NULL)
- 		pfree(last_file_name);
- 	last_file_name = filename;
  }
  
  
--- 1041,1172 ----
  	 * postmaster because it ain't gonna work in the EXEC_BACKEND case.) So we
  	 * will always append in that situation, even though truncating would
  	 * usually be safe.
+ 	 *
+ 	 * For consistency, we treat CSV logs the same even though they aren't
+ 	 * opened in the postmaster.
  	 */
! 	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 = fopen(filename, "w");
! 		else
! 			fh = fopen(filename, "a");
  
! 		if (!fh)
  		{
+ 			int			saveerrno = errno;
+ 
  			ereport(LOG,
! 					(errcode_for_file_access(),
! 					 errmsg("could not open new log file \"%s\": %m",
! 							filename)));
! 
! 			/*
! 			 * ENFILE/EMFILE are not too surprising on a busy system; just keep
! 			 * using the old file till we manage to get a new one. Otherwise,
! 			 * assume something's wrong with Log_directory and stop trying to
! 			 * create files.
! 			 */
! 			if (saveerrno != ENFILE && saveerrno != EMFILE)
! 			{
! 				ereport(LOG,
! 						(errmsg("disabling automatic rotation (use SIGHUP to reenable)")));
! 				Log_RotationAge = 0;
! 				Log_RotationSize = 0;
! 			}
! 			pfree(filename);
! 			return;
  		}
! 
! 		setvbuf(fh, NULL, LBF_MODE, 0);
! 
! #ifdef WIN32
! 		_setmode(_fileno(fh), _O_TEXT); /* use CRLF line endings on Windows */
! #endif
! 
! 		/* On Windows, need to interlock against data-transfer thread */
! #ifdef WIN32
! 		EnterCriticalSection(&sysfileSection);
! #endif
! 		fclose(syslogFile);
! 		syslogFile = fh;
! #ifdef WIN32
! 		LeaveCriticalSection(&sysfileSection);
! #endif
! 
! 		/* instead of pfree'ing filename, remember it for next time */
! 		if (last_file_name != NULL)
! 			pfree(last_file_name);
! 		last_file_name = filename;
! 
! 
  	}
  
! 	/* same as above, but for csv file. */
! 
! 	if (csvlogFile != NULL && ( 
! 			time_based_rotation || 
! 			(size_rotation_for & LOG_DESTINATION_STDERR)))
! 	{
! 		if (Log_truncate_on_rotation && time_based_rotation &&
! 			last_csvfile_name != NULL && 
! 			strcmp(csvfilename, last_csvfile_name) != 0)
! 			
! 			fh = fopen(csvfilename, "w");
! 		else
! 			fh = fopen(csvfilename, "a");
! 
! 		if (!fh)
! 		{
! 			int			saveerrno = errno;
! 
! 			ereport(LOG,
! 					(errcode_for_file_access(),
! 					 errmsg("could not open new log file \"%s\": %m",
! 							csvfilename)));
! 
! 			/*
! 			 * ENFILE/EMFILE are not too surprising on a busy system; just keep
! 			 * using the old file till we manage to get a new one. Otherwise,
! 			 * assume something's wrong with Log_directory and stop trying to
! 			 * create files.
! 			 */
! 			if (saveerrno != ENFILE && saveerrno != EMFILE)
! 			{
! 				ereport(LOG,
! 						(errmsg("disabling automatic rotation (use SIGHUP to reenable)")));
! 				Log_RotationAge = 0;
! 				Log_RotationSize = 0;
! 			}
! 			pfree(csvfilename);
! 			return;
! 		}
! 
! 		setvbuf(fh, NULL, LBF_MODE, 0);
  
  #ifdef WIN32
! 		_setmode(_fileno(fh), _O_TEXT); /* use CRLF line endings on Windows */
  #endif
  
! 		/* On Windows, need to interlock against data-transfer thread */
  #ifdef WIN32
! 		EnterCriticalSection(&sysfileSection);
  #endif
! 		fclose(csvlogFile);
! 		syslogFile = fh;
  #ifdef WIN32
! 		LeaveCriticalSection(&sysfileSection);
  #endif
  
+ 		/* instead of pfree'ing filename, remember it for next time */
+ 		if (last_csvfile_name != NULL)
+ 			pfree(last_csvfile_name);
+ 		last_csvfile_name = filename;
+ 	}
+ 
  	set_next_rotation_time();
  
  }
  
  
***************
*** 1034,1040 ****
   * Result is palloc'd.
   */
  static char *
! logfile_getname(pg_time_t timestamp)
  {
  	char	   *filename;
  	int			len;
--- 1176,1182 ----
   * Result is palloc'd.
   */
  static char *
! logfile_getname(pg_time_t timestamp, char * suffix)
  {
  	char	   *filename;
  	int			len;
***************
*** 1058,1063 ****
--- 1200,1215 ----
  				 Log_filename, (unsigned long) timestamp);
  	}
  
+ 	if (suffix != NULL)
+ 	{
+ 		len = strlen(filename);
+ 		if (len > 4 && (strcmp(filename+(len-4),".log") == 0))
+ 		{
+ 			len -= 4;
+ 		}
+ 		strncpy(filename + len, suffix, MAXPGPATH - len);
+ 	}
+ 
  	return filename;
  }
  
Index: src/backend/utils/error/elog.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/utils/error/elog.c,v
retrieving revision 1.193
diff -c -r1.193 elog.c
*** src/backend/utils/error/elog.c	4 Aug 2007 19:29:25 -0000	1.193
--- src/backend/utils/error/elog.c	8 Aug 2007 16:32:20 -0000
***************
*** 104,109 ****
--- 104,117 ----
  
  static int	recursion_depth = 0;	/* to detect actual recursion */
  
+ /* buffers for formatted timestamps that might be used by both
+  * log_line_prefix and csv logs.
+  */
+ 
+ #define FORMATTED_TS_LEN 128
+ static char formatted_start_time[FORMATTED_TS_LEN];
+ static char formatted_log_time[FORMATTED_TS_LEN];
+ 
  
  /* Macro for checking errordata_stack_depth is reasonable */
  #define CHECK_STACK_DEPTH() \
***************
*** 124,131 ****
  static const char *error_severity(int elevel);
  static void append_with_tabs(StringInfo buf, const char *str);
  static bool is_log_level_output(int elevel, int log_min_level);
! static void write_pipe_chunks(int fd, char *data, int len);
! 
  
  /*
   * errstart --- begin an error-reporting cycle
--- 132,139 ----
  static const char *error_severity(int elevel);
  static void append_with_tabs(StringInfo buf, const char *str);
  static bool is_log_level_output(int elevel, int log_min_level);
! static void write_pipe_chunks(char *data, int len, int dest);
! static void get_error_message(StringInfo buf, ErrorData *edata);
  
  /*
   * errstart --- begin an error-reporting cycle
***************
*** 1434,1445 ****
  	/*
  	 * This is one of the few places where we'd rather not inherit a static
  	 * variable's value from the postmaster.  But since we will, reset it when
! 	 * MyProcPid changes.
  	 */
  	if (log_my_pid != MyProcPid)
  	{
  		log_line_number = 0;
  		log_my_pid = MyProcPid;
  	}
  	log_line_number++;
  
--- 1442,1455 ----
  	/*
  	 * This is one of the few places where we'd rather not inherit a static
  	 * variable's value from the postmaster.  But since we will, reset it when
! 	 * MyProcPid changes. MyStartTime also changes when MyProcPid does, so
! 	 * reset the formatted start timestamp too.
  	 */
  	if (log_my_pid != MyProcPid)
  	{
  		log_line_number = 0;
  		log_my_pid = MyProcPid;
+ 		formatted_start_time[0] = '\0';
  	}
  	log_line_number++;
  
***************
*** 1498,1505 ****
  					struct timeval tv;
  					pg_time_t	stamp_time;
  					pg_tz	   *tz;
! 					char		strfbuf[128],
! 								msbuf[8];
  
  					gettimeofday(&tv, NULL);
  					stamp_time = (pg_time_t) tv.tv_sec;
--- 1508,1514 ----
  					struct timeval tv;
  					pg_time_t	stamp_time;
  					pg_tz	   *tz;
! 					char		msbuf[8];
  
  					gettimeofday(&tv, NULL);
  					stamp_time = (pg_time_t) tv.tv_sec;
***************
*** 1512,1527 ****
  					 */
  					tz = log_timezone ? log_timezone : gmt_timezone;
  
! 					pg_strftime(strfbuf, sizeof(strfbuf),
  								/* leave room for milliseconds... */
  								"%Y-%m-%d %H:%M:%S     %Z",
  								pg_localtime(&stamp_time, tz));
  
  					/* 'paste' milliseconds into place... */
  					sprintf(msbuf, ".%03d", (int) (tv.tv_usec / 1000));
! 					strncpy(strfbuf + 19, msbuf, 4);
  
! 					appendStringInfoString(buf, strfbuf);
  				}
  				break;
  			case 't':
--- 1521,1536 ----
  					 */
  					tz = log_timezone ? log_timezone : gmt_timezone;
  
! 					pg_strftime(formatted_log_time, FORMATTED_TS_LEN,
  								/* leave room for milliseconds... */
  								"%Y-%m-%d %H:%M:%S     %Z",
  								pg_localtime(&stamp_time, tz));
  
  					/* 'paste' milliseconds into place... */
  					sprintf(msbuf, ".%03d", (int) (tv.tv_usec / 1000));
! 					strncpy(formatted_log_time + 19, msbuf, 4);
  
! 					appendStringInfoString(buf, formatted_log_time);
  				}
  				break;
  			case 't':
***************
*** 1539,1556 ****
  				}
  				break;
  			case 's':
  				{
  					pg_time_t	stamp_time = (pg_time_t) MyStartTime;
  					pg_tz	   *tz;
- 					char		strfbuf[128];
  
  					tz = log_timezone ? log_timezone : gmt_timezone;
  
! 					pg_strftime(strfbuf, sizeof(strfbuf),
  								"%Y-%m-%d %H:%M:%S %Z",
  								pg_localtime(&stamp_time, tz));
- 					appendStringInfoString(buf, strfbuf);
  				}
  				break;
  			case 'i':
  				if (MyProcPort)
--- 1548,1565 ----
  				}
  				break;
  			case 's':
+ 				if (formatted_start_time[0] == '\0')
  				{
  					pg_time_t	stamp_time = (pg_time_t) MyStartTime;
  					pg_tz	   *tz;
  
  					tz = log_timezone ? log_timezone : gmt_timezone;
  
! 					pg_strftime(formatted_start_time, FORMATTED_TS_LEN,
  								"%Y-%m-%d %H:%M:%S %Z",
  								pg_localtime(&stamp_time, tz));
  				}
+ 				appendStringInfoString(buf, formatted_start_time);
  				break;
  			case 'i':
  				if (MyProcPort)
***************
*** 1596,1601 ****
--- 1605,1849 ----
  	}
  }
  
+ 
+ /*
+  * append a CSV'd version of a string to a StringInfo
+  * We use the PostgreSQL defaults for CSV, i.e. quote = escape = '"'
+  */
+ 
+ static inline void 
+ appendCSVLiteral(StringInfo buf, const char* data)
+ {
+   const char * p = data;
+   char c;
+ 
+   appendStringInfoCharMacro(buf, '"');
+   while ( (c = *p++) != '\0' )
+   {
+       if (c == '"')
+           appendStringInfoCharMacro(buf, '"');
+       appendStringInfoCharMacro(buf, c);
+   }
+   appendStringInfoCharMacro(buf, '"');
+ }
+ 
+ /* 
+  * Constructs the error message, depending on the Errordata it gets, 
+  * in CSV (comma seperated values) format. The COPY command 
+  * can then be used to load the messages into a table.
+  */
+ 
+ static void
+ write_csvlog(ErrorData *edata)
+ {
+ 	StringInfoData msgbuf;
+ 	StringInfoData buf;
+ 
+     /* static counter for line numbers */
+     static long log_line_number = 0;
+ 
+     /* has counter been reset in current process? */
+     static int  log_my_pid = 0;
+ 
+     /*
+      * This is one of the few places where we'd rather not inherit a static
+      * variable's value from the postmaster.  But since we will, reset it when
+      * MyProcPid changes.
+      */
+     if (log_my_pid != MyProcPid)
+     {
+         log_line_number = 0;
+         log_my_pid = MyProcPid;
+ 		formatted_start_time[0] = '\0';
+     }
+     log_line_number++;
+ 
+ 	initStringInfo(&msgbuf);
+ 	initStringInfo(&buf);
+ 
+ 	/* 
+ 	 * The format of the log output in CSV format:
+ 	 * timestamp with milliseconds, username, databasename, session id,
+ 	 * host and port number, process id, process line number, command tag, 
+      * session start time, transaction id, error severity, sql state code, 
+      * statement or error message.
+ 	 */
+   
+ 	/* timestamp_with_milliseconds */
+ 	/* 
+ 	 * Check if the timestamp is already calculated for the syslog message,
+ 	 * if it is, then no need to calculate it again, will use the same,
+ 	 * else get the current timestamp. This is done to put same timestamp
+ 	 * in both syslog and csvlog messages.
+ 	 */
+ 	if (formatted_log_time[0] == '\0')
+ 	{
+ 		struct timeval tv;
+ 		pg_time_t	stamp_time;
+ 		pg_tz	   *tz;
+ 		char		msbuf[8];
+ 		
+ 		gettimeofday(&tv, NULL);
+ 		stamp_time = (pg_time_t) tv.tv_sec;
+ 		
+ 		/*
+ 		 * Normally we print log timestamps in log_timezone, but
+ 		 * during startup we could get here before that's set.
+ 		 * If so, fall back to gmt_timezone (which guc.c ensures
+ 		 * is set up before Log_line_prefix can become nonempty).
+ 		 */
+ 		tz = log_timezone ? log_timezone : gmt_timezone;
+ 		
+ 		pg_strftime(formatted_log_time, FORMATTED_TS_LEN,
+ 					/* leave room for milliseconds... */
+ 					"%Y-%m-%d %H:%M:%S     %Z",
+ 					pg_localtime(&stamp_time, tz));
+ 
+ 		/* 'paste' milliseconds into place... */
+ 		sprintf(msbuf, ".%03d", (int) (tv.tv_usec / 1000));
+ 		strncpy(formatted_log_time + 19, msbuf, 4);
+ 	}
+ 	appendStringInfoString(&buf, formatted_log_time);
+ 	appendStringInfoChar(&buf, ',');
+ 
+ 	/* username */
+ 	if (MyProcPort)
+ 	{
+ 		const char *username = MyProcPort->user_name;
+ 		if (username == NULL || *username == '\0')
+ 			username = _("[unknown]");
+ 
+ 		appendCSVLiteral(&buf, username);
+ 	}
+ 	appendStringInfoChar(&buf, ',');
+ 
+ 	/* databasename */
+ 	if (MyProcPort)
+ 	{
+ 		const char *dbname = MyProcPort->database_name;
+ 
+ 		if (dbname == NULL || *dbname == '\0')
+ 			dbname = _("[unknown]");
+ 		
+ 		appendCSVLiteral(&buf, dbname);
+ 	}
+ 	appendStringInfoChar(&buf, ',');
+ 
+ 	/* session id */
+ 	appendStringInfo(&buf, "%lx.%x",
+ 					 (long) MyStartTime, MyProcPid);
+ 	appendStringInfoChar(&buf, ',');
+ 
+ 	/* Remote host and port */
+ 	if (MyProcPort && MyProcPort->remote_host)
+ 	{
+ 		appendStringInfo(&buf, "%s", MyProcPort->remote_host);
+ 		if (MyProcPort->remote_port && MyProcPort->remote_port[0] != '\0')
+ 			appendStringInfo(&buf, ":%s", MyProcPort->remote_port);
+ 	}
+ 
+ 	appendStringInfoChar(&buf, ',');
+ 
+ 	/* Process id  */
+ 	if (MyProcPid != 0)
+ 		appendStringInfo(&buf, "%d", MyProcPid);
+ 
+ 	appendStringInfoChar(&buf, ',');
+ 
+ 	/* Line number */
+ 	appendStringInfo(&buf, "%ld", log_line_number);
+ 
+ 	appendStringInfoChar(&buf, ',');
+ 
+ 	/* PS display */
+ 	if (MyProcPort)
+ 	{
+ 		const char *psdisp;
+ 		int         displen;
+ 
+ 		psdisp = get_ps_display(&displen);
+ 		appendStringInfo(&msgbuf, "%.*s", displen, psdisp);
+ 		appendCSVLiteral(&buf, msgbuf.data);
+ 		resetStringInfo(&msgbuf);
+ 	}
+ 
+ 	appendStringInfoChar(&buf, ',');
+ 
+ 	/* session start timestamp */
+ 	if (formatted_start_time[0] == '\0')
+ 	{
+ 		pg_time_t	stamp_time = (pg_time_t) MyStartTime;
+ 		pg_tz	   *tz;
+ 					
+ 		tz = log_timezone ? log_timezone : gmt_timezone;
+ 
+ 		pg_strftime(formatted_start_time, FORMATTED_TS_LEN,
+ 					"%Y-%m-%d %H:%M:%S %Z",
+ 					pg_localtime(&stamp_time, tz));
+ 	}
+ 	appendStringInfoString(&buf, formatted_start_time);
+ 	appendStringInfoChar(&buf, ',');
+ 
+ 
+ 	/* Transaction id */
+ 	if (MyProcPort)
+ 	{
+ 		if (IsTransactionState())
+ 			appendStringInfo(&buf, "%u", GetTopTransactionId());
+ 		else
+ 			appendStringInfo(&buf, "%u", InvalidTransactionId);
+ 	}
+ 
+ 	appendStringInfoChar(&buf, ',');
+ 
+ 	/* Error severity */
+ 	if (error_severity(edata->elevel) != NULL)
+ 		appendStringInfo(&buf, "%s,", error_severity(edata->elevel));
+ 	else
+ 		appendStringInfoString(&buf, ",");
+   
+ 	/* SQL state code */
+ 	if (Log_error_verbosity >= PGERROR_VERBOSE)
+ 		appendStringInfo(&buf, "%s", 
+ 						 unpack_sql_state(edata->sqlerrcode));
+ 	appendStringInfoChar(&buf, ',');
+  
+ 	/* Error message and cursor position if any */
+ 	get_error_message(&msgbuf, edata);
+ 
+ 	appendCSVLiteral(&buf, msgbuf.data);
+ 
+ 	appendStringInfoChar(&buf, '\n');
+ 
+ 	/* 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(msgbuf.data);
+ 	pfree(buf.data);
+ }
+ 
+ /*
+  * Appends the buffer with the error message and the cursor position.
+  */
+ static void
+ get_error_message(StringInfo buf, ErrorData *edata)
+ {
+ 	if (edata->message)
+ 		appendStringInfo(buf, "%s", edata->message);
+ 	else
+ 		appendStringInfo(buf, "%s", _("missing error text"));
+ 
+ 	if (edata->cursorpos > 0)
+ 		appendStringInfo(buf, _(" at character %d"),
+ 						 edata->cursorpos);
+ 	else if (edata->internalpos > 0)
+ 		appendStringInfo(buf, _(" at character %d"),
+ 						 edata->internalpos);
+ }
+ 
  /*
   * Unpack MAKE_SQLSTATE code. Note that this returns a pointer to a
   * static buffer.
***************
*** 1627,1632 ****
--- 1875,1882 ----
  
  	initStringInfo(&buf);
  
+ 	formatted_log_time[0] = '\0';
+ 
  	log_line_prefix(&buf);
  	appendStringInfo(&buf, "%s:  ", error_severity(edata->elevel));
  
***************
*** 1766,1772 ****
  		 * syslogger.  Otherwise, just do a vanilla write to stderr.
  		 */
  		if (redirection_done && !am_syslogger)
! 			write_pipe_chunks(fileno(stderr), buf.data, buf.len);
  #ifdef WIN32
  		/*
  		 * In a win32 service environment, there is no usable stderr. Capture
--- 2016,2022 ----
  		 * syslogger.  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
  		/*
  		 * In a win32 service environment, there is no usable stderr. Capture
***************
*** 1782,1790 ****
  			write(fileno(stderr), buf.data, buf.len);
  	}
  
  	/* If in the syslogger process, try to write messages direct to file */
  	if (am_syslogger)
! 		write_syslogger_file(buf.data, buf.len);
  
  	pfree(buf.data);
  }
--- 2032,2062 ----
  			write(fileno(stderr), buf.data, buf.len);
  	}
  
+ 	if (Log_destination & LOG_DESTINATION_CSVLOG)
+ 	{
+ 		if (redirection_done || am_syslogger)
+ 		{
+ 			/* send CSV data if it's safe to do so (syslogger doesn't need
+ 			 * the pipe)
+ 			 */
+ 			write_csvlog(edata);
+ 		}
+ 		else
+ 		{
+ 			char * msg = _("Not safe to send CSV data\n");
+ 			write(fileno(stderr),msg,strlen(msg));
+ 			if ( ! (Log_destination & LOG_DESTINATION_STDERR) &&
+ 				 whereToSendOutput != DestDebug)
+ 			{
+ 				/* write message to stderr unless we just sent it above */
+ 				write(fileno(stderr), buf.data, buf.len);
+ 			}
+ 		}
+ 	}
+ 
  	/* 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);
  
  	pfree(buf.data);
  }
***************
*** 1793,1802 ****
   * Send data to the syslogger using the chunked protocol
   */
  static void
! write_pipe_chunks(int fd, char *data, int len)
  {
  	PipeProtoChunk p;
  
  	Assert(len > 0);
  
  	p.proto.nuls[0] = p.proto.nuls[1] = '\0';
--- 2065,2076 ----
   * Send data to the syslogger using the chunked protocol
   */
  static void
! write_pipe_chunks(char *data, int len, int dest)
  {
  	PipeProtoChunk p;
  
+ 	int fd = fileno(stderr);
+ 
  	Assert(len > 0);
  
  	p.proto.nuls[0] = p.proto.nuls[1] = '\0';
***************
*** 1805,1811 ****
  	/* write all but the last chunk */
  	while (len > PIPE_MAX_PAYLOAD)
  	{
! 		p.proto.is_last = 'f';
  		p.proto.len = PIPE_MAX_PAYLOAD;
  		memcpy(p.proto.data, data, PIPE_MAX_PAYLOAD);
  		write(fd, &p, PIPE_HEADER_SIZE + PIPE_MAX_PAYLOAD);
--- 2079,2085 ----
  	/* write all but the last chunk */
  	while (len > PIPE_MAX_PAYLOAD)
  	{
! 		p.proto.is_last = (dest == LOG_DESTINATION_CSVLOG ? 'F' : 'f');
  		p.proto.len = PIPE_MAX_PAYLOAD;
  		memcpy(p.proto.data, data, PIPE_MAX_PAYLOAD);
  		write(fd, &p, PIPE_HEADER_SIZE + PIPE_MAX_PAYLOAD);
***************
*** 1814,1820 ****
  	}
  
  	/* write the last chunk */
! 	p.proto.is_last = 't';
  	p.proto.len = len;
  	memcpy(p.proto.data, data, len);
  	write(fd, &p, PIPE_HEADER_SIZE + len);
--- 2088,2094 ----
  	}
  
  	/* write the last chunk */
! 	p.proto.is_last = (dest == LOG_DESTINATION_CSVLOG ? 'T' : 't');
  	p.proto.len = len;
  	memcpy(p.proto.data, data, len);
  	write(fd, &p, PIPE_HEADER_SIZE + len);
Index: src/backend/utils/misc/guc.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/utils/misc/guc.c,v
retrieving revision 1.411
diff -c -r1.411 guc.c
*** src/backend/utils/misc/guc.c	8 Aug 2007 16:00:46 -0000	1.411
--- src/backend/utils/misc/guc.c	8 Aug 2007 16:32:24 -0000
***************
*** 2240,2246 ****
  		{"log_destination", PGC_SIGHUP, LOGGING_WHERE,
  			gettext_noop("Sets the destination for server log output."),
  			gettext_noop("Valid values are combinations of \"stderr\", \"syslog\", "
! 						 "and \"eventlog\", depending on the platform."),
  			GUC_LIST_INPUT
  		},
  		&log_destination_string,
--- 2240,2246 ----
  		{"log_destination", PGC_SIGHUP, LOGGING_WHERE,
  			gettext_noop("Sets the destination for server log output."),
  			gettext_noop("Valid values are combinations of \"stderr\", \"syslog\", "
! 						 " \"csvlog\" and \"eventlog\", depending on the platform."),
  			GUC_LIST_INPUT
  		},
  		&log_destination_string,
***************
*** 6312,6317 ****
--- 6312,6319 ----
  
  		if (pg_strcasecmp(tok, "stderr") == 0)
  			newlogdest |= LOG_DESTINATION_STDERR;
+ 		else if (pg_strcasecmp(tok, "csvlog") == 0)
+            newlogdest |= LOG_DESTINATION_CSVLOG;
  #ifdef HAVE_SYSLOG
  		else if (pg_strcasecmp(tok, "syslog") == 0)
  			newlogdest |= LOG_DESTINATION_SYSLOG;
Index: src/backend/utils/misc/postgresql.conf.sample
===================================================================
RCS file: /cvsroot/pgsql/src/backend/utils/misc/postgresql.conf.sample,v
retrieving revision 1.223
diff -c -r1.223 postgresql.conf.sample
*** src/backend/utils/misc/postgresql.conf.sample	4 Aug 2007 01:26:54 -0000	1.223
--- src/backend/utils/misc/postgresql.conf.sample	8 Aug 2007 16:32:24 -0000
***************
*** 229,236 ****
  # - Where to Log -
  
  #log_destination = 'stderr'		# Valid values are combinations of 
! 					# stderr, syslog and eventlog, 
  					# depending on platform.
  
  # This is used when logging to stderr:
  #redirect_stderr = off			# Enable capturing of stderr into log 
--- 229,237 ----
  # - Where to Log -
  
  #log_destination = 'stderr'		# Valid values are combinations of 
! 					# stderr, csvlog, syslog and eventlog, 
  					# depending on platform.
+ 					# csvlog requires redirect_stderr to be on
  
  # This is used when logging to stderr:
  #redirect_stderr = off			# Enable capturing of stderr into log 
Index: src/include/postmaster/syslogger.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/postmaster/syslogger.h,v
retrieving revision 1.10
diff -c -r1.10 syslogger.h
*** src/include/postmaster/syslogger.h	25 Jul 2007 12:22:53 -0000	1.10
--- src/include/postmaster/syslogger.h	8 Aug 2007 16:32:25 -0000
***************
*** 24,32 ****
   * also cope with non-protocol data coming down the pipe, though we cannot
   * guarantee long strings won't get split apart.
   *
!  * We use 't' or 'f' instead of a bool for is_last to make the protocol a tiny
!  * bit more robust against finding a false double nul byte prologue.  But we
!  * still might find it in the len and/or pid bytes unless we're careful.
   */
  
  #ifdef PIPE_BUF
--- 24,32 ----
   * also cope with non-protocol data coming down the pipe, though we cannot
   * guarantee long strings won't get split apart.
   *
!  * We use non-nul bytes in is_last to make the protocol a tiny bit 
!  * more robust against finding a false double nul byte prologue. But
!  * we still might find it in the len and/or pid bytes unless we're careful.
   */
  
  #ifdef PIPE_BUF
***************
*** 46,52 ****
  	char		nuls[2];		/* always \0\0 */
  	uint16		len;			/* size of this chunk (counts data only) */
  	int32		pid;			/* writer's pid */
! 	char		is_last;		/* last chunk of message? 't' or 'f' */
  	char		data[1];		/* data payload starts here */
  } PipeProtoHeader;
  
--- 46,54 ----
  	char		nuls[2];		/* always \0\0 */
  	uint16		len;			/* size of this chunk (counts data only) */
  	int32		pid;			/* writer's pid */
! 	char		is_last;		/* last chunk of message? 't' or 'f' 
! 	                             * ('T' or 'F' for CSV case)
! 								 */
  	char		data[1];		/* data payload starts here */
  } PipeProtoHeader;
  
***************
*** 79,85 ****
  
  extern int	SysLogger_Start(void);
  
! extern void write_syslogger_file(const char *buffer, int count);
  
  #ifdef EXEC_BACKEND
  extern void SysLoggerMain(int argc, char *argv[]);
--- 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[]);
Index: src/include/utils/elog.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/utils/elog.h,v
retrieving revision 1.87
diff -c -r1.87 elog.h
*** src/include/utils/elog.h	25 Jul 2007 12:22:54 -0000	1.87
--- src/include/utils/elog.h	8 Aug 2007 16:32:25 -0000
***************
*** 291,296 ****
--- 291,297 ----
  #define LOG_DESTINATION_STDERR	 1
  #define LOG_DESTINATION_SYSLOG	 2
  #define LOG_DESTINATION_EVENTLOG 4
+ #define LOG_DESTINATION_CSVLOG   8
  
  /* Other exported functions */
  extern void DebugFileOpen(void);
---------------------------(end of broadcast)---------------------------
TIP 3: Have you checked our extensive FAQ?

               http://www.postgresql.org/docs/faq

Reply via email to