From 7f4107ce917af64818fe0650882ff94b170f75dc Mon Sep 17 00:00:00 2001 From: Laurenz Albe Date: Sat, 15 Mar 2025 07:09:15 +0100 Subject: [PATCH] Add parameter log_suppress_errcodes The parameter contains a list of SQLSTATEs for which FATAL and ERROR messages are not logged. This is to suppress messages that are of little interest to the database administrator, but tend to clutter the log. Author: Laurenz Albe Reviewed-by: Jim Jones Reviewed-by: Jelte Fennema-Nio Reviewed-by: Justin Pryzby Reviewed-by: Rafia Sabih Discussion: https://postgr.es/m/408f399e7de1416c47bab7e260327ed5ad92838c.camel%40cybertec.at --- doc/src/sgml/config.sgml | 35 ++++ src/backend/utils/error/elog.c | 156 ++++++++++++++++++ src/backend/utils/misc/guc_tables.c | 11 ++ src/backend/utils/misc/postgresql.conf.sample | 3 + src/include/pg_config_manual.h | 10 ++ src/include/utils/guc.h | 1 + src/include/utils/guc_hooks.h | 2 + 7 files changed, 218 insertions(+) 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);