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