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);