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
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
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->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
* construction and won't ever go back.
*/
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 */
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
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->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();
}
/*
* 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)));
#ifndef PG_SYSLOG_LIMIT
#endif
/*
* Write a message line to syslog if the syslog option is set.
*
* 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.
*/
static void
write_syslog(int level, const char *line)
{
static unsigned long seq = 0;
static int syslog_fac = LOG_LOCAL0;
if (!openlog_done)
{
if (pg_strcasecmp(Syslog_facility, "LOCAL0") == 0)
syslog_fac = LOG_LOCAL0;
if (pg_strcasecmp(Syslog_facility, "LOCAL1") == 0)
syslog_fac = LOG_LOCAL1;
if (pg_strcasecmp(Syslog_facility, "LOCAL2") == 0)
syslog_fac = LOG_LOCAL2;
if (pg_strcasecmp(Syslog_facility, "LOCAL3") == 0)
syslog_fac = LOG_LOCAL3;
if (pg_strcasecmp(Syslog_facility, "LOCAL4") == 0)
syslog_fac = LOG_LOCAL4;
if (pg_strcasecmp(Syslog_facility, "LOCAL5") == 0)
syslog_fac = LOG_LOCAL5;
if (pg_strcasecmp(Syslog_facility, "LOCAL6") == 0)
syslog_fac = LOG_LOCAL6;
if (pg_strcasecmp(Syslog_facility, "LOCAL7") == 0)
syslog_fac = LOG_LOCAL7;
openlog(Syslog_ident, LOG_PID | LOG_NDELAY | LOG_NOWAIT, syslog_fac);
openlog_done = true;
}
/*
* We add a sequence number to each log message to suppress "same"
* messages.
*/
seq++;
/* divide into multiple syslog() calls if message is too long */
/* or if the message contains embedded NewLine(s) '\n' */
if (len > PG_SYSLOG_LIMIT || strchr(line, '\n') != 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--;
continue;
}
strncpy(buf, line, PG_SYSLOG_LIMIT);
buf[PG_SYSLOG_LIMIT] = '\0';
if (strchr(buf, '\n') != NULL)
*strchr(buf, '\n') = '\0';
buflen = strlen(buf);
/* trim to multibyte letter boundary */
buflen = pg_mbcliplen(buf, buflen, buflen);
if (buflen <= 0)
buf[buflen] = '\0';
/* already word boundary? */
if (!isspace((unsigned char) line[buflen]) &&
line[buflen] != '\0')
/* 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 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;
}
}
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
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;
}
ReportEvent(evtHandle,
eventlevel,
0,
NULL,
1,
0,
&line,
NULL);
}
* Format tag info for log lines; append to the provided buffer.
static void
log_line_prefix(StringInfo buf)
/* static counter for line numbers */
static long log_line_number = 0;
/* has counter been reset in current process? */
static int log_my_pid = 0;
/*
* This is one of the few places where we'd rather not inherit a
* static variable's value from the postmaster. But since we will,
* reset it when MyProcPid changes.
*/
if (log_my_pid != MyProcPid)
{
log_line_number = 0;
log_my_pid = MyProcPid;
}
log_line_number++;
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 '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':
if (MyProcPort)
{
appendStringInfo(buf, "%lx.%x",
}
break;
case 'p':
appendStringInfo(buf, "%d", MyProcPid);
break;
case 'l':
appendStringInfo(buf, "%ld", log_line_number);
break;
case 'm':
{
/*
* Note: for %m, %t, and %s we deliberately use the
* C library's strftime/localtime, and not the
* equivalent functions from src/timezone. This
* ensures that all backends will report log entries
* in the same timezone, namely whatever C-library
* setting they inherit from the postmaster. If we
* used src/timezone then local settings of the
* TimeZone GUC variable would confuse the log.
*/
time_t stamp_time;
char strfbuf[128], msbuf[8];
struct timeval tv;
gettimeofday(&tv, NULL);
stamp_time = tv.tv_sec;
strftime(strfbuf, sizeof(strfbuf),
/* leave room for milliseconds... */
/* Win32 timezone names are too long so don't print them. */
#ifndef WIN32
"%Y-%m-%d %H:%M:%S %Z",
#else
"%Y-%m-%d %H:%M:%S ",
#endif
localtime(&stamp_time));
/* 'paste' milliseconds into place... */
sprintf(msbuf, ".%03d", (int) (tv.tv_usec/1000));
strncpy(strfbuf+19, msbuf, 4);
appendStringInfoString(buf, strfbuf);
}
break;
case 't':
{
time_t stamp_time = time(NULL);
char strfbuf[128];
strftime(strfbuf, sizeof(strfbuf),
/* Win32 timezone names are too long so don't print them. */
#ifndef WIN32
"%Y-%m-%d %H:%M:%S %Z",
#else
"%Y-%m-%d %H:%M:%S",
#endif
localtime(&stamp_time));
appendStringInfoString(buf, strfbuf);
}
break;
case 's':
if (MyProcPort)
{
time_t stamp_time = MyProcPort->session_start.tv_sec;
char strfbuf[128];
strftime(strfbuf, sizeof(strfbuf),
"%Y-%m-%d %H:%M:%S %Z",
localtime(&stamp_time));
appendStringInfoString(buf, strfbuf);
}
break;
case 'i':
if (MyProcPort)
appendStringInfo(buf, "%s", MyProcPort->commandTag);
break;
case 'r':
if (MyProcPort)
{
appendStringInfo(buf, "%s", MyProcPort->remote_host);
if (strlen(MyProcPort->remote_port) > 0)
appendStringInfo(buf, "(%s)",
MyProcPort->remote_port);
}
break;
case 'h':
if (MyProcPort)
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 'x':
if (MyProcPort)
{
if (IsTransactionState())
appendStringInfo(buf, "%u", GetTopTransactionId());
else
appendStringInfo(buf, "%u", InvalidTransactionId);
}
break;
appendStringInfoChar(buf, '%');
break;
default:
/* format error - ignore it */
break;
/*
* Unpack MAKE_SQLSTATE code. Note that this returns a pointer to a
* static buffer.
*/
char *
unpack_sql_state(int sql_state)
{
static char buf[12];
int i;
for (i = 0; i < 5; i++)
{
buf[i] = PGUNSIXBIT(sql_state);
sql_state >>= 6;
}
buf[i] = '\0';
return buf;
}
/*
* Write error report to server's log
*/
static void
send_message_to_server_log(ErrorData *edata)
initStringInfo(&buf);
log_line_prefix(&buf);
appendStringInfo(&buf, "%s: ", error_severity(edata->elevel));
if (Log_error_verbosity >= PGERROR_VERBOSE)
appendStringInfo(&buf, "%s: ", unpack_sql_state(edata->sqlerrcode));
if (edata->message)
append_with_tabs(&buf, edata->message);
append_with_tabs(&buf, _("missing error text"));
if (edata->cursorpos > 0)
appendStringInfo(&buf, _(" at character %d"),
edata->cursorpos);
else if (edata->internalpos > 0)
appendStringInfo(&buf, _(" at character %d"),
edata->internalpos);
appendStringInfoChar(&buf, '\n');
if (Log_error_verbosity >= PGERROR_DEFAULT)
{
if (edata->detail)
log_line_prefix(&buf);
appendStringInfoString(&buf, _("DETAIL: "));
append_with_tabs(&buf, edata->detail);
appendStringInfoChar(&buf, '\n');
}
if (edata->hint)
log_line_prefix(&buf);
appendStringInfoString(&buf, _("HINT: "));
append_with_tabs(&buf, edata->hint);
appendStringInfoChar(&buf, '\n');
}
if (edata->internalquery)
{
log_line_prefix(&buf);
appendStringInfoString(&buf, _("QUERY: "));
append_with_tabs(&buf, edata->internalquery);
appendStringInfoChar(&buf, '\n');
}
if (edata->context)
log_line_prefix(&buf);
appendStringInfoString(&buf, _("CONTEXT: "));
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)
{
log_line_prefix(&buf);
appendStringInfo(&buf, _("LOCATION: %s, %s:%d\n"),
edata->funcname, edata->filename,
edata->lineno);
else if (edata->filename)
{
log_line_prefix(&buf);
appendStringInfo(&buf, _("LOCATION: %s:%d\n"),
edata->filename, edata->lineno);
}
}
* If the user wants the query that generated this error logged, do
* it.
*/
if (edata->elevel >= log_min_error_statement && debug_query_string != NULL)
log_line_prefix(&buf);
appendStringInfoString(&buf, _("STATEMENT: "));
append_with_tabs(&buf, debug_query_string);
appendStringInfoChar(&buf, '\n');
}
#ifdef HAVE_SYSLOG
/* Write to syslog, if enabled */
if (Log_destination & LOG_DESTINATION_SYSLOG)
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
{
int syslog_level;
switch (edata->elevel)
{
case DEBUG5:
case DEBUG4:
case DEBUG3:
case DEBUG2:
case DEBUG1:
syslog_level = LOG_DEBUG;
break;
case LOG:
case COMMERROR:
case INFO:
syslog_level = LOG_INFO;
break;
case NOTICE:
case WARNING:
syslog_level = LOG_NOTICE;
break;
case ERROR:
syslog_level = LOG_WARNING;
break;
case FATAL:
syslog_level = LOG_ERR;
break;
case PANIC:
default:
syslog_level = LOG_CRIT;
break;
}
write_syslog(syslog_level, buf.data);
}
#ifdef WIN32
/* Write to eventlog, if enabled */
if (Log_destination & LOG_DESTINATION_EVENTLOG)
{
write_eventlog(edata->elevel, buf.data);
}
#endif /* WIN32 */
/* Write to stderr, if enabled */
if ((Log_destination & LOG_DESTINATION_STDERR) || whereToSendOutput == Debug)
{
#ifdef WIN32
/*
* In a win32 service environment, there is no usable stderr. Capture
* anything going there and write it to the eventlog instead.
*
* If stderr redirection is active, it's ok to write to stderr
* because that's really a pipe to the syslogger process.
*/
if ((!Redirect_stderr || am_syslogger) && pgwin32_is_service())
write_eventlog(edata->elevel, buf.data);
else
#endif
fprintf(stderr, "%s", buf.data);
}
/* If in the syslogger process, try to write messages direct to file */
if (am_syslogger)
write_syslogger_file(buf.data, buf.len);
pfree(buf.data);
}
/*
* Write error report to client
*/
static void
send_message_to_frontend(ErrorData *edata)
{
StringInfoData msgbuf;
/* 'N' (Notice) is for nonfatal conditions, 'E' is for errors */
pq_beginmessage(&msgbuf, (edata->elevel < ERROR) ? 'N' : 'E');
if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
{
/* New style with separate fields */
Peter Eisentraut
committed
pq_sendbyte(&msgbuf, PG_DIAG_SEVERITY);
pq_sendstring(&msgbuf, error_severity(edata->elevel));
/* unpack MAKE_SQLSTATE code */
ssval = edata->sqlerrcode;
for (i = 0; i < 5; i++)
{
tbuf[i] = PGUNSIXBIT(ssval);
ssval >>= 6;
}
tbuf[i] = '\0';
Peter Eisentraut
committed
pq_sendbyte(&msgbuf, PG_DIAG_SQLSTATE);
pq_sendstring(&msgbuf, tbuf);
/* M field is required per protocol, so always send something */
Peter Eisentraut
committed
pq_sendbyte(&msgbuf, PG_DIAG_MESSAGE_PRIMARY);
if (edata->message)
pq_sendstring(&msgbuf, edata->message);
else
pq_sendstring(&msgbuf, _("missing error text"));
if (edata->detail)
{
Peter Eisentraut
committed
pq_sendbyte(&msgbuf, PG_DIAG_MESSAGE_DETAIL);
pq_sendstring(&msgbuf, edata->detail);
}
if (edata->hint)
{
Peter Eisentraut
committed
pq_sendbyte(&msgbuf, PG_DIAG_MESSAGE_HINT);
pq_sendstring(&msgbuf, edata->hint);
}
if (edata->context)
{
Peter Eisentraut
committed
pq_sendbyte(&msgbuf, PG_DIAG_CONTEXT);
pq_sendstring(&msgbuf, edata->context);
}
if (edata->cursorpos > 0)
{
snprintf(tbuf, sizeof(tbuf), "%d", edata->cursorpos);
Peter Eisentraut
committed
pq_sendbyte(&msgbuf, PG_DIAG_STATEMENT_POSITION);
pq_sendstring(&msgbuf, tbuf);
}
if (edata->internalpos > 0)
{
snprintf(tbuf, sizeof(tbuf), "%d", edata->internalpos);
pq_sendbyte(&msgbuf, PG_DIAG_INTERNAL_POSITION);
pq_sendstring(&msgbuf, tbuf);
}
if (edata->internalquery)
{
pq_sendbyte(&msgbuf, PG_DIAG_INTERNAL_QUERY);
pq_sendstring(&msgbuf, edata->internalquery);
}
if (edata->filename)
{
Peter Eisentraut
committed
pq_sendbyte(&msgbuf, PG_DIAG_SOURCE_FILE);
pq_sendstring(&msgbuf, edata->filename);
}
if (edata->lineno > 0)
{
snprintf(tbuf, sizeof(tbuf), "%d", edata->lineno);
Peter Eisentraut
committed
pq_sendbyte(&msgbuf, PG_DIAG_SOURCE_LINE);
pq_sendstring(&msgbuf, tbuf);
}
if (edata->funcname)
{
Peter Eisentraut
committed
pq_sendbyte(&msgbuf, PG_DIAG_SOURCE_FUNCTION);
pq_sendstring(&msgbuf, edata->funcname);
}
}
else
{
/* Old style --- gin up a backwards-compatible message */
StringInfoData buf;
initStringInfo(&buf);
appendStringInfo(&buf, "%s: ", error_severity(edata->elevel));
if (edata->show_funcname && edata->funcname)
appendStringInfo(&buf, "%s: ", edata->funcname);
if (edata->message)
appendStringInfoString(&buf, edata->message);
appendStringInfoString(&buf, _("missing error text"));
if (edata->cursorpos > 0)
appendStringInfo(&buf, _(" at character %d"),
edata->cursorpos);
else if (edata->internalpos > 0)
appendStringInfo(&buf, _(" at character %d"),
edata->internalpos);
appendStringInfoChar(&buf, '\n');
pq_sendstring(&msgbuf, buf.data);
pfree(buf.data);
}
pq_endmessage(&msgbuf);
/*
* This flush is normally not necessary, since postgres.c will flush
* out waiting data when control returns to the main loop. But it
* seems best to leave it here, so that the client has some clue what
* happened if the backend dies before getting back to the main loop
* ... error/notice messages should not be a performance-critical path
* anyway, so an extra flush won't hurt much ...
*/
pq_flush();
}
/*
* Support routines for formatting error messages.
*/
/*
* expand_fmt_string --- process special format codes in a format string
*
* We must replace %m with the appropriate strerror string, since vsnprintf
* won't know what to do with it.
*
* The result is a palloc'd string.
*/
static char *
expand_fmt_string(const char *fmt, ErrorData *edata)
const char *cp;
initStringInfo(&buf);
for (cp = fmt; *cp; cp++)
{
if (cp[0] == '%' && cp[1] != '\0')
{
cp++;
if (*cp == 'm')
{
/*
* Replace %m by system error string. If there are any
* %'s in the string, we'd better double them so that
* vsnprintf won't misinterpret.
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
*/
const char *cp2;
cp2 = useful_strerror(edata->saved_errno);
for (; *cp2; cp2++)
{
if (*cp2 == '%')
appendStringInfoCharMacro(&buf, '%');
appendStringInfoCharMacro(&buf, *cp2);
}
}
else
{
/* copy % and next char --- this avoids trouble with %%m */
appendStringInfoCharMacro(&buf, '%');
appendStringInfoCharMacro(&buf, *cp);
}
}
else
appendStringInfoCharMacro(&buf, *cp);
}
return buf.data;
}
/*
* A slightly cleaned-up version of strerror()
*/
static const char *
useful_strerror(int errnum)
{
/* this buffer is only used if errno has a bogus value */
static char errorstr_buf[48];
#ifdef WIN32
/* Winsock error code range, per WinError.h */
if (errnum >= 10000 && errnum <= 11999)
return pgwin32_socket_strerror(errnum);
#endif
str = strerror(errnum);
/*
* Some strerror()s return an empty string for out-of-range errno.
* This is ANSI C spec compliant, but not exactly useful.
*/
if (str == NULL || *str == '\0')
{
/*
* translator: This string will be truncated at 47 characters
* expanded.
*/
snprintf(errorstr_buf, sizeof(errorstr_buf),
_("operating system error %d"), errnum);
str = errorstr_buf;
}
return str;
}
/*
* error_severity --- get localized string representing elevel
*/
static const char *
error_severity(int elevel)
{
const char *prefix;
switch (elevel)
{
case DEBUG1:
case DEBUG2:
case DEBUG3:
case DEBUG4:
case DEBUG5:
prefix = _("DEBUG");
break;
case LOG:
case COMMERROR:
prefix = _("LOG");
break;
case INFO:
prefix = _("INFO");
break;
case NOTICE:
prefix = _("NOTICE");
break;
prefix = _("WARNING");
case ERROR:
prefix = _("ERROR");
break;
case FATAL:
prefix = _("FATAL");
break;
case PANIC:
prefix = _("PANIC");
break;
default:
prefix = "???";
break;
}
return prefix;
}
/*
* append_with_tabs
* Append the string to the StringInfo buffer, inserting a tab after any
* newline.
*/
static void
append_with_tabs(StringInfo buf, const char *str)
while ((ch = *str++) != '\0')
appendStringInfoCharMacro(buf, ch);
if (ch == '\n')
appendStringInfoCharMacro(buf, '\t');
}
}
* Write errors to stderr (or by equal means when stderr is