From 213f647657f318141e3866087a17a863a0f322d9 Mon Sep 17 00:00:00 2001 From: Oskari Saarenmaa Date: Tue, 14 Jan 2014 15:47:39 +0200 Subject: [PATCH] Filter error log statements by sqlstate Allow the default log_min_error_statement to be overridden per sqlstate to make it possible to filter out some error types while maintaining a low log_min_error_statement or enable logging for some error types when the default is to not log anything. --- doc/src/sgml/config.sgml | 30 ++++++ src/backend/utils/error/elog.c | 220 +++++++++++++++++++++++++++++++++++++- src/backend/utils/misc/guc.c | 14 ++- src/include/utils/guc.h | 4 + src/include/utils/guc_tables.h | 1 + src/test/regress/expected/guc.out | 24 +++++ src/test/regress/sql/guc.sql | 8 ++ 7 files changed, 298 insertions(+), 3 deletions(-) diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 0f2f2bf..73a58ad 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -3743,6 +3743,36 @@ local0.* /var/log/postgresql + + log_sqlstate_error_statement (string) + + log_sqlstate_error_statement configuration parameter + + + + Controls which error types in SQL statements condition are recorded + in the server log. This overrides the global per error type and can be used to + disable logging for certain error types and/or to enable logging for + other types. + + + The value must be a comma-separated list in the form + 'ERRORCODE:LOGLEVEL,...'. For example, a setting + of 'P0001:PANIC,22012:ERROR' would never log the + SQL statements for errors generated by the PL/pgSQL + RAISE statement but would always log the + statements causing division by zero errors. + + See for the list of valid error + codes and for valid log + levels. + + Only superusers can change this setting. + + + + log_min_duration_statement (integer) diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index 8705586..2e74fd5 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -74,7 +74,9 @@ #include "storage/ipc.h" #include "storage/proc.h" #include "tcop/tcopprot.h" +#include "utils/builtins.h" #include "utils/guc.h" +#include "utils/guc_tables.h" #include "utils/memutils.h" #include "utils/ps_status.h" @@ -111,6 +113,11 @@ char *Log_line_prefix = NULL; /* format for extra log line info */ int Log_destination = LOG_DESTINATION_STDERR; char *Log_destination_string = NULL; +static uint64 *log_sqlstate_error_statement = NULL; +static size_t log_sqlstate_error_statement_len = 0; + +static int get_sqlstate_error_level(int sqlstate); + #ifdef HAVE_SYSLOG /* @@ -2496,6 +2503,7 @@ static void write_csvlog(ErrorData *edata) { StringInfoData buf; + int requested_log_level; bool print_stmt = false; /* static counter for line numbers */ @@ -2639,7 +2647,10 @@ write_csvlog(ErrorData *edata) appendStringInfoChar(&buf, ','); /* user query --- only reported if not disabled by the caller */ - if (is_log_level_output(edata->elevel, log_min_error_statement) && + requested_log_level = get_sqlstate_error_level(edata->sqlerrcode); + if (requested_log_level < 0) + requested_log_level = log_min_error_statement; + if (is_log_level_output(edata->elevel, requested_log_level) && debug_query_string != NULL && !edata->hide_stmt) print_stmt = true; @@ -2712,6 +2723,7 @@ static void send_message_to_server_log(ErrorData *edata) { StringInfoData buf; + int requested_log_level; initStringInfo(&buf); @@ -2796,7 +2808,10 @@ send_message_to_server_log(ErrorData *edata) /* * If the user wants the query that generated this error logged, do it. */ - if (is_log_level_output(edata->elevel, log_min_error_statement) && + requested_log_level = get_sqlstate_error_level(edata->sqlerrcode); + if (requested_log_level < 0) + requested_log_level = log_min_error_statement; + if (is_log_level_output(edata->elevel, requested_log_level) && debug_query_string != NULL && !edata->hide_stmt) { @@ -3598,3 +3613,204 @@ trace_recovery(int trace_level) return trace_level; } + + +/* + * Statement log error level can be overriden per sqlstate, the variable + * log_sqlstate_error_statement is a sorted array of + * log_sqlstate_error_statement_len uint64s where the high 32 bits contain + * the sqlstate and low 32 bits contain the required error level. + */ + +/* + * get_sqlstate_error_level - perform a binary search for the requested + * state and return if it was found; return -1 if it was not found. + */ +static int +get_sqlstate_error_level(int sqlstate) +{ + uint64 left = 0, + right = log_sqlstate_error_statement_len; + + while (left < right) + { + uint64 middle = left + (right - left) / 2; + int m_sqlstate = log_sqlstate_error_statement[middle] >> 32; + + if (m_sqlstate == sqlstate) + return log_sqlstate_error_statement[middle] & 0xFFFFFFFF; + else if (m_sqlstate < sqlstate) + left = middle + 1; + else + right = middle; + } + return -1; +} + +/* + * check_log_sqlstate_error - validate sqlstate:errorlevel lists and create + * sorted uint64 arrays from them; last occurance of each sqlstate is used. + */ +bool +check_log_sqlstate_error(char **newval, void **extra, GucSource source) +{ + const struct config_enum_entry *enum_entry; + char *rawstring, + *new_newval, + *new_newval_end, + *vptr; + List *elemlist; + ListCell *l; + uint64 *new_array = NULL; + int i, + new_array_len = 0; + + /* 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; + } + + /* + * GUC wants malloced results, allocate room for as many elements on the + * list plus one to hold the array size + */ + new_array = (uint64 *) malloc(sizeof(uint64) * (list_length(elemlist) + 1)); + if (!new_array) + { + pfree(rawstring); + list_free(elemlist); + return false; + } + + /* validate list and insert the results in a sorted array */ + foreach(l, elemlist) + { + char *tok = lfirst(l), + *level_str = strchr(tok, ':'); + int level = -1, + sqlstate; + uint64 value; + + if (level_str != NULL && (level_str - tok) == 5) + { + for (enum_entry = server_message_level_options; + enum_entry && enum_entry->name; + enum_entry++) + { + if (pg_strcasecmp(enum_entry->name, level_str + 1) == 0) + { + level = enum_entry->val; + break; + } + } + } + if (level < 0) + { + GUC_check_errdetail("Invalid sqlstate error definition: \"%s\".", tok); + new_array_len = -1; + break; + } + sqlstate = MAKE_SQLSTATE(pg_ascii_toupper(tok[0]), pg_ascii_toupper(tok[1]), + pg_ascii_toupper(tok[2]), pg_ascii_toupper(tok[3]), + pg_ascii_toupper(tok[4])); + value = (((uint64) sqlstate) << 32) | ((uint64) level); + + for (i = 0; i <= new_array_len; i++) + { + if (i == new_array_len) + { + new_array[++new_array_len] = value; + break; + } + else if (sqlstate == (int) (new_array[i + 1] >> 32)) + { + new_array[i + 1] = value; + break; + } + else if (sqlstate < (int) (new_array[i + 1] >> 32)) + { + memmove(&new_array[i + 2], &new_array[i + 1], + (new_array_len - i) * sizeof(uint64)); + ++new_array_len; + new_array[i + 1] = value; + break; + } + } + } + + pfree(rawstring); + list_free(elemlist); + + if (new_array_len < 0) + { + free(new_array); + return false; + } + + /* store the length in the first field */ + new_array[0] = new_array_len; + + /* + * calculate the maximum length for the canonical string and + * allocate a buffer for it. sqlstate is always 5 characters and + * the longest error level string currently is 'warning'. + */ + new_newval = (char *) malloc(strlen("XX000:warning,") * new_array_len + 1); + if (!new_newval) + { + free(new_array); + return false; + } + + vptr = new_newval; + for (i = 1; i <= new_array_len; i++) + { + const char *level_str = "null"; + + for (enum_entry = server_message_level_options; + enum_entry && enum_entry->name; + enum_entry++) + { + if (enum_entry->val == (new_array[i] & 0xFFFFFFFF)) + { + level_str = enum_entry->name; + break; + } + } + + vptr += snprintf(vptr, new_newval_end - vptr, "%s%s:%s", + (i > 1) ? "," : "", + unpack_sql_state(new_array[i] >> 32), + level_str); + } + *vptr = 0; + + free(*newval); + *newval = new_newval; + *extra = new_array; + + return true; +} + +/* + * assign_log_sqlstate_error - take the array generated by + * check_log_sqlstate_error into use. 'extra' is an array of uint64s where + * the first element contains the list length and the remainder is + * sqlstate:errorlevel values. + */ +void +assign_log_sqlstate_error(const char *newval, void *extra) +{ + uint64 *myextra = (uint64 *) extra; + + log_sqlstate_error_statement_len = myextra[0]; + log_sqlstate_error_statement = &myextra[1]; +} diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 1217098..f6cbee5 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -245,7 +245,7 @@ static const struct config_enum_entry client_message_level_options[] = { {NULL, 0, false} }; -static const struct config_enum_entry server_message_level_options[] = { +const struct config_enum_entry server_message_level_options[] = { {"debug", DEBUG2, true}, {"debug5", DEBUG5, false}, {"debug4", DEBUG4, false}, @@ -465,6 +465,7 @@ static char *server_version_string; static int server_version_num; static char *timezone_string; static char *log_timezone_string; +static char *log_sqlstate_error_statement_str; static char *timezone_abbreviations_string; static char *XactIsoLevel_string; static char *session_authorization_string; @@ -2947,6 +2948,17 @@ static struct config_string ConfigureNamesString[] = }, { + {"log_sqlstate_error_statement", PGC_SUSET, LOGGING_WHEN, + gettext_noop("Overrides minimum error level per error type"), + gettext_noop("Value must be a comma-separated list in the format " + "\"sqlstate:level,...\"."), + }, + &log_sqlstate_error_statement_str, + "", + check_log_sqlstate_error, assign_log_sqlstate_error, NULL + }, + + { {"syslog_ident", PGC_SIGHUP, LOGGING_WHERE, gettext_noop("Sets the program name used to identify PostgreSQL " "messages in syslog."), diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index 3adcc99..2ed5677 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -391,4 +391,8 @@ extern bool check_effective_cache_size(int *newval, void **extra, GucSource sour extern void set_default_effective_cache_size(void); extern void assign_xlog_sync_method(int new_sync_method, void *extra); +/* in src/backend/utils/error/elog.c */ +extern void assign_log_sqlstate_error(const char *newval, void *extra); +extern bool check_log_sqlstate_error(char **newval, void **extra, GucSource source); + #endif /* GUC_H */ diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h index 47ff880..1e577a4 100644 --- a/src/include/utils/guc_tables.h +++ b/src/include/utils/guc_tables.h @@ -251,6 +251,7 @@ extern const char *const config_group_names[]; extern const char *const config_type_names[]; extern const char *const GucContext_Names[]; extern const char *const GucSource_Names[]; +extern const struct config_enum_entry server_message_level_options[]; /* get the current set of variables */ extern struct config_generic **get_guc_variables(void); diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out index 4f0065c..0d0c4e0 100644 --- a/src/test/regress/expected/guc.out +++ b/src/test/regress/expected/guc.out @@ -736,3 +736,27 @@ NOTICE: text search configuration "no_such_config" does not exist select func_with_bad_set(); ERROR: invalid value for parameter "default_text_search_config": "no_such_config" reset check_function_bodies; +-- Test logging options +SET log_sqlstate_error_statement = 'XX000:PANIC,22012:error,xx000:log'; +SHOW log_sqlstate_error_statement; + log_sqlstate_error_statement +------------------------------ + XX000:log,22012:error +(1 row) + +SET log_sqlstate_error_statement = ''; +SHOW log_sqlstate_error_statement; + log_sqlstate_error_statement +------------------------------ + +(1 row) + +SET log_sqlstate_error_statement = 'x'; +ERROR: invalid value for parameter "log_sqlstate_error_statement": "x" +DETAIL: Invalid sqlstate error definition: "x". +SHOW log_sqlstate_error_statement; + log_sqlstate_error_statement +------------------------------ + +(1 row) + diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql index 3de8a6b..508a8a4 100644 --- a/src/test/regress/sql/guc.sql +++ b/src/test/regress/sql/guc.sql @@ -275,3 +275,11 @@ set default_text_search_config = no_such_config; select func_with_bad_set(); reset check_function_bodies; + +-- Test logging options +SET log_sqlstate_error_statement = 'XX000:PANIC,22012:error,xx000:log'; +SHOW log_sqlstate_error_statement; +SET log_sqlstate_error_statement = ''; +SHOW log_sqlstate_error_statement; +SET log_sqlstate_error_statement = 'x'; +SHOW log_sqlstate_error_statement; -- 1.8.4.2