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.

I've tried to do something like this using rsyslog filters, but that's pretty awkward and doesn't work at all when the statement is split to multiple syslog messages.

https://github.com/saaros/postgres/compare/log-by-sqlstate

src/backend/utils/error/elog.c | 183 ++++++++++++++++++++++++++++++++++++++++-
 src/backend/utils/misc/guc.c   |  14 +++-
 src/include/utils/guc.h        |   4 +
 src/include/utils/guc_tables.h |   1 +
 4 files changed, 199 insertions(+), 3 deletions(-)

/ Oskari
>From 61fe332f35f49c59257e9dcd0b5e2ff80f1f4055 Mon Sep 17 00:00:00 2001
From: Oskari Saarenmaa <o...@ohmu.fi>
Date: Thu, 9 Jan 2014 20:49:28 +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.
---
 src/backend/utils/error/elog.c | 183 ++++++++++++++++++++++++++++++++++++++++-
 src/backend/utils/misc/guc.c   |  14 +++-
 src/include/utils/guc.h        |   4 +
 src/include/utils/guc_tables.h |   1 +
 4 files changed, 199 insertions(+), 3 deletions(-)

diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 3de162b..c843e1a 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
 
 /*
@@ -2475,6 +2482,7 @@ static void
 write_csvlog(ErrorData *edata)
 {
 	StringInfoData buf;
+	int		requested_log_level;
 	bool		print_stmt = false;
 
 	/* static counter for line numbers */
@@ -2618,7 +2626,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;
@@ -2691,6 +2702,7 @@ static void
 send_message_to_server_log(ErrorData *edata)
 {
 	StringInfoData buf;
+	int requested_log_level;
 
 	initStringInfo(&buf);
 
@@ -2775,7 +2787,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)
 	{
@@ -3577,3 +3592,167 @@ trace_recovery(int trace_level)
 
 	return trace_level;
 }
+
+
+/*
+*/
+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;
+}
+
+bool
+check_log_sqlstate_error(char **newval, void **extra, GucSource source)
+{
+	const struct config_enum_entry *enum_entry;
+	char	   *rawstring, *new_newval, *rp;
+	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;
+
+	/* return the canonical version */
+	new_newval = (char *) malloc(strlen("XX000:warning,") * new_array_len);
+	if (!new_newval)
+	{
+		free(new_array);
+		return false;
+	}
+
+	rp = 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;
+			}
+		}
+		if (i > 1)
+			*rp++ = ',';
+		rp += sprintf(rp, "%s:%s",
+				unpack_sql_state(new_array[i] >> 32), level_str);
+	}
+
+	free(*newval);
+	*newval = new_newval;
+	*extra = new_array;
+
+	return true;
+}
+
+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..775a20e 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);
-- 
1.8.4.2

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to