diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 2946122..8deb679 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17966,6 +17966,15 @@ postgres=# SELECT * FROM pg_xlogfile_name_offset(pg_stop_backup()); Return information about a file. + + + pg_report_log(logleveltext, message anyelement[, ishidestmt boolean ] [, detail text] [, hint text] [, sqlstate text]) + + void + + Report message or error. + + @@ -18034,6 +18043,32 @@ SELECT (pg_stat_file('filename')).modification; + + pg_report_log + + + pg_report_log is useful to write custom messages + or raise exception. This function don't support the PANIC, FATAL + log levels due to their unique internal DB usage, which may cause + the database instability. Using ishidestmt which default values + is true, function can write or ignore the current SQL statement + into log destination. Also, we can have DETAIL, HINT log messages + by provding detail, hint as function + arguments, which are NULL by default. The parameter sqlstate + allows to set a SQLSTATE of raised exception. Default value of this + parameter is 'P0001' for ERROR only level. + + Typical usages include: + +postgres=# SELECT pg_report_log('NOTICE', 'Custom Message', true); +NOTICE: Custom Message + pg_report_log +--------------- + +(1 row) + + + diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index ccc030f..7e551f2 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -940,3 +940,22 @@ RETURNS jsonb LANGUAGE INTERNAL STRICT IMMUTABLE AS 'jsonb_set'; + +CREATE OR REPLACE FUNCTION pg_report_log(loglevel text, message text, + ishidestmt boolean DEFAULT true, detail text DEFAULT NULL, + hint text DEFAULT NULL, sqlstate text DEFAULT 'P0001') +RETURNS VOID +LANGUAGE INTERNAL +VOLATILE +AS 'pg_report_log'; + +CREATE OR REPLACE FUNCTION pg_report_log(loglevel text, message anyelement, + ishidestmt boolean DEFAULT true, detail text DEFAULT NULL, + hint text DEFAULT NULL, sqlstate text DEFAULT 'P0001') +RETURNS VOID +VOLATILE +AS +$$ +SELECT pg_report_log($1::pg_catalog.text, $2::pg_catalog.text, $3, $4, $5, $6) +$$ +LANGUAGE SQL; diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c index c0495d9..20be263 100644 --- a/src/backend/utils/adt/misc.c +++ b/src/backend/utils/adt/misc.c @@ -75,6 +75,138 @@ current_query(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } + +/* + * Parsing error levels + */ +typedef struct +{ + char *err_level; + int ival; +} error_levels; + +/* + * Translate text based elog level to integer value. + * + * Returns true, when it found known elog elevel else + * returns false; + */ +static bool +parse_error_level(const char* err_level, int *ival) +{ + error_levels err_levels[]={ + {"DEBUG5", DEBUG5}, + {"DEBUG4", DEBUG4}, + {"DEBUG3", DEBUG3}, + {"DEBUG2", DEBUG2}, + {"DEBUG1", DEBUG1}, + {"LOG", LOG}, + {"INFO", INFO}, + {"NOTICE", NOTICE}, + {"WARNING", WARNING}, + {"ERROR", ERROR}, + /* + * Adding PGERROR to elevels if WIN32 + */ + #ifdef WIN32 + {"PGERROR", PGERROR}, + #endif + {NULL, 0} + }; + + error_levels *current; + + for (current = err_levels; current->err_level != NULL; current++) + { + if (pg_strcasecmp(current->err_level, err_level) == 0) + { + *ival = current->ival; + + return true; + } + } + + return false; +} + +/* + * pg_report_log() + * + * Printing custom log messages in log file. + */ +Datum +pg_report_log(PG_FUNCTION_ARGS) +{ + int elog_level; + char *elog_level_str; + int sqlstate = 0; + char *sqlstate_str; + bool ishidestmt = false; + char *err_message = NULL; + char *err_detail = NULL; + char *err_hint = NULL; + + /* log level */ + if (PG_ARGISNULL(0)) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("log level must not be null"))); + + elog_level_str = text_to_cstring(PG_GETARG_TEXT_PP(0)); + if (!parse_error_level(elog_level_str, &elog_level)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid or disallowed log level: \'%s\'", elog_level_str))); + + /* message */ + if (PG_ARGISNULL(1)) + err_message = "The message is null"; + else + err_message = text_to_cstring(PG_GETARG_TEXT_PP(1)); + + /* ishidestmt */ + if (!PG_ARGISNULL(2)) + ishidestmt = PG_GETARG_BOOL(2); + + /* detail */ + if (!PG_ARGISNULL(3)) + err_detail = text_to_cstring(PG_GETARG_TEXT_PP(3)); + + /* hint */ + if (!PG_ARGISNULL(4)) + err_hint = text_to_cstring(PG_GETARG_TEXT_PP(4)); + + /* sqlstate */ + if (!PG_ARGISNULL(5)) + { + sqlstate_str = text_to_cstring(PG_GETARG_TEXT_PP(5)); + if (strlen(sqlstate_str) != 5 || + strspn(sqlstate_str, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid SQLSTATE code: \'%s\'", sqlstate_str))); + + sqlstate = MAKE_SQLSTATE(sqlstate_str[0], + sqlstate_str[1], + sqlstate_str[2], + sqlstate_str[3], + sqlstate_str[4]); + } + else if (elog_level >= ERROR) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("sqlstate must not be null when loglevel is ERROR"))); + + ereport(elog_level, + ((sqlstate != 0) ? errcode(sqlstate) : 0, + errmsg_internal("%s", err_message), + (err_detail != NULL) ? errdetail_internal("%s", err_detail) : 0, + (err_hint != NULL) ? errhint("%s", err_hint) : 0, + errhidestmt(ishidestmt))); + + PG_RETURN_VOID(); +} + /* * Send a signal to another backend. * diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index f688454..1550d7d 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -5351,6 +5351,13 @@ DESCR("row security for current context active on table by table oid"); DATA(insert OID = 3299 ( row_security_active PGNSP PGUID 12 1 0 0 0 f f f f t f s s 1 0 16 "25" _null_ _null_ _null_ _null_ _null_ row_security_active_name _null_ _null_ _null_ )); DESCR("row security for current context active on table by table name"); +/* Logging function */ + +DATA(insert OID = 6015 ( pg_report_log PGNSP PGUID 12 1 0 0 0 f f f f f f v 6 0 2278 "25 25 16 25 25 25" _null_ _null_ "{loglevel, message, ishidestmt, detail, hint, sqlstate}" _null_ _null_ pg_report_log _null_ _null_ _null_ )); +DESCR("write message to log file"); +DATA(insert OID = 6016 ( pg_report_log PGNSP PGUID 14 1 0 0 0 f f f f f f v 6 0 2278 "25 2283 16 25 25 25" _null_ _null_ "{loglevel, message, ishidestmt, detail, hint, sqlstate}" _null_ _null_ "SELECT pg_report_log($1, $2::pg_catalog.text, $3, $4, $5, $6)" _null_ _null_ _null_ )); +DESCR("write message to log file"); + /* * Symbolic values for proparallel column: these indicate whether a function * can be safely be run in a parallel backend, during parallelism but diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index fc1679e..0dd1425 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -495,6 +495,7 @@ extern Datum pg_typeof(PG_FUNCTION_ARGS); extern Datum pg_collation_for(PG_FUNCTION_ARGS); extern Datum pg_relation_is_updatable(PG_FUNCTION_ARGS); extern Datum pg_column_is_updatable(PG_FUNCTION_ARGS); +extern Datum pg_report_log(PG_FUNCTION_ARGS); /* oid.c */ extern Datum oidin(PG_FUNCTION_ARGS); diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h index 7715719..63e177b 100644 --- a/src/include/utils/elog.h +++ b/src/include/utils/elog.h @@ -16,6 +16,13 @@ #include +/* + * XXX + * If you are adding another elevel, make sure you update the + * parse_error_level() in src/backend/utils/adt/misc.c, with the + * new elevel + */ + /* Error level codes */ #define DEBUG5 10 /* Debugging messages, in categories of * decreasing detail. */ diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index c63abf4..747aabf 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -97,7 +97,7 @@ test: rules # ---------- # Another group of parallel tests # ---------- -test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb json_encoding indirect_toast equivclass +test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json jsonb json_encoding indirect_toast reportlog equivclass # ---------- # Another group of parallel tests # NB: temp.sql does a reconnect which transiently uses 2 connections, diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 88dcd64..cab3c7a 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -159,3 +159,4 @@ test: with test: xml test: event_trigger test: stats +test: reportlog