* Stephen Frost ([email protected]) wrote: > Ugh, sorry about that, I should have realized that needed to be done. > Updated patch attached.
Errr, for real this time.
Thanks,
Stephen
commit 25e94dcb390f56502bc46e683b438c20d2dc74e0
Author: Stephen Frost <[email protected]>
Date: Tue Feb 15 08:50:17 2011 -0500
assign_csvlog_fields() - reset context on error
On error, we need to make sure to reset the memory context back
to what it was when we entered.
*** a/doc/src/sgml/config.sgml
--- b/doc/src/sgml/config.sgml
***************
*** 3542,3548 **** local0.* /var/log/postgresql
</row>
<row>
<entry><literal>%u</literal></entry>
! <entry>User name</entry>
<entry>yes</entry>
</row>
<row>
--- 3542,3561 ----
</row>
<row>
<entry><literal>%u</literal></entry>
! <entry>Session user name, typically the user name which was used
! to authenticate to <productname>PostgreSQL</productname> with,
! but can be changed by a superuser, see <command>SET SESSION
! AUTHORIZATION</></entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>%U</literal></entry>
! <entry>Current role name, when set with <command>SET ROLE</>;
! the current role identifier is relevant for permission checking;
! Returns 'none' if the current role matches the session user.
! Note: Log messages from inside <literal>SECURITY DEFINER</>
! functions will show the calling role, not the effective role
! inside the <literal>SECURITY DEFINER</> function</entry>
<entry>yes</entry>
</row>
<row>
***************
*** 3659,3664 **** FROM pg_stat_activity;
--- 3672,3717 ----
</listitem>
</varlistentry>
+ <varlistentry id="guc-csvlog-fields" xreflabel="csvlog_fields">
+ <term><varname>csvlog_fields</varname> (<type>string</type>)</term>
+ <indexterm>
+ <primary><varname>csvlog_fields</> configuration parameter</primary>
+ </indexterm>
+ <listitem>
+ <para>
+ Controls the set and order of the fields which are written out in
+ the CSV-format log file.
+
+ The default is:
+ <literal>log_time, user_name, database_name, process_id,
+ connection_from, session_id, session_line_num, command_tag,
+ session_start_time, virtual_transaction_id, transaction_id,
+ error_severity, sql_state_code, message, detail, hint,
+ internal_query, internal_query_pos, context, query, query_pos,
+ location, application_name</literal>
+
+ For details on what these fields are, refer to the
+ <varname>log_line_prefix</varname> and
+ <xref linkend="runtime-config-logging-csvlog"> documentation.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-csvlog-header" xreflabel="csvlog_header">
+ <term><varname>csvlog_header</varname> (<type>boolean</type>)</term>
+ <indexterm>
+ <primary><varname>csvlog_header</> configuration parameter</primary>
+ </indexterm>
+ <listitem>
+ <para>
+ Controls if a header should be output for each file logged through
+ the CSV-format logging.
+
+ The default is: <literal>false</literal>, for backwards compatibility.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-log-lock-waits" xreflabel="log_lock_waits">
<term><varname>log_lock_waits</varname> (<type>boolean</type>)</term>
<indexterm>
***************
*** 3766,3799 **** FROM pg_stat_activity;
Including <literal>csvlog</> in the <varname>log_destination</> list
provides a convenient way to import log files into a database table.
This option emits log lines in comma-separated-values
! (<acronym>CSV</>) format,
! with these columns:
! timestamp with milliseconds,
! user name,
! database name,
! process ID,
! client host:port number,
! session ID,
! per-session line number,
! command tag,
! session start time,
! virtual transaction ID,
! regular transaction ID,
! error severity,
! SQLSTATE code,
! error message,
! error message detail,
! hint,
! internal query that led to the error (if any),
! character count of the error position therein,
! error context,
! user query that led to the error (if any and enabled by
! <varname>log_min_error_statement</>),
! character count of the error position therein,
! location of the error in the PostgreSQL source code
! (if <varname>log_error_verbosity</> is set to <literal>verbose</>),
! and application name.
! Here is a sample table definition for storing CSV-format log output:
<programlisting>
CREATE TABLE postgres_log
--- 3819,3971 ----
Including <literal>csvlog</> in the <varname>log_destination</> list
provides a convenient way to import log files into a database table.
This option emits log lines in comma-separated-values
! (<acronym>CSV</>) format. The following table defines the fields
! which can be included in the CSV output, their meanings, and if they
! are included in the default CSV layout (the default ordering matches
! the order of this table).
!
! <informaltable>
! <tgroup cols="3">
! <thead>
! <row>
! <entry>CSV Field Name</entry>
! <entry>Definition</entry>
! <entry>Included by Default</entry>
! </row>
! </thead>
! <tbody>
! <row>
! <entry><literal>log_time</literal></entry>
! <entry>timestamp with milliseconds</entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>user_name</literal></entry>
! <entry>session user name</entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>role_name</literal></entry>
! <entry>current role name</entry>
! <entry>no</entry>
! </row>
! <row>
! <entry><literal>database_name</literal></entry>
! <entry>name of database connected to</entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>process_id</literal></entry>
! <entry>process ID of the backend PG process</entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>connection_from</literal></entry>
! <entry>client host/IP and port number</entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>session_id</literal></entry>
! <entry>ID of the session</entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>session_line_number</literal></entry>
! <entry>per-session line number</entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>command_tag</literal></entry>
! <entry>Command tag of the logged command</entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>session_start_time</literal></entry>
! <entry>Start time of the current session</entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>virtual_transaction_id</literal></entry>
! <entry>Virtual Transaction ID</entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>transaction_id</literal></entry>
! <entry>Regular Transaction ID</entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>error_severity</literal></entry>
! <entry>Error severity code of the log message</entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>sql_state_code</literal></entry>
! <entry>SQLSTATE code of the command being logged</entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>message</literal></entry>
! <entry>Error message</entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>detail</literal></entry>
! <entry>Error message detail</entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>hint</literal></entry>
! <entry>Error message hint</entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>internal_query</literal></entry>
! <entry>internal query that led to the error (if any)</entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>internal_query_pos</literal></entry>
! <entry>character count of the error position of the internal query</entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>context</literal></entry>
! <entry>error context</entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>query</literal></entry>
! <entry>user query that led to the error (if any and enabled by <varname>log_min_error_statement</varname>)</entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>query_pos</literal></entry>
! <entry>character count of the error position of the user query</entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>location</literal></entry>
! <entry>location of the error in the PostgreSQL source code (if <varname>log_error_verbosity</varname> is set to <literal>verbose</literal>)</entry>
! <entry>yes</entry>
! </row>
! <row>
! <entry><literal>application_name</literal></entry>
! <entry>Name of the connecting application, if provided by the application</entry>
! <entry>yes</entry>
! </row>
! </tbody>
! </tgroup>
! </informaltable>
!
! The set of columns to be included, and their order, in the CSV
! output can be controlled using the <varname>csvlog_fields</varname> option.
!
! For additional details on the definition of the above columns, refer
! to the documentation for <varname>log_line_prefix</varname>.
!
! Here is a sample table definition for storing the default CSV-format
! log output:
<programlisting>
CREATE TABLE postgres_log
*** a/src/backend/commands/variable.c
--- b/src/backend/commands/variable.c
***************
*** 847,852 **** assign_session_authorization(const char *value, bool doit, GucSource source)
--- 847,857 ----
return result;
}
+ /*
+ * function to return the stored session username, needed because we
+ * can't do catalog lookups when possibly being called after an error,
+ * eg: from elog.c or part of GUC handling.
+ */
const char *
show_session_authorization(void)
{
***************
*** 972,977 **** assign_role(const char *value, bool doit, GucSource source)
--- 977,987 ----
return result;
}
+ /*
+ * function to return the stored role username, needed because we
+ * can't do catalog lookups when possibly being called after an error,
+ * eg: from elog.c or part of GUC handling.
+ */
const char *
show_role(void)
{
*** a/src/backend/postmaster/syslogger.c
--- b/src/backend/postmaster/syslogger.c
***************
*** 147,152 **** static char *logfile_getname(pg_time_t timestamp, const char *suffix);
--- 147,153 ----
static void set_next_rotation_time(void);
static void sigHupHandler(SIGNAL_ARGS);
static void sigUsr1Handler(SIGNAL_ARGS);
+ static void write_csvlog_header(FILE *out_fh);
/*
***************
*** 988,993 **** pipeThread(void *arg)
--- 989,1019 ----
#endif /* WIN32 */
/*
+ * Internal function for writing out the header of a CSV-style log file
+ * to the passed-in file handle.
+ */
+ static void
+ write_csvlog_header(FILE *out_fh)
+ {
+ int rc;
+ int header_length = strlen(csvlog_fields);
+
+ /* Write out the csvlog_fields GUC, which matches the CSV log format
+ * header, at least, if we did everything right. */
+ rc = fwrite(csvlog_fields, 1, header_length, out_fh);
+
+ /* can't use ereport here because of possible recursion */
+ if (rc != header_length)
+ write_stderr("could not write to new log file: %s\n", strerror(errno));
+
+ rc = fputc('\n', out_fh);
+ if (rc != '\n')
+ write_stderr("could not write to new log file: %s\n", strerror(errno));
+
+ return;
+ }
+
+ /*
* open the csv log file - we do this opportunistically, because
* we don't know if CSV logging will be wanted.
*/
***************
*** 995,1004 **** static void
open_csvlogfile(void)
{
char *filename;
filename = logfile_getname(time(NULL), ".csv");
! csvlogFile = logfile_open(filename, "a", false);
pfree(filename);
}
--- 1021,1037 ----
open_csvlogfile(void)
{
char *filename;
+ FILE *fh;
filename = logfile_getname(time(NULL), ".csv");
! fh = logfile_open(filename, "a", false);
!
! /* Check if we are asked to write out a header for the CSV file. */
! if (csvlog_header)
! write_csvlog_header(fh);
!
! csvlogFile = fh;
pfree(filename);
}
***************
*** 1165,1170 **** logfile_rotate(bool time_based_rotation, int size_rotation_for)
--- 1198,1207 ----
return;
}
+ /* Check if we are asked to write out a header for the CSV file. */
+ if (csvlog_header)
+ write_csvlog_header(fh);
+
fclose(csvlogFile);
csvlogFile = fh;
*** a/src/backend/utils/error/elog.c
--- b/src/backend/utils/error/elog.c
***************
*** 3,8 ****
--- 3,17 ----
* elog.c
* error logging and reporting
*
+ * A few comments about situations where error processing is called:
+ *
+ * We need to be cautious of both a performance hit when logging, since
+ * log messages can be generated at a huge rate if every command is being
+ * logged and we also need to watch out for what can happen when we are
+ * trying to log from an aborted transaction. Specifically, attempting to
+ * do SysCache lookups and possibly use other usually available backend
+ * systems will fail badly when logging from an aborted transaction.
+ *
* Some notes about recursion and errors during error processing:
*
* We need to be robust about recursive-error scenarios --- for example,
***************
*** 59,73 ****
--- 68,85 ----
#include "access/transam.h"
#include "access/xact.h"
+ #include "commands/variable.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
+ #include "nodes/pg_list.h"
#include "postmaster/postmaster.h"
#include "postmaster/syslogger.h"
#include "storage/ipc.h"
#include "storage/proc.h"
#include "tcop/tcopprot.h"
+ #include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/ps_status.h"
***************
*** 93,98 **** extern bool redirection_done;
--- 105,144 ----
int Log_error_verbosity = PGERROR_VERBOSE;
char *Log_line_prefix = NULL; /* format for extra log line info */
int Log_destination = LOG_DESTINATION_STDERR;
+ char *csvlog_fields = NULL;
+ bool csvlog_header = false;
+
+ static List *csvlog_field_list = NIL;
+
+ /* To add a CSV field option, you need to update the enum in elog.h, check
+ * if the last value in the enum changed and if so update MAX_CSVLOG_OPTS,
+ * add code to handle the option in write_csv(), and add it here. */
+ const char *CSVFieldNames[] = {
+ "log_time", /* CSVLOG_LOG_TIME */
+ "user_name", /* CSVLOG_USER_NAME */
+ "role_name", /* CSVLOG_ROLE_NAME */
+ "database_name", /* CSVLOG_DATABASE_NAME */
+ "process_id", /* CSVLOG_PROCESS_ID */
+ "connection_from", /* CSVLOG_CONNECTION_FROM */
+ "session_id", /* CSVLOG_SESSION_ID */
+ "session_line_num", /* CSVLOG_SESSION_LINE_NUM */
+ "command_tag", /* CSVLOG_COMMAND_TAG */
+ "session_start_time", /* CSVLOG_SESSION_START_TIME */
+ "virtual_transaction_id", /* CSVLOG_VIRTUAL_TRANSACTION_ID */
+ "transaction_id", /* CSVLOG_TRANSACTION_ID */
+ "error_severity", /* CSVLOG_ERROR_SEVERITY */
+ "sql_state_code", /* CSVLOG_SQL_STATE_CODE */
+ "message", /* CSVLOG_MESSAGE */
+ "detail", /* CSVLOG_DETAIL */
+ "hint", /* CSVLOG_HINT */
+ "internal_query", /* CSVLOG_INTERNAL_QUERY */
+ "internal_query_pos", /* CSVLOG_INTERNAL_QUERY_POS */
+ "context", /* CSVLOG_CONTEXT */
+ "query", /* CSVLOG_QUERY */
+ "query_pos", /* CSVLOG_QUERY_POS */
+ "location", /* CSVLOG_LOCATION */
+ "application_name" /* CSVLOG_APPLICATION_NAME */
+ };
#ifdef HAVE_SYSLOG
***************
*** 161,166 **** static void write_csvlog(ErrorData *edata);
--- 207,217 ----
static void setup_formatted_log_time(void);
static void setup_formatted_start_time(void);
+ /* extern'd and used from guc.c... */
+ const char *assign_csvlog_fields(const char *newval, bool doit,
+ GucSource source);
+
+
/*
* in_error_recursion_trouble --- are we at risk of infinite error recursion?
***************
*** 1817,1831 **** log_line_prefix(StringInfo buf, ErrorData *edata)
}
break;
case 'u':
- if (MyProcPort)
{
! const char *username = MyProcPort->user_name;
!
! if (username == NULL || *username == '\0')
! username = _("[unknown]");
! appendStringInfoString(buf, username);
}
break;
case 'd':
if (MyProcPort)
{
--- 1868,1891 ----
}
break;
case 'u':
{
! const char *session_auth = show_session_authorization();
!
! if (*session_auth != '\0')
! appendStringInfoString(buf, session_auth);
! else if (MyProcPort)
! {
! const char *username = MyProcPort->user_name;
!
! if (username == NULL || *username == '\0')
! username = _("[unknown]");
! appendStringInfoString(buf, username);
! }
}
break;
+ case 'U':
+ appendStringInfoString(buf, show_role());
+ break;
case 'd':
if (MyProcPort)
{
***************
*** 1921,1926 **** log_line_prefix(StringInfo buf, ErrorData *edata)
--- 1981,2077 ----
}
/*
+ * Called when the GUC csvlog_fields() option has been set
+ * (currently only allowed in postmaster.conf, on PG restart).
+ *
+ * Processes the list passed in from the GUC system and updates the
+ * csvlog_field_list variable, which will then be used to generate
+ * CSV log output.
+ */
+ const char *
+ assign_csvlog_fields(const char *newval, bool doit, GucSource source)
+ {
+ /* Verify the list is valid */
+ List *new_csv_fields = NIL; /* List we're building */
+ List *column_list = NIL; /* List of columns from user */
+ ListCell *l;
+ char *rawstring; /* Copy of user string */
+ MemoryContext oldcontext;
+
+ /* Need a modifyable version to pass to SplitIdentifierString */
+ rawstring = pstrdup(newval);
+
+ /* Parse string into list of identifiers */
+ if (!SplitIdentifierString(rawstring, ',', &column_list))
+ {
+ list_free(column_list);
+ pfree(rawstring);
+ return NULL;
+ }
+
+ /* Empty isn't a valid option */
+ if (column_list == NIL)
+ {
+ pfree(rawstring);
+ return NULL;
+ }
+
+ /*
+ * We need the allocations done for the csvlog_field_list to
+ * be preserved, so allocate them in TopMemoryContext.
+ */
+ oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+ /*
+ * Loop through all of the fields provided by the user and build
+ * up our new_csv_fields list which will be processed by write_csvlog
+ */
+ foreach(l, column_list)
+ {
+ int curr_option;
+
+ /* Loop through all of the valid field options to try and match the
+ * current entry in the list to one of them. */
+ for (curr_option = 0; curr_option < MAX_CSVLOG_OPTS; curr_option++)
+ if (pg_strcasecmp(lfirst(l),CSVFieldNames[curr_option]) == 0)
+ {
+ new_csv_fields = lappend_int(new_csv_fields,curr_option);
+ break;
+ }
+
+ /* check if no option matched, and if so, return error */
+ if (curr_option == MAX_CSVLOG_OPTS)
+ {
+ /* Switch back to the calling context */
+ MemoryContextSwitchTo(oldcontext);
+
+ list_free(column_list);
+ pfree(rawstring);
+
+ return NULL;
+ }
+ }
+
+ if (doit)
+ {
+ /* put new list in place */
+ List *old_list = csvlog_field_list;
+
+ csvlog_field_list = new_csv_fields;
+
+ list_free(old_list);
+ }
+
+ list_free(column_list);
+ pfree(rawstring);
+
+ /* Switch back to the calling context */
+ MemoryContextSwitchTo(oldcontext);
+
+ return newval;
+ }
+
+ /*
* append a CSV'd version of a string to a StringInfo
* We use the PostgreSQL defaults for CSV, i.e. quote = escape = '"'
* If it's NULL, append nothing.
***************
*** 1946,1957 **** appendCSVLiteral(StringInfo buf, const char *data)
}
/*
! * Constructs the error message, depending on the Errordata it gets, in a CSV
! * format which is described in doc/src/sgml/config.sgml.
*/
static void
write_csvlog(ErrorData *edata)
{
StringInfoData buf;
bool print_stmt = false;
--- 2097,2110 ----
}
/*
! * Constructs the error message, depending on the Errordata it gets, in the CSV
! * format requested by the user, based on the csvlog_fields GUC.
*/
static void
write_csvlog(ErrorData *edata)
{
+ int num_fields;
+ bool first_field = true;
StringInfoData buf;
bool print_stmt = false;
***************
*** 1961,1966 **** write_csvlog(ErrorData *edata)
--- 2114,2126 ----
/* has counter been reset in current process? */
static int log_my_pid = 0;
+ ListCell *l;
+
+ const char *session_auth = show_session_authorization();
+
+ /* csvlog_field_list should never be empty when we reach here */
+ Assert(csvlog_field_list != NIL);
+
/*
* 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
***************
*** 1977,2134 **** write_csvlog(ErrorData *edata)
initStringInfo(&buf);
/*
! * timestamp with milliseconds
! *
! * Check if the timestamp is already calculated for the syslog message,
! * and use it if so. Otherwise, get the current timestamp. This is done
! * to put same timestamp in both syslog and csvlog messages.
*/
! if (formatted_log_time[0] == '\0')
! setup_formatted_log_time();
! appendStringInfoString(&buf, formatted_log_time);
! appendStringInfoChar(&buf, ',');
! /* username */
! if (MyProcPort)
! appendCSVLiteral(&buf, MyProcPort->user_name);
! appendStringInfoChar(&buf, ',');
! /* database name */
! if (MyProcPort)
! appendCSVLiteral(&buf, MyProcPort->database_name);
! appendStringInfoChar(&buf, ',');
! /* Process id */
! if (MyProcPid != 0)
! appendStringInfo(&buf, "%d", MyProcPid);
! appendStringInfoChar(&buf, ',');
! /* Remote host and port */
! if (MyProcPort && MyProcPort->remote_host)
! {
! appendStringInfoChar(&buf, '"');
! appendStringInfoString(&buf, MyProcPort->remote_host);
! if (MyProcPort->remote_port && MyProcPort->remote_port[0] != '\0')
! {
! appendStringInfoChar(&buf, ':');
! appendStringInfoString(&buf, MyProcPort->remote_port);
! }
! appendStringInfoChar(&buf, '"');
! }
! appendStringInfoChar(&buf, ',');
! /* session id */
! appendStringInfo(&buf, "%lx.%x", (long) MyStartTime, MyProcPid);
! appendStringInfoChar(&buf, ',');
! /* Line number */
! appendStringInfo(&buf, "%ld", log_line_number);
! appendStringInfoChar(&buf, ',');
! /* PS display */
! if (MyProcPort)
! {
! StringInfoData msgbuf;
! const char *psdisp;
! int displen;
! initStringInfo(&msgbuf);
! psdisp = get_ps_display(&displen);
! appendBinaryStringInfo(&msgbuf, psdisp, displen);
! appendCSVLiteral(&buf, msgbuf.data);
! pfree(msgbuf.data);
! }
! appendStringInfoChar(&buf, ',');
! /* session start timestamp */
! if (formatted_start_time[0] == '\0')
! setup_formatted_start_time();
! appendStringInfoString(&buf, formatted_start_time);
! appendStringInfoChar(&buf, ',');
! /* Virtual transaction id */
! /* keep VXID format in sync with lockfuncs.c */
! if (MyProc != NULL && MyProc->backendId != InvalidBackendId)
! appendStringInfo(&buf, "%d/%u", MyProc->backendId, MyProc->lxid);
! appendStringInfoChar(&buf, ',');
! /* Transaction id */
! appendStringInfo(&buf, "%u", GetTopTransactionIdIfAny());
! appendStringInfoChar(&buf, ',');
! /* Error severity */
! appendStringInfoString(&buf, error_severity(edata->elevel));
! appendStringInfoChar(&buf, ',');
! /* SQL state code */
! appendStringInfoString(&buf, unpack_sql_state(edata->sqlerrcode));
! appendStringInfoChar(&buf, ',');
! /* errmessage */
! appendCSVLiteral(&buf, edata->message);
! appendStringInfoChar(&buf, ',');
! /* errdetail or errdetail_log */
! if (edata->detail_log)
! appendCSVLiteral(&buf, edata->detail_log);
! else
! appendCSVLiteral(&buf, edata->detail);
! appendStringInfoChar(&buf, ',');
! /* errhint */
! appendCSVLiteral(&buf, edata->hint);
! appendStringInfoChar(&buf, ',');
! /* internal query */
! appendCSVLiteral(&buf, edata->internalquery);
! appendStringInfoChar(&buf, ',');
! /* if printed internal query, print internal pos too */
! if (edata->internalpos > 0 && edata->internalquery != NULL)
! appendStringInfo(&buf, "%d", edata->internalpos);
! appendStringInfoChar(&buf, ',');
! /* errcontext */
! appendCSVLiteral(&buf, edata->context);
! appendStringInfoChar(&buf, ',');
! /* user query --- only reported if not disabled by the caller */
! if (is_log_level_output(edata->elevel, log_min_error_statement) &&
! debug_query_string != NULL &&
! !edata->hide_stmt)
! print_stmt = true;
! if (print_stmt)
! appendCSVLiteral(&buf, debug_query_string);
! appendStringInfoChar(&buf, ',');
! if (print_stmt && edata->cursorpos > 0)
! appendStringInfo(&buf, "%d", edata->cursorpos);
! appendStringInfoChar(&buf, ',');
!
! /* file error location */
! if (Log_error_verbosity >= PGERROR_VERBOSE)
! {
! StringInfoData msgbuf;
!
! initStringInfo(&msgbuf);
!
! if (edata->funcname && edata->filename)
! appendStringInfo(&msgbuf, "%s, %s:%d",
! edata->funcname, edata->filename,
! edata->lineno);
! else if (edata->filename)
! appendStringInfo(&msgbuf, "%s:%d",
! edata->filename, edata->lineno);
! appendCSVLiteral(&buf, msgbuf.data);
! pfree(msgbuf.data);
! }
! appendStringInfoChar(&buf, ',');
! /* application name */
! if (application_name)
! appendCSVLiteral(&buf, application_name);
appendStringInfoChar(&buf, '\n');
--- 2137,2385 ----
initStringInfo(&buf);
/*
! * Get the number of fields, so we make sure to *not* include a comma
! * after the last field.
*/
! num_fields = list_length(csvlog_field_list);
! /*
! * Loop through the fields requested by the user, in the order requested, in
! * the csvlog_fields GUC.
! */
! foreach(l, csvlog_field_list)
! {
! /* If this isn't the first field, prepend a comma to seperate this
! * field from the previous one */
! if (!first_field)
! appendStringInfoChar(&buf, ',');
! else
! first_field = false;
! switch (lfirst_int(l))
! {
! case CSVLOG_LOG_TIME:
! {
! /*
! * timestamp with milliseconds
! *
! * Check if the timestamp is already calculated for the syslog message,
! * and use it if so. Otherwise, get the current timestamp. This is done
! * to put same timestamp in both syslog and csvlog messages.
! */
! if (formatted_log_time[0] == '\0')
! setup_formatted_log_time();
!
! appendStringInfoString(&buf, formatted_log_time);
! }
! break;
! case CSVLOG_USER_NAME:
! {
! /* session username, as done for %u */
! if (*session_auth != '\0')
! appendCSVLiteral(&buf, session_auth);
! else
! /* username */
! if (MyProcPort)
! {
! const char *username = MyProcPort->user_name;
! if (username == NULL || *username == '\0')
! username = _("[unknown]");
! appendCSVLiteral(&buf, MyProcPort->user_name);
! }
! }
! break;
! case CSVLOG_ROLE_NAME:
! /* current role, not updated if someone renames it in another
! * session, of course */
! appendCSVLiteral(&buf, show_role());
! break;
! case CSVLOG_DATABASE_NAME:
! {
! /* database name */
! if (MyProcPort)
! appendCSVLiteral(&buf, MyProcPort->database_name);
! }
! break;
!
! case CSVLOG_PROCESS_ID:
! {
! /* Process id */
! if (MyProcPid != 0)
! appendStringInfo(&buf, "%d", MyProcPid);
! }
! break;
! case CSVLOG_CONNECTION_FROM:
! {
! /* Remote host and port */
! if (MyProcPort && MyProcPort->remote_host)
! {
! appendStringInfoChar(&buf, '"');
! appendStringInfoString(&buf, MyProcPort->remote_host);
! if (MyProcPort->remote_port && MyProcPort->remote_port[0] != '\0')
! {
! appendStringInfoChar(&buf, ':');
! appendStringInfoString(&buf, MyProcPort->remote_port);
! }
! appendStringInfoChar(&buf, '"');
! }
! }
! break;
! case CSVLOG_SESSION_ID:
! /* session id */
! appendStringInfo(&buf, "%lx.%x", (long) MyStartTime, MyProcPid);
! break;
! case CSVLOG_SESSION_LINE_NUM:
! /* Line number */
! appendStringInfo(&buf, "%ld", log_line_number);
! break;
! case CSVLOG_COMMAND_TAG:
! {
! /* PS display */
! if (MyProcPort)
! {
! StringInfoData msgbuf;
! const char *psdisp;
! int displen;
! initStringInfo(&msgbuf);
! psdisp = get_ps_display(&displen);
! appendBinaryStringInfo(&msgbuf, psdisp, displen);
! appendCSVLiteral(&buf, msgbuf.data);
! pfree(msgbuf.data);
! }
! }
! break;
! case CSVLOG_SESSION_START_TIME:
! {
! /* session start timestamp */
! if (formatted_start_time[0] == '\0')
! setup_formatted_start_time();
! appendStringInfoString(&buf, formatted_start_time);
! }
! break;
! case CSVLOG_VIRTUAL_TRANSACTION_ID:
! {
! /* Virtual transaction id */
! /* keep VXID format in sync with lockfuncs.c */
! if (MyProc != NULL && MyProc->backendId != InvalidBackendId)
! appendStringInfo(&buf, "%d/%u", MyProc->backendId, MyProc->lxid);
! }
! break;
! case CSVLOG_TRANSACTION_ID:
! /* Transaction id */
! appendStringInfo(&buf, "%u", GetTopTransactionIdIfAny());
! break;
! case CSVLOG_ERROR_SEVERITY:
! /* Error severity */
! appendStringInfoString(&buf, error_severity(edata->elevel));
! break;
! case CSVLOG_SQL_STATE_CODE:
! /* SQL state code */
! appendStringInfoString(&buf, unpack_sql_state(edata->sqlerrcode));
! break;
! case CSVLOG_MESSAGE:
! /* errmessage */
! appendCSVLiteral(&buf, edata->message);
! break;
! case CSVLOG_DETAIL:
! {
! /* errdetail or errdetail_log */
! if (edata->detail_log)
! appendCSVLiteral(&buf, edata->detail_log);
! else
! appendCSVLiteral(&buf, edata->detail);
! }
! break;
!
! case CSVLOG_HINT:
! /* errhint */
! appendCSVLiteral(&buf, edata->hint);
! break;
! case CSVLOG_INTERNAL_QUERY:
! /* internal query */
! appendCSVLiteral(&buf, edata->internalquery);
! break;
! case CSVLOG_INTERNAL_QUERY_POS:
! {
! /* if printed internal query, print internal pos too */
! if (edata->internalpos > 0 && edata->internalquery != NULL)
! appendStringInfo(&buf, "%d", edata->internalpos);
! }
! break;
! case CSVLOG_CONTEXT:
! /* errcontext */
! appendCSVLiteral(&buf, edata->context);
! break;
! case CSVLOG_QUERY:
! {
! /* user query --- only reported if not disabled by the caller */
! if (is_log_level_output(edata->elevel, log_min_error_statement) &&
! debug_query_string != NULL &&
! !edata->hide_stmt)
! print_stmt = true;
! if (print_stmt)
! appendCSVLiteral(&buf, debug_query_string);
! }
! break;
!
! case CSVLOG_QUERY_POS:
! {
! if (print_stmt && edata->cursorpos > 0)
! appendStringInfo(&buf, "%d", edata->cursorpos);
! }
! break;
!
! case CSVLOG_LOCATION:
! {
! /* file error location */
! if (Log_error_verbosity >= PGERROR_VERBOSE)
! {
! StringInfoData msgbuf;
!
! initStringInfo(&msgbuf);
!
! if (edata->funcname && edata->filename)
! appendStringInfo(&msgbuf, "%s, %s:%d",
! edata->funcname, edata->filename,
! edata->lineno);
! else if (edata->filename)
! appendStringInfo(&msgbuf, "%s:%d",
! edata->filename, edata->lineno);
! appendCSVLiteral(&buf, msgbuf.data);
! pfree(msgbuf.data);
! }
! }
! break;
! case CSVLOG_APPLICATION_NAME:
! {
! /* application name */
! if (application_name)
! appendCSVLiteral(&buf, application_name);
! }
! break;
! }
! }
appendStringInfoChar(&buf, '\n');
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
***************
*** 65,70 ****
--- 65,71 ----
#include "tsearch/ts_cache.h"
#include "utils/builtins.h"
#include "utils/bytea.h"
+ #include "utils/elog.h"
#include "utils/guc_tables.h"
#include "utils/memutils.h"
#include "utils/pg_locale.h"
***************
*** 191,196 **** static char *config_enum_get_options(struct config_enum * record,
--- 192,200 ----
const char *prefix, const char *suffix,
const char *separator);
+ /* Needs to be defined here because elog.h can't #include guc.h */
+ extern const char *assign_csvlog_fields(const char *newval,
+ bool doit, GucSource source);
/*
* Options for enum values defined in this module.
***************
*** 1034,1039 **** static struct config_bool ConfigureNamesBool[] =
--- 1038,1052 ----
false, NULL, NULL
},
{
+ {"csvlog_header", PGC_POSTMASTER, LOGGING_WHAT,
+ gettext_noop("Enables including a header on CSV log files."),
+ NULL,
+ },
+ &csvlog_header,
+ false, NULL, NULL
+ },
+
+ {
{"sql_inheritance", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
gettext_noop("Causes subtables to be included by default in various commands."),
NULL
***************
*** 2326,2331 **** static struct config_string ConfigureNamesString[] =
--- 2339,2355 ----
},
{
+ {"csvlog_fields", PGC_POSTMASTER, LOGGING_WHAT,
+ gettext_noop("Controls fields logged to CSV logfiles."),
+ gettext_noop("If blank, the default set of fields is used."),
+ GUC_LIST_INPUT
+ },
+ &csvlog_fields,
+ "log_time, user_name, database_name, process_id, connection_from, session_id, session_line_num, command_tag, session_start_time, virtual_transaction_id, transaction_id, error_severity, sql_state_code, message, detail, hint, internal_query, internal_query_pos, context, query, query_pos, location, application_name",
+ assign_csvlog_fields, NULL
+ },
+
+ {
{"log_timezone", PGC_SIGHUP, LOGGING_WHAT,
gettext_noop("Sets the time zone to use in log messages."),
NULL
*** a/src/backend/utils/misc/postgresql.conf.sample
--- b/src/backend/utils/misc/postgresql.conf.sample
***************
*** 360,366 ****
#log_hostname = off
#log_line_prefix = '' # special values:
# %a = application name
! # %u = user name
# %d = database name
# %r = remote host and port
# %h = remote host
--- 360,367 ----
#log_hostname = off
#log_line_prefix = '' # special values:
# %a = application name
! # %u = session user name
! # %U = current role name
# %d = database name
# %r = remote host and port
# %h = remote host
***************
*** 378,383 ****
--- 379,389 ----
# processes
# %% = '%'
# e.g. '<%u%%%d> '
+
+ # fields to include in the CSV log output
+ #csvlog_fields = 'log_time, user_name, database_name, process_id, connection_from, session_id, session_line_num, command_tag, session_start_time, virtual_transaction_id, transaction_id, error_severity, sql_state_code, message, detail, hint, internal_query, internal_query_pos, context, query, query_pos, location, application_name'
+ #csvlog_header = false # should csvlog files have a header?
+
#log_lock_waits = off # log lock waits >= deadlock_timeout
#log_statement = 'none' # none, ddl, mod, all
#log_temp_files = -1 # log temporary files equal or larger
*** a/src/include/utils/elog.h
--- b/src/include/utils/elog.h
***************
*** 330,337 **** typedef enum
--- 330,386 ----
extern int Log_error_verbosity;
extern char *Log_line_prefix;
+ extern char *csvlog_fields; /* List of fields to log with CSV logging */
+ extern bool csvlog_header; /* Whether to include a header on CSV log files */
extern int Log_destination;
+ /*
+ * Enum of the CSV fields we understand for CSV-based logging,
+ * if an new field is added, the enum has to be updated, the
+ * definition of field names in elog.c needs to be updated, and the
+ * new field needs to be handled in write_csv() in elog.c.
+ * Also be sure to update MAX_CSVLOG_OPTS if you change what the last
+ * option in the enum list is.
+ */
+ typedef enum LogCSVFields
+ {
+ CSVLOG_LOG_TIME,
+ CSVLOG_USER_NAME,
+ CSVLOG_ROLE_NAME,
+ CSVLOG_DATABASE_NAME,
+ CSVLOG_PROCESS_ID,
+ CSVLOG_CONNECTION_FROM,
+ CSVLOG_SESSION_ID,
+ CSVLOG_SESSION_LINE_NUM,
+ CSVLOG_COMMAND_TAG,
+ CSVLOG_SESSION_START_TIME,
+ CSVLOG_VIRTUAL_TRANSACTION_ID,
+ CSVLOG_TRANSACTION_ID,
+ CSVLOG_ERROR_SEVERITY,
+ CSVLOG_SQL_STATE_CODE,
+ CSVLOG_MESSAGE,
+ CSVLOG_DETAIL,
+ CSVLOG_HINT,
+ CSVLOG_INTERNAL_QUERY,
+ CSVLOG_INTERNAL_QUERY_POS,
+ CSVLOG_CONTEXT,
+ CSVLOG_QUERY,
+ CSVLOG_QUERY_POS,
+ CSVLOG_LOCATION,
+ CSVLOG_APPLICATION_NAME
+ } LogCSVFields;
+
+ /* Make sure to update this if you add CSV log options and change
+ * what the last CSVLOG option is */
+ #define MAX_CSVLOG_OPTS CSVLOG_APPLICATION_NAME+1
+
+ /*
+ * Array of the names of each of the CSV fields we allow for logging,
+ * if an new field is added, the enum has to be updated *and* the
+ * definition of field names in elog.c needs to be updated.
+ */
+ extern const char *CSVFieldNames[];
+
/* Log destination bitmap */
#define LOG_DESTINATION_STDERR 1
#define LOG_DESTINATION_SYSLOG 2
*** a/src/tools/pgindent/typedefs.list
--- b/src/tools/pgindent/typedefs.list
***************
*** 855,860 **** LockTagType
--- 855,861 ----
LockTupleMode
LockingClause
LogStmtLevel
+ LogCSVFields
LogicalTape
LogicalTapeSet
MAGIC
signature.asc
Description: Digital signature
