diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index fea683cb49ce..0f275ee81adf 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7017,6 +7017,41 @@ local0.* /var/log/postgresql
+
+ log_suppress_errcodes (string)
+
+ log_suppress_errcodes configuration parameter
+
+
+
+
+ Causes ERROR and FATAL messages
+ from client backend processes with certain error codes to be excluded
+ from the log.
+ The value is a comma-separated list of five-character error codes as
+ listed in . An error code that
+ represents a class of errors (ends with three zeros) suppresses logging
+ of all error codes within that class. For example, the entry
+ 08000 (connection_exception)
+ would suppress an error with code 08P01
+ (protocol_violation). The default setting is
+ empty, that is, all error codes get logged.
+ Only superusers and users with the appropriate SET
+ privilege can change this setting.
+
+
+
+ This setting allows you to skip error logging for messages that are
+ frequent but irrelevant. Supressing such messages makes it easier to
+ spot relevant messages in the log and keeps log files from growing too
+ big. For example, if you have a monitoring system that regularly
+ establishes a TCP connection to the server without sending a correct
+ startup packet, you could suppress the protocol violation errors by
+ adding the error code 08P01 to the list.
+
+
+
+
log_min_duration_statement (integer)
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 8a6b6905079d..2966ab361899 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -109,12 +109,16 @@ 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;
+char *log_suppress_errcodes = NULL;
bool syslog_sequence_numbers = true;
bool syslog_split_messages = true;
/* Processed form of backtrace_functions GUC */
static char *backtrace_function_list;
+/* Processed form form of log_suppress_errcodes (zero-terminated array) */
+static int *suppressed_errcodes;
+
#ifdef HAVE_SYSLOG
/*
@@ -859,6 +863,30 @@ errcode(int sqlerrcode)
edata->sqlerrcode = sqlerrcode;
+ /*
+ * ERROR and FATAL messages with codes in log_suppress_errcodes don't get
+ * logged. We only want to suppress error messages from ordinary backend
+ * processes.
+ */
+ if ((MyBackendType == B_BACKEND ||
+ MyBackendType == B_STANDALONE_BACKEND) &&
+ (edata->elevel == ERROR ||
+ edata->elevel == FATAL) &&
+ suppressed_errcodes != NULL)
+ {
+ int *state;
+
+ for (state = suppressed_errcodes; *state != 0; state++)
+ /* error categories match all error codes in the category */
+ if (sqlerrcode == *state ||
+ (ERRCODE_IS_CATEGORY(*state) &&
+ ERRCODE_TO_CATEGORY(sqlerrcode) == *state))
+ {
+ edata->output_to_server = false;
+ break;
+ }
+ }
+
return 0; /* return value does not matter */
}
@@ -2324,6 +2352,134 @@ assign_log_destination(const char *newval, void *extra)
Log_destination = *((int *) extra);
}
+/*
+ * GUC check_hook for log_suppress_errcodes
+ *
+ * Split the string on the commas, check the SQLSTATEs for validity, convert
+ * them into the packed integer form and store them in a 0-terminated array.
+ * That array is returned as "extra" and will be assigned to
+ * "suppressed_errcodes" in the assign hook.
+ *
+ * Replace the actual parameter with a sanitized version reassembled from that
+ * array.
+ */
+bool
+check_log_suppress_errcodes(char **newval, void **extra, GucSource source)
+{
+ /* SplitIdentifierString modifies the string */
+ char *new_copy = pstrdup(*newval);
+ int listlen;
+ int *statelist = NULL;
+ int index = 0;
+ int param_len = 1; /* size of the parameter value replacement */
+ List *states;
+ ListCell *c;
+
+ if (!SplitIdentifierString(new_copy, ',', &states))
+ {
+ GUC_check_errdetail("List syntax is invalid.");
+ goto failed;
+ }
+
+ listlen = list_length(states);
+ /* we need guc_malloc(), because that will be returned in "extra" */
+ statelist = guc_malloc(LOG, sizeof(int) * (listlen + 1));
+ if (!statelist)
+ goto failed;
+
+ /* check all error codes for validity and compile them into statelist */
+ foreach(c, states)
+ {
+ char *state = lfirst(c);
+ char *p;
+ int errcode;
+ int i;
+ bool duplicate = false;
+
+ if (strlen(state) != 5)
+ {
+ GUC_check_errdetail("error codes must have 5 characters.");
+ goto failed;
+ }
+
+ /*
+ * Check the the values are alphanumeric and convert them to upper
+ * case (SplitIdentifierString converted them to lower case).
+ */
+ for (p = state; *p != '\0'; p++)
+ if (*p >= 'a' && *p <= 'z')
+ *p += 'A' - 'a';
+ else if (*p < '0' || *p > '9')
+ {
+ GUC_check_errdetail("error codes can only contain digits and ASCII letters.");
+ goto failed;
+ }
+
+ errcode = MAKE_SQLSTATE(state[0], state[1], state[2], state[3], state[4]);
+ /* ignore 0: it cannot be an error code, and we use it to end the list */
+ if (errcode == ERRCODE_SUCCESSFUL_COMPLETION)
+ continue;
+
+ /* ignore duplicate entries */
+ for (i = 0; i < index; i++)
+ if (statelist[i] == errcode)
+ duplicate = true;
+ if (duplicate)
+ continue;
+
+ statelist[index++] = errcode;
+ param_len += 6;
+ }
+ statelist[index] = 0;
+
+ /* will be assigned to "suppressed_errcodes" */
+ *extra = statelist;
+
+ /*
+ * Now that we have successfully parsed the SQLSTATEs, reassemble a string
+ * list for the actual parameter value. That will remove extra spaces and
+ * duplicates and convert the values to upper case.
+ */
+ guc_free(*newval);
+ *newval = guc_malloc(LOG, param_len);
+ if (!*newval)
+ goto failed;
+
+ list_free(states);
+ pfree(new_copy);
+
+ (*newval)[0] = '\0';
+ index = 0;
+ while (statelist[index] != 0)
+ {
+ if (index > 0)
+ strncat(*newval, ",", 2);
+ strncat(*newval, unpack_sql_state(statelist[index]), 6);
+ index++;
+ }
+
+ return true;
+
+failed:
+ list_free(states);
+ pfree(new_copy);
+ guc_free(statelist); /* won't gag on NULL */
+ return false;
+}
+
+/*
+ * GUC assign_hook for log_suppress_errcodes
+ */
+void
+assign_log_suppress_errcodes(const char *newval, void *extra)
+{
+ /* store NULL instead of an empty array for performance */
+ if (*(int *) extra == 0)
+ suppressed_errcodes = NULL;
+ else
+ suppressed_errcodes = extra;
+}
+
/*
* GUC assign_hook for syslog_ident
*/
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 4eaeca89f2c7..a075cddf05cf 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -4710,6 +4710,17 @@ struct config_string ConfigureNamesString[] =
check_canonical_path, NULL, NULL
},
+ {
+ {"log_suppress_errcodes", PGC_SUSET, LOGGING_WHEN,
+ gettext_noop("ERROR and FATAL messages with these error codes don't get logged."),
+ NULL,
+ GUC_LIST_INPUT
+ },
+ &log_suppress_errcodes,
+ DEFAULT_LOG_SUPPRESS_ERRCODES,
+ check_log_suppress_errcodes, assign_log_suppress_errcodes, NULL
+ },
+
{
{"ssl_library", PGC_INTERNAL, PRESET_OPTIONS,
gettext_noop("Shows the name of the SSL library."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index ff56a1f0732c..e528ae68163f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -553,6 +553,9 @@
# fatal
# panic (effectively off)
+#log_suppress_errcodes = '' # FATAL and ERROR messages with these error codes
+ # are not logged
+
#log_min_duration_statement = -1 # -1 is disabled, 0 logs all statements
# and their durations, > 0 logs only
# statements running at least this number
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 125d3eb5fff5..eb05b865e4cf 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -200,6 +200,16 @@
*/
#define DEFAULT_EVENT_SOURCE "PostgreSQL"
+/*
+ * Default value for log_suppress_errcodes. ERROR or FATAL messages with
+ * these error codes are never logged. Error classes (error codes ending with
+ * three zeros) match all error codes in the class. The idea is to suppress
+ * messages that usually don't indicate a serious problem but tend to pollute
+ * the log file.
+ */
+
+#define DEFAULT_LOG_SUPPRESS_ERRCODES ""
+
/*
* Assumed cache line size. This doesn't affect correctness, but can be used
* for low-level optimizations. This is mostly used to pad various data
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f619100467df..274ab6a9bead 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -271,6 +271,7 @@ extern PGDLLIMPORT bool log_duration;
extern PGDLLIMPORT int log_parameter_max_length;
extern PGDLLIMPORT int log_parameter_max_length_on_error;
extern PGDLLIMPORT int log_min_error_statement;
+extern PGDLLIMPORT char *log_suppress_errcodes;
extern PGDLLIMPORT int log_min_messages;
extern PGDLLIMPORT int client_min_messages;
extern PGDLLIMPORT int log_min_duration_sample;
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 799fa7ace684..4deb72cfbef5 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -78,6 +78,8 @@ extern bool check_log_destination(char **newval, void **extra,
extern void assign_log_destination(const char *newval, void *extra);
extern const char *show_log_file_mode(void);
extern bool check_log_stats(bool *newval, void **extra, GucSource source);
+extern bool check_log_suppress_errcodes(char **newval, void **extra, GucSource source);
+extern void assign_log_suppress_errcodes(const char *newval, void *extra);
extern bool check_log_timezone(char **newval, void **extra, GucSource source);
extern void assign_log_timezone(const char *newval, void *extra);
extern const char *show_log_timezone(void);