diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 13:44:03 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 13:44:03 +0000 |
commit | 293913568e6a7a86fd1479e1cff8e2ecb58d6568 (patch) | |
tree | fc3b469a3ec5ab71b36ea97cc7aaddb838423a0c /src/backend/utils/error | |
parent | Initial commit. (diff) | |
download | postgresql-16-293913568e6a7a86fd1479e1cff8e2ecb58d6568.tar.xz postgresql-16-293913568e6a7a86fd1479e1cff8e2ecb58d6568.zip |
Adding upstream version 16.2.upstream/16.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/backend/utils/error')
-rw-r--r-- | src/backend/utils/error/Makefile | 21 | ||||
-rw-r--r-- | src/backend/utils/error/assert.c | 67 | ||||
-rw-r--r-- | src/backend/utils/error/csvlog.c | 264 | ||||
-rw-r--r-- | src/backend/utils/error/elog.c | 3788 | ||||
-rw-r--r-- | src/backend/utils/error/jsonlog.c | 303 | ||||
-rw-r--r-- | src/backend/utils/error/meson.build | 8 |
6 files changed, 4451 insertions, 0 deletions
diff --git a/src/backend/utils/error/Makefile b/src/backend/utils/error/Makefile new file mode 100644 index 0000000..65ba61f --- /dev/null +++ b/src/backend/utils/error/Makefile @@ -0,0 +1,21 @@ +#------------------------------------------------------------------------- +# +# Makefile-- +# Makefile for utils/error +# +# IDENTIFICATION +# src/backend/utils/error/Makefile +# +#------------------------------------------------------------------------- + +subdir = src/backend/utils/error +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global + +OBJS = \ + assert.o \ + csvlog.o \ + elog.o \ + jsonlog.o + +include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/utils/error/assert.c b/src/backend/utils/error/assert.c new file mode 100644 index 0000000..719dd7b --- /dev/null +++ b/src/backend/utils/error/assert.c @@ -0,0 +1,67 @@ +/*------------------------------------------------------------------------- + * + * assert.c + * Assert support code. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/error/assert.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <unistd.h> +#ifdef HAVE_EXECINFO_H +#include <execinfo.h> +#endif + +/* + * ExceptionalCondition - Handles the failure of an Assert() + * + * We intentionally do not go through elog() here, on the grounds of + * wanting to minimize the amount of infrastructure that has to be + * working to report an assertion failure. + */ +void +ExceptionalCondition(const char *conditionName, + const char *fileName, + int lineNumber) +{ + /* Report the failure on stderr (or local equivalent) */ + if (!PointerIsValid(conditionName) + || !PointerIsValid(fileName)) + write_stderr("TRAP: ExceptionalCondition: bad arguments in PID %d\n", + (int) getpid()); + else + write_stderr("TRAP: failed Assert(\"%s\"), File: \"%s\", Line: %d, PID: %d\n", + conditionName, fileName, lineNumber, (int) getpid()); + + /* Usually this shouldn't be needed, but make sure the msg went out */ + fflush(stderr); + + /* If we have support for it, dump a simple backtrace */ +#ifdef HAVE_BACKTRACE_SYMBOLS + { + void *buf[100]; + int nframes; + + nframes = backtrace(buf, lengthof(buf)); + backtrace_symbols_fd(buf, nframes, fileno(stderr)); + } +#endif + + /* + * If configured to do so, sleep indefinitely to allow user to attach a + * debugger. It would be nice to use pg_usleep() here, but that can sleep + * at most 2G usec or ~33 minutes, which seems too short. + */ +#ifdef SLEEP_ON_ASSERT + sleep(1000000); +#endif + + abort(); +} diff --git a/src/backend/utils/error/csvlog.c b/src/backend/utils/error/csvlog.c new file mode 100644 index 0000000..29c83a7 --- /dev/null +++ b/src/backend/utils/error/csvlog.c @@ -0,0 +1,264 @@ +/*------------------------------------------------------------------------- + * + * csvlog.c + * CSV logging + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/error/csvlog.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/xact.h" +#include "libpq/libpq.h" +#include "lib/stringinfo.h" +#include "miscadmin.h" +#include "postmaster/bgworker.h" +#include "postmaster/syslogger.h" +#include "storage/lock.h" +#include "storage/proc.h" +#include "tcop/tcopprot.h" +#include "utils/backend_status.h" +#include "utils/elog.h" +#include "utils/guc.h" +#include "utils/ps_status.h" + + +/* + * 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. + */ +static inline void +appendCSVLiteral(StringInfo buf, const char *data) +{ + const char *p = data; + char c; + + /* 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, '"'); +} + +/* + * write_csvlog -- Generate and write CSV log entry + * + * Constructs the error message, depending on the Errordata it gets, in a CSV + * format which is described in doc/src/sgml/config.sgml. + */ +void +write_csvlog(ErrorData *edata) +{ + StringInfoData buf; + bool print_stmt = false; + + /* 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; + reset_formatted_start_time(); + } + log_line_number++; + + initStringInfo(&buf); + + /* timestamp with milliseconds */ + appendStringInfoString(&buf, get_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 */ + appendStringInfoString(&buf, get_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 */ + if (!edata->hide_ctx) + appendCSVLiteral(&buf, edata->context); + appendStringInfoChar(&buf, ','); + + /* user query --- only reported if not disabled by the caller */ + print_stmt = check_log_of_query(edata); + 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, ','); + + /* backend type */ + appendCSVLiteral(&buf, get_backend_type_for_log()); + appendStringInfoChar(&buf, ','); + + /* leader PID */ + if (MyProc) + { + PGPROC *leader = MyProc->lockGroupLeader; + + /* + * Show the leader only for active parallel workers. This leaves out + * the leader of a parallel group. + */ + if (leader && leader->pid != MyProcPid) + appendStringInfo(&buf, "%d", leader->pid); + } + appendStringInfoChar(&buf, ','); + + /* query id */ + appendStringInfo(&buf, "%lld", (long long) pgstat_get_my_query_id()); + + appendStringInfoChar(&buf, '\n'); + + /* If in the syslogger process, try to write messages direct to file */ + if (MyBackendType == B_LOGGER) + write_syslogger_file(buf.data, buf.len, LOG_DESTINATION_CSVLOG); + else + write_pipe_chunks(buf.data, buf.len, LOG_DESTINATION_CSVLOG); + + pfree(buf.data); +} diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c new file mode 100644 index 0000000..893f8a5 --- /dev/null +++ b/src/backend/utils/error/elog.c @@ -0,0 +1,3788 @@ +/*------------------------------------------------------------------------- + * + * elog.c + * error logging and reporting + * + * Because of the extremely high rate at which log messages can be generated, + * we need to be mindful of the performance cost of obtaining any information + * that may be logged. Also, it's important to keep in mind that this code may + * get called from within an aborted transaction, in which case operations + * such as syscache lookups are unsafe. + * + * Some notes about recursion and errors during error processing: + * + * We need to be robust about recursive-error scenarios --- for example, + * if we run out of memory, it's important to be able to report that fact. + * There are a number of considerations that go into this. + * + * First, distinguish between re-entrant use and actual recursion. It + * is possible for an error or warning message to be emitted while the + * parameters for an error message are being computed. In this case + * errstart has been called for the outer message, and some field values + * may have already been saved, but we are not actually recursing. We handle + * this by providing a (small) stack of ErrorData records. The inner message + * can be computed and sent without disturbing the state of the outer message. + * (If the inner message is actually an error, this isn't very interesting + * because control won't come back to the outer message generator ... but + * if the inner message is only debug or log data, this is critical.) + * + * Second, actual recursion will occur if an error is reported by one of + * the elog.c routines or something they call. By far the most probable + * scenario of this sort is "out of memory"; and it's also the nastiest + * to handle because we'd likely also run out of memory while trying to + * report this error! Our escape hatch for this case is to reset the + * ErrorContext to empty before trying to process the inner error. Since + * ErrorContext is guaranteed to have at least 8K of space in it (see mcxt.c), + * we should be able to process an "out of memory" message successfully. + * Since we lose the prior error state due to the reset, we won't be able + * to return to processing the original error, but we wouldn't have anyway. + * (NOTE: the escape hatch is not used for recursive situations where the + * inner message is of less than ERROR severity; in that case we just + * try to process it and return normally. Usually this will work, but if + * it ends up in infinite recursion, we will PANIC due to error stack + * overflow.) + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/error/elog.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <fcntl.h> +#include <time.h> +#include <unistd.h> +#include <signal.h> +#include <ctype.h> +#ifdef HAVE_SYSLOG +#include <syslog.h> +#endif +#ifdef HAVE_EXECINFO_H +#include <execinfo.h> +#endif + +#include "access/transam.h" +#include "access/xact.h" +#include "libpq/libpq.h" +#include "libpq/pqformat.h" +#include "mb/pg_wchar.h" +#include "nodes/miscnodes.h" +#include "miscadmin.h" +#include "pgstat.h" +#include "postmaster/bgworker.h" +#include "postmaster/postmaster.h" +#include "postmaster/syslogger.h" +#include "storage/ipc.h" +#include "storage/proc.h" +#include "tcop/tcopprot.h" +#include "utils/guc_hooks.h" +#include "utils/memutils.h" +#include "utils/ps_status.h" +#include "utils/varlena.h" + + +/* In this module, access gettext() via err_gettext() */ +#undef _ +#define _(x) err_gettext(x) + + +/* Global variables */ +ErrorContextCallback *error_context_stack = NULL; + +sigjmp_buf *PG_exception_stack = NULL; + +extern bool redirection_done; + +/* + * Hook for intercepting messages before they are sent to the server log. + * Note that the hook will not get called for messages that are suppressed + * by log_min_messages. Also note that logging hooks implemented in preload + * libraries will miss any log messages that are generated before the + * library is loaded. + */ +emit_log_hook_type emit_log_hook = NULL; + +/* GUC parameters */ +int Log_error_verbosity = PGERROR_DEFAULT; +char *Log_line_prefix = NULL; /* format for extra log line info */ +int Log_destination = LOG_DESTINATION_STDERR; +char *Log_destination_string = NULL; +bool syslog_sequence_numbers = true; +bool syslog_split_messages = true; + +/* Processed form of backtrace_symbols GUC */ +static char *backtrace_symbol_list; + +#ifdef HAVE_SYSLOG + +/* + * Max string length to send to syslog(). Note that this doesn't count the + * sequence-number prefix we add, and of course it doesn't count the prefix + * added by syslog itself. Solaris and sysklogd truncate the final message + * at 1024 bytes, so this value leaves 124 bytes for those prefixes. (Most + * other syslog implementations seem to have limits of 2KB or so.) + */ +#ifndef PG_SYSLOG_LIMIT +#define PG_SYSLOG_LIMIT 900 +#endif + +static bool openlog_done = false; +static char *syslog_ident = NULL; +static int syslog_facility = LOG_LOCAL0; + +static void write_syslog(int level, const char *line); +#endif + +#ifdef WIN32 +extern char *event_source; + +static void write_eventlog(int level, const char *line, int len); +#endif + +/* We provide a small stack of ErrorData records for re-entrant cases */ +#define ERRORDATA_STACK_SIZE 5 + +static ErrorData errordata[ERRORDATA_STACK_SIZE]; + +static int errordata_stack_depth = -1; /* index of topmost active frame */ + +static int recursion_depth = 0; /* to detect actual recursion */ + +/* + * Saved timeval and buffers for formatted timestamps that might be used by + * both log_line_prefix and csv logs. + */ +static struct timeval saved_timeval; +static bool saved_timeval_set = false; + +#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() \ + do { \ + if (errordata_stack_depth < 0) \ + { \ + errordata_stack_depth = -1; \ + ereport(ERROR, (errmsg_internal("errstart was not called"))); \ + } \ + } while (0) + + +static const char *err_gettext(const char *str) pg_attribute_format_arg(1); +static ErrorData *get_error_stack_entry(void); +static void set_stack_entry_domain(ErrorData *edata, const char *domain); +static void set_stack_entry_location(ErrorData *edata, + const char *filename, int lineno, + const char *funcname); +static bool matches_backtrace_functions(const char *funcname); +static pg_noinline void set_backtrace(ErrorData *edata, int num_skip); +static void set_errdata_field(MemoryContextData *cxt, char **ptr, const char *str); +static void FreeErrorDataContents(ErrorData *edata); +static void write_console(const char *line, int len); +static const char *process_log_prefix_padding(const char *p, int *ppadding); +static void log_line_prefix(StringInfo buf, ErrorData *edata); +static void send_message_to_server_log(ErrorData *edata); +static void send_message_to_frontend(ErrorData *edata); +static void append_with_tabs(StringInfo buf, const char *str); + + +/* + * is_log_level_output -- is elevel logically >= log_min_level? + * + * We use this for tests that should consider LOG to sort out-of-order, + * between ERROR and FATAL. Generally this is the right thing for testing + * whether a message should go to the postmaster log, whereas a simple >= + * test is correct for testing whether the message should go to the client. + */ +static inline bool +is_log_level_output(int elevel, int log_min_level) +{ + if (elevel == LOG || elevel == LOG_SERVER_ONLY) + { + if (log_min_level == LOG || log_min_level <= ERROR) + return true; + } + else if (elevel == WARNING_CLIENT_ONLY) + { + /* never sent to log, regardless of log_min_level */ + return false; + } + else if (log_min_level == LOG) + { + /* elevel != LOG */ + if (elevel >= FATAL) + return true; + } + /* Neither is LOG */ + else if (elevel >= log_min_level) + return true; + + return false; +} + +/* + * Policy-setting subroutines. These are fairly simple, but it seems wise + * to have the code in just one place. + */ + +/* + * should_output_to_server --- should message of given elevel go to the log? + */ +static inline bool +should_output_to_server(int elevel) +{ + return is_log_level_output(elevel, log_min_messages); +} + +/* + * should_output_to_client --- should message of given elevel go to the client? + */ +static inline bool +should_output_to_client(int elevel) +{ + if (whereToSendOutput == DestRemote && elevel != LOG_SERVER_ONLY) + { + /* + * client_min_messages is honored only after we complete the + * authentication handshake. This is required both for security + * reasons and because many clients can't handle NOTICE messages + * during authentication. + */ + if (ClientAuthInProgress) + return (elevel >= ERROR); + else + return (elevel >= client_min_messages || elevel == INFO); + } + return false; +} + + +/* + * message_level_is_interesting --- would ereport/elog do anything? + * + * Returns true if ereport/elog with this elevel will not be a no-op. + * This is useful to short-circuit any expensive preparatory work that + * might be needed for a logging message. There is no point in + * prepending this to a bare ereport/elog call, however. + */ +bool +message_level_is_interesting(int elevel) +{ + /* + * Keep this in sync with the decision-making in errstart(). + */ + if (elevel >= ERROR || + should_output_to_server(elevel) || + should_output_to_client(elevel)) + return true; + return false; +} + + +/* + * in_error_recursion_trouble --- are we at risk of infinite error recursion? + * + * This function exists to provide common control of various fallback steps + * that we take if we think we are facing infinite error recursion. See the + * callers for details. + */ +bool +in_error_recursion_trouble(void) +{ + /* Pull the plug if recurse more than once */ + return (recursion_depth > 2); +} + +/* + * One of those fallback steps is to stop trying to localize the error + * message, since there's a significant probability that that's exactly + * what's causing the recursion. + */ +static inline const char * +err_gettext(const char *str) +{ +#ifdef ENABLE_NLS + if (in_error_recursion_trouble()) + return str; + else + return gettext(str); +#else + return str; +#endif +} + +/* + * errstart_cold + * A simple wrapper around errstart, but hinted to be "cold". Supporting + * compilers are more likely to move code for branches containing this + * function into an area away from the calling function's code. This can + * result in more commonly executed code being more compact and fitting + * on fewer cache lines. + */ +pg_attribute_cold bool +errstart_cold(int elevel, const char *domain) +{ + return errstart(elevel, domain); +} + +/* + * errstart --- begin an error-reporting cycle + * + * Create and initialize error stack entry. Subsequently, errmsg() and + * perhaps other routines will be called to further populate the stack entry. + * Finally, errfinish() will be called to actually process the error report. + * + * Returns true in normal case. Returns false to short-circuit the error + * report (if it's a warning or lower and not to be reported anywhere). + */ +bool +errstart(int elevel, const char *domain) +{ + ErrorData *edata; + bool output_to_server; + bool output_to_client = false; + int i; + + /* + * Check some cases in which we want to promote an error into a more + * severe error. None of this logic applies for non-error messages. + */ + if (elevel >= ERROR) + { + /* + * If we are inside a critical section, all errors become PANIC + * errors. See miscadmin.h. + */ + if (CritSectionCount > 0) + elevel = PANIC; + + /* + * Check reasons for treating ERROR as FATAL: + * + * 1. we have no handler to pass the error to (implies we are in the + * postmaster or in backend startup). + * + * 2. ExitOnAnyError mode switch is set (initdb uses this). + * + * 3. the error occurred after proc_exit has begun to run. (It's + * proc_exit's responsibility to see that this doesn't turn into + * infinite recursion!) + */ + if (elevel == ERROR) + { + if (PG_exception_stack == NULL || + ExitOnAnyError || + proc_exit_inprogress) + elevel = FATAL; + } + + /* + * If the error level is ERROR or more, errfinish is not going to + * return to caller; therefore, if there is any stacked error already + * in progress it will be lost. This is more or less okay, except we + * do not want to have a FATAL or PANIC error downgraded because the + * reporting process was interrupted by a lower-grade error. So check + * the stack and make sure we panic if panic is warranted. + */ + for (i = 0; i <= errordata_stack_depth; i++) + elevel = Max(elevel, errordata[i].elevel); + } + + /* + * Now decide whether we need to process this report at all; if it's + * warning or less and not enabled for logging, just return false without + * starting up any error logging machinery. + */ + output_to_server = should_output_to_server(elevel); + output_to_client = should_output_to_client(elevel); + if (elevel < ERROR && !output_to_server && !output_to_client) + return false; + + /* + * We need to do some actual work. Make sure that memory context + * initialization has finished, else we can't do anything useful. + */ + if (ErrorContext == NULL) + { + /* Oops, hard crash time; very little we can do safely here */ + write_stderr("error occurred before error message processing is available\n"); + exit(2); + } + + /* + * Okay, crank up a stack entry to store the info in. + */ + + if (recursion_depth++ > 0 && elevel >= ERROR) + { + /* + * Oops, error during error processing. Clear ErrorContext as + * discussed at top of file. We will not return to the original + * error's reporter or handler, so we don't need it. + */ + MemoryContextReset(ErrorContext); + + /* + * Infinite error recursion might be due to something broken in a + * context traceback routine. Abandon them too. We also abandon + * attempting to print the error statement (which, if long, could + * itself be the source of the recursive failure). + */ + if (in_error_recursion_trouble()) + { + error_context_stack = NULL; + debug_query_string = NULL; + } + } + + /* Initialize data for this error frame */ + edata = get_error_stack_entry(); + edata->elevel = elevel; + edata->output_to_server = output_to_server; + edata->output_to_client = output_to_client; + set_stack_entry_domain(edata, domain); + /* Select default errcode based on elevel */ + if (elevel >= ERROR) + edata->sqlerrcode = ERRCODE_INTERNAL_ERROR; + else if (elevel >= WARNING) + edata->sqlerrcode = ERRCODE_WARNING; + else + edata->sqlerrcode = ERRCODE_SUCCESSFUL_COMPLETION; + + /* + * Any allocations for this error state level should go into ErrorContext + */ + edata->assoc_context = ErrorContext; + + recursion_depth--; + return true; +} + +/* + * errfinish --- end an error-reporting cycle + * + * Produce the appropriate error report(s) and pop the error stack. + * + * If elevel, as passed to errstart(), is ERROR or worse, control does not + * return to the caller. See elog.h for the error level definitions. + */ +void +errfinish(const char *filename, int lineno, const char *funcname) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + int elevel; + MemoryContext oldcontext; + ErrorContextCallback *econtext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + + /* Save the last few bits of error state into the stack entry */ + set_stack_entry_location(edata, filename, lineno, funcname); + + elevel = edata->elevel; + + /* + * Do processing in ErrorContext, which we hope has enough reserved space + * to report an error. + */ + oldcontext = MemoryContextSwitchTo(ErrorContext); + + /* Collect backtrace, if enabled and we didn't already */ + if (!edata->backtrace && + edata->funcname && + backtrace_functions && + matches_backtrace_functions(edata->funcname)) + set_backtrace(edata, 2); + + /* + * Call any context callback functions. Errors occurring in callback + * functions will be treated as recursive errors --- this ensures we will + * avoid infinite recursion (see errstart). + */ + for (econtext = error_context_stack; + econtext != NULL; + econtext = econtext->previous) + econtext->callback(econtext->arg); + + /* + * If ERROR (not more nor less) we pass it off to the current handler. + * Printing it and popping the stack is the responsibility of the handler. + */ + if (elevel == ERROR) + { + /* + * We do some minimal cleanup before longjmp'ing so that handlers can + * execute in a reasonably sane state. + * + * Reset InterruptHoldoffCount in case we ereport'd from inside an + * interrupt holdoff section. (We assume here that no handler will + * itself be inside a holdoff section. If necessary, such a handler + * could save and restore InterruptHoldoffCount for itself, but this + * should make life easier for most.) + */ + InterruptHoldoffCount = 0; + QueryCancelHoldoffCount = 0; + + CritSectionCount = 0; /* should be unnecessary, but... */ + + /* + * Note that we leave CurrentMemoryContext set to ErrorContext. The + * handler should reset it to something else soon. + */ + + recursion_depth--; + PG_RE_THROW(); + } + + /* Emit the message to the right places */ + EmitErrorReport(); + + /* Now free up subsidiary data attached to stack entry, and release it */ + FreeErrorDataContents(edata); + errordata_stack_depth--; + + /* Exit error-handling context */ + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + + /* + * Perform error recovery action as specified by elevel. + */ + if (elevel == FATAL) + { + /* + * For a FATAL error, we let proc_exit clean up and exit. + * + * If we just reported a startup failure, the client will disconnect + * on receiving it, so don't send any more to the client. + */ + if (PG_exception_stack == NULL && whereToSendOutput == DestRemote) + whereToSendOutput = DestNone; + + /* + * fflush here is just to improve the odds that we get to see the + * error message, in case things are so hosed that proc_exit crashes. + * Any other code you might be tempted to add here should probably be + * in an on_proc_exit or on_shmem_exit callback instead. + */ + fflush(NULL); + + /* + * Let the cumulative stats system know. Only mark the session as + * terminated by fatal error if there is no other known cause. + */ + if (pgStatSessionEndCause == DISCONNECT_NORMAL) + pgStatSessionEndCause = DISCONNECT_FATAL; + + /* + * Do normal process-exit cleanup, then return exit code 1 to indicate + * FATAL termination. The postmaster may or may not consider this + * worthy of panic, depending on which subprocess returns it. + */ + proc_exit(1); + } + + if (elevel >= PANIC) + { + /* + * Serious crash time. Postmaster will observe SIGABRT process exit + * status and kill the other backends too. + * + * XXX: what if we are *in* the postmaster? abort() won't kill our + * children... + */ + fflush(NULL); + abort(); + } + + /* + * Check for cancel/die interrupt first --- this is so that the user can + * stop a query emitting tons of notice or warning messages, even if it's + * in a loop that otherwise fails to check for interrupts. + */ + CHECK_FOR_INTERRUPTS(); +} + + +/* + * errsave_start --- begin a "soft" error-reporting cycle + * + * If "context" isn't an ErrorSaveContext node, this behaves as + * errstart(ERROR, domain), and the errsave() macro ends up acting + * exactly like ereport(ERROR, ...). + * + * If "context" is an ErrorSaveContext node, but the node creator only wants + * notification of the fact of a soft error without any details, we just set + * the error_occurred flag in the ErrorSaveContext node and return false, + * which will cause us to skip the remaining error processing steps. + * + * Otherwise, create and initialize error stack entry and return true. + * Subsequently, errmsg() and perhaps other routines will be called to further + * populate the stack entry. Finally, errsave_finish() will be called to + * tidy up. + */ +bool +errsave_start(struct Node *context, const char *domain) +{ + ErrorSaveContext *escontext; + ErrorData *edata; + + /* + * Do we have a context for soft error reporting? If not, just punt to + * errstart(). + */ + if (context == NULL || !IsA(context, ErrorSaveContext)) + return errstart(ERROR, domain); + + /* Report that a soft error was detected */ + escontext = (ErrorSaveContext *) context; + escontext->error_occurred = true; + + /* Nothing else to do if caller wants no further details */ + if (!escontext->details_wanted) + return false; + + /* + * Okay, crank up a stack entry to store the info in. + */ + + recursion_depth++; + + /* Initialize data for this error frame */ + edata = get_error_stack_entry(); + edata->elevel = LOG; /* signal all is well to errsave_finish */ + set_stack_entry_domain(edata, domain); + /* Select default errcode based on the assumed elevel of ERROR */ + edata->sqlerrcode = ERRCODE_INTERNAL_ERROR; + + /* + * Any allocations for this error state level should go into the caller's + * context. We don't need to pollute ErrorContext, or even require it to + * exist, in this code path. + */ + edata->assoc_context = CurrentMemoryContext; + + recursion_depth--; + return true; +} + +/* + * errsave_finish --- end a "soft" error-reporting cycle + * + * If errsave_start() decided this was a regular error, behave as + * errfinish(). Otherwise, package up the error details and save + * them in the ErrorSaveContext node. + */ +void +errsave_finish(struct Node *context, const char *filename, int lineno, + const char *funcname) +{ + ErrorSaveContext *escontext = (ErrorSaveContext *) context; + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* verify stack depth before accessing *edata */ + CHECK_STACK_DEPTH(); + + /* + * If errsave_start punted to errstart, then elevel will be ERROR or + * perhaps even PANIC. Punt likewise to errfinish. + */ + if (edata->elevel >= ERROR) + { + errfinish(filename, lineno, funcname); + pg_unreachable(); + } + + /* + * Else, we should package up the stack entry contents and deliver them to + * the caller. + */ + recursion_depth++; + + /* Save the last few bits of error state into the stack entry */ + set_stack_entry_location(edata, filename, lineno, funcname); + + /* Replace the LOG value that errsave_start inserted */ + edata->elevel = ERROR; + + /* + * We skip calling backtrace and context functions, which are more likely + * to cause trouble than provide useful context; they might act on the + * assumption that a transaction abort is about to occur. + */ + + /* + * Make a copy of the error info for the caller. All the subsidiary + * strings are already in the caller's context, so it's sufficient to + * flat-copy the stack entry. + */ + escontext->error_data = palloc_object(ErrorData); + memcpy(escontext->error_data, edata, sizeof(ErrorData)); + + /* Exit error-handling context */ + errordata_stack_depth--; + recursion_depth--; +} + + +/* + * get_error_stack_entry --- allocate and initialize a new stack entry + * + * The entry should be freed, when we're done with it, by calling + * FreeErrorDataContents() and then decrementing errordata_stack_depth. + * + * Returning the entry's address is just a notational convenience, + * since it had better be errordata[errordata_stack_depth]. + * + * Although the error stack is not large, we don't expect to run out of space. + * Using more than one entry implies a new error report during error recovery, + * which is possible but already suggests we're in trouble. If we exhaust the + * stack, almost certainly we are in an infinite loop of errors during error + * recovery, so we give up and PANIC. + * + * (Note that this is distinct from the recursion_depth checks, which + * guard against recursion while handling a single stack entry.) + */ +static ErrorData * +get_error_stack_entry(void) +{ + ErrorData *edata; + + /* Allocate error frame */ + errordata_stack_depth++; + if (unlikely(errordata_stack_depth >= ERRORDATA_STACK_SIZE)) + { + /* Wups, stack not big enough */ + errordata_stack_depth = -1; /* make room on stack */ + ereport(PANIC, (errmsg_internal("ERRORDATA_STACK_SIZE exceeded"))); + } + + /* Initialize error frame to all zeroes/NULLs */ + edata = &errordata[errordata_stack_depth]; + memset(edata, 0, sizeof(ErrorData)); + + /* Save errno immediately to ensure error parameter eval can't change it */ + edata->saved_errno = errno; + + return edata; +} + +/* + * set_stack_entry_domain --- fill in the internationalization domain + */ +static void +set_stack_entry_domain(ErrorData *edata, const char *domain) +{ + /* the default text domain is the backend's */ + edata->domain = domain ? domain : PG_TEXTDOMAIN("postgres"); + /* initialize context_domain the same way (see set_errcontext_domain()) */ + edata->context_domain = edata->domain; +} + +/* + * set_stack_entry_location --- fill in code-location details + * + * Store the values of __FILE__, __LINE__, and __func__ from the call site. + * We make an effort to normalize __FILE__, since compilers are inconsistent + * about how much of the path they'll include, and we'd prefer that the + * behavior not depend on that (especially, that it not vary with build path). + */ +static void +set_stack_entry_location(ErrorData *edata, + const char *filename, int lineno, + const char *funcname) +{ + if (filename) + { + const char *slash; + + /* keep only base name, useful especially for vpath builds */ + slash = strrchr(filename, '/'); + if (slash) + filename = slash + 1; + /* Some Windows compilers use backslashes in __FILE__ strings */ + slash = strrchr(filename, '\\'); + if (slash) + filename = slash + 1; + } + + edata->filename = filename; + edata->lineno = lineno; + edata->funcname = funcname; +} + +/* + * matches_backtrace_functions --- checks whether the given funcname matches + * backtrace_functions + * + * See check_backtrace_functions. + */ +static bool +matches_backtrace_functions(const char *funcname) +{ + const char *p; + + if (!backtrace_symbol_list || funcname == NULL || funcname[0] == '\0') + return false; + + p = backtrace_symbol_list; + for (;;) + { + if (*p == '\0') /* end of backtrace_symbol_list */ + break; + + if (strcmp(funcname, p) == 0) + return true; + p += strlen(p) + 1; + } + + return false; +} + + +/* + * errcode --- add SQLSTATE error code to the current error + * + * The code is expected to be represented as per MAKE_SQLSTATE(). + */ +int +errcode(int sqlerrcode) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + edata->sqlerrcode = sqlerrcode; + + return 0; /* return value does not matter */ +} + + +/* + * errcode_for_file_access --- add SQLSTATE error code to the current error + * + * The SQLSTATE code is chosen based on the saved errno value. We assume + * that the failing operation was some type of disk file access. + * + * NOTE: the primary error message string should generally include %m + * when this is used. + */ +int +errcode_for_file_access(void) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + switch (edata->saved_errno) + { + /* Permission-denied failures */ + case EPERM: /* Not super-user */ + case EACCES: /* Permission denied */ +#ifdef EROFS + case EROFS: /* Read only file system */ +#endif + edata->sqlerrcode = ERRCODE_INSUFFICIENT_PRIVILEGE; + break; + + /* File not found */ + case ENOENT: /* No such file or directory */ + edata->sqlerrcode = ERRCODE_UNDEFINED_FILE; + break; + + /* Duplicate file */ + case EEXIST: /* File exists */ + edata->sqlerrcode = ERRCODE_DUPLICATE_FILE; + break; + + /* Wrong object type or state */ + case ENOTDIR: /* Not a directory */ + case EISDIR: /* Is a directory */ +#if defined(ENOTEMPTY) && (ENOTEMPTY != EEXIST) /* same code on AIX */ + case ENOTEMPTY: /* Directory not empty */ +#endif + edata->sqlerrcode = ERRCODE_WRONG_OBJECT_TYPE; + break; + + /* Insufficient resources */ + case ENOSPC: /* No space left on device */ + edata->sqlerrcode = ERRCODE_DISK_FULL; + break; + + case ENOMEM: /* Out of memory */ + edata->sqlerrcode = ERRCODE_OUT_OF_MEMORY; + break; + + case ENFILE: /* File table overflow */ + case EMFILE: /* Too many open files */ + edata->sqlerrcode = ERRCODE_INSUFFICIENT_RESOURCES; + break; + + /* Hardware failure */ + case EIO: /* I/O error */ + edata->sqlerrcode = ERRCODE_IO_ERROR; + break; + + /* All else is classified as internal errors */ + default: + edata->sqlerrcode = ERRCODE_INTERNAL_ERROR; + break; + } + + return 0; /* return value does not matter */ +} + +/* + * errcode_for_socket_access --- add SQLSTATE error code to the current error + * + * The SQLSTATE code is chosen based on the saved errno value. We assume + * that the failing operation was some type of socket access. + * + * NOTE: the primary error message string should generally include %m + * when this is used. + */ +int +errcode_for_socket_access(void) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + switch (edata->saved_errno) + { + /* Loss of connection */ + case ALL_CONNECTION_FAILURE_ERRNOS: + edata->sqlerrcode = ERRCODE_CONNECTION_FAILURE; + break; + + /* All else is classified as internal errors */ + default: + edata->sqlerrcode = ERRCODE_INTERNAL_ERROR; + break; + } + + return 0; /* return value does not matter */ +} + + +/* + * This macro handles expansion of a format string and associated parameters; + * it's common code for errmsg(), errdetail(), etc. Must be called inside + * a routine that is declared like "const char *fmt, ..." and has an edata + * pointer set up. The message is assigned to edata->targetfield, or + * appended to it if appendval is true. The message is subject to translation + * if translateit is true. + * + * Note: we pstrdup the buffer rather than just transferring its storage + * to the edata field because the buffer might be considerably larger than + * really necessary. + */ +#define EVALUATE_MESSAGE(domain, targetfield, appendval, translateit) \ + { \ + StringInfoData buf; \ + /* Internationalize the error format string */ \ + if ((translateit) && !in_error_recursion_trouble()) \ + fmt = dgettext((domain), fmt); \ + initStringInfo(&buf); \ + if ((appendval) && edata->targetfield) { \ + appendStringInfoString(&buf, edata->targetfield); \ + appendStringInfoChar(&buf, '\n'); \ + } \ + /* Generate actual output --- have to use appendStringInfoVA */ \ + for (;;) \ + { \ + va_list args; \ + int needed; \ + errno = edata->saved_errno; \ + va_start(args, fmt); \ + needed = appendStringInfoVA(&buf, fmt, args); \ + va_end(args); \ + if (needed == 0) \ + break; \ + enlargeStringInfo(&buf, needed); \ + } \ + /* Save the completed message into the stack item */ \ + if (edata->targetfield) \ + pfree(edata->targetfield); \ + edata->targetfield = pstrdup(buf.data); \ + pfree(buf.data); \ + } + +/* + * Same as above, except for pluralized error messages. The calling routine + * must be declared like "const char *fmt_singular, const char *fmt_plural, + * unsigned long n, ...". Translation is assumed always wanted. + */ +#define EVALUATE_MESSAGE_PLURAL(domain, targetfield, appendval) \ + { \ + const char *fmt; \ + StringInfoData buf; \ + /* Internationalize the error format string */ \ + if (!in_error_recursion_trouble()) \ + fmt = dngettext((domain), fmt_singular, fmt_plural, n); \ + else \ + fmt = (n == 1 ? fmt_singular : fmt_plural); \ + initStringInfo(&buf); \ + if ((appendval) && edata->targetfield) { \ + appendStringInfoString(&buf, edata->targetfield); \ + appendStringInfoChar(&buf, '\n'); \ + } \ + /* Generate actual output --- have to use appendStringInfoVA */ \ + for (;;) \ + { \ + va_list args; \ + int needed; \ + errno = edata->saved_errno; \ + va_start(args, n); \ + needed = appendStringInfoVA(&buf, fmt, args); \ + va_end(args); \ + if (needed == 0) \ + break; \ + enlargeStringInfo(&buf, needed); \ + } \ + /* Save the completed message into the stack item */ \ + if (edata->targetfield) \ + pfree(edata->targetfield); \ + edata->targetfield = pstrdup(buf.data); \ + pfree(buf.data); \ + } + + +/* + * errmsg --- add a primary error message text to the current error + * + * In addition to the usual %-escapes recognized by printf, "%m" in + * fmt is replaced by the error message for the caller's value of errno. + * + * Note: no newline is needed at the end of the fmt string, since + * ereport will provide one for the output methods that need it. + */ +int +errmsg(const char *fmt,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + edata->message_id = fmt; + EVALUATE_MESSAGE(edata->domain, message, false, true); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + +/* + * Add a backtrace to the containing ereport() call. This is intended to be + * added temporarily during debugging. + */ +int +errbacktrace(void) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + set_backtrace(edata, 1); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + + return 0; +} + +/* + * Compute backtrace data and add it to the supplied ErrorData. num_skip + * specifies how many inner frames to skip. Use this to avoid showing the + * internal backtrace support functions in the backtrace. This requires that + * this and related functions are not inlined. + */ +static void +set_backtrace(ErrorData *edata, int num_skip) +{ + StringInfoData errtrace; + + initStringInfo(&errtrace); + +#ifdef HAVE_BACKTRACE_SYMBOLS + { + void *buf[100]; + int nframes; + char **strfrms; + + nframes = backtrace(buf, lengthof(buf)); + strfrms = backtrace_symbols(buf, nframes); + if (strfrms == NULL) + return; + + for (int i = num_skip; i < nframes; i++) + appendStringInfo(&errtrace, "\n%s", strfrms[i]); + free(strfrms); + } +#else + appendStringInfoString(&errtrace, + "backtrace generation is not supported by this installation"); +#endif + + edata->backtrace = errtrace.data; +} + +/* + * errmsg_internal --- add a primary error message text to the current error + * + * This is exactly like errmsg() except that strings passed to errmsg_internal + * are not translated, and are customarily left out of the + * internationalization message dictionary. This should be used for "can't + * happen" cases that are probably not worth spending translation effort on. + * We also use this for certain cases where we *must* not try to translate + * the message because the translation would fail and result in infinite + * error recursion. + */ +int +errmsg_internal(const char *fmt,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + edata->message_id = fmt; + EVALUATE_MESSAGE(edata->domain, message, false, false); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + + +/* + * errmsg_plural --- add a primary error message text to the current error, + * with support for pluralization of the message text + */ +int +errmsg_plural(const char *fmt_singular, const char *fmt_plural, + unsigned long n,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + edata->message_id = fmt_singular; + EVALUATE_MESSAGE_PLURAL(edata->domain, message, false); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + + +/* + * errdetail --- add a detail error message text to the current error + */ +int +errdetail(const char *fmt,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + EVALUATE_MESSAGE(edata->domain, detail, false, true); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + + +/* + * errdetail_internal --- add a detail error message text to the current error + * + * This is exactly like errdetail() except that strings passed to + * errdetail_internal are not translated, and are customarily left out of the + * internationalization message dictionary. This should be used for detail + * messages that seem not worth translating for one reason or another + * (typically, that they don't seem to be useful to average users). + */ +int +errdetail_internal(const char *fmt,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + EVALUATE_MESSAGE(edata->domain, detail, false, false); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + + +/* + * errdetail_log --- add a detail_log error message text to the current error + */ +int +errdetail_log(const char *fmt,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + EVALUATE_MESSAGE(edata->domain, detail_log, false, true); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + +/* + * errdetail_log_plural --- add a detail_log error message text to the current error + * with support for pluralization of the message text + */ +int +errdetail_log_plural(const char *fmt_singular, const char *fmt_plural, + unsigned long n,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + EVALUATE_MESSAGE_PLURAL(edata->domain, detail_log, false); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + + +/* + * errdetail_plural --- add a detail error message text to the current error, + * with support for pluralization of the message text + */ +int +errdetail_plural(const char *fmt_singular, const char *fmt_plural, + unsigned long n,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + EVALUATE_MESSAGE_PLURAL(edata->domain, detail, false); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + + +/* + * errhint --- add a hint error message text to the current error + */ +int +errhint(const char *fmt,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + EVALUATE_MESSAGE(edata->domain, hint, false, true); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + + +/* + * errhint_plural --- add a hint error message text to the current error, + * with support for pluralization of the message text + */ +int +errhint_plural(const char *fmt_singular, const char *fmt_plural, + unsigned long n,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + EVALUATE_MESSAGE_PLURAL(edata->domain, hint, false); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + + +/* + * errcontext_msg --- add a context error message text to the current error + * + * Unlike other cases, multiple calls are allowed to build up a stack of + * context information. We assume earlier calls represent more-closely-nested + * states. + */ +int +errcontext_msg(const char *fmt,...) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + MemoryContext oldcontext; + + recursion_depth++; + CHECK_STACK_DEPTH(); + oldcontext = MemoryContextSwitchTo(edata->assoc_context); + + EVALUATE_MESSAGE(edata->context_domain, context, true, true); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + return 0; /* return value does not matter */ +} + +/* + * set_errcontext_domain --- set message domain to be used by errcontext() + * + * errcontext_msg() can be called from a different module than the original + * ereport(), so we cannot use the message domain passed in errstart() to + * translate it. Instead, each errcontext_msg() call should be preceded by + * a set_errcontext_domain() call to specify the domain. This is usually + * done transparently by the errcontext() macro. + */ +int +set_errcontext_domain(const char *domain) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + /* the default text domain is the backend's */ + edata->context_domain = domain ? domain : PG_TEXTDOMAIN("postgres"); + + return 0; /* return value does not matter */ +} + + +/* + * errhidestmt --- optionally suppress STATEMENT: field of log entry + * + * This should be called if the message text already includes the statement. + */ +int +errhidestmt(bool hide_stmt) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + edata->hide_stmt = hide_stmt; + + return 0; /* return value does not matter */ +} + +/* + * errhidecontext --- optionally suppress CONTEXT: field of log entry + * + * This should only be used for verbose debugging messages where the repeated + * inclusion of context would bloat the log volume too much. + */ +int +errhidecontext(bool hide_ctx) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + edata->hide_ctx = hide_ctx; + + return 0; /* return value does not matter */ +} + +/* + * errposition --- add cursor position to the current error + */ +int +errposition(int cursorpos) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + edata->cursorpos = cursorpos; + + return 0; /* return value does not matter */ +} + +/* + * internalerrposition --- add internal cursor position to the current error + */ +int +internalerrposition(int cursorpos) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + edata->internalpos = cursorpos; + + return 0; /* return value does not matter */ +} + +/* + * internalerrquery --- add internal query text to the current error + * + * Can also pass NULL to drop the internal query text entry. This case + * 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(edata->assoc_context, query); + + return 0; /* return value does not matter */ +} + +/* + * err_generic_string -- used to set individual ErrorData string fields + * identified by PG_DIAG_xxx codes. + * + * This intentionally only supports fields that don't use localized strings, + * so that there are no translation considerations. + * + * Most potential callers should not use this directly, but instead prefer + * higher-level abstractions, such as errtablecol() (see relcache.c). + */ +int +err_generic_string(int field, const char *str) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + switch (field) + { + case PG_DIAG_SCHEMA_NAME: + set_errdata_field(edata->assoc_context, &edata->schema_name, str); + break; + case PG_DIAG_TABLE_NAME: + set_errdata_field(edata->assoc_context, &edata->table_name, str); + break; + case PG_DIAG_COLUMN_NAME: + set_errdata_field(edata->assoc_context, &edata->column_name, str); + break; + case PG_DIAG_DATATYPE_NAME: + set_errdata_field(edata->assoc_context, &edata->datatype_name, str); + break; + case PG_DIAG_CONSTRAINT_NAME: + set_errdata_field(edata->assoc_context, &edata->constraint_name, str); + break; + default: + elog(ERROR, "unsupported ErrorData field id: %d", field); + break; + } + + return 0; /* return value does not matter */ +} + +/* + * set_errdata_field --- set an ErrorData string field + */ +static void +set_errdata_field(MemoryContextData *cxt, char **ptr, const char *str) +{ + Assert(*ptr == NULL); + *ptr = MemoryContextStrdup(cxt, str); +} + +/* + * 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; +} + +/* + * 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; +} + + +/* + * Functions to allow construction of error message strings separately from + * the ereport() call itself. + * + * The expected calling convention is + * + * pre_format_elog_string(errno, domain), var = format_elog_string(format,...) + * + * which can be hidden behind a macro such as GUC_check_errdetail(). We + * assume that any functions called in the arguments of format_elog_string() + * cannot result in re-entrant use of these functions --- otherwise the wrong + * text domain might be used, or the wrong errno substituted for %m. This is + * okay for the current usage with GUC check hooks, but might need further + * effort someday. + * + * The result of format_elog_string() is stored in ErrorContext, and will + * therefore survive until FlushErrorState() is called. + */ +static int save_format_errnumber; +static const char *save_format_domain; + +void +pre_format_elog_string(int errnumber, const char *domain) +{ + /* Save errno before evaluation of argument functions can change it */ + save_format_errnumber = errnumber; + /* Save caller's text domain */ + save_format_domain = domain; +} + +char * +format_elog_string(const char *fmt,...) +{ + ErrorData errdata; + ErrorData *edata; + MemoryContext oldcontext; + + /* Initialize a mostly-dummy error frame */ + edata = &errdata; + MemSet(edata, 0, sizeof(ErrorData)); + /* the default text domain is the backend's */ + edata->domain = save_format_domain ? save_format_domain : PG_TEXTDOMAIN("postgres"); + /* set the errno to be used to interpret %m */ + edata->saved_errno = save_format_errnumber; + + oldcontext = MemoryContextSwitchTo(ErrorContext); + + edata->message_id = fmt; + EVALUATE_MESSAGE(edata->domain, message, false, true); + + MemoryContextSwitchTo(oldcontext); + + return edata->message; +} + + +/* + * 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(edata->assoc_context); + + /* + * Call hook before sending message to log. The hook function is allowed + * to turn off edata->output_to_server, so we must recheck that afterward. + * Making any other change in the content of edata is not considered + * supported. + * + * Note: the reason why the hook can only turn off output_to_server, and + * not turn it on, is that it'd be unreliable: we will never get here at + * all if errstart() deems the message uninteresting. A hook that could + * make decisions in that direction would have to hook into errstart(), + * where it would have much less information available. emit_log_hook is + * intended for custom log filtering and custom log message transmission + * mechanisms. + * + * The log hook has access to both the translated and original English + * error message text, which is passed through to allow it to be used as a + * message identifier. Note that the original text is not available for + * detail, detail_log, hint and context text elements. + */ + if (edata->output_to_server && emit_log_hook) + (*emit_log_hook) (edata); + + /* 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); + if (newedata->hint) + newedata->hint = pstrdup(newedata->hint); + if (newedata->context) + newedata->context = pstrdup(newedata->context); + if (newedata->backtrace) + newedata->backtrace = pstrdup(newedata->backtrace); + if (newedata->schema_name) + newedata->schema_name = pstrdup(newedata->schema_name); + if (newedata->table_name) + newedata->table_name = pstrdup(newedata->table_name); + if (newedata->column_name) + newedata->column_name = pstrdup(newedata->column_name); + if (newedata->datatype_name) + newedata->datatype_name = pstrdup(newedata->datatype_name); + if (newedata->constraint_name) + newedata->constraint_name = pstrdup(newedata->constraint_name); + if (newedata->internalquery) + newedata->internalquery = pstrdup(newedata->internalquery); + + /* Use the calling context for string allocation */ + newedata->assoc_context = CurrentMemoryContext; + + 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) +{ + FreeErrorDataContents(edata); + pfree(edata); +} + +/* + * FreeErrorDataContents --- free the subsidiary data of an ErrorData. + * + * This can be used on either an error stack entry or a copied ErrorData. + */ +static void +FreeErrorDataContents(ErrorData *edata) +{ + if (edata->message) + pfree(edata->message); + if (edata->detail) + pfree(edata->detail); + if (edata->detail_log) + pfree(edata->detail_log); + if (edata->hint) + pfree(edata->hint); + if (edata->context) + pfree(edata->context); + if (edata->backtrace) + pfree(edata->backtrace); + if (edata->schema_name) + pfree(edata->schema_name); + if (edata->table_name) + pfree(edata->table_name); + if (edata->column_name) + pfree(edata->column_name); + if (edata->datatype_name) + pfree(edata->datatype_name); + if (edata->constraint_name) + pfree(edata->constraint_name); + if (edata->internalquery) + pfree(edata->internalquery); +} + +/* + * 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); +} + +/* + * ThrowErrorData --- report an error described by an ErrorData structure + * + * This is somewhat like ReThrowError, but it allows elevels besides ERROR, + * and the boolean flags such as output_to_server are computed via the + * default rules rather than being copied from the given ErrorData. + * This is primarily used to re-report errors originally reported by + * background worker processes and then propagated (with or without + * modification) to the backend responsible for them. + */ +void +ThrowErrorData(ErrorData *edata) +{ + ErrorData *newedata; + MemoryContext oldcontext; + + if (!errstart(edata->elevel, edata->domain)) + return; /* error is not to be reported at all */ + + newedata = &errordata[errordata_stack_depth]; + recursion_depth++; + oldcontext = MemoryContextSwitchTo(newedata->assoc_context); + + /* Copy the supplied fields to the error stack entry. */ + if (edata->sqlerrcode != 0) + newedata->sqlerrcode = edata->sqlerrcode; + if (edata->message) + newedata->message = pstrdup(edata->message); + if (edata->detail) + newedata->detail = pstrdup(edata->detail); + if (edata->detail_log) + newedata->detail_log = pstrdup(edata->detail_log); + if (edata->hint) + newedata->hint = pstrdup(edata->hint); + if (edata->context) + newedata->context = pstrdup(edata->context); + if (edata->backtrace) + newedata->backtrace = pstrdup(edata->backtrace); + /* assume message_id is not available */ + if (edata->schema_name) + newedata->schema_name = pstrdup(edata->schema_name); + if (edata->table_name) + newedata->table_name = pstrdup(edata->table_name); + if (edata->column_name) + newedata->column_name = pstrdup(edata->column_name); + if (edata->datatype_name) + newedata->datatype_name = pstrdup(edata->datatype_name); + if (edata->constraint_name) + newedata->constraint_name = pstrdup(edata->constraint_name); + newedata->cursorpos = edata->cursorpos; + newedata->internalpos = edata->internalpos; + if (edata->internalquery) + newedata->internalquery = pstrdup(edata->internalquery); + + MemoryContextSwitchTo(oldcontext); + recursion_depth--; + + /* Process the error. */ + errfinish(edata->filename, edata->lineno, edata->funcname); +} + +/* + * 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); + + newedata = get_error_stack_entry(); + 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->backtrace) + newedata->backtrace = pstrdup(newedata->backtrace); + if (newedata->schema_name) + newedata->schema_name = pstrdup(newedata->schema_name); + if (newedata->table_name) + newedata->table_name = pstrdup(newedata->table_name); + if (newedata->column_name) + newedata->column_name = pstrdup(newedata->column_name); + if (newedata->datatype_name) + newedata->datatype_name = pstrdup(newedata->datatype_name); + if (newedata->constraint_name) + newedata->constraint_name = pstrdup(newedata->constraint_name); + if (newedata->internalquery) + newedata->internalquery = pstrdup(newedata->internalquery); + + /* Reset the assoc_context to be ErrorContext */ + newedata->assoc_context = ErrorContext; + + 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. + */ + 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. + */ + edata->output_to_server = should_output_to_server(FATAL); + edata->output_to_client = should_output_to_client(FATAL); + + /* + * 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(edata->filename, edata->lineno, edata->funcname); + } + + /* Doesn't return ... */ + ExceptionalCondition("pg_re_throw tried to return", __FILE__, __LINE__); +} + + +/* + * GetErrorContextStack - Return the context stack, for display/diags + * + * Returns a pstrdup'd string in the caller's context which includes the PG + * error call stack. It is the caller's responsibility to ensure this string + * is pfree'd (or its context cleaned up) when done. + * + * This information is collected by traversing the error contexts and calling + * each context's callback function, each of which is expected to call + * errcontext() to return a string which can be presented to the user. + */ +char * +GetErrorContextStack(void) +{ + ErrorData *edata; + ErrorContextCallback *econtext; + + /* + * Crank up a stack entry to store the info in. + */ + recursion_depth++; + + edata = get_error_stack_entry(); + + /* + * Set up assoc_context to be the caller's context, so any allocations + * done (which will include edata->context) will use their context. + */ + edata->assoc_context = CurrentMemoryContext; + + /* + * Call any context callback functions to collect the context information + * into edata->context. + * + * Errors occurring in callback functions should go through the regular + * error handling code which should handle any recursive errors, though we + * double-check above, just in case. + */ + for (econtext = error_context_stack; + econtext != NULL; + econtext = econtext->previous) + econtext->callback(econtext->arg); + + /* + * Clean ourselves off the stack, any allocations done should have been + * using edata->assoc_context, which we set up earlier to be the caller's + * context, so we're free to just remove our entry off the stack and + * decrement recursion depth and exit. + */ + errordata_stack_depth--; + recursion_depth--; + + /* + * Return a pointer to the string the caller asked for, which should have + * been allocated in their context. + */ + return edata->context; +} + + +/* + * Initialization of error output file + */ +void +DebugFileOpen(void) +{ + 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, + (errcode_for_file_access(), + 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, + (errcode_for_file_access(), + 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, + (errcode_for_file_access(), + errmsg("could not reopen file \"%s\" as stdout: %m", + OutputFileName))); + } +} + + +/* + * GUC check_hook for backtrace_functions + * + * We split the input string, where commas separate function names + * and certain whitespace chars are ignored, into a \0-separated (and + * \0\0-terminated) list of function names. This formulation allows + * easy scanning when an error is thrown while avoiding the use of + * non-reentrant strtok(), as well as keeping the output data in a + * single palloc() chunk. + */ +bool +check_backtrace_functions(char **newval, void **extra, GucSource source) +{ + int newvallen = strlen(*newval); + char *someval; + int validlen; + int i; + int j; + + /* + * Allow characters that can be C identifiers and commas as separators, as + * well as some whitespace for readability. + */ + validlen = strspn(*newval, + "0123456789_" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + ", \n\t"); + if (validlen != newvallen) + { + GUC_check_errdetail("invalid character"); + return false; + } + + if (*newval[0] == '\0') + { + *extra = NULL; + return true; + } + + /* + * Allocate space for the output and create the copy. We could discount + * whitespace chars to save some memory, but it doesn't seem worth the + * trouble. + */ + someval = guc_malloc(ERROR, newvallen + 1 + 1); + for (i = 0, j = 0; i < newvallen; i++) + { + if ((*newval)[i] == ',') + someval[j++] = '\0'; /* next item */ + else if ((*newval)[i] == ' ' || + (*newval)[i] == '\n' || + (*newval)[i] == '\t') + ; /* ignore these */ + else + someval[j++] = (*newval)[i]; /* copy anything else */ + } + + /* two \0s end the setting */ + someval[j] = '\0'; + someval[j + 1] = '\0'; + + *extra = someval; + return true; +} + +/* + * GUC assign_hook for backtrace_functions + */ +void +assign_backtrace_functions(const char *newval, void *extra) +{ + backtrace_symbol_list = (char *) extra; +} + +/* + * GUC check_hook for log_destination + */ +bool +check_log_destination(char **newval, void **extra, GucSource source) +{ + char *rawstring; + List *elemlist; + ListCell *l; + int newlogdest = 0; + int *myextra; + + /* Need a modifiable copy of string */ + rawstring = pstrdup(*newval); + + /* Parse string into list of identifiers */ + if (!SplitIdentifierString(rawstring, ',', &elemlist)) + { + /* syntax error in list */ + GUC_check_errdetail("List syntax is invalid."); + pfree(rawstring); + list_free(elemlist); + return false; + } + + foreach(l, elemlist) + { + char *tok = (char *) lfirst(l); + + if (pg_strcasecmp(tok, "stderr") == 0) + newlogdest |= LOG_DESTINATION_STDERR; + else if (pg_strcasecmp(tok, "csvlog") == 0) + newlogdest |= LOG_DESTINATION_CSVLOG; + else if (pg_strcasecmp(tok, "jsonlog") == 0) + newlogdest |= LOG_DESTINATION_JSONLOG; +#ifdef HAVE_SYSLOG + else if (pg_strcasecmp(tok, "syslog") == 0) + newlogdest |= LOG_DESTINATION_SYSLOG; +#endif +#ifdef WIN32 + else if (pg_strcasecmp(tok, "eventlog") == 0) + newlogdest |= LOG_DESTINATION_EVENTLOG; +#endif + else + { + GUC_check_errdetail("Unrecognized key word: \"%s\".", tok); + pfree(rawstring); + list_free(elemlist); + return false; + } + } + + pfree(rawstring); + list_free(elemlist); + + myextra = (int *) guc_malloc(ERROR, sizeof(int)); + *myextra = newlogdest; + *extra = (void *) myextra; + + return true; +} + +/* + * GUC assign_hook for log_destination + */ +void +assign_log_destination(const char *newval, void *extra) +{ + Log_destination = *((int *) extra); +} + +/* + * GUC assign_hook for syslog_ident + */ +void +assign_syslog_ident(const char *newval, void *extra) +{ +#ifdef HAVE_SYSLOG + /* + * 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, newval) != 0) + { + if (openlog_done) + { + closelog(); + openlog_done = false; + } + free(syslog_ident); + syslog_ident = strdup(newval); + /* if the strdup fails, we will cope in write_syslog() */ + } +#endif + /* Without syslog support, just ignore it */ +} + +/* + * GUC assign_hook for syslog_facility + */ +void +assign_syslog_facility(int newval, void *extra) +{ +#ifdef HAVE_SYSLOG + /* + * As above, don't thrash the syslog connection unnecessarily. + */ + if (syslog_facility != newval) + { + if (openlog_done) + { + closelog(); + openlog_done = false; + } + syslog_facility = newval; + } +#endif + /* Without syslog support, just ignore it */ +} + +#ifdef HAVE_SYSLOG + +/* + * 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 (syslog_split_messages && (len > PG_SYSLOG_LIMIT || nlpos != NULL)) + { + int chunk_nr = 0; + + 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) + return; + 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])) + i--; + + if (i > 0) /* else couldn't divide word boundary */ + { + buflen = i; + buf[i] = '\0'; + } + } + + chunk_nr++; + + if (syslog_sequence_numbers) + syslog(level, "[%lu-%d] %s", seq, chunk_nr, buf); + else + syslog(level, "[%d] %s", chunk_nr, buf); + + line += buflen; + len -= buflen; + } + } + else + { + /* message short enough */ + if (syslog_sequence_numbers) + syslog(level, "[%lu] %s", seq, line); + else + syslog(level, "%s", line); + } +} +#endif /* HAVE_SYSLOG */ + +#ifdef WIN32 +/* + * Get the PostgreSQL equivalent of the Windows ANSI code page. "ANSI" system + * interfaces (e.g. CreateFileA()) expect string arguments in this encoding. + * Every process in a given system will find the same value at all times. + */ +static int +GetACPEncoding(void) +{ + static int encoding = -2; + + if (encoding == -2) + encoding = pg_codepage_to_encoding(GetACP()); + + return encoding; +} + +/* + * 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, + event_source ? event_source : DEFAULT_EVENT_SOURCE); + if (evtHandle == NULL) + { + evtHandle = INVALID_HANDLE_VALUE; + return; + } + } + + switch (level) + { + case DEBUG5: + case DEBUG4: + case DEBUG3: + case DEBUG2: + case DEBUG1: + case LOG: + case LOG_SERVER_ONLY: + case INFO: + case NOTICE: + eventlevel = EVENTLOG_INFORMATION_TYPE; + break; + case WARNING: + case WARNING_CLIENT_ONLY: + eventlevel = EVENTLOG_WARNING_TYPE; + break; + case ERROR: + case FATAL: + case PANIC: + default: + eventlevel = EVENTLOG_ERROR_TYPE; + break; + } + + /* + * If message character encoding matches the encoding expected by + * ReportEventA(), call it to avoid the hazards of conversion. Otherwise, + * try to convert the message to UTF16 and write it with ReportEventW(). + * Fall back on ReportEventA() if conversion failed. + * + * Since we palloc the structure required for conversion, also fall + * through to writing unconverted if we have not yet set up + * CurrentMemoryContext. + * + * Also verify that we are not on our way into error recursion trouble due + * to error messages thrown deep inside pgwin32_message_to_UTF16(). + */ + if (!in_error_recursion_trouble() && + CurrentMemoryContext != NULL && + GetMessageEncoding() != GetACPEncoding()) + { + utf16 = pgwin32_message_to_UTF16(line, len, NULL); + if (utf16) + { + ReportEventW(evtHandle, + eventlevel, + 0, + 0, /* All events are Id 0 */ + NULL, + 1, + 0, + (LPCWSTR *) &utf16, + NULL); + /* XXX Try ReportEventA() when ReportEventW() fails? */ + + pfree(utf16); + return; + } + } + ReportEventA(evtHandle, + eventlevel, + 0, + 0, /* All events are Id 0 */ + NULL, + 1, + 0, + &line, + NULL); +} +#endif /* WIN32 */ + +static void +write_console(const char *line, int len) +{ + int rc; + +#ifdef WIN32 + + /* + * Try to convert the message to UTF16 and write it with WriteConsoleW(). + * Fall back on write() if anything fails. + * + * In contrast to write_eventlog(), don't skip straight to write() based + * on the applicable encodings. Unlike WriteConsoleW(), write() depends + * on the suitability of the console output code page. Since we put + * stderr into binary mode in SubPostmasterMain(), write() skips the + * necessary translation anyway. + * + * WriteConsoleW() will fail if stderr is redirected, so just fall through + * to writing unconverted to the logfile in this case. + * + * Since we palloc the structure required for conversion, also fall + * through to writing unconverted if we have not yet set up + * CurrentMemoryContext. + */ + if (!in_error_recursion_trouble() && + !redirection_done && + CurrentMemoryContext != NULL) + { + WCHAR *utf16; + int utf16len; + + utf16 = pgwin32_message_to_UTF16(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 platforms is not implemented yet. It requires + * non-throw version of pg_do_encoding_conversion(), that converts + * unconvertible characters to '?' without errors. + * + * XXX: We have a no-throw version now. It doesn't convert to '?' though. + */ +#endif + + /* + * We ignore any error from write() here. We have no useful way to report + * it ... certainly whining on stderr isn't likely to be productive. + */ + rc = write(fileno(stderr), line, len); + (void) rc; +} + +/* + * get_formatted_log_time -- compute and get the log timestamp. + * + * The timestamp is computed if not set yet, so as it is kept consistent + * among all the log destinations that require it to be consistent. Note + * that the computed timestamp is returned in a static buffer, not + * palloc()'d. + */ +char * +get_formatted_log_time(void) +{ + pg_time_t stamp_time; + char msbuf[13]; + + /* leave if already computed */ + if (formatted_log_time[0] != '\0') + return formatted_log_time; + + if (!saved_timeval_set) + { + gettimeofday(&saved_timeval, NULL); + saved_timeval_set = true; + } + + stamp_time = (pg_time_t) saved_timeval.tv_sec; + + /* + * Note: we expect that guc.c will ensure that log_timezone is set up (at + * least with a minimal GMT value) before Log_line_prefix can become + * nonempty or CSV mode can be selected. + */ + pg_strftime(formatted_log_time, FORMATTED_TS_LEN, + /* leave room for milliseconds... */ + "%Y-%m-%d %H:%M:%S %Z", + pg_localtime(&stamp_time, log_timezone)); + + /* 'paste' milliseconds into place... */ + sprintf(msbuf, ".%03d", (int) (saved_timeval.tv_usec / 1000)); + memcpy(formatted_log_time + 19, msbuf, 4); + + return formatted_log_time; +} + +/* + * reset_formatted_start_time -- reset the start timestamp + */ +void +reset_formatted_start_time(void) +{ + formatted_start_time[0] = '\0'; +} + +/* + * get_formatted_start_time -- compute and get the start timestamp. + * + * The timestamp is computed if not set yet. Note that the computed + * timestamp is returned in a static buffer, not palloc()'d. + */ +char * +get_formatted_start_time(void) +{ + pg_time_t stamp_time = (pg_time_t) MyStartTime; + + /* leave if already computed */ + if (formatted_start_time[0] != '\0') + return formatted_start_time; + + /* + * Note: we expect that guc.c will ensure that log_timezone is set up (at + * least with a minimal GMT value) before Log_line_prefix can become + * nonempty or CSV mode can be selected. + */ + pg_strftime(formatted_start_time, FORMATTED_TS_LEN, + "%Y-%m-%d %H:%M:%S %Z", + pg_localtime(&stamp_time, log_timezone)); + + return formatted_start_time; +} + +/* + * check_log_of_query -- check if a query can be logged + */ +bool +check_log_of_query(ErrorData *edata) +{ + /* log required? */ + if (!is_log_level_output(edata->elevel, log_min_error_statement)) + return false; + + /* query log wanted? */ + if (edata->hide_stmt) + return false; + + /* query string available? */ + if (debug_query_string == NULL) + return false; + + return true; +} + +/* + * get_backend_type_for_log -- backend type for log entries + * + * Returns a pointer to a static buffer, not palloc()'d. + */ +const char * +get_backend_type_for_log(void) +{ + const char *backend_type_str; + + if (MyProcPid == PostmasterPid) + backend_type_str = "postmaster"; + else if (MyBackendType == B_BG_WORKER) + backend_type_str = MyBgworkerEntry->bgw_type; + else + backend_type_str = GetBackendTypeDesc(MyBackendType); + + return backend_type_str; +} + +/* + * process_log_prefix_padding --- helper function for processing the format + * string in log_line_prefix + * + * Note: This function returns NULL if it finds something which + * it deems invalid in the format string. + */ +static const char * +process_log_prefix_padding(const char *p, int *ppadding) +{ + int paddingsign = 1; + int padding = 0; + + if (*p == '-') + { + p++; + + if (*p == '\0') /* Did the buf end in %- ? */ + return NULL; + paddingsign = -1; + } + + /* generate an int version of the numerical string */ + while (*p >= '0' && *p <= '9') + padding = padding * 10 + (*p++ - '0'); + + /* format is invalid if it ends with the padding number */ + if (*p == '\0') + return NULL; + + padding *= paddingsign; + *ppadding = padding; + return p; +} + +/* + * Format log status information using Log_line_prefix. + */ +static void +log_line_prefix(StringInfo buf, ErrorData *edata) +{ + log_status_format(buf, Log_line_prefix, edata); +} + +/* + * Format log status info; append to the provided buffer. + */ +void +log_status_format(StringInfo buf, const char *format, 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; + int padding; + const char *p; + + /* + * 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. 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; + reset_formatted_start_time(); + } + log_line_number++; + + if (format == NULL) + return; /* in case guc hasn't run yet */ + + for (p = format; *p != '\0'; p++) + { + if (*p != '%') + { + /* literal char, just copy */ + appendStringInfoChar(buf, *p); + continue; + } + + /* must be a '%', so skip to the next char */ + p++; + if (*p == '\0') + break; /* format error - ignore it */ + else if (*p == '%') + { + /* string contains %% */ + appendStringInfoChar(buf, '%'); + continue; + } + + + /* + * Process any formatting which may exist after the '%'. Note that + * process_log_prefix_padding moves p past the padding number if it + * exists. + * + * Note: Since only '-', '0' to '9' are valid formatting characters we + * can do a quick check here to pre-check for formatting. If the char + * is not formatting then we can skip a useless function call. + * + * Further note: At least on some platforms, passing %*s rather than + * %s to appendStringInfo() is substantially slower, so many of the + * cases below avoid doing that unless non-zero padding is in fact + * specified. + */ + if (*p > '9') + padding = 0; + else if ((p = process_log_prefix_padding(p, &padding)) == NULL) + break; + + /* process the option */ + switch (*p) + { + case 'a': + if (MyProcPort) + { + const char *appname = application_name; + + if (appname == NULL || *appname == '\0') + appname = _("[unknown]"); + if (padding != 0) + appendStringInfo(buf, "%*s", padding, appname); + else + appendStringInfoString(buf, appname); + } + else if (padding != 0) + appendStringInfoSpaces(buf, + padding > 0 ? padding : -padding); + + break; + case 'b': + { + const char *backend_type_str = get_backend_type_for_log(); + + if (padding != 0) + appendStringInfo(buf, "%*s", padding, backend_type_str); + else + appendStringInfoString(buf, backend_type_str); + break; + } + case 'u': + if (MyProcPort) + { + const char *username = MyProcPort->user_name; + + if (username == NULL || *username == '\0') + username = _("[unknown]"); + if (padding != 0) + appendStringInfo(buf, "%*s", padding, username); + else + appendStringInfoString(buf, username); + } + else if (padding != 0) + appendStringInfoSpaces(buf, + padding > 0 ? padding : -padding); + break; + case 'd': + if (MyProcPort) + { + const char *dbname = MyProcPort->database_name; + + if (dbname == NULL || *dbname == '\0') + dbname = _("[unknown]"); + if (padding != 0) + appendStringInfo(buf, "%*s", padding, dbname); + else + appendStringInfoString(buf, dbname); + } + else if (padding != 0) + appendStringInfoSpaces(buf, + padding > 0 ? padding : -padding); + break; + case 'c': + if (padding != 0) + { + char strfbuf[128]; + + snprintf(strfbuf, sizeof(strfbuf) - 1, "%lx.%x", + (long) (MyStartTime), MyProcPid); + appendStringInfo(buf, "%*s", padding, strfbuf); + } + else + appendStringInfo(buf, "%lx.%x", (long) (MyStartTime), MyProcPid); + break; + case 'p': + if (padding != 0) + appendStringInfo(buf, "%*d", padding, MyProcPid); + else + appendStringInfo(buf, "%d", MyProcPid); + break; + + case 'P': + if (MyProc) + { + PGPROC *leader = MyProc->lockGroupLeader; + + /* + * Show the leader only for active parallel workers. This + * leaves out the leader of a parallel group. + */ + if (leader == NULL || leader->pid == MyProcPid) + appendStringInfoSpaces(buf, + padding > 0 ? padding : -padding); + else if (padding != 0) + appendStringInfo(buf, "%*d", padding, leader->pid); + else + appendStringInfo(buf, "%d", leader->pid); + } + else if (padding != 0) + appendStringInfoSpaces(buf, + padding > 0 ? padding : -padding); + break; + + case 'l': + if (padding != 0) + appendStringInfo(buf, "%*ld", padding, log_line_number); + else + appendStringInfo(buf, "%ld", log_line_number); + break; + case 'm': + /* force a log timestamp reset */ + formatted_log_time[0] = '\0'; + (void) get_formatted_log_time(); + + if (padding != 0) + appendStringInfo(buf, "%*s", padding, formatted_log_time); + else + appendStringInfoString(buf, formatted_log_time); + break; + case 't': + { + pg_time_t stamp_time = (pg_time_t) time(NULL); + char strfbuf[128]; + + pg_strftime(strfbuf, sizeof(strfbuf), + "%Y-%m-%d %H:%M:%S %Z", + pg_localtime(&stamp_time, log_timezone)); + if (padding != 0) + appendStringInfo(buf, "%*s", padding, strfbuf); + else + appendStringInfoString(buf, strfbuf); + } + break; + case 'n': + { + char strfbuf[128]; + + if (!saved_timeval_set) + { + gettimeofday(&saved_timeval, NULL); + saved_timeval_set = true; + } + + snprintf(strfbuf, sizeof(strfbuf), "%ld.%03d", + (long) saved_timeval.tv_sec, + (int) (saved_timeval.tv_usec / 1000)); + + if (padding != 0) + appendStringInfo(buf, "%*s", padding, strfbuf); + else + appendStringInfoString(buf, strfbuf); + } + break; + case 's': + { + char *start_time = get_formatted_start_time(); + + if (padding != 0) + appendStringInfo(buf, "%*s", padding, start_time); + else + appendStringInfoString(buf, start_time); + } + break; + case 'i': + if (MyProcPort) + { + const char *psdisp; + int displen; + + psdisp = get_ps_display(&displen); + if (padding != 0) + appendStringInfo(buf, "%*s", padding, psdisp); + else + appendBinaryStringInfo(buf, psdisp, displen); + } + else if (padding != 0) + appendStringInfoSpaces(buf, + padding > 0 ? padding : -padding); + break; + case 'r': + if (MyProcPort && MyProcPort->remote_host) + { + if (padding != 0) + { + if (MyProcPort->remote_port && MyProcPort->remote_port[0] != '\0') + { + /* + * This option is slightly special as the port + * number may be appended onto the end. Here we + * need to build 1 string which contains the + * remote_host and optionally the remote_port (if + * set) so we can properly align the string. + */ + + char *hostport; + + hostport = psprintf("%s(%s)", MyProcPort->remote_host, MyProcPort->remote_port); + appendStringInfo(buf, "%*s", padding, hostport); + pfree(hostport); + } + else + appendStringInfo(buf, "%*s", padding, MyProcPort->remote_host); + } + else + { + /* padding is 0, so we don't need a temp buffer */ + appendStringInfoString(buf, MyProcPort->remote_host); + if (MyProcPort->remote_port && + MyProcPort->remote_port[0] != '\0') + appendStringInfo(buf, "(%s)", + MyProcPort->remote_port); + } + } + else if (padding != 0) + appendStringInfoSpaces(buf, + padding > 0 ? padding : -padding); + break; + case 'h': + if (MyProcPort && MyProcPort->remote_host) + { + if (padding != 0) + appendStringInfo(buf, "%*s", padding, MyProcPort->remote_host); + else + appendStringInfoString(buf, MyProcPort->remote_host); + } + else if (padding != 0) + appendStringInfoSpaces(buf, + padding > 0 ? padding : -padding); + break; + case 'q': + /* in postmaster and friends, stop if %q is seen */ + /* in a backend, just ignore */ + if (MyProcPort == NULL) + return; + break; + case 'v': + /* keep VXID format in sync with lockfuncs.c */ + if (MyProc != NULL && MyProc->backendId != InvalidBackendId) + { + if (padding != 0) + { + char strfbuf[128]; + + snprintf(strfbuf, sizeof(strfbuf) - 1, "%d/%u", + MyProc->backendId, MyProc->lxid); + appendStringInfo(buf, "%*s", padding, strfbuf); + } + else + appendStringInfo(buf, "%d/%u", MyProc->backendId, MyProc->lxid); + } + else if (padding != 0) + appendStringInfoSpaces(buf, + padding > 0 ? padding : -padding); + break; + case 'x': + if (padding != 0) + appendStringInfo(buf, "%*u", padding, GetTopTransactionIdIfAny()); + else + appendStringInfo(buf, "%u", GetTopTransactionIdIfAny()); + break; + case 'e': + if (padding != 0) + appendStringInfo(buf, "%*s", padding, unpack_sql_state(edata->sqlerrcode)); + else + appendStringInfoString(buf, unpack_sql_state(edata->sqlerrcode)); + break; + case 'Q': + if (padding != 0) + appendStringInfo(buf, "%*lld", padding, + (long long) pgstat_get_my_query_id()); + else + appendStringInfo(buf, "%lld", + (long long) pgstat_get_my_query_id()); + 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) +{ + StringInfoData buf; + bool fallback_to_stderr = false; + + initStringInfo(&buf); + + saved_timeval_set = false; + formatted_log_time[0] = '\0'; + + log_line_prefix(&buf, edata); + appendStringInfo(&buf, "%s: ", _(error_severity(edata->elevel))); + + if (Log_error_verbosity >= PGERROR_VERBOSE) + appendStringInfo(&buf, "%s: ", unpack_sql_state(edata->sqlerrcode)); + + if (edata->message) + append_with_tabs(&buf, edata->message); + else + 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) + { + log_line_prefix(&buf, edata); + appendStringInfoString(&buf, _("DETAIL: ")); + append_with_tabs(&buf, edata->detail_log); + appendStringInfoChar(&buf, '\n'); + } + else if (edata->detail) + { + log_line_prefix(&buf, edata); + appendStringInfoString(&buf, _("DETAIL: ")); + append_with_tabs(&buf, edata->detail); + appendStringInfoChar(&buf, '\n'); + } + if (edata->hint) + { + log_line_prefix(&buf, edata); + appendStringInfoString(&buf, _("HINT: ")); + append_with_tabs(&buf, edata->hint); + appendStringInfoChar(&buf, '\n'); + } + if (edata->internalquery) + { + log_line_prefix(&buf, edata); + appendStringInfoString(&buf, _("QUERY: ")); + append_with_tabs(&buf, edata->internalquery); + appendStringInfoChar(&buf, '\n'); + } + if (edata->context && !edata->hide_ctx) + { + log_line_prefix(&buf, edata); + 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, edata); + appendStringInfo(&buf, _("LOCATION: %s, %s:%d\n"), + edata->funcname, edata->filename, + edata->lineno); + } + else if (edata->filename) + { + log_line_prefix(&buf, edata); + appendStringInfo(&buf, _("LOCATION: %s:%d\n"), + edata->filename, edata->lineno); + } + } + if (edata->backtrace) + { + log_line_prefix(&buf, edata); + appendStringInfoString(&buf, _("BACKTRACE: ")); + append_with_tabs(&buf, edata->backtrace); + appendStringInfoChar(&buf, '\n'); + } + } + + /* + * If the user wants the query that generated this error logged, do it. + */ + if (check_log_of_query(edata)) + { + log_line_prefix(&buf, edata); + 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) + { + int syslog_level; + + switch (edata->elevel) + { + case DEBUG5: + case DEBUG4: + case DEBUG3: + case DEBUG2: + case DEBUG1: + syslog_level = LOG_DEBUG; + break; + case LOG: + case LOG_SERVER_ONLY: + case INFO: + syslog_level = LOG_INFO; + break; + case NOTICE: + case WARNING: + case WARNING_CLIENT_ONLY: + 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); + } +#endif /* HAVE_SYSLOG */ + +#ifdef WIN32 + /* Write to eventlog, if enabled */ + if (Log_destination & LOG_DESTINATION_EVENTLOG) + { + write_eventlog(edata->elevel, buf.data, buf.len); + } +#endif /* WIN32 */ + + /* Write to csvlog, if enabled */ + if (Log_destination & LOG_DESTINATION_CSVLOG) + { + /* + * Send CSV data if it's safe to do so (syslogger doesn't need the + * pipe). If this is not possible, fallback to an entry written to + * stderr. + */ + if (redirection_done || MyBackendType == B_LOGGER) + write_csvlog(edata); + else + fallback_to_stderr = true; + } + + /* Write to JSON log, if enabled */ + if (Log_destination & LOG_DESTINATION_JSONLOG) + { + /* + * Send JSON data if it's safe to do so (syslogger doesn't need the + * pipe). If this is not possible, fallback to an entry written to + * stderr. + */ + if (redirection_done || MyBackendType == B_LOGGER) + { + write_jsonlog(edata); + } + else + fallback_to_stderr = true; + } + + /* + * Write to stderr, if enabled or if required because of a previous + * limitation. + */ + if ((Log_destination & LOG_DESTINATION_STDERR) || + whereToSendOutput == DestDebug || + fallback_to_stderr) + { + /* + * Use the chunking protocol if we know the syslogger should be + * catching stderr output, and we are not ourselves the syslogger. + * Otherwise, just do a vanilla write to stderr. + */ + if (redirection_done && MyBackendType != B_LOGGER) + write_pipe_chunks(buf.data, buf.len, LOG_DESTINATION_STDERR); +#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 was OK to write to stderr above + * because that's really a pipe to the syslogger process. + */ + else if (pgwin32_is_service()) + write_eventlog(edata->elevel, buf.data, buf.len); +#endif + else + write_console(buf.data, buf.len); + } + + /* If in the syslogger process, try to write messages direct to file */ + if (MyBackendType == B_LOGGER) + write_syslogger_file(buf.data, buf.len, LOG_DESTINATION_STDERR); + + /* No more need of the message formatted for stderr */ + pfree(buf.data); +} + +/* + * Send data to the syslogger using the chunked protocol + * + * Note: when there are multiple backends writing into the syslogger pipe, + * it's critical that each write go into the pipe indivisibly, and not + * get interleaved with data from other processes. Fortunately, the POSIX + * spec requires that writes to pipes be atomic so long as they are not + * more than PIPE_BUF bytes long. So we divide long messages into chunks + * that are no more than that length, and send one chunk per write() call. + * The collector process knows how to reassemble the chunks. + * + * Because of the atomic write requirement, there are only two possible + * results from write() here: -1 for failure, or the requested number of + * bytes. There is not really anything we can do about a failure; retry would + * probably be an infinite loop, and we can't even report the error usefully. + * (There is noplace else we could send it!) So we might as well just ignore + * the result from write(). However, on some platforms you get a compiler + * warning from ignoring write()'s result, so do a little dance with casting + * rc to void to shut up the compiler. + */ +void +write_pipe_chunks(char *data, int len, int dest) +{ + PipeProtoChunk p; + int fd = fileno(stderr); + int rc; + + Assert(len > 0); + + p.proto.nuls[0] = p.proto.nuls[1] = '\0'; + p.proto.pid = MyProcPid; + p.proto.flags = 0; + if (dest == LOG_DESTINATION_STDERR) + p.proto.flags |= PIPE_PROTO_DEST_STDERR; + else if (dest == LOG_DESTINATION_CSVLOG) + p.proto.flags |= PIPE_PROTO_DEST_CSVLOG; + else if (dest == LOG_DESTINATION_JSONLOG) + p.proto.flags |= PIPE_PROTO_DEST_JSONLOG; + + /* write all but the last chunk */ + while (len > PIPE_MAX_PAYLOAD) + { + /* no need to set PIPE_PROTO_IS_LAST yet */ + p.proto.len = PIPE_MAX_PAYLOAD; + memcpy(p.proto.data, data, PIPE_MAX_PAYLOAD); + rc = write(fd, &p, PIPE_HEADER_SIZE + PIPE_MAX_PAYLOAD); + (void) rc; + data += PIPE_MAX_PAYLOAD; + len -= PIPE_MAX_PAYLOAD; + } + + /* write the last chunk */ + p.proto.flags |= PIPE_PROTO_IS_LAST; + p.proto.len = len; + memcpy(p.proto.data, data, len); + rc = write(fd, &p, PIPE_HEADER_SIZE + len); + (void) rc; +} + + +/* + * Append a text string to the error report being built for the client. + * + * This is ordinarily identical to pq_sendstring(), but if we are in + * error recursion trouble we skip encoding conversion, because of the + * possibility that the problem is a failure in the encoding conversion + * subsystem itself. Code elsewhere should ensure that the passed-in + * strings will be plain 7-bit ASCII, and thus not in need of conversion, + * in such cases. (In particular, we disable localization of error messages + * to help ensure that's true.) + */ +static void +err_sendstring(StringInfo buf, const char *str) +{ + if (in_error_recursion_trouble()) + pq_send_ascii_string(buf, str); + else + pq_sendstring(buf, str); +} + +/* + * Write error report to client + */ +static void +send_message_to_frontend(ErrorData *edata) +{ + StringInfoData msgbuf; + + /* + * We no longer support pre-3.0 FE/BE protocol, except here. If a client + * tries to connect using an older protocol version, it's nice to send the + * "protocol version not supported" error in a format the client + * understands. If protocol hasn't been set yet, early in backend + * startup, assume modern protocol. + */ + if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3 || FrontendProtocol == 0) + { + /* New style with separate fields */ + const char *sev; + char tbuf[12]; + + /* 'N' (Notice) is for nonfatal conditions, 'E' is for errors */ + pq_beginmessage(&msgbuf, (edata->elevel < ERROR) ? 'N' : 'E'); + + sev = error_severity(edata->elevel); + pq_sendbyte(&msgbuf, PG_DIAG_SEVERITY); + err_sendstring(&msgbuf, _(sev)); + pq_sendbyte(&msgbuf, PG_DIAG_SEVERITY_NONLOCALIZED); + err_sendstring(&msgbuf, sev); + + pq_sendbyte(&msgbuf, PG_DIAG_SQLSTATE); + err_sendstring(&msgbuf, unpack_sql_state(edata->sqlerrcode)); + + /* M field is required per protocol, so always send something */ + pq_sendbyte(&msgbuf, PG_DIAG_MESSAGE_PRIMARY); + if (edata->message) + err_sendstring(&msgbuf, edata->message); + else + err_sendstring(&msgbuf, _("missing error text")); + + if (edata->detail) + { + pq_sendbyte(&msgbuf, PG_DIAG_MESSAGE_DETAIL); + err_sendstring(&msgbuf, edata->detail); + } + + /* detail_log is intentionally not used here */ + + if (edata->hint) + { + pq_sendbyte(&msgbuf, PG_DIAG_MESSAGE_HINT); + err_sendstring(&msgbuf, edata->hint); + } + + if (edata->context) + { + pq_sendbyte(&msgbuf, PG_DIAG_CONTEXT); + err_sendstring(&msgbuf, edata->context); + } + + if (edata->schema_name) + { + pq_sendbyte(&msgbuf, PG_DIAG_SCHEMA_NAME); + err_sendstring(&msgbuf, edata->schema_name); + } + + if (edata->table_name) + { + pq_sendbyte(&msgbuf, PG_DIAG_TABLE_NAME); + err_sendstring(&msgbuf, edata->table_name); + } + + if (edata->column_name) + { + pq_sendbyte(&msgbuf, PG_DIAG_COLUMN_NAME); + err_sendstring(&msgbuf, edata->column_name); + } + + if (edata->datatype_name) + { + pq_sendbyte(&msgbuf, PG_DIAG_DATATYPE_NAME); + err_sendstring(&msgbuf, edata->datatype_name); + } + + if (edata->constraint_name) + { + pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_NAME); + err_sendstring(&msgbuf, edata->constraint_name); + } + + if (edata->cursorpos > 0) + { + snprintf(tbuf, sizeof(tbuf), "%d", edata->cursorpos); + pq_sendbyte(&msgbuf, PG_DIAG_STATEMENT_POSITION); + err_sendstring(&msgbuf, tbuf); + } + + if (edata->internalpos > 0) + { + snprintf(tbuf, sizeof(tbuf), "%d", edata->internalpos); + pq_sendbyte(&msgbuf, PG_DIAG_INTERNAL_POSITION); + err_sendstring(&msgbuf, tbuf); + } + + if (edata->internalquery) + { + pq_sendbyte(&msgbuf, PG_DIAG_INTERNAL_QUERY); + err_sendstring(&msgbuf, edata->internalquery); + } + + if (edata->filename) + { + pq_sendbyte(&msgbuf, PG_DIAG_SOURCE_FILE); + err_sendstring(&msgbuf, edata->filename); + } + + if (edata->lineno > 0) + { + snprintf(tbuf, sizeof(tbuf), "%d", edata->lineno); + pq_sendbyte(&msgbuf, PG_DIAG_SOURCE_LINE); + err_sendstring(&msgbuf, tbuf); + } + + if (edata->funcname) + { + pq_sendbyte(&msgbuf, PG_DIAG_SOURCE_FUNCTION); + err_sendstring(&msgbuf, edata->funcname); + } + + pq_sendbyte(&msgbuf, '\0'); /* terminator */ + + pq_endmessage(&msgbuf); + } + else + { + /* Old style --- gin up a backwards-compatible message */ + StringInfoData buf; + + initStringInfo(&buf); + + appendStringInfo(&buf, "%s: ", _(error_severity(edata->elevel))); + + if (edata->message) + appendStringInfoString(&buf, edata->message); + else + appendStringInfoString(&buf, _("missing error text")); + + appendStringInfoChar(&buf, '\n'); + + /* 'N' (Notice) is for nonfatal conditions, 'E' is for errors */ + pq_putmessage_v2((edata->elevel < ERROR) ? 'N' : 'E', buf.data, buf.len + 1); + + pfree(buf.data); + } + + /* + * 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. + */ + + +/* + * error_severity --- get string representing elevel + * + * The string is not localized here, but we mark the strings for translation + * so that callers can invoke _() on the result. + */ +const char * +error_severity(int elevel) +{ + const char *prefix; + + switch (elevel) + { + case DEBUG1: + case DEBUG2: + case DEBUG3: + case DEBUG4: + case DEBUG5: + prefix = gettext_noop("DEBUG"); + break; + case LOG: + case LOG_SERVER_ONLY: + prefix = gettext_noop("LOG"); + break; + case INFO: + prefix = gettext_noop("INFO"); + break; + case NOTICE: + prefix = gettext_noop("NOTICE"); + break; + case WARNING: + case WARNING_CLIENT_ONLY: + prefix = gettext_noop("WARNING"); + break; + case ERROR: + prefix = gettext_noop("ERROR"); + break; + case FATAL: + prefix = gettext_noop("FATAL"); + break; + case PANIC: + prefix = gettext_noop("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) +{ + char ch; + + while ((ch = *str++) != '\0') + { + appendStringInfoCharMacro(buf, ch); + if (ch == '\n') + appendStringInfoCharMacro(buf, '\t'); + } +} + + +/* + * Write errors to stderr (or by equal means when stderr is + * not available). Used before ereport/elog can be used + * safely (memory context, GUC load etc) + */ +void +write_stderr(const char *fmt,...) +{ + va_list ap; + +#ifdef WIN32 + char errbuf[2048]; /* Arbitrary size? */ +#endif + + fmt = _(fmt); + + va_start(ap, fmt); +#ifndef WIN32 + /* On Unix, we just fprintf to stderr */ + vfprintf(stderr, fmt, ap); + fflush(stderr); +#else + vsnprintf(errbuf, sizeof(errbuf), fmt, ap); + + /* + * On Win32, we print to stderr if running on a console, or write to + * eventlog if running as a service + */ + if (pgwin32_is_service()) /* Running as a service */ + { + write_eventlog(ERROR, errbuf, strlen(errbuf)); + } + else + { + /* Not running as service, write to stderr */ + write_console(errbuf, strlen(errbuf)); + fflush(stderr); + } +#endif + va_end(ap); +} + + +/* + * Write a message to STDERR using only async-signal-safe functions. This can + * be used to safely emit a message from a signal handler. + * + * TODO: It is likely possible to safely do a limited amount of string + * interpolation (e.g., %s and %d), but that is not presently supported. + */ +void +write_stderr_signal_safe(const char *str) +{ + int nwritten = 0; + int ntotal = strlen(str); + + while (nwritten < ntotal) + { + int rc; + + rc = write(STDERR_FILENO, str + nwritten, ntotal - nwritten); + + /* Just give up on error. There isn't much else we can do. */ + if (rc == -1) + return; + + nwritten += rc; + } +} + + +/* + * Adjust the level of a recovery-related message per trace_recovery_messages. + * + * The argument is the default log level of the message, eg, DEBUG2. (This + * should only be applied to DEBUGn log messages, otherwise it's a no-op.) + * If the level is >= trace_recovery_messages, we return LOG, causing the + * message to be logged unconditionally (for most settings of + * log_min_messages). Otherwise, we return the argument unchanged. + * The message will then be shown based on the setting of log_min_messages. + * + * Intention is to keep this for at least the whole of the 9.0 production + * release, so we can more easily diagnose production problems in the field. + * It should go away eventually, though, because it's an ugly and + * hard-to-explain kluge. + */ +int +trace_recovery(int trace_level) +{ + if (trace_level < LOG && + trace_level >= trace_recovery_messages) + return LOG; + + return trace_level; +} diff --git a/src/backend/utils/error/jsonlog.c b/src/backend/utils/error/jsonlog.c new file mode 100644 index 0000000..e327774 --- /dev/null +++ b/src/backend/utils/error/jsonlog.c @@ -0,0 +1,303 @@ +/*------------------------------------------------------------------------- + * + * jsonlog.c + * JSON logging + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/error/jsonlog.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/xact.h" +#include "libpq/libpq.h" +#include "lib/stringinfo.h" +#include "miscadmin.h" +#include "postmaster/bgworker.h" +#include "postmaster/syslogger.h" +#include "storage/lock.h" +#include "storage/proc.h" +#include "tcop/tcopprot.h" +#include "utils/backend_status.h" +#include "utils/elog.h" +#include "utils/guc.h" +#include "utils/json.h" +#include "utils/ps_status.h" + +static void appendJSONKeyValueFmt(StringInfo buf, const char *key, + bool escape_key, + const char *fmt,...) pg_attribute_printf(4, 5); + +/* + * appendJSONKeyValue + * + * Append to a StringInfo a comma followed by a JSON key and a value. + * The key is always escaped. The value can be escaped optionally, that + * is dependent on the data type of the key. + */ +static void +appendJSONKeyValue(StringInfo buf, const char *key, const char *value, + bool escape_value) +{ + Assert(key != NULL); + + if (value == NULL) + return; + + appendStringInfoChar(buf, ','); + escape_json(buf, key); + appendStringInfoChar(buf, ':'); + + if (escape_value) + escape_json(buf, value); + else + appendStringInfoString(buf, value); +} + + +/* + * appendJSONKeyValueFmt + * + * Evaluate the fmt string and then invoke appendJSONKeyValue() as the + * value of the JSON property. Both the key and value will be escaped by + * appendJSONKeyValue(). + */ +static void +appendJSONKeyValueFmt(StringInfo buf, const char *key, + bool escape_key, const char *fmt,...) +{ + int save_errno = errno; + size_t len = 128; /* initial assumption about buffer size */ + char *value; + + for (;;) + { + va_list args; + size_t newlen; + + /* Allocate result buffer */ + value = (char *) palloc(len); + + /* Try to format the data. */ + errno = save_errno; + va_start(args, fmt); + newlen = pvsnprintf(value, len, fmt, args); + va_end(args); + + if (newlen < len) + break; /* success */ + + /* Release buffer and loop around to try again with larger len. */ + pfree(value); + len = newlen; + } + + appendJSONKeyValue(buf, key, value, escape_key); + + /* Clean up */ + pfree(value); +} + +/* + * Write logs in json format. + */ +void +write_jsonlog(ErrorData *edata) +{ + StringInfoData buf; + char *start_time; + char *log_time; + + /* static counter for line numbers */ + static long log_line_number = 0; + + /* Has the counter been reset in the 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; + reset_formatted_start_time(); + } + log_line_number++; + + initStringInfo(&buf); + + /* Initialize string */ + appendStringInfoChar(&buf, '{'); + + /* timestamp with milliseconds */ + log_time = get_formatted_log_time(); + + /* + * First property does not use appendJSONKeyValue as it does not have + * comma prefix. + */ + escape_json(&buf, "timestamp"); + appendStringInfoChar(&buf, ':'); + escape_json(&buf, log_time); + + /* username */ + if (MyProcPort) + appendJSONKeyValue(&buf, "user", MyProcPort->user_name, true); + + /* database name */ + if (MyProcPort) + appendJSONKeyValue(&buf, "dbname", MyProcPort->database_name, true); + + /* Process ID */ + if (MyProcPid != 0) + appendJSONKeyValueFmt(&buf, "pid", false, "%d", MyProcPid); + + /* Remote host and port */ + if (MyProcPort && MyProcPort->remote_host) + { + appendJSONKeyValue(&buf, "remote_host", MyProcPort->remote_host, true); + if (MyProcPort->remote_port && MyProcPort->remote_port[0] != '\0') + appendJSONKeyValue(&buf, "remote_port", MyProcPort->remote_port, false); + } + + /* Session id */ + appendJSONKeyValueFmt(&buf, "session_id", true, "%lx.%x", + (long) MyStartTime, MyProcPid); + + /* Line number */ + appendJSONKeyValueFmt(&buf, "line_num", false, "%ld", log_line_number); + + /* PS display */ + if (MyProcPort) + { + StringInfoData msgbuf; + const char *psdisp; + int displen; + + initStringInfo(&msgbuf); + psdisp = get_ps_display(&displen); + appendBinaryStringInfo(&msgbuf, psdisp, displen); + appendJSONKeyValue(&buf, "ps", msgbuf.data, true); + + pfree(msgbuf.data); + } + + /* session start timestamp */ + start_time = get_formatted_start_time(); + appendJSONKeyValue(&buf, "session_start", start_time, true); + + /* Virtual transaction id */ + /* keep VXID format in sync with lockfuncs.c */ + if (MyProc != NULL && MyProc->backendId != InvalidBackendId) + appendJSONKeyValueFmt(&buf, "vxid", true, "%d/%u", MyProc->backendId, + MyProc->lxid); + + /* Transaction id */ + appendJSONKeyValueFmt(&buf, "txid", false, "%u", + GetTopTransactionIdIfAny()); + + /* Error severity */ + if (edata->elevel) + appendJSONKeyValue(&buf, "error_severity", + (char *) error_severity(edata->elevel), true); + + /* SQL state code */ + if (edata->sqlerrcode) + appendJSONKeyValue(&buf, "state_code", + unpack_sql_state(edata->sqlerrcode), true); + + /* errmessage */ + appendJSONKeyValue(&buf, "message", edata->message, true); + + /* errdetail or error_detail log */ + if (edata->detail_log) + appendJSONKeyValue(&buf, "detail", edata->detail_log, true); + else + appendJSONKeyValue(&buf, "detail", edata->detail, true); + + /* errhint */ + if (edata->hint) + appendJSONKeyValue(&buf, "hint", edata->hint, true); + + /* internal query */ + if (edata->internalquery) + appendJSONKeyValue(&buf, "internal_query", edata->internalquery, + true); + + /* if printed internal query, print internal pos too */ + if (edata->internalpos > 0 && edata->internalquery != NULL) + appendJSONKeyValueFmt(&buf, "internal_position", false, "%d", + edata->internalpos); + + /* errcontext */ + if (edata->context && !edata->hide_ctx) + appendJSONKeyValue(&buf, "context", edata->context, true); + + /* user query --- only reported if not disabled by the caller */ + if (check_log_of_query(edata)) + { + appendJSONKeyValue(&buf, "statement", debug_query_string, true); + if (edata->cursorpos > 0) + appendJSONKeyValueFmt(&buf, "cursor_position", false, "%d", + edata->cursorpos); + } + + /* file error location */ + if (Log_error_verbosity >= PGERROR_VERBOSE) + { + if (edata->funcname) + appendJSONKeyValue(&buf, "func_name", edata->funcname, true); + if (edata->filename) + { + appendJSONKeyValue(&buf, "file_name", edata->filename, true); + appendJSONKeyValueFmt(&buf, "file_line_num", false, "%d", + edata->lineno); + } + } + + /* Application name */ + if (application_name && application_name[0] != '\0') + appendJSONKeyValue(&buf, "application_name", application_name, true); + + /* backend type */ + appendJSONKeyValue(&buf, "backend_type", get_backend_type_for_log(), true); + + /* leader PID */ + if (MyProc) + { + PGPROC *leader = MyProc->lockGroupLeader; + + /* + * Show the leader only for active parallel workers. This leaves out + * the leader of a parallel group. + */ + if (leader && leader->pid != MyProcPid) + appendJSONKeyValueFmt(&buf, "leader_pid", false, "%d", + leader->pid); + } + + /* query id */ + appendJSONKeyValueFmt(&buf, "query_id", false, "%lld", + (long long) pgstat_get_my_query_id()); + + /* Finish string */ + appendStringInfoChar(&buf, '}'); + appendStringInfoChar(&buf, '\n'); + + /* If in the syslogger process, try to write messages direct to file */ + if (MyBackendType == B_LOGGER) + write_syslogger_file(buf.data, buf.len, LOG_DESTINATION_JSONLOG); + else + write_pipe_chunks(buf.data, buf.len, LOG_DESTINATION_JSONLOG); + + pfree(buf.data); +} diff --git a/src/backend/utils/error/meson.build b/src/backend/utils/error/meson.build new file mode 100644 index 0000000..86b7075 --- /dev/null +++ b/src/backend/utils/error/meson.build @@ -0,0 +1,8 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +backend_sources += files( + 'assert.c', + 'csvlog.c', + 'elog.c', + 'jsonlog.c', +) |