mirror of
https://github.com/postgres/postgres.git
synced 2025-12-01 12:18:01 +03:00
Provide for logfiles in machine readable CSV format. In consequence, rename
redirect_stderr to logging_collector. Original patch from Arul Shaji, subsequently modified by Greg Smith, and then heavily modified by me.
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/misc.c,v 1.56 2007/01/05 22:19:41 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/adt/misc.c,v 1.57 2007/08/19 01:41:25 adunstan Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -144,10 +144,10 @@ pg_rotate_logfile(PG_FUNCTION_ARGS)
|
||||
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
||||
(errmsg("must be superuser to rotate log files"))));
|
||||
|
||||
if (!Redirect_stderr)
|
||||
if (!Logging_collector)
|
||||
{
|
||||
ereport(WARNING,
|
||||
(errmsg("rotation not possible because log redirection not active")));
|
||||
(errmsg("rotation not possible because log collection not active")));
|
||||
PG_RETURN_BOOL(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.193 2007/08/04 19:29:25 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/error/elog.c,v 1.194 2007/08/19 01:41:25 adunstan Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -104,6 +104,14 @@ static int errordata_stack_depth = -1; /* index of topmost active frame */
|
||||
|
||||
static int recursion_depth = 0; /* to detect actual recursion */
|
||||
|
||||
/* buffers for formatted timestamps that might be used by both
|
||||
* log_line_prefix and csv logs.
|
||||
*/
|
||||
|
||||
#define FORMATTED_TS_LEN 128
|
||||
static char formatted_start_time[FORMATTED_TS_LEN];
|
||||
static char formatted_log_time[FORMATTED_TS_LEN];
|
||||
|
||||
|
||||
/* Macro for checking errordata_stack_depth is reasonable */
|
||||
#define CHECK_STACK_DEPTH() \
|
||||
@@ -124,8 +132,8 @@ static const char *useful_strerror(int errnum);
|
||||
static const char *error_severity(int elevel);
|
||||
static void append_with_tabs(StringInfo buf, const char *str);
|
||||
static bool is_log_level_output(int elevel, int log_min_level);
|
||||
static void write_pipe_chunks(int fd, char *data, int len);
|
||||
|
||||
static void write_pipe_chunks(char *data, int len, int dest);
|
||||
static void get_error_message(StringInfo buf, ErrorData *edata);
|
||||
|
||||
/*
|
||||
* errstart --- begin an error-reporting cycle
|
||||
@@ -1434,12 +1442,14 @@ log_line_prefix(StringInfo buf)
|
||||
/*
|
||||
* 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.
|
||||
* MyProcPid changes. MyStartTime also changes when MyProcPid does, so
|
||||
* reset the formatted start timestamp too.
|
||||
*/
|
||||
if (log_my_pid != MyProcPid)
|
||||
{
|
||||
log_line_number = 0;
|
||||
log_my_pid = MyProcPid;
|
||||
formatted_start_time[0] = '\0';
|
||||
}
|
||||
log_line_number++;
|
||||
|
||||
@@ -1498,8 +1508,7 @@ log_line_prefix(StringInfo buf)
|
||||
struct timeval tv;
|
||||
pg_time_t stamp_time;
|
||||
pg_tz *tz;
|
||||
char strfbuf[128],
|
||||
msbuf[8];
|
||||
char msbuf[8];
|
||||
|
||||
gettimeofday(&tv, NULL);
|
||||
stamp_time = (pg_time_t) tv.tv_sec;
|
||||
@@ -1512,16 +1521,16 @@ log_line_prefix(StringInfo buf)
|
||||
*/
|
||||
tz = log_timezone ? log_timezone : gmt_timezone;
|
||||
|
||||
pg_strftime(strfbuf, sizeof(strfbuf),
|
||||
pg_strftime(formatted_log_time, FORMATTED_TS_LEN,
|
||||
/* leave room for milliseconds... */
|
||||
"%Y-%m-%d %H:%M:%S %Z",
|
||||
pg_localtime(&stamp_time, tz));
|
||||
|
||||
/* 'paste' milliseconds into place... */
|
||||
sprintf(msbuf, ".%03d", (int) (tv.tv_usec / 1000));
|
||||
strncpy(strfbuf + 19, msbuf, 4);
|
||||
strncpy(formatted_log_time + 19, msbuf, 4);
|
||||
|
||||
appendStringInfoString(buf, strfbuf);
|
||||
appendStringInfoString(buf, formatted_log_time);
|
||||
}
|
||||
break;
|
||||
case 't':
|
||||
@@ -1539,18 +1548,18 @@ log_line_prefix(StringInfo buf)
|
||||
}
|
||||
break;
|
||||
case 's':
|
||||
if (formatted_start_time[0] == '\0')
|
||||
{
|
||||
pg_time_t stamp_time = (pg_time_t) MyStartTime;
|
||||
pg_tz *tz;
|
||||
char strfbuf[128];
|
||||
|
||||
tz = log_timezone ? log_timezone : gmt_timezone;
|
||||
|
||||
pg_strftime(strfbuf, sizeof(strfbuf),
|
||||
pg_strftime(formatted_start_time, FORMATTED_TS_LEN,
|
||||
"%Y-%m-%d %H:%M:%S %Z",
|
||||
pg_localtime(&stamp_time, tz));
|
||||
appendStringInfoString(buf, strfbuf);
|
||||
}
|
||||
appendStringInfoString(buf, formatted_start_time);
|
||||
break;
|
||||
case 'i':
|
||||
if (MyProcPort)
|
||||
@@ -1596,6 +1605,245 @@ log_line_prefix(StringInfo buf)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* append a CSV'd version of a string to a StringInfo
|
||||
* We use the PostgreSQL defaults for CSV, i.e. quote = escape = '"'
|
||||
*/
|
||||
|
||||
static inline void
|
||||
appendCSVLiteral(StringInfo buf, const char* data)
|
||||
{
|
||||
const char * p = data;
|
||||
char c;
|
||||
|
||||
appendStringInfoCharMacro(buf, '"');
|
||||
while ( (c = *p++) != '\0' )
|
||||
{
|
||||
if (c == '"')
|
||||
appendStringInfoCharMacro(buf, '"');
|
||||
appendStringInfoCharMacro(buf, c);
|
||||
}
|
||||
appendStringInfoCharMacro(buf, '"');
|
||||
}
|
||||
|
||||
/*
|
||||
* Constructs the error message, depending on the Errordata it gets,
|
||||
* in CSV (comma seperated values) format. The COPY command
|
||||
* can then be used to load the messages into a table.
|
||||
*/
|
||||
|
||||
static void
|
||||
write_csvlog(ErrorData *edata)
|
||||
{
|
||||
StringInfoData msgbuf;
|
||||
StringInfoData buf;
|
||||
|
||||
/* static counter for line numbers */
|
||||
static long log_line_number = 0;
|
||||
|
||||
/* has counter been reset in current process? */
|
||||
static int log_my_pid = 0;
|
||||
|
||||
/*
|
||||
* This is one of the few places where we'd rather not inherit a static
|
||||
* variable's value from the postmaster. But since we will, reset it when
|
||||
* MyProcPid changes.
|
||||
*/
|
||||
if (log_my_pid != MyProcPid)
|
||||
{
|
||||
log_line_number = 0;
|
||||
log_my_pid = MyProcPid;
|
||||
formatted_start_time[0] = '\0';
|
||||
}
|
||||
log_line_number++;
|
||||
|
||||
initStringInfo(&msgbuf);
|
||||
initStringInfo(&buf);
|
||||
|
||||
/*
|
||||
* The format of the log output in CSV format:
|
||||
* timestamp with milliseconds, username, databasename, session id,
|
||||
* host and port number, process id, process line number, command tag,
|
||||
* session start time, transaction id, error severity, sql state code,
|
||||
* statement or error message.
|
||||
*/
|
||||
|
||||
/* timestamp_with_milliseconds */
|
||||
/*
|
||||
* Check if the timestamp is already calculated for the syslog message,
|
||||
* if it is, then no need to calculate it again, will use the same,
|
||||
* else get the current timestamp. This is done to put same timestamp
|
||||
* in both syslog and csvlog messages.
|
||||
*/
|
||||
if (formatted_log_time[0] == '\0')
|
||||
{
|
||||
struct timeval tv;
|
||||
pg_time_t stamp_time;
|
||||
pg_tz *tz;
|
||||
char msbuf[8];
|
||||
|
||||
gettimeofday(&tv, NULL);
|
||||
stamp_time = (pg_time_t) tv.tv_sec;
|
||||
|
||||
/*
|
||||
* Normally we print log timestamps in log_timezone, but
|
||||
* during startup we could get here before that's set.
|
||||
* If so, fall back to gmt_timezone (which guc.c ensures
|
||||
* is set up before Log_line_prefix can become nonempty).
|
||||
*/
|
||||
tz = log_timezone ? log_timezone : gmt_timezone;
|
||||
|
||||
pg_strftime(formatted_log_time, FORMATTED_TS_LEN,
|
||||
/* leave room for milliseconds... */
|
||||
"%Y-%m-%d %H:%M:%S %Z",
|
||||
pg_localtime(&stamp_time, tz));
|
||||
|
||||
/* 'paste' milliseconds into place... */
|
||||
sprintf(msbuf, ".%03d", (int) (tv.tv_usec / 1000));
|
||||
strncpy(formatted_log_time + 19, msbuf, 4);
|
||||
}
|
||||
appendStringInfoString(&buf, formatted_log_time);
|
||||
appendStringInfoChar(&buf, ',');
|
||||
|
||||
/* username */
|
||||
if (MyProcPort)
|
||||
{
|
||||
const char *username = MyProcPort->user_name;
|
||||
if (username == NULL || *username == '\0')
|
||||
username = _("[unknown]");
|
||||
|
||||
appendCSVLiteral(&buf, username);
|
||||
}
|
||||
appendStringInfoChar(&buf, ',');
|
||||
|
||||
/* databasename */
|
||||
if (MyProcPort)
|
||||
{
|
||||
const char *dbname = MyProcPort->database_name;
|
||||
|
||||
if (dbname == NULL || *dbname == '\0')
|
||||
dbname = _("[unknown]");
|
||||
|
||||
appendCSVLiteral(&buf, dbname);
|
||||
}
|
||||
appendStringInfoChar(&buf, ',');
|
||||
|
||||
/* session id */
|
||||
appendStringInfo(&buf, "%lx.%x",
|
||||
(long) MyStartTime, MyProcPid);
|
||||
appendStringInfoChar(&buf, ',');
|
||||
|
||||
/* Remote host and port */
|
||||
if (MyProcPort && MyProcPort->remote_host)
|
||||
{
|
||||
appendStringInfo(&buf, "%s", MyProcPort->remote_host);
|
||||
if (MyProcPort->remote_port && MyProcPort->remote_port[0] != '\0')
|
||||
appendStringInfo(&buf, ":%s", MyProcPort->remote_port);
|
||||
}
|
||||
|
||||
appendStringInfoChar(&buf, ',');
|
||||
|
||||
/* Process id */
|
||||
if (MyProcPid != 0)
|
||||
appendStringInfo(&buf, "%d", MyProcPid);
|
||||
|
||||
appendStringInfoChar(&buf, ',');
|
||||
|
||||
/* Line number */
|
||||
appendStringInfo(&buf, "%ld", log_line_number);
|
||||
|
||||
appendStringInfoChar(&buf, ',');
|
||||
|
||||
/* PS display */
|
||||
if (MyProcPort)
|
||||
{
|
||||
const char *psdisp;
|
||||
int displen;
|
||||
|
||||
psdisp = get_ps_display(&displen);
|
||||
appendStringInfo(&msgbuf, "%.*s", displen, psdisp);
|
||||
appendCSVLiteral(&buf, msgbuf.data);
|
||||
resetStringInfo(&msgbuf);
|
||||
}
|
||||
|
||||
appendStringInfoChar(&buf, ',');
|
||||
|
||||
/* session start timestamp */
|
||||
if (formatted_start_time[0] == '\0')
|
||||
{
|
||||
pg_time_t stamp_time = (pg_time_t) MyStartTime;
|
||||
pg_tz *tz;
|
||||
|
||||
tz = log_timezone ? log_timezone : gmt_timezone;
|
||||
|
||||
pg_strftime(formatted_start_time, FORMATTED_TS_LEN,
|
||||
"%Y-%m-%d %H:%M:%S %Z",
|
||||
pg_localtime(&stamp_time, tz));
|
||||
}
|
||||
appendStringInfoString(&buf, formatted_start_time);
|
||||
appendStringInfoChar(&buf, ',');
|
||||
|
||||
|
||||
/* Transaction id */
|
||||
if (MyProcPort)
|
||||
{
|
||||
if (IsTransactionState())
|
||||
appendStringInfo(&buf, "%u", GetTopTransactionId());
|
||||
else
|
||||
appendStringInfo(&buf, "%u", InvalidTransactionId);
|
||||
}
|
||||
|
||||
appendStringInfoChar(&buf, ',');
|
||||
|
||||
/* Error severity */
|
||||
if (error_severity(edata->elevel) != NULL)
|
||||
appendStringInfo(&buf, "%s,", error_severity(edata->elevel));
|
||||
else
|
||||
appendStringInfoString(&buf, ",");
|
||||
|
||||
/* SQL state code */
|
||||
if (Log_error_verbosity >= PGERROR_VERBOSE)
|
||||
appendStringInfo(&buf, "%s",
|
||||
unpack_sql_state(edata->sqlerrcode));
|
||||
appendStringInfoChar(&buf, ',');
|
||||
|
||||
/* Error message and cursor position if any */
|
||||
get_error_message(&msgbuf, edata);
|
||||
|
||||
appendCSVLiteral(&buf, msgbuf.data);
|
||||
|
||||
appendStringInfoChar(&buf, '\n');
|
||||
|
||||
/* If in the syslogger process, try to write messages direct to file */
|
||||
if (am_syslogger)
|
||||
write_syslogger_file(buf.data, buf.len, LOG_DESTINATION_CSVLOG);
|
||||
else
|
||||
write_pipe_chunks(buf.data, buf.len, LOG_DESTINATION_CSVLOG);
|
||||
|
||||
pfree(msgbuf.data);
|
||||
pfree(buf.data);
|
||||
}
|
||||
|
||||
/*
|
||||
* Appends the buffer with the error message and the cursor position.
|
||||
*/
|
||||
static void
|
||||
get_error_message(StringInfo buf, ErrorData *edata)
|
||||
{
|
||||
if (edata->message)
|
||||
appendStringInfo(buf, "%s", edata->message);
|
||||
else
|
||||
appendStringInfo(buf, "%s", _("missing error text"));
|
||||
|
||||
if (edata->cursorpos > 0)
|
||||
appendStringInfo(buf, _(" at character %d"),
|
||||
edata->cursorpos);
|
||||
else if (edata->internalpos > 0)
|
||||
appendStringInfo(buf, _(" at character %d"),
|
||||
edata->internalpos);
|
||||
}
|
||||
|
||||
/*
|
||||
* Unpack MAKE_SQLSTATE code. Note that this returns a pointer to a
|
||||
* static buffer.
|
||||
@@ -1627,6 +1875,8 @@ send_message_to_server_log(ErrorData *edata)
|
||||
|
||||
initStringInfo(&buf);
|
||||
|
||||
formatted_log_time[0] = '\0';
|
||||
|
||||
log_line_prefix(&buf);
|
||||
appendStringInfo(&buf, "%s: ", error_severity(edata->elevel));
|
||||
|
||||
@@ -1766,7 +2016,7 @@ send_message_to_server_log(ErrorData *edata)
|
||||
* syslogger. Otherwise, just do a vanilla write to stderr.
|
||||
*/
|
||||
if (redirection_done && !am_syslogger)
|
||||
write_pipe_chunks(fileno(stderr), buf.data, buf.len);
|
||||
write_pipe_chunks(buf.data, buf.len, LOG_DESTINATION_STDERR);
|
||||
#ifdef WIN32
|
||||
/*
|
||||
* In a win32 service environment, there is no usable stderr. Capture
|
||||
@@ -1782,9 +2032,31 @@ send_message_to_server_log(ErrorData *edata)
|
||||
write(fileno(stderr), buf.data, buf.len);
|
||||
}
|
||||
|
||||
if (Log_destination & LOG_DESTINATION_CSVLOG)
|
||||
{
|
||||
if (redirection_done || am_syslogger)
|
||||
{
|
||||
/* send CSV data if it's safe to do so (syslogger doesn't need
|
||||
* the pipe)
|
||||
*/
|
||||
write_csvlog(edata);
|
||||
}
|
||||
else
|
||||
{
|
||||
char * msg = _("Not safe to send CSV data\n");
|
||||
write(fileno(stderr),msg,strlen(msg));
|
||||
if ( ! (Log_destination & LOG_DESTINATION_STDERR) &&
|
||||
whereToSendOutput != DestDebug)
|
||||
{
|
||||
/* write message to stderr unless we just sent it above */
|
||||
write(fileno(stderr), buf.data, buf.len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If in the syslogger process, try to write messages direct to file */
|
||||
if (am_syslogger)
|
||||
write_syslogger_file(buf.data, buf.len);
|
||||
write_syslogger_file(buf.data, buf.len, LOG_DESTINATION_STDERR);
|
||||
|
||||
pfree(buf.data);
|
||||
}
|
||||
@@ -1793,10 +2065,12 @@ send_message_to_server_log(ErrorData *edata)
|
||||
* Send data to the syslogger using the chunked protocol
|
||||
*/
|
||||
static void
|
||||
write_pipe_chunks(int fd, char *data, int len)
|
||||
write_pipe_chunks(char *data, int len, int dest)
|
||||
{
|
||||
PipeProtoChunk p;
|
||||
|
||||
int fd = fileno(stderr);
|
||||
|
||||
Assert(len > 0);
|
||||
|
||||
p.proto.nuls[0] = p.proto.nuls[1] = '\0';
|
||||
@@ -1805,7 +2079,7 @@ write_pipe_chunks(int fd, char *data, int len)
|
||||
/* write all but the last chunk */
|
||||
while (len > PIPE_MAX_PAYLOAD)
|
||||
{
|
||||
p.proto.is_last = 'f';
|
||||
p.proto.is_last = (dest == LOG_DESTINATION_CSVLOG ? 'F' : 'f');
|
||||
p.proto.len = PIPE_MAX_PAYLOAD;
|
||||
memcpy(p.proto.data, data, PIPE_MAX_PAYLOAD);
|
||||
write(fd, &p, PIPE_HEADER_SIZE + PIPE_MAX_PAYLOAD);
|
||||
@@ -1814,7 +2088,7 @@ write_pipe_chunks(int fd, char *data, int len)
|
||||
}
|
||||
|
||||
/* write the last chunk */
|
||||
p.proto.is_last = 't';
|
||||
p.proto.is_last = (dest == LOG_DESTINATION_CSVLOG ? 'T' : 't');
|
||||
p.proto.len = len;
|
||||
memcpy(p.proto.data, data, len);
|
||||
write(fd, &p, PIPE_HEADER_SIZE + len);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* Written by Peter Eisentraut <peter_e@gmx.net>.
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.412 2007/08/13 19:27:11 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.413 2007/08/19 01:41:25 adunstan Exp $
|
||||
*
|
||||
*--------------------------------------------------------------------
|
||||
*/
|
||||
@@ -968,11 +968,11 @@ static struct config_bool ConfigureNamesBool[] =
|
||||
false, NULL, NULL
|
||||
},
|
||||
{
|
||||
{"redirect_stderr", PGC_POSTMASTER, LOGGING_WHERE,
|
||||
gettext_noop("Start a subprocess to capture stderr output into log files."),
|
||||
{"logging_collector", PGC_POSTMASTER, LOGGING_WHERE,
|
||||
gettext_noop("Start a subprocess to capture stderr output and/or csvlogs into log files."),
|
||||
NULL
|
||||
},
|
||||
&Redirect_stderr,
|
||||
&Logging_collector,
|
||||
false, NULL, NULL
|
||||
},
|
||||
{
|
||||
@@ -2241,7 +2241,7 @@ static struct config_string ConfigureNamesString[] =
|
||||
{"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."),
|
||||
" \"csvlog\" and \"eventlog\", depending on the platform."),
|
||||
GUC_LIST_INPUT
|
||||
},
|
||||
&log_destination_string,
|
||||
@@ -6313,6 +6313,8 @@ assign_log_destination(const char *value, bool doit, GucSource source)
|
||||
|
||||
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;
|
||||
|
||||
@@ -229,15 +229,16 @@
|
||||
# - Where to Log -
|
||||
|
||||
#log_destination = 'stderr' # Valid values are combinations of
|
||||
# stderr, syslog and eventlog,
|
||||
# stderr, csvlog, syslog and eventlog,
|
||||
# depending on platform.
|
||||
# csvlog requires logging_collector to be on
|
||||
|
||||
# This is used when logging to stderr:
|
||||
#redirect_stderr = off # Enable capturing of stderr into log
|
||||
# files
|
||||
#logging_collector = off # Enable capturing of stderr and csvlog
|
||||
# into log files. Required to be on for csvlogs.
|
||||
# (change requires restart)
|
||||
|
||||
# These are only used if redirect_stderr is on:
|
||||
# These are only used if logging_collector 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.
|
||||
|
||||
Reference in New Issue
Block a user