Newer
Older
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
* is intended for use in error callback subroutines that are editorializing
* on the layout of the error report.
*/
int
internalerrquery(const char *query)
{
ErrorData *edata = &errordata[errordata_stack_depth];
/* we don't bother incrementing recursion_depth */
CHECK_STACK_DEPTH();
if (edata->internalquery)
{
pfree(edata->internalquery);
edata->internalquery = NULL;
}
if (query)
edata->internalquery = MemoryContextStrdup(ErrorContext, query);
return 0; /* return value does not matter */
}
/*
* geterrcode --- return the currently set SQLSTATE error code
*
* This is only intended for use in error callback subroutines, since there
* is no other place outside elog.c where the concept is meaningful.
*/
int
geterrcode(void)
{
ErrorData *edata = &errordata[errordata_stack_depth];
/* we don't bother incrementing recursion_depth */
CHECK_STACK_DEPTH();
return edata->sqlerrcode;
}
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
/*
* geterrposition --- return the currently set error position (0 if none)
*
* This is only intended for use in error callback subroutines, since there
* is no other place outside elog.c where the concept is meaningful.
*/
int
geterrposition(void)
{
ErrorData *edata = &errordata[errordata_stack_depth];
/* we don't bother incrementing recursion_depth */
CHECK_STACK_DEPTH();
return edata->cursorpos;
}
/*
* getinternalerrposition --- same for internal error position
*
* This is only intended for use in error callback subroutines, since there
* is no other place outside elog.c where the concept is meaningful.
*/
int
getinternalerrposition(void)
{
ErrorData *edata = &errordata[errordata_stack_depth];
/* we don't bother incrementing recursion_depth */
CHECK_STACK_DEPTH();
return edata->internalpos;
}
* elog_start --- startup for old-style API
*
* All that we do here is stash the hidden filename/lineno/funcname
* arguments into a stack entry.
* We need this to be separate from elog_finish because there's no other
* portable way to deal with inserting extra arguments into the elog call.
* (If macros with variable numbers of arguments were portable, it'd be
* easy, but they aren't.)
*/
void
elog_start(const char *filename, int lineno, const char *funcname)
{
ErrorData *edata;
if (++errordata_stack_depth >= ERRORDATA_STACK_SIZE)
{
/*
* Wups, stack not big enough. We treat this as a PANIC condition
* because it suggests an infinite loop of errors during error
* recovery. Note that the message is intentionally not localized,
* else failure to convert it to client encoding could cause further
* recursion.
*/
errordata_stack_depth = -1; /* make room on stack */
ereport(PANIC, (errmsg_internal("ERRORDATA_STACK_SIZE exceeded")));
}
edata = &errordata[errordata_stack_depth];
edata->filename = filename;
edata->lineno = lineno;
edata->funcname = funcname;
/* errno is saved now so that error parameter eval can't change it */
edata->saved_errno = errno;
}
/*
* elog_finish --- finish up for old-style API
*/
void
{
ErrorData *edata = &errordata[errordata_stack_depth];
MemoryContext oldcontext;
CHECK_STACK_DEPTH();
/*
* Do errstart() to see if we actually want to report the message.
*/
errordata_stack_depth--;
errno = edata->saved_errno;
if (!errstart(elevel, edata->filename, edata->lineno, edata->funcname, NULL))
return; /* nothing to do */
/*
* Format error message just like errmsg_internal().
*/
recursion_depth++;
oldcontext = MemoryContextSwitchTo(ErrorContext);
EVALUATE_MESSAGE(message, false, false);
MemoryContextSwitchTo(oldcontext);
recursion_depth--;
/*
* And let errfinish() finish up.
*/
errfinish(0);
}
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
/*
* Actual output of the top-of-stack error message
*
* In the ereport(ERROR) case this is called from PostgresMain (or not at all,
* if the error is caught by somebody). For all other severity levels this
* is called by errfinish.
*/
void
EmitErrorReport(void)
{
ErrorData *edata = &errordata[errordata_stack_depth];
MemoryContext oldcontext;
recursion_depth++;
CHECK_STACK_DEPTH();
oldcontext = MemoryContextSwitchTo(ErrorContext);
/* Send to server log, if enabled */
if (edata->output_to_server)
send_message_to_server_log(edata);
/* Send to client, if enabled */
if (edata->output_to_client)
send_message_to_frontend(edata);
MemoryContextSwitchTo(oldcontext);
recursion_depth--;
}
/*
* CopyErrorData --- obtain a copy of the topmost error stack entry
*
* This is only for use in error handler code. The data is copied into the
* current memory context, so callers should always switch away from
* ErrorContext first; otherwise it will be lost when FlushErrorState is done.
*/
ErrorData *
CopyErrorData(void)
{
ErrorData *edata = &errordata[errordata_stack_depth];
ErrorData *newedata;
/*
* we don't increment recursion_depth because out-of-memory here does not
* indicate a problem within the error subsystem.
*/
CHECK_STACK_DEPTH();
Assert(CurrentMemoryContext != ErrorContext);
/* Copy the struct itself */
newedata = (ErrorData *) palloc(sizeof(ErrorData));
memcpy(newedata, edata, sizeof(ErrorData));
/* Make copies of separately-allocated fields */
if (newedata->message)
newedata->message = pstrdup(newedata->message);
if (newedata->detail)
newedata->detail = pstrdup(newedata->detail);
if (newedata->detail_log)
newedata->detail_log = pstrdup(newedata->detail_log);
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
if (newedata->hint)
newedata->hint = pstrdup(newedata->hint);
if (newedata->context)
newedata->context = pstrdup(newedata->context);
if (newedata->internalquery)
newedata->internalquery = pstrdup(newedata->internalquery);
return newedata;
}
/*
* FreeErrorData --- free the structure returned by CopyErrorData.
*
* Error handlers should use this in preference to assuming they know all
* the separately-allocated fields.
*/
void
FreeErrorData(ErrorData *edata)
{
if (edata->message)
pfree(edata->message);
if (edata->detail)
pfree(edata->detail);
if (edata->detail_log)
pfree(edata->detail_log);
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
if (edata->hint)
pfree(edata->hint);
if (edata->context)
pfree(edata->context);
if (edata->internalquery)
pfree(edata->internalquery);
pfree(edata);
}
/*
* FlushErrorState --- flush the error state after error recovery
*
* This should be called by an error handler after it's done processing
* the error; or as soon as it's done CopyErrorData, if it intends to
* do stuff that is likely to provoke another error. You are not "out" of
* the error subsystem until you have done this.
*/
void
FlushErrorState(void)
{
/*
* Reset stack to empty. The only case where it would be more than one
* deep is if we serviced an error that interrupted construction of
* another message. We assume control escaped out of that message
*/
errordata_stack_depth = -1;
recursion_depth = 0;
/* Delete all data in ErrorContext */
MemoryContextResetAndDeleteChildren(ErrorContext);
}
/*
* ReThrowError --- re-throw a previously copied error
*
* A handler can do CopyErrorData/FlushErrorState to get out of the error
* subsystem, then do some processing, and finally ReThrowError to re-throw
* the original error. This is slower than just PG_RE_THROW() but should
* be used if the "some processing" is likely to incur another error.
*/
void
ReThrowError(ErrorData *edata)
{
ErrorData *newedata;
Assert(edata->elevel == ERROR);
/* Push the data back into the error context */
recursion_depth++;
MemoryContextSwitchTo(ErrorContext);
if (++errordata_stack_depth >= ERRORDATA_STACK_SIZE)
{
/*
* Wups, stack not big enough. We treat this as a PANIC condition
* because it suggests an infinite loop of errors during error
* recovery.
*/
errordata_stack_depth = -1; /* make room on stack */
ereport(PANIC, (errmsg_internal("ERRORDATA_STACK_SIZE exceeded")));
}
newedata = &errordata[errordata_stack_depth];
memcpy(newedata, edata, sizeof(ErrorData));
/* Make copies of separately-allocated fields */
if (newedata->message)
newedata->message = pstrdup(newedata->message);
if (newedata->detail)
newedata->detail = pstrdup(newedata->detail);
if (newedata->detail_log)
newedata->detail_log = pstrdup(newedata->detail_log);
if (newedata->hint)
newedata->hint = pstrdup(newedata->hint);
if (newedata->context)
newedata->context = pstrdup(newedata->context);
if (newedata->internalquery)
newedata->internalquery = pstrdup(newedata->internalquery);
recursion_depth--;
PG_RE_THROW();
}
/*
* pg_re_throw --- out-of-line implementation of PG_RE_THROW() macro
*/
void
pg_re_throw(void)
{
/* If possible, throw the error to the next outer setjmp handler */
if (PG_exception_stack != NULL)
siglongjmp(*PG_exception_stack, 1);
else
{
/*
* If we get here, elog(ERROR) was thrown inside a PG_TRY block, which
* we have now exited only to discover that there is no outer setjmp
* handler to pass the error to. Had the error been thrown outside
* the block to begin with, we'd have promoted the error to FATAL, so
* the correct behavior is to make it FATAL now; that is, emit it and
* then call proc_exit.
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
*/
ErrorData *edata = &errordata[errordata_stack_depth];
Assert(errordata_stack_depth >= 0);
Assert(edata->elevel == ERROR);
edata->elevel = FATAL;
/*
* At least in principle, the increase in severity could have changed
* where-to-output decisions, so recalculate. This should stay in
* sync with errstart(), which see for comments.
*/
if (IsPostmasterEnvironment)
edata->output_to_server = is_log_level_output(FATAL,
log_min_messages);
else
edata->output_to_server = (FATAL >= log_min_messages);
if (whereToSendOutput == DestRemote)
{
if (ClientAuthInProgress)
edata->output_to_client = true;
else
edata->output_to_client = (FATAL >= client_min_messages);
}
/*
* We can use errfinish() for the rest, but we don't want it to call
* any error context routines a second time. Since we know we are
* about to exit, it should be OK to just clear the context stack.
*/
error_context_stack = NULL;
errfinish(0);
}
/* We mustn't return... */
ExceptionalCondition("pg_re_throw tried to return", "FailedAssertion",
__FILE__, __LINE__);
/*
* Since ExceptionalCondition isn't declared noreturn because of
* TrapMacro(), we need this to keep gcc from complaining.
*/
abort();
}
/*
* Initialization of error output file
*/
void
DebugFileOpen(void)
Bruce Momjian
committed
int fd,
istty;
if (OutputFileName[0])
{
/*
* A debug-output file name was given.
*
* Make sure we can write the file, and find out if it's a tty.
*/
if ((fd = open(OutputFileName, O_CREAT | O_APPEND | O_WRONLY,
0666)) < 0)
ereport(FATAL,
errmsg("could not open file \"%s\": %m", OutputFileName)));
istty = isatty(fd);
close(fd);
/*
* Redirect our stderr to the debug output file.
*/
if (!freopen(OutputFileName, "a", stderr))
ereport(FATAL,
Peter Eisentraut
committed
errmsg("could not reopen file \"%s\" as stderr: %m",
OutputFileName)));
* If the file is a tty and we're running under the postmaster, try to
* send stdout there as well (if it isn't a tty then stderr will block
* out stdout, so we may as well let stdout go wherever it was going
* before).
if (istty && IsUnderPostmaster)
if (!freopen(OutputFileName, "a", stdout))
ereport(FATAL,
errmsg("could not reopen file \"%s\" as stdout: %m",
OutputFileName)));
/*
* Set or update the parameters for syslog logging
*/
void
set_syslog_parameters(const char *ident, int facility)
{
/*
* guc.c is likely to call us repeatedly with same parameters, so don't
* thrash the syslog connection unnecessarily. Also, we do not re-open
* the connection until needed, since this routine will get called whether
* or not Log_destination actually mentions syslog.
* Note that we make our own copy of the ident string rather than relying
* on guc.c's. This may be overly paranoid, but it ensures that we cannot
* accidentally free a string that syslog is still using.
*/
if (syslog_ident == NULL || strcmp(syslog_ident, ident) != 0 ||
syslog_facility != facility)
{
if (openlog_done)
{
closelog();
openlog_done = false;
}
if (syslog_ident)
free(syslog_ident);
syslog_ident = strdup(ident);
/* if the strdup fails, we will cope in write_syslog() */
syslog_facility = facility;
}
}
* Write a message line to syslog
*/
static void
write_syslog(int level, const char *line)
{
static unsigned long seq = 0;
int len;
const char *nlpos;
/* Open syslog connection if not done yet */
if (!openlog_done)
{
openlog(syslog_ident ? syslog_ident : "postgres",
LOG_PID | LOG_NDELAY | LOG_NOWAIT,
syslog_facility);
openlog_done = true;
}
/*
* We add a sequence number to each log message to suppress "same"
* messages.
*/
seq++;
* Our problem here is that many syslog implementations don't handle long
* messages in an acceptable manner. While this function doesn't help that
* fact, it does work around by splitting up messages into smaller pieces.
* We divide into multiple syslog() calls if message is too long or if the
* message contains embedded newline(s).
*/
len = strlen(line);
nlpos = strchr(line, '\n');
if (len > PG_SYSLOG_LIMIT || nlpos != NULL)
while (len > 0)
{
char buf[PG_SYSLOG_LIMIT + 1];
int buflen;
int i;
/* if we start at a newline, move ahead one char */
if (line[0] == '\n')
{
line++;
len--;
/* we need to recompute the next newline's position, too */
nlpos = strchr(line, '\n');
continue;
}
/* copy one line, or as much as will fit, to buf */
if (nlpos != NULL)
buflen = nlpos - line;
else
buflen = len;
buflen = Min(buflen, PG_SYSLOG_LIMIT);
memcpy(buf, line, buflen);
buf[buflen] = '\0';
/* trim to multibyte letter boundary */
buflen = pg_mbcliplen(buf, buflen, buflen);
if (buflen <= 0)
buf[buflen] = '\0';
/* already word boundary? */
if (line[buflen] != '\0' &&
!isspace((unsigned char) line[buflen]))
/* try to divide at word boundary */
i = buflen - 1;
while (i > 0 && !isspace((unsigned char) buf[i]))
if (i > 0) /* else couldn't divide word boundary */
{
buflen = i;
buf[i] = '\0';
}
}
chunk_nr++;
syslog(level, "[%lu-%d] %s", seq, chunk_nr, buf);
line += buflen;
len -= buflen;
}
}
else
{
/* message short enough */
syslog(level, "[%lu] %s", seq, line);
#endif /* HAVE_SYSLOG */
#ifdef WIN32
/*
* Write a message line to the windows event log
*/
static void
write_eventlog(int level, const char *line, int len)
WCHAR *utf16;
int eventlevel = EVENTLOG_ERROR_TYPE;
static HANDLE evtHandle = INVALID_HANDLE_VALUE;
if (evtHandle == INVALID_HANDLE_VALUE)
{
evtHandle = RegisterEventSource(NULL, "PostgreSQL");
if (evtHandle == NULL)
{
evtHandle = INVALID_HANDLE_VALUE;
return;
}
}
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
switch (level)
{
case DEBUG5:
case DEBUG4:
case DEBUG3:
case DEBUG2:
case DEBUG1:
case LOG:
case COMMERROR:
case INFO:
case NOTICE:
eventlevel = EVENTLOG_INFORMATION_TYPE;
break;
case WARNING:
eventlevel = EVENTLOG_WARNING_TYPE;
break;
case ERROR:
case FATAL:
case PANIC:
default:
eventlevel = EVENTLOG_ERROR_TYPE;
break;
}
/*
* Convert message to UTF16 text and write it with ReportEventW, but
* fall-back into ReportEventA if conversion failed.
*
* Also verify that we are not on our way into error recursion trouble due
* to error messages thrown deep inside pgwin32_toUTF16().
*/
if (GetDatabaseEncoding() != GetPlatformEncoding() &&
!in_error_recursion_trouble())
{
utf16 = pgwin32_toUTF16(line, len, NULL);
if (utf16)
{
ReportEventW(evtHandle,
eventlevel,
0,
0, /* All events are Id 0 */
NULL,
1,
0,
(LPCWSTR *) &utf16,
NULL);
pfree(utf16);
return;
}
}
ReportEventA(evtHandle,
eventlevel,
0,
0, /* All events are Id 0 */
NULL,
1,
0,
&line,
NULL);
static void
write_console(const char *line, int len)
{
#ifdef WIN32
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
/*
* WriteConsoleW() will fail of stdout is redirected, so just fall through
* to writing unconverted to the logfile in this case.
*/
if (GetDatabaseEncoding() != GetPlatformEncoding() &&
!in_error_recursion_trouble() &&
!redirection_done)
{
WCHAR *utf16;
int utf16len;
utf16 = pgwin32_toUTF16(line, len, &utf16len);
if (utf16 != NULL)
{
HANDLE stdHandle;
DWORD written;
stdHandle = GetStdHandle(STD_ERROR_HANDLE);
if (WriteConsoleW(stdHandle, utf16, utf16len, &written, NULL))
{
pfree(utf16);
return;
}
/*
* In case WriteConsoleW() failed, fall back to writing the
* message unconverted.
*/
pfree(utf16);
}
}
#else
/*
* Conversion on non-win32 platform is not implemented yet. It requires
* non-throw version of pg_do_encoding_conversion(), that converts
* unconvertable characters to '?' without errors.
*/
#endif
write(fileno(stderr), line, len);
}
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
/*
* setup formatted_log_time, for consistent times between CSV and regular logs
*/
static void
setup_formatted_log_time(void)
{
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... */
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
"%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);
}
/*
* setup formatted_start_time
*/
static void
setup_formatted_start_time(void)
{
pg_time_t stamp_time = (pg_time_t) MyStartTime;
pg_tz *tz;
/*
* 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_start_time, FORMATTED_TS_LEN,
"%Y-%m-%d %H:%M:%S %Z",
pg_localtime(&stamp_time, tz));
}
* Format tag info for log lines; append to the provided buffer.
static void
log_line_prefix(StringInfo buf, ErrorData *edata)
/* 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
Andrew Dunstan
committed
* 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;
Andrew Dunstan
committed
formatted_start_time[0] = '\0';
}
log_line_number++;
if (Log_line_prefix == NULL)
return; /* in case guc hasn't run yet */
format_len = strlen(Log_line_prefix);
for (i = 0; i < format_len; i++)
if (Log_line_prefix[i] != '%')
/* literal char, just copy */
appendStringInfoChar(buf, Log_line_prefix[i]);
continue;
}
/* go to char after '%' */
i++;
if (i >= format_len)
break; /* format error - ignore it */
/* process the option */
switch (Log_line_prefix[i])
case 'a':
if (MyProcPort)
{
const char *appname = application_name;
if (appname == NULL || *appname == '\0')
appname = _("[unknown]");
appendStringInfo(buf, "%s", appname);
}
break;
case 'u':
if (MyProcPort)
{
const char *username = MyProcPort->user_name;
if (username == NULL || *username == '\0')
username = _("[unknown]");
appendStringInfo(buf, "%s", username);
}
break;
if (MyProcPort)
{
const char *dbname = MyProcPort->database_name;
if (dbname == NULL || *dbname == '\0')
dbname = _("[unknown]");
appendStringInfo(buf, "%s", dbname);
}
break;
case 'c':
appendStringInfo(buf, "%lx.%x", (long) (MyStartTime), MyProcPid);
break;
case 'p':
appendStringInfo(buf, "%d", MyProcPid);
break;
case 'l':
appendStringInfo(buf, "%ld", log_line_number);
break;
case 'm':
setup_formatted_log_time();
appendStringInfoString(buf, formatted_log_time);
break;
case 't':
{
pg_time_t stamp_time = (pg_time_t) time(NULL);
pg_tz *tz;
tz = log_timezone ? log_timezone : gmt_timezone;
pg_strftime(strfbuf, sizeof(strfbuf),
"%Y-%m-%d %H:%M:%S %Z",
pg_localtime(&stamp_time, tz));
appendStringInfoString(buf, strfbuf);
}
break;
case 's':
Andrew Dunstan
committed
if (formatted_start_time[0] == '\0')
setup_formatted_start_time();
Andrew Dunstan
committed
appendStringInfoString(buf, formatted_start_time);
break;
case 'i':
if (MyProcPort)
{
const char *psdisp;
int displen;
psdisp = get_ps_display(&displen);
appendBinaryStringInfo(buf, psdisp, displen);
break;
case 'r':
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);
}
break;
case 'h':
if (MyProcPort && MyProcPort->remote_host)
appendStringInfo(buf, "%s", MyProcPort->remote_host);
break;
case 'q':
/* in postmaster and friends, stop if %q is seen */
/* in a backend, just ignore */
if (MyProcPort == NULL)
i = format_len;
break;
case 'v':
/* keep VXID format in sync with lockfuncs.c */
if (MyProc != NULL && MyProc->backendId != InvalidBackendId)
appendStringInfo(buf, "%d/%u",
MyProc->backendId, MyProc->lxid);
break;
case 'x':
appendStringInfo(buf, "%u", GetTopTransactionIdIfAny());
case 'e':
appendStringInfoString(buf, unpack_sql_state(edata->sqlerrcode));
break;
appendStringInfoChar(buf, '%');
break;
default:
/* format error - ignore it */
break;
Andrew Dunstan
committed
/*
* append a CSV'd version of a string to a StringInfo
* We use the PostgreSQL defaults for CSV, i.e. quote = escape = '"'
Alvaro Herrera
committed
* If it's NULL, append nothing.
Andrew Dunstan
committed
*/
static inline void
appendCSVLiteral(StringInfo buf, const char *data)
Andrew Dunstan
committed
{
Alvaro Herrera
committed
/* avoid confusing an empty string with NULL */
if (p == NULL)
return;
appendStringInfoCharMacro(buf, '"');
while ((c = *p++) != '\0')
{
if (c == '"')
appendStringInfoCharMacro(buf, '"');
appendStringInfoCharMacro(buf, c);
}
appendStringInfoCharMacro(buf, '"');
Andrew Dunstan
committed
}
Alvaro Herrera
committed
* Constructs the error message, depending on the Errordata it gets, in a CSV
* format which is described in doc/src/sgml/config.sgml.
Andrew Dunstan
committed
*/
static void
write_csvlog(ErrorData *edata)
{
StringInfoData buf;
bool print_stmt = false;
Andrew Dunstan
committed
/* 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;
Andrew Dunstan
committed
formatted_start_time[0] = '\0';
Andrew Dunstan
committed
initStringInfo(&buf);
Alvaro Herrera
committed
* 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.
Andrew Dunstan
committed
*/
if (formatted_log_time[0] == '\0')
setup_formatted_log_time();
Andrew Dunstan
committed
appendStringInfoString(&buf, formatted_log_time);
appendStringInfoChar(&buf, ',');
/* username */
if (MyProcPort)
Alvaro Herrera
committed
appendCSVLiteral(&buf, MyProcPort->user_name);
Andrew Dunstan
committed
appendStringInfoChar(&buf, ',');
Alvaro Herrera
committed
/* database name */
Andrew Dunstan
committed
if (MyProcPort)
Alvaro Herrera
committed
appendCSVLiteral(&buf, MyProcPort->database_name);
Andrew Dunstan
committed
appendStringInfoChar(&buf, ',');
/* Process id */
if (MyProcPid != 0)
appendStringInfo(&buf, "%d", MyProcPid);