Index: doc/src/sgml/config.sgml =================================================================== RCS file: /d3/pgsql/cvsrepo/pgsql/doc/src/sgml/config.sgml,v retrieving revision 1.124 diff -c -r1.124 config.sgml *** doc/src/sgml/config.sgml 17 May 2007 23:36:04 -0000 1.124 --- doc/src/sgml/config.sgml 28 May 2007 17:46:57 -0000 *************** *** 2229,2235 **** PostgreSQL supports several methods for logging server messages, including ! stderr and syslog. On Windows, eventlog is also supported. Set this parameter to a list of desired log destinations separated by --- 2229,2235 ---- PostgreSQL supports several methods for logging server messages, including ! stderr, csvlog and syslog. On Windows, eventlog is also supported. Set this parameter to a list of desired log destinations separated by *************** *** 2238,2243 **** --- 2238,2249 ---- This parameter can only be set in the postgresql.conf file or on the server command line. + If log_destination is set to csvlog, + the log is output as comma seperated values. The format is: + timestamp with milliseconds, username, database name, session id, host:port number, + process id, per process line number, command tag, session start time, transaction id, + error severity, SQL state code, statement/error message. + *************** *** 2267,2274 **** ! When redirect_stderr is enabled, this parameter ! determines the directory in which log files will be created. It can be specified as an absolute path, or relative to the cluster data directory. This parameter can only be set in the postgresql.conf --- 2273,2280 ---- ! When redirect_stderr is enabled or log_destination is set to csvlog, ! this parameter determines the directory in which log files will be created. It can be specified as an absolute path, or relative to the cluster data directory. This parameter can only be set in the postgresql.conf *************** *** 2284,2291 **** ! When redirect_stderr is enabled, this parameter ! sets the file names of the created log files. The value is treated as a strftime pattern, so %-escapes can be used to specify time-varying file names. --- 2290,2297 ---- ! When redirect_stderr is enabled or log_destination is set to csvlog, ! this parameter sets the file names of the created log files. The value is treated as a strftime pattern, so %-escapes can be used to specify time-varying file names. *************** *** 2298,2303 **** --- 2304,2314 ---- This parameter can only be set in the postgresql.conf file or on the server command line. + + If log_destination is set to csvlog, + '.csv' will be appended to the file name. In the case of the example above, the + file name will be server_log.1093827753.csv + *************** *** 2308,2315 **** ! When redirect_stderr is enabled, this parameter ! determines the maximum lifetime of an individual log file. After this many minutes have elapsed, a new log file will be created. Set to zero to disable time-based creation of new log files. --- 2319,2326 ---- ! When redirect_stderr is enabled or log_destination is set to csvlog, ! this parameter determines the maximum lifetime of an individual log file. After this many minutes have elapsed, a new log file will be created. Set to zero to disable time-based creation of new log files. *************** *** 2326,2333 **** ! When redirect_stderr is enabled, this parameter ! determines the maximum size of an individual log file. After this many kilobytes have been emitted into a log file, a new log file will be created. Set to zero to disable size-based creation of new log files. --- 2337,2344 ---- ! When redirect_stderr is enabled or log_destination is set to csvlog, ! this parameter determines the maximum size of an individual log file. After this many kilobytes have been emitted into a log file, a new log file will be created. Set to zero to disable size-based creation of new log files. *************** *** 2344,2351 **** ! When redirect_stderr is enabled, this parameter will cause ! PostgreSQL to truncate (overwrite), rather than append to, any existing log file of the same name. However, truncation will occur only when a new file is being opened due to time-based rotation, not during server startup or size-based --- 2355,2362 ---- ! When redirect_stderr is enabled or log_destination is set to csvlog, ! this parameter will cause PostgreSQL to truncate (overwrite), rather than append to, any existing log file of the same name. However, truncation will occur only when a new file is being opened due to time-based rotation, not during server startup or size-based *************** *** 2974,2979 **** --- 2985,3083 ---- + + Using the csvlog + + + Putting csvlog into the log_destination list gives an + easy way to import the log files into a database table. + Here is a sample table that csvlog output could be + copied into: + + + + CREATE TABLE pglog + ( + log_time timestamp, + username text, + database_name text, + sessionid text, + connection_from text, + process_id text, + process_line_num int, + command_tag text, + session_start_time timestamp, + transaction_id int, + error_severity text, + sql_state_code text, + statement text + ); + CREATE UNIQUE INDEX i_pglog_log_time_process ON pglog + (log_time,process_id,process_line_num); + + + + + In order to import into this table, use the COPY FROM command: + + + + COPY pglog FROM '/full/path/to/logfile.csv' WITH csv; + + + + There are a few things you will need to do in order for + csvlog importing to produced files that you can import + easily and automatically: + + + + + Use a consistant, predictable naming scheme for your log files + with log_filename. This lets you guess what + the file name will be and know when an individual log file is + complete and therefore ready to be imported. + + + + + + Set log_rotation_size = 0 to disable size-based + log rotation. That feature makes it more difficult to predict what + the log file name you need to import will be, and more importantly it + may split your log file in the middle of a line of output. Such a + partial line will cause an error in the COPY, and the entire import + will fail. + + + + + + Set log_truncate_on_rotate = on so that old log data isn't + mixed with the new in the same file. + + + + + + The example above includes a useful unique index on the log + file data, which will protect against accidentally importing + the same information twice. The COPY command commits all of + the data it imports at one time, and any single error will + cause the entire import to fail. Therefore, if you import + a partial log file, then later import the entire thing, + the unique index violation will cause the import to fail. + You need to wait until the log file is complete and closed + before trying to import the whole thing. Operating that way + will also protect you from accidentally trying to import + a partial line that hasn't been completely written yet, which + will also cause the COPY to fail. + + + + + + Index: src/backend/postmaster/postmaster.c =================================================================== RCS file: /d3/pgsql/cvsrepo/pgsql/src/backend/postmaster/postmaster.c,v retrieving revision 1.527 diff -c -r1.527 postmaster.c *** src/backend/postmaster/postmaster.c 22 Mar 2007 19:53:30 -0000 1.527 --- src/backend/postmaster/postmaster.c 27 May 2007 16:45:40 -0000 *************** *** 336,343 **** --- 336,345 ---- HANDLE PostmasterHandle; HANDLE initial_signal_pipe; HANDLE syslogPipe[2]; + HANDLE csvlogPipe[2]; #else int syslogPipe[2]; + int csvlogPipe[2]; #endif char my_exec_path[MAXPGPATH]; char pkglib_path[MAXPGPATH]; *************** *** 1225,1231 **** } /* If we have lost the system logger, try to start a new one */ ! if (SysLoggerPID == 0 && Redirect_stderr) SysLoggerPID = SysLogger_Start(); /* --- 1227,1234 ---- } /* If we have lost the system logger, try to start a new one */ ! if ( (SysLoggerPID == 0 && Redirect_stderr) || ! (SysLoggerPID == 0 && (Log_destination & LOG_DESTINATION_CSVLOG)) ) SysLoggerPID = SysLogger_Start(); /* *************** *** 1775,1784 **** --- 1778,1795 ---- if (syslogPipe[0] >= 0) close(syslogPipe[0]); syslogPipe[0] = -1; + + if (csvlogPipe[0] >= 0) + close(csvlogPipe[0]); + csvlogPipe[0] = -1; #else if (syslogPipe[0]) CloseHandle(syslogPipe[0]); syslogPipe[0] = 0; + + if (csvlogPipe[0]) + CloseHandle(csvlogPipe[0]); + csvlogPipe[0] = 0; #endif } } *************** *** 3957,3962 **** --- 3968,3974 ---- #endif memcpy(¶m->syslogPipe, &syslogPipe, sizeof(syslogPipe)); + memcpy(¶m->csvlogPipe, &csvlogPipe, sizeof(csvlogPipe)); strlcpy(param->my_exec_path, my_exec_path, MAXPGPATH); *************** *** 4158,4163 **** --- 4170,4176 ---- #endif memcpy(&syslogPipe, ¶m->syslogPipe, sizeof(syslogPipe)); + memcpy(&csvlogPipe, ¶m->csvlogPipe, sizeof(csvlogPipe)); strlcpy(my_exec_path, param->my_exec_path, MAXPGPATH); Index: src/backend/postmaster/syslogger.c =================================================================== RCS file: /d3/pgsql/cvsrepo/pgsql/src/backend/postmaster/syslogger.c,v retrieving revision 1.30 diff -c -r1.30 syslogger.c *** src/backend/postmaster/syslogger.c 5 Jan 2007 22:19:36 -0000 1.30 --- src/backend/postmaster/syslogger.c 27 May 2007 16:45:40 -0000 *************** *** 81,99 **** static bool pipe_eof_seen = false; static FILE *syslogFile = NULL; static char *last_file_name = NULL; /* These must be exported for EXEC_BACKEND case ... annoying */ #ifndef WIN32 int syslogPipe[2] = {-1, -1}; #else HANDLE syslogPipe[2] = {0, 0}; #endif #ifdef WIN32 ! static HANDLE threadHandle = 0; static CRITICAL_SECTION sysfileSection; #endif /* --- 81,105 ---- static bool pipe_eof_seen = false; static FILE *syslogFile = NULL; + static FILE *csvlogFile = NULL; static char *last_file_name = NULL; /* These must be exported for EXEC_BACKEND case ... annoying */ #ifndef WIN32 int syslogPipe[2] = {-1, -1}; + int csvlogPipe[2] = {-1, -1}; #else HANDLE syslogPipe[2] = {0, 0}; + HANDLE csvlogPipe[2] = {0, 0}; #endif #ifdef WIN32 ! static HANDLE syslog_threadHandle = 0; static CRITICAL_SECTION sysfileSection; + + static HANDLE csvlog_threadHandle = 0; + static CRITICAL_SECTION csvfileSection; #endif /* *************** *** 108,120 **** static pid_t syslogger_forkexec(void); static void syslogger_parseArgs(int argc, char *argv[]); #endif ! static void write_syslogger_file_binary(const char *buffer, int count); #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); --- 114,129 ---- static pid_t syslogger_forkexec(void); static void syslogger_parseArgs(int argc, char *argv[]); #endif ! static void read_log_pipe(int log_type); ! static void write_syslogger_file_binary(const char *buffer, int count, int log_type); #ifdef WIN32 static unsigned int __stdcall pipeThread(void *arg); #endif static void logfile_rotate(bool time_based_rotation); ! static void logfile_rotate_worker(bool time_based_rotation, int log_type, ! char* filename, FILE **dest_file); ! static char *logfile_getname(pg_time_t timestamp, int log_type); static void set_next_rotation_time(void); static void sigHupHandler(SIGNAL_ARGS); static void sigUsr1Handler(SIGNAL_ARGS); *************** *** 150,156 **** * assumes that all interesting messages generated in the syslogger will * come through elog.c and will be sent to write_syslogger_file. */ ! if (redirection_done) { int fd = open(NULL_DEV, O_WRONLY, 0); --- 159,165 ---- * assumes that all interesting messages generated in the syslogger will * come through elog.c and will be sent to write_syslogger_file. */ ! if ((Redirect_stderr) && (redirection_done) ) { int fd = open(NULL_DEV, O_WRONLY, 0); *************** *** 176,185 **** --- 185,202 ---- if (syslogPipe[1] >= 0) close(syslogPipe[1]); syslogPipe[1] = -1; + + if (csvlogPipe[1] >= 0) + close(csvlogPipe[1]); + csvlogPipe[1] = -1; #else if (syslogPipe[1]) CloseHandle(syslogPipe[1]); syslogPipe[1] = 0; + + if (csvlogPipe[1]) + CloseHandle(csvlogPipe[1]); + csvlogPipe[1] = 0; #endif /* *************** *** 222,234 **** PG_SETMASK(&UnBlockSig); #ifdef WIN32 ! /* Fire up separate data transfer thread */ ! InitializeCriticalSection(&sysfileSection); { unsigned int tid; ! threadHandle = (HANDLE) _beginthreadex(0, 0, pipeThread, 0, 0, &tid); } #endif /* WIN32 */ --- 239,264 ---- PG_SETMASK(&UnBlockSig); #ifdef WIN32 ! /* Fire up separate data transfer thread for syslog*/ ! if (Redirect_stderr) ! { ! unsigned int tid; ! int logtype = STDERR_LOGFILE; + InitializeCriticalSection(&sysfileSection); + syslog_threadHandle = (HANDLE) _beginthreadex(0, 0, pipeThread, + &logtype, 0, &tid); + } + + /* Fire up separate data transfer thread for csvlog*/ + if (Log_destination & LOG_DESTINATION_CSVLOG) { unsigned int tid; + int logtype = CSV_LOGFILE; ! InitializeCriticalSection(&csvfileSection); ! csvlog_threadHandle = (HANDLE) _beginthreadex(0, 0, pipeThread, ! &logtype, 0, &tid); } #endif /* WIN32 */ *************** *** 244,257 **** { bool time_based_rotation = false; - #ifndef WIN32 - char logbuffer[1024]; - int bytesRead; - int rc; - fd_set rfds; - struct timeval timeout; - #endif - if (got_SIGHUP) { got_SIGHUP = false; --- 274,279 ---- *************** *** 298,366 **** if (!rotation_requested && Log_RotationSize > 0) { /* 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 ! ! /* ! * Wait for some data, timing out after 1 second ! */ ! FD_ZERO(&rfds); ! FD_SET(syslogPipe[0], &rfds); ! timeout.tv_sec = 1; ! timeout.tv_usec = 0; ! ! rc = select(syslogPipe[0] + 1, &rfds, NULL, NULL, &timeout); ! ! if (rc < 0) ! { ! if (errno != EINTR) ! ereport(LOG, ! (errcode_for_socket_access(), ! errmsg("select() failed in logger process: %m"))); ! } ! else if (rc > 0 && FD_ISSET(syslogPipe[0], &rfds)) ! { ! bytesRead = piperead(syslogPipe[0], ! logbuffer, sizeof(logbuffer)); ! ! if (bytesRead < 0) ! { ! if (errno != EINTR) ! ereport(LOG, ! (errcode_for_socket_access(), ! errmsg("could not read from logger pipe: %m"))); ! } ! else if (bytesRead > 0) ! { ! write_syslogger_file_binary(logbuffer, bytesRead); ! continue; ! } ! else ! { ! /* ! * Zero bytes read when select() is saying read-ready means ! * EOF on the pipe: that is, there are no longer any processes ! * with the pipe write end open. Therefore, the postmaster ! * and all backends are shut down, and we are done. ! */ ! pipe_eof_seen = true; ! } ! } ! #else /* WIN32 */ ! ! /* ! * On Windows we leave it to a separate thread to transfer data and ! * detect pipe EOF. The main thread just wakes up once a second to ! * check for SIGHUP and rotation conditions. ! */ ! pg_usleep(1000000L); ! #endif /* WIN32 */ if (pipe_eof_seen) { --- 320,346 ---- if (!rotation_requested && Log_RotationSize > 0) { /* Do a rotation if file is too big */ ! if ( (syslogFile) && (ftell(syslogFile) >= Log_RotationSize * 1024L) ) ! rotation_requested = true; ! ! /* As a first version, we will rotate both the log ! * files if either of them is big. The difference ! * between these two files are not going to be that ! * great anyway. */ ! if ( (csvlogFile) && (ftell(csvlogFile) >= Log_RotationSize * 1024L) ) rotation_requested = true; } if (rotation_requested) logfile_rotate(time_based_rotation); ! /* Read the syslog pipe if redirect_stderr is on. */ ! if (Redirect_stderr) ! read_log_pipe(STDERR_LOGFILE); ! ! /* Read the csvlog pipe if csvlog is on. */ ! if (Log_destination & LOG_DESTINATION_CSVLOG) ! read_log_pipe(CSV_LOGFILE); if (pipe_eof_seen) { *************** *** 385,394 **** int SysLogger_Start(void) { pid_t sysloggerPid; - char *filename; ! if (!Redirect_stderr) return 0; /* --- 365,376 ---- int SysLogger_Start(void) { + char *csv_filename; + char *stderr_filename; + pid_t sysloggerPid; ! if ( (!Redirect_stderr) && (!(Log_destination & LOG_DESTINATION_CSVLOG)) ) return 0; /* *************** *** 403,431 **** * pipe open, so we can pass it down to the reincarnated syslogger. This * is a bit klugy but we have little choice. */ ! #ifndef WIN32 ! if (syslogPipe[0] < 0) { ! if (pgpipe(syslogPipe) < 0) ! ereport(FATAL, ! (errcode_for_socket_access(), ! (errmsg("could not create pipe for syslog: %m")))); ! } #else ! if (!syslogPipe[0]) ! { ! SECURITY_ATTRIBUTES sa; ! ! memset(&sa, 0, sizeof(SECURITY_ATTRIBUTES)); ! sa.nLength = sizeof(SECURITY_ATTRIBUTES); ! sa.bInheritHandle = TRUE; ! if (!CreatePipe(&syslogPipe[0], &syslogPipe[1], &sa, 32768)) ! ereport(FATAL, ! (errcode_for_file_access(), ! (errmsg("could not create pipe for syslog: %m")))); } #endif /* * Create log directory if not present; ignore errors --- 385,446 ---- * pipe open, so we can pass it down to the reincarnated syslogger. This * is a bit klugy but we have little choice. */ ! ! /* Create the syslog pipe only if we need to redirect stderr */ ! if (Redirect_stderr) { ! #ifndef WIN32 ! if (syslogPipe[0] < 0) ! { ! if (pgpipe(syslogPipe) < 0) ! ereport(FATAL, ! (errcode_for_socket_access(), ! (errmsg("could not create pipe for syslog: %m")))); ! } #else ! if (!syslogPipe[0]) ! { ! SECURITY_ATTRIBUTES sa; ! memset(&sa, 0, sizeof(SECURITY_ATTRIBUTES)); ! sa.nLength = sizeof(SECURITY_ATTRIBUTES); ! sa.bInheritHandle = TRUE; ! ! if (!CreatePipe(&syslogPipe[0], &syslogPipe[1], &sa, LOG_BUFFER_SIZE)) ! ereport(FATAL, ! (errcode_for_file_access(), ! (errmsg("could not create pipe for syslog: %m")))); ! } ! #endif } + + /* Create the csv log pipe if we need csv type log output */ + if (Log_destination & LOG_DESTINATION_CSVLOG) + { + #ifndef WIN32 + if (csvlogPipe[0] < 0) + { + if (pgpipe(csvlogPipe) < 0) + ereport(FATAL, + (errcode_for_socket_access(), + (errmsg("could not create pipe for csvlog: %m")))); + } + #else + if (!csvlogPipe[0]) + { + SECURITY_ATTRIBUTES sa; + + memset(&sa, 0, sizeof(SECURITY_ATTRIBUTES)); + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = TRUE; + + if (!CreatePipe(&csvlogPipe[0], &csvlogPipe[1], &sa, LOG_BUFFER_SIZE)) + ereport(FATAL, + (errcode_for_file_access(), + (errmsg("could not create pipe for csvlog: %m")))); + } #endif + } /* * Create log directory if not present; ignore errors *************** *** 436,454 **** * 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"); ! if (!syslogFile) ! ereport(FATAL, (errcode_for_file_access(), (errmsg("could not create log file \"%s\": %m", ! filename)))); - setvbuf(syslogFile, NULL, LBF_MODE, 0); - pfree(filename); #ifdef EXEC_BACKEND switch ((sysloggerPid = syslogger_forkexec())) --- 451,487 ---- * The initial logfile is created right in the postmaster, to verify that * the Log_directory is writable. */ ! if (Redirect_stderr) ! { ! stderr_filename = logfile_getname(time(NULL), STDERR_LOGFILE); ! syslogFile = fopen(stderr_filename, "a"); ! if (!syslogFile) ! ereport(FATAL, ! (errcode_for_file_access(), ! (errmsg("could not create log file \"%s\": %m", ! stderr_filename)))); ! ! setvbuf(syslogFile, NULL, LBF_MODE, 0); ! pfree(stderr_filename); ! } ! if (Log_destination & LOG_DESTINATION_CSVLOG) ! { ! csv_filename = logfile_getname(time(NULL), CSV_LOGFILE); ! ! csvlogFile = fopen(csv_filename, "a"); ! if (!csvlogFile) ! ereport(FATAL, (errcode_for_file_access(), (errmsg("could not create log file \"%s\": %m", ! csv_filename)))); ! ! setvbuf(csvlogFile, NULL, LBF_MODE, 0); ! pfree(csv_filename); ! } #ifdef EXEC_BACKEND switch ((sysloggerPid = syslogger_forkexec())) *************** *** 482,488 **** /* success, in postmaster */ /* now we redirect stderr, if not done already */ ! if (!redirection_done) { #ifndef WIN32 fflush(stdout); --- 515,521 ---- /* success, in postmaster */ /* now we redirect stderr, if not done already */ ! if (Redirect_stderr && !redirection_done) { #ifndef WIN32 fflush(stdout); *************** *** 516,524 **** redirection_done = true; } ! /* postmaster will never write the file; close it */ ! fclose(syslogFile); syslogFile = NULL; return (int) sysloggerPid; } --- 549,563 ---- redirection_done = true; } ! /* postmaster will never write the files; close it */ ! if (syslogFile) ! fclose(syslogFile); syslogFile = NULL; + + if (csvlogFile) + fclose(csvlogFile); + csvlogFile = NULL; + return (int) sysloggerPid; } *************** *** 537,547 **** static pid_t syslogger_forkexec(void) { ! char *av[10]; ! int ac = 0, ! bufc = 0, ! i; ! char numbuf[2][32]; av[ac++] = "postgres"; av[ac++] = "--forklog"; --- 576,586 ---- static pid_t syslogger_forkexec(void) { ! char *av[11]; ! int ac = 0, ! bufc = 0, ! i; ! char numbuf[3][32]; av[ac++] = "postgres"; av[ac++] = "--forklog"; *************** *** 554,559 **** --- 593,603 ---- else strcpy(numbuf[bufc++], "-1"); snprintf(numbuf[bufc++], 32, "%d", (int) redirection_done); + + if (csvlogFile != NULL) + snprintf(numbuf[bufc++], 32, "%d", fileno(csvlogFile)); + else + strcpy(numbuf[bufc++], "-1"); #else /* WIN32 */ if (syslogFile != NULL) snprintf(numbuf[bufc++], 32, "%ld", *************** *** 561,566 **** --- 605,616 ---- else strcpy(numbuf[bufc++], "0"); snprintf(numbuf[bufc++], 32, "%d", (int) redirection_done); + + if (csvlogFile != NULL) + snprintf(numbuf[bufc++], 32, "%ld", + _get_osfhandle(_fileno(csvlogFile))); + else + strcpy(numbuf[bufc++], "0"); #endif /* WIN32 */ /* Add to the arg list */ *************** *** 584,590 **** { int fd; ! Assert(argc == 5); argv += 3; #ifndef WIN32 --- 634,640 ---- { int fd; ! Assert(argc == 6); argv += 3; #ifndef WIN32 *************** *** 595,600 **** --- 645,657 ---- setvbuf(syslogFile, NULL, LBF_MODE, 0); } redirection_done = (bool) atoi(*argv++); + + fd = atoi(*argv++); + if (fd != -1) + { + csvlogFile = fdopen(fd, "a"); + setvbuf(csvlogFile, NULL, LBF_MODE, 0); + } #else /* WIN32 */ fd = atoi(*argv++); if (fd != 0) *************** *** 607,612 **** --- 664,680 ---- } } redirection_done = (bool) atoi(*argv++); + + fd = atoi(*argv++); + if (fd != 0) + { + fd = _open_osfhandle(fd, _O_APPEND); + if (fd > 0) + { + csvlogFile = fdopen(fd, "a"); + setvbuf(csvlogFile, NULL, LBF_MODE, 0); + } + } #endif /* WIN32 */ } #endif /* EXEC_BACKEND */ *************** *** 625,631 **** * even though its stderr does not point at the syslog pipe. */ void ! write_syslogger_file(const char *buffer, int count) { #ifdef WIN32 --- 693,699 ---- * even though its stderr does not point at the syslog pipe. */ void ! write_syslogger_file(const char *buffer, int count, int log_type) { #ifdef WIN32 *************** *** 649,663 **** n++; if (n >= sizeof(convbuf) - 1) { ! write_syslogger_file_binary(convbuf, n); p = convbuf; n = 0; } } if (n > 0) ! write_syslogger_file_binary(convbuf, n); #else /* !WIN32 */ ! write_syslogger_file_binary(buffer, count); #endif } --- 717,731 ---- n++; if (n >= sizeof(convbuf) - 1) { ! write_syslogger_file_binary(convbuf, n, log_type); p = convbuf; n = 0; } } if (n > 0) ! write_syslogger_file_binary(convbuf, n, log_type); #else /* !WIN32 */ ! write_syslogger_file_binary(buffer, count, log_type); #endif } *************** *** 668,683 **** * so we must send it to the file without further translation. */ static void ! write_syslogger_file_binary(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 /* can't use ereport here because of possible recursion */ --- 736,774 ---- * so we must send it to the file without further translation. */ static void ! write_syslogger_file_binary(const char *buffer, int count, int log_type) { ! int rc; ! FILE **fh = NULL; ! ! /* Select the file to write to based on the log_type. */ ! switch (log_type) ! { ! case STDERR_LOGFILE: ! fh = (FILE **) &syslogFile; ! break; ! case CSV_LOGFILE: ! fh = (FILE **) &csvlogFile; ! break; ! default: ! return; ! } #ifndef WIN32 ! rc = fwrite(buffer, 1, count, *fh); #else ! if (log_type == STDERR_LOGFILE) ! { ! EnterCriticalSection(&sysfileSection); ! rc = fwrite(buffer, 1, count, *fh); ! LeaveCriticalSection(&sysfileSection); ! } ! else if (log_type == CSV_LOGFILE) ! { ! EnterCriticalSection(&csvfileSection); ! rc = fwrite(buffer, 1, count, *fh); ! LeaveCriticalSection(&csvfileSection); ! } #endif /* can't use ereport here because of possible recursion */ *************** *** 700,722 **** DWORD bytesRead; char logbuffer[1024]; ! for (;;) ! { ! if (!ReadFile(syslogPipe[0], logbuffer, sizeof(logbuffer), ! &bytesRead, 0)) ! { ! DWORD error = GetLastError(); ! if (error == ERROR_HANDLE_EOF || ! error == ERROR_BROKEN_PIPE) ! break; ! _dosmaperr(error); ! ereport(LOG, ! (errcode_for_file_access(), ! errmsg("could not read from logger pipe: %m"))); ! } ! else if (bytesRead > 0) ! write_syslogger_file_binary(logbuffer, bytesRead); } /* We exit the above loop only upon detecting pipe EOF */ --- 791,845 ---- DWORD bytesRead; char logbuffer[1024]; ! int logtype = 0; ! logtype = *(int *)arg; ! ! switch (logtype) ! { ! case STDERR_LOGFILE: ! for(;;) ! { ! if (!ReadFile(syslogPipe[0], logbuffer, sizeof(logbuffer), ! &bytesRead, 0)) ! { ! DWORD error = GetLastError(); ! ! if (error == ERROR_HANDLE_EOF || ! error == ERROR_BROKEN_PIPE) ! break; ! _dosmaperr(error); ! ereport(LOG, ! (errcode_for_file_access(), ! errmsg("could not read from logger pipe: %m"))); ! } ! else if (bytesRead > 0) ! write_syslogger_file_binary(logbuffer, bytesRead, ! STDERR_LOGFILE); ! } ! break; ! case CSV_LOGFILE: ! for(;;) ! { ! if (!ReadFile(csvlogPipe[0], logbuffer, sizeof(logbuffer), ! &bytesRead, 0)) ! { ! DWORD error = GetLastError(); ! ! if (error == ERROR_HANDLE_EOF || ! error == ERROR_BROKEN_PIPE) ! break; ! _dosmaperr(error); ! ereport(LOG, ! (errcode_for_file_access(), ! errmsg("could not read from logger pipe: %m"))); ! } ! else if (bytesRead > 0) ! write_syslogger_file_binary(logbuffer, bytesRead, CSV_LOGFILE); ! } ! break; ! default: ! break; } /* We exit the above loop only upon detecting pipe EOF */ *************** *** 727,751 **** #endif /* WIN32 */ /* ! * perform logfile rotation */ static void logfile_rotate(bool time_based_rotation) { ! char *filename; ! FILE *fh; rotation_requested = false; /* ! * When doing a time-based rotation, invent the new logfile name based on ! * the planned rotation time, not current time, to avoid "slippage" in the ! * 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) --- 850,980 ---- #endif /* WIN32 */ /* ! * Reads the requested pipe. ! * ! * This method is common for both syslog and csvlog pipes. ! * Based on the log_type this method will know which pipe to read and passes ! * the data to the appropriate file type. ! */ ! static void ! read_log_pipe(int log_type) ! { ! ! #ifndef WIN32 ! char logbuffer[1024]; ! int bytesRead = 0; ! int rc; ! fd_set rfds; ! bool dataInPipe = false; ! struct timeval timeout; ! ! /* ! * Set timeout for a second ! */ ! timeout.tv_sec = 1; ! timeout.tv_usec = 0; ! ! switch (log_type) ! { ! case STDERR_LOGFILE: ! FD_ZERO(&rfds); ! FD_SET(syslogPipe[0], &rfds); ! rc = select(syslogPipe[0] + 1, &rfds, NULL, NULL, &timeout); ! if (rc < 0) ! { ! if (errno != EINTR) ! ereport(LOG, ! (errcode_for_socket_access(), ! errmsg("select() failed in logger process: %m"))); ! } ! else if (rc > 0 && FD_ISSET(syslogPipe[0], &rfds)) ! { ! bytesRead = piperead(syslogPipe[0], logbuffer, sizeof(logbuffer)); ! dataInPipe = true; ! } ! break; ! case CSV_LOGFILE: ! FD_ZERO(&rfds); ! FD_SET(csvlogPipe[0], &rfds); ! rc = select(csvlogPipe[0] + 1, &rfds, NULL, NULL, &timeout); ! if (rc < 0) ! { ! if (errno != EINTR) ! ereport(LOG, ! (errcode_for_socket_access(), ! errmsg("select() failed in logger process: %m"))); ! } ! else if (rc > 0 && FD_ISSET(csvlogPipe[0], &rfds)) ! { ! bytesRead = piperead(csvlogPipe[0], logbuffer, sizeof(logbuffer)); ! dataInPipe = true; ! } ! break; ! default: ! return; ! } ! ! if (dataInPipe) ! { ! if (bytesRead < 0) ! { ! if (errno != EINTR) ! ereport(LOG, ! (errcode_for_socket_access(), ! errmsg("could not read from logger pipe: %m"))); ! } ! else if (bytesRead > 0) ! { ! write_syslogger_file_binary(logbuffer, bytesRead, log_type); ! } ! else ! { ! /* ! * Zero bytes read when select() is saying read-ready means ! * EOF on the pipe: that is, there are no longer any processes ! * with the pipe write end open. Therefore, the postmaster ! * and all backends are shut down, and we are done. ! */ ! pipe_eof_seen = true; ! } ! } ! #else /* WIN32 */ ! /* ! * On Windows we leave it to a separate thread to transfer data and ! * detect pipe EOF. The main thread just wakes up once a second to ! * check for SIGHUP and rotation conditions. ! */ ! pg_usleep(1000000L); ! #endif /* WIN32 */ ! } ! ! /* ! * Log file rotation controller. Decides the filename and which file needs ! * to be rotated. The worker method below this does the actual rotation. */ static void logfile_rotate(bool time_based_rotation) { ! char *filename; ! char *csv_filename; ! ! pg_time_t timestamp; ! bool overwrite_logfile; rotation_requested = false; + timestamp = time(NULL); + overwrite_logfile = false; + /* ! * When doing a time-based rotation, invent the new logfile name based ! * on the planned rotation time, not current time, to avoid "slippage" ! * in the file name when we don't do the rotation immediately. */ if (time_based_rotation) ! timestamp = next_rotation_time; ! ! filename = logfile_getname(timestamp, STDERR_LOGFILE); /* * Decide whether to overwrite or append. We can overwrite if (a) *************** *** 761,766 **** --- 990,1025 ---- */ if (Log_truncate_on_rotation && time_based_rotation && last_file_name != NULL && strcmp(filename, last_file_name) != 0) + overwrite_logfile = true; + + if (Redirect_stderr) + logfile_rotate_worker(overwrite_logfile, STDERR_LOGFILE, filename, + (FILE **) &syslogFile); + + if (Log_destination & LOG_DESTINATION_CSVLOG) + { + csv_filename = logfile_getname(timestamp, CSV_LOGFILE); + logfile_rotate_worker(overwrite_logfile, CSV_LOGFILE, csv_filename, + (FILE **) &csvlogFile); + } + + 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; + } + + /* + * logfile rotation worker - Does the actual file rotation + */ + static void + logfile_rotate_worker(bool overwrite_logfile, int log_type, char* filename, FILE **dest_file) + { + FILE *fh; + + if (overwrite_logfile) fh = fopen(filename, "w"); else fh = fopen(filename, "a"); *************** *** 795,824 **** /* 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; } /* ! * construct logfile name using timestamp information * * Result is palloc'd. */ static char * ! logfile_getname(pg_time_t timestamp) { char *filename; int len; --- 1054,1083 ---- /* On Windows, need to interlock against data-transfer thread */ #ifdef WIN32 ! if (log_type == STDERR_LOGFILE) ! EnterCriticalSection(&sysfileSection); ! else ! EnterCriticalSection(&csvfileSection); #endif ! fclose(*dest_file); ! *dest_file = fh; #ifdef WIN32 ! if (log_type == STDERR_LOGFILE) ! LeaveCriticalSection(&sysfileSection); ! else ! LeaveCriticalSection(&csvfileSection); #endif } /* ! * construct logfile name using timestamp information. Adds a '.csv' as ! * extension to csvlog files if enabled. * * Result is palloc'd. */ static char * ! logfile_getname(pg_time_t timestamp, int log_type) { char *filename; int len; *************** *** 843,848 **** --- 1102,1114 ---- Log_filename, (unsigned long) timestamp); } + if (log_type == CSV_LOGFILE) + { + len = strlen(filename); + /* Append .csv to the new filename */ + snprintf(filename + len, MAXPGPATH - len, ".csv"); + } + return filename; } Index: src/backend/utils/error/elog.c =================================================================== RCS file: /d3/pgsql/cvsrepo/pgsql/src/backend/utils/error/elog.c,v retrieving revision 1.185 diff -c -r1.185 elog.c *** src/backend/utils/error/elog.c 4 May 2007 02:01:02 -0000 1.185 --- src/backend/utils/error/elog.c 27 May 2007 17:41:06 -0000 *************** *** 78,83 **** --- 78,85 ---- extern pid_t SysLoggerPID; + char timestamp[128]; + /* GUC parameters */ PGErrorVerbosity Log_error_verbosity = PGERROR_VERBOSE; char *Log_line_prefix = NULL; /* format for extra log line info */ *************** *** 125,130 **** --- 127,136 ---- 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_csvlog(ErrorData *edata); + static void get_error_message(StringInfo buf, ErrorData *edata); + static void get_timestamp(StringInfo buf); + static size_t escape_string_literal(char *to, const char *from); /* * errstart --- begin an error-reporting cycle *************** *** 1493,1533 **** appendStringInfo(buf, "%ld", log_line_number); break; case 'm': ! { ! /* ! * Note: for %m, %t, and %s we deliberately use the C ! * library's strftime/localtime, and not the equivalent ! * functions from src/timezone. This ensures that all ! * backends will report log entries in the same timezone, ! * namely whatever C-library setting they inherit from the ! * postmaster. If we used src/timezone then local ! * settings of the TimeZone GUC variable would confuse the ! * log. ! */ ! time_t stamp_time; ! char strfbuf[128], ! msbuf[8]; ! struct timeval tv; ! ! gettimeofday(&tv, NULL); ! stamp_time = tv.tv_sec; ! ! strftime(strfbuf, sizeof(strfbuf), ! /* leave room for milliseconds... */ ! /* Win32 timezone names are too long so don't print them */ ! #ifndef WIN32 ! "%Y-%m-%d %H:%M:%S %Z", ! #else ! "%Y-%m-%d %H:%M:%S ", ! #endif ! localtime(&stamp_time)); ! ! /* 'paste' milliseconds into place... */ ! sprintf(msbuf, ".%03d", (int) (tv.tv_usec / 1000)); ! strncpy(strfbuf + 19, msbuf, 4); ! ! appendStringInfoString(buf, strfbuf); ! } break; case 't': { --- 1499,1505 ---- appendStringInfo(buf, "%ld", log_line_number); break; case 'm': ! get_timestamp(buf); break; case 't': { *************** *** 1639,1644 **** --- 1611,1617 ---- { StringInfoData buf; + memset(timestamp, '\0', sizeof(timestamp)); initStringInfo(&buf); log_line_prefix(&buf); *************** *** 1647,1663 **** if (Log_error_verbosity >= PGERROR_VERBOSE) appendStringInfo(&buf, "%s: ", unpack_sql_state(edata->sqlerrcode)); ! if (edata->message) ! append_with_tabs(&buf, edata->message); ! else ! append_with_tabs(&buf, _("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); appendStringInfoChar(&buf, '\n'); --- 1620,1627 ---- if (Log_error_verbosity >= PGERROR_VERBOSE) appendStringInfo(&buf, "%s: ", unpack_sql_state(edata->sqlerrcode)); ! /* Get the error message and cursor position if any */ ! get_error_message(&buf, edata); appendStringInfoChar(&buf, '\n'); *************** *** 1793,1799 **** /* 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); } --- 1757,1767 ---- /* If in the syslogger process, try to write messages direct to file */ if (am_syslogger) ! write_syslogger_file(buf.data, buf.len, STDERR_LOGFILE); ! ! /* Output log in csv format, if enabled */ ! if(Log_destination & LOG_DESTINATION_CSVLOG) ! write_csvlog(edata); pfree(buf.data); } *************** *** 2170,2172 **** --- 2138,2476 ---- return false; } + + + /* + * 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) + { + char *csv_log_stmt = NULL; + 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; + } + 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 (timestamp[0] == '\0') + get_timestamp(&buf); + else + appendStringInfoString(&buf, timestamp); + + appendStringInfoString(&buf, ","); + + /* username */ + if (MyProcPort) + { + const char *username = MyProcPort->user_name; + if (username == NULL || *username == '\0') + username = _("[unknown]"); + + csv_log_stmt = palloc(strlen(username) * 2 + 1); + escape_string_literal(csv_log_stmt, username); + appendStringInfo(&buf, "\"%s\",", csv_log_stmt); + pfree(csv_log_stmt); + } + else + appendStringInfoString(&buf, ","); + + /* databasename */ + if (MyProcPort) + { + const char *dbname = MyProcPort->database_name; + + if (dbname == NULL || *dbname == '\0') + dbname = _("[unknown]"); + + csv_log_stmt = palloc(strlen(dbname) * 2 + 1); + escape_string_literal(csv_log_stmt, dbname); + appendStringInfo(&buf, "\"%s\",", csv_log_stmt); + pfree(csv_log_stmt); + } + else + appendStringInfoString(&buf, ","); + + /* session id */ + if (MyProcPort) + { + appendStringInfo(&buf, "%lx.%x,", + (long) (MyProcPort->session_start), MyProcPid); + } + else + appendStringInfoString(&buf, ","); + + /* Remote host and port */ + if (MyProcPort) + { + if (MyProcPort->remote_host) + { + appendStringInfo(&buf, "%s", MyProcPort->remote_host); + if (MyProcPort->remote_port && + MyProcPort->remote_port[0] != '\0') + appendStringInfo(&buf, ":%s,", + MyProcPort->remote_port); + else + appendStringInfoString(&buf, ","); + } + else + appendStringInfoString(&buf, ","); + } + else + appendStringInfoString(&buf, ","); + + /* Process id and line number */ + if (MyProcPid != 0) + appendStringInfo(&buf, "%d,%ld,", MyProcPid, log_line_number); + else + appendStringInfo(&buf, ",%ld,", log_line_number); + + /* Command tag */ + if (MyProcPort) + { + const char *psdisp; + int displen; + + psdisp = get_ps_display(&displen); + appendStringInfo(&buf, "%.*s,", displen, psdisp); + } + else + appendStringInfoString(&buf, ","); + + /* session start timestamp */ + if (MyProcPort) + { + char strfbuf[128]; + + strftime(strfbuf, sizeof(strfbuf), + /* Win32 timezone names are too long so don't print them */ + #ifndef WIN32 + "%Y-%m-%d %H:%M:%S %Z", + #else + "%Y-%m-%d %H:%M:%S", + #endif + localtime(&MyProcPort->session_start)); + appendStringInfoString(&buf, strfbuf); + appendStringInfoString(&buf, ","); + } + else + appendStringInfoString(&buf, ","); + + /* Transaction id */ + if (MyProcPort) + { + if (IsTransactionState()) + appendStringInfo(&buf, "%u,", GetTopTransactionId()); + else + appendStringInfo(&buf, "%u,", InvalidTransactionId); + } + else + appendStringInfoString(&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)); + else + appendStringInfoString(&buf, ","); + + /* Error message and cursor position if any */ + get_error_message(&msgbuf, edata); + csv_log_stmt = palloc(strlen(msgbuf.data) * 2 + 1); + escape_string_literal(csv_log_stmt, msgbuf.data); + + appendStringInfo(&buf, "\"%s\"", csv_log_stmt); + appendStringInfoString(&buf, "\n"); + + /* If in the syslogger process, try to write messages direct to file */ + if (am_syslogger) + write_syslogger_file(buf.data, buf.len, CSV_LOGFILE); + else + print_csvlog(buf); + + pfree(csv_log_stmt); + 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) + { + StringInfoData msgbuf; + + initStringInfo(&msgbuf); + + if (edata->message) + append_with_tabs(&msgbuf, edata->message); + else + append_with_tabs(&msgbuf, _("missing error text")); + + if (edata->cursorpos > 0) + appendStringInfo(&msgbuf, _(" at character %d"), + edata->cursorpos); + else if (edata->internalpos > 0) + appendStringInfo(&msgbuf, _(" at character %d"), + edata->internalpos); + appendStringInfo(buf, "%s", pstrdup(msgbuf.data)); + } + + /* + * Calculates the current timestamp. Appends the calculated timestamp + * to the buffer passed in. + */ + static void + get_timestamp(StringInfo buf) + { + /* + * Note: for %m, %t, and %s we deliberately use the C + * library's strftime/localtime, and not the equivalent + * functions from src/timezone. This ensures that all + * backends will report log entries in the same timezone, + * namely whatever C-library setting they inherit from the + * postmaster. If we used src/timezone then local + * settings of the TimeZone GUC variable would confuse the + * log. + */ + time_t stamp_time; + char msbuf[8]; + struct timeval tv; + + gettimeofday(&tv, NULL); + stamp_time = tv.tv_sec; + + strftime(timestamp, sizeof(timestamp), + /* leave room for milliseconds... */ + /* Win32 timezone names are too long so don't print them. */ + #ifndef WIN32 + "%Y-%m-%d %H:%M:%S %Z", + #else + "%Y-%m-%d %H:%M:%S ", + #endif + localtime(&stamp_time)); + + /* 'paste' milliseconds into place... */ + sprintf(msbuf, ".%03d", (int) (tv.tv_usec / 1000)); + strncpy(timestamp + 19, msbuf, 4); + + appendStringInfoString(buf, timestamp); + } + + /* + * Escapes special characters in the string to conform + * with the csv type output. + * Replaces " with "", ' with '' and \ with \\. + */ + static size_t + escape_string_literal(char *to, const char *from) + { + const char *source = from; + char *target = to; + size_t remaining = 0; + int client_encoding = 0; + + if (from == NULL) + return remaining; + + remaining = strlen(from); + client_encoding = pg_get_client_encoding(); + + while (remaining > 0 && *source != '\0') + { + char c = *source; + int len; + int i; + + /* Fast path for plain ASCII */ + if (!IS_HIGHBIT_SET(c)) + { + /* Apply quoting if needed */ + if (CSV_STR_DOUBLE(c, false)) + *target++ = c; + /* Copy the character */ + *target++ = c; + source++; + remaining--; + continue; + } + + /* Slow path for possible multibyte characters */ + len = pg_encoding_mblen(client_encoding, source); + + /* Copy the character */ + for (i = 0; i < len; i++) + { + if (remaining == 0 || *source == '\0') + break; + *target++ = *source++; + remaining--; + } + + /* + * If we hit premature end of string (ie, incomplete multibyte + * character), try to pad out to the correct length with spaces. + * We may not be able to pad completely, but we will always be + * able to insert at least one pad space (since we'd not have + * quoted a multibyte character). This should be enough to make + * a string that the server will error out on. + */ + if (i < len) + { + for (; i < len; i++) + { + if (((size_t) (target - to)) / 2 >= strlen(from)) + break; + *target++ = ' '; + } + break; + } + } + + /* Write the terminating NUL character. */ + *target = '\0'; + + return target - to; + } Index: src/backend/utils/misc/guc.c =================================================================== RCS file: /d3/pgsql/cvsrepo/pgsql/src/backend/utils/misc/guc.c,v retrieving revision 1.391 diff -c -r1.391 guc.c *** src/backend/utils/misc/guc.c 8 May 2007 16:33:51 -0000 1.391 --- src/backend/utils/misc/guc.c 27 May 2007 16:45:40 -0000 *************** *** 2181,2187 **** { {"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 }, --- 2181,2187 ---- { {"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 }, *************** *** 6151,6156 **** --- 6151,6158 ---- 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: /d3/pgsql/cvsrepo/pgsql/src/backend/utils/misc/postgresql.conf.sample,v retrieving revision 1.215 diff -c -r1.215 postgresql.conf.sample *** src/backend/utils/misc/postgresql.conf.sample 18 Apr 2007 16:44:18 -0000 1.215 --- src/backend/utils/misc/postgresql.conf.sample 27 May 2007 16:45:40 -0000 *************** *** 227,233 **** # - 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: --- 227,233 ---- # - Where to Log - #log_destination = 'stderr' # Valid values are combinations of ! # stderr, syslog, csvlog and eventlog, # depending on platform. # This is used when logging to stderr: *************** *** 235,241 **** # files # (change requires restart) ! # These are only used if redirect_stderr is on: #log_directory = 'pg_log' # Directory where log files are written # Can be absolute or relative to PGDATA #log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' # Log file name pattern. --- 235,241 ---- # files # (change requires restart) ! # These are only used if redirect_stderr is on, or if log_destination is csvlog: #log_directory = 'pg_log' # Directory where log files are written # Can be absolute or relative to PGDATA #log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' # Log file name pattern. Index: src/include/postmaster/syslogger.h =================================================================== RCS file: /d3/pgsql/cvsrepo/pgsql/src/include/postmaster/syslogger.h,v retrieving revision 1.8 diff -c -r1.8 syslogger.h *** src/include/postmaster/syslogger.h 5 Jan 2007 22:19:57 -0000 1.8 --- src/include/postmaster/syslogger.h 27 May 2007 16:45:40 -0000 *************** *** 12,17 **** --- 12,23 ---- #ifndef _SYSLOGGER_H #define _SYSLOGGER_H + #define LOG_BUFFER_SIZE 32768 + + #define STDERR_LOGFILE 1 + #define CSV_LOGFILE 2 + + /* GUC options */ extern bool Redirect_stderr; extern int Log_RotationAge; *************** *** 24,40 **** #ifndef WIN32 extern int syslogPipe[2]; #else extern HANDLE syslogPipe[2]; #endif 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[]); #endif #endif /* _SYSLOGGER_H */ --- 30,60 ---- #ifndef WIN32 extern int syslogPipe[2]; + extern int csvlogPipe[2]; #else extern HANDLE syslogPipe[2]; + extern HANDLE csvlogPipe[2]; #endif extern int SysLogger_Start(void); ! extern void write_syslogger_file(const char *buffer, int count, int log_type); #ifdef EXEC_BACKEND extern void SysLoggerMain(int argc, char *argv[]); #endif + /* + * Macro for writing the buffer data into the csvlog pipe. + */ + #ifdef WIN32 + DWORD writtenbytes; + #define print_csvlog(b) \ + WriteFile(csvlogPipe[1], b.data, b.len, &writtenbytes, NULL); + #else + #define print_csvlog(b) \ + write(csvlogPipe[1], b.data, b.len); + #endif + #endif /* _SYSLOGGER_H */ Index: src/include/utils/elog.h =================================================================== RCS file: /d3/pgsql/cvsrepo/pgsql/src/include/utils/elog.h,v retrieving revision 1.86 diff -c -r1.86 elog.h *** src/include/utils/elog.h 4 May 2007 02:01:02 -0000 1.86 --- src/include/utils/elog.h 27 May 2007 16:45:40 -0000 *************** *** 64,69 **** --- 64,76 ---- #define ERRCODE_TO_CATEGORY(ec) ((ec) & ((1 << 12) - 1)) #define ERRCODE_IS_CATEGORY(ec) (((ec) & ~((1 << 12) - 1)) == 0) + /* + * Support macro for escaping strings. In addition to SQL_STR_DOUBLE defined + * in c.h, this also escapes the double quotes. + */ + #define CSV_STR_DOUBLE(ch, escape_backslash) \ + ((ch) == '\'' || (ch) == '\"' || ((ch) == '\\' && (escape_backslash))) + /* SQLSTATE codes for errors are defined in a separate file */ #include "utils/errcodes.h" *************** *** 291,296 **** --- 298,304 ---- #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);