diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml new file mode 100644 index b3b78d2..c0b6a72 *** a/doc/src/sgml/func.sgml --- b/doc/src/sgml/func.sgml *************** postgres=# SELECT * FROM pg_xlogfile_nam *** 17925,17930 **** --- 17925,17939 ---- Return information about a file. + + + pg_report_log(logleveltext, message anyelement[, ishidestmt boolean ] [, detail text] [, hint text] [, sqlstate text]) + + void + + Report message or error. + + *************** SELECT (pg_stat_file('filename')).modifi *** 17993,17998 **** --- 18002,18026 ---- + + pg_report_log + + + pg_report_log is useful to write custom messages + into current log destination and returns void. + 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. Using iserrstate which default values is true, enables the function to raise the SQLSTATE as ERRCODE_RAISE_EXCEPTION for the only ERROR 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 new file mode 100644 index ccc030f..1755335 *** a/src/backend/catalog/system_views.sql --- b/src/backend/catalog/system_views.sql *************** RETURNS jsonb *** 940,942 **** --- 940,961 ---- 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 NULL) + 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 NULL) + 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 new file mode 100644 index c0495d9..fd65aae *** a/src/backend/utils/adt/misc.c --- b/src/backend/utils/adt/misc.c *************** current_query(PG_FUNCTION_ARGS) *** 75,80 **** --- 75,210 ---- 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; + 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 log or disallowed 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 + sqlstate = (elog_level >= ERROR) ? ERRCODE_RAISE_EXCEPTION : 0; + + 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 new file mode 100644 index ddf7c67..d82db09 *** a/src/include/catalog/pg_proc.h --- b/src/include/catalog/pg_proc.h *************** DESCR("row security for current context *** 5349,5354 **** --- 5349,5361 ---- DATA(insert OID = 3299 ( row_security_active PGNSP PGUID 12 1 0 0 0 f f f f t f 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 proargmodes column. Note that these must agree with * the FunctionParameterMode enum in parsenodes.h; we declare them here to diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h new file mode 100644 index fc1679e..0dd1425 *** a/src/include/utils/builtins.h --- b/src/include/utils/builtins.h *************** extern Datum pg_typeof(PG_FUNCTION_ARGS) *** 495,500 **** --- 495,501 ---- 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 new file mode 100644 index 7684717..4ec2eb1 *** a/src/include/utils/elog.h --- b/src/include/utils/elog.h *************** *** 16,21 **** --- 16,28 ---- #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/expected/reportlog.out b/src/test/regress/expected/reportlog.out new file mode 100644 index ...a02ed76 *** a/src/test/regress/expected/reportlog.out --- b/src/test/regress/expected/reportlog.out *************** *** 0 **** --- 1,104 ---- + -- + -- Test for Report Log With WARNING + -- + SELECT pg_catalog.pg_report_log('WARNING', 'Custom Message'); --OK + WARNING: Custom Message + pg_report_log + --------------- + + (1 row) + + -- + -- Test for ERROR with default ishidestmt + -- + SELECT pg_catalog.pg_report_log('ERROR', 'Custom Message'); --ERROR + ERROR: Custom Message + -- + -- Test for ERROR with ishidestmt + -- + SELECT pg_catalog.pg_report_log('ERROR', 'Custom Message', true); --ERROR + ERROR: Custom Message + -- + -- Test for anyelement + -- + SELECT pg_catalog.pg_report_log('WARNING', -1234.34); --OK + WARNING: -1234.34 + pg_report_log + --------------- + + (1 row) + + -- + -- Test for denial of FATAL + -- + SELECT pg_catalog.pg_report_log('FATAL', 'Fatal Message'); -- ERROR + ERROR: invalid log or disallowed level: 'FATAL' + -- + -- Test for optional arguements + -- + SELECT pg_catalog.pg_report_log('WARNING', 'Warning Message', true, 'WARNING DETAIL'); --OK + WARNING: Warning Message + DETAIL: WARNING DETAIL + pg_report_log + --------------- + + (1 row) + + -- + -- Test for NULL log level + -- + SELECT pg_catalog.pg_report_log(NULL, NULL); --ERROR + ERROR: log level must not be null + -- + -- Test for NULL Message + -- + SELECT pg_catalog.pg_report_log('NOTICE', NULL); --OK + NOTICE: The message is null + pg_report_log + --------------- + + (1 row) + + -- + -- Test for SQLSTATE. The below test should print P0001, + -- which is an error code for ERRCODE_RAISE_EXCEPTION + -- + DO $$ + BEGIN + BEGIN + PERFORM pg_catalog.pg_report_log('ERROR', NULL, NULL, NULL, NULL); + EXCEPTION WHEN SQLSTATE 'P0001' THEN + RAISE NOTICE 'handled exception with SQLSTATE: %', SQLSTATE; + END; + + BEGIN + PERFORM pg_catalog.pg_report_log('ERROR', 'custom error', sqlstate => 'P1234'); + EXCEPTION WHEN SQLSTATE 'P1234' THEN + RAISE NOTICE 'handled exception with SQLSTATE: %', SQLSTATE; + END; + + END $$; + NOTICE: handled exception with SQLSTATE: P0001 + NOTICE: handled exception with SQLSTATE: P1234 + -- + -- Test for all NULL inputs, except elevel + -- + SELECT pg_catalog.pg_report_log('WARNING', NULL, NULL, NULL, NULL, NULL); --OK + WARNING: The message is null + pg_report_log + --------------- + + (1 row) + + -- + -- Test for all NOT NULL arguments + -- + SELECT pg_catalog.pg_report_log('WARNING', 'Warning Message', true, 'some detail', 'some hint', 'P1234'); --OK + WARNING: Warning Message + DETAIL: some detail + HINT: some hint + pg_report_log + --------------- + + (1 row) + diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule new file mode 100644 index 6fc5d1e..4cd193d *** a/src/test/regress/parallel_schedule --- b/src/test/regress/parallel_schedule *************** test: rules *** 97,103 **** # ---------- # 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 indirect_toast equivclass # ---------- # Another group of parallel tests # NB: temp.sql does a reconnect which transiently uses 2 connections, --- 97,103 ---- # ---------- # 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 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 new file mode 100644 index 2ae51cf..6c9f5c3 *** a/src/test/regress/serial_schedule --- b/src/test/regress/serial_schedule *************** test: with *** 158,160 **** --- 158,161 ---- test: xml test: event_trigger test: stats + test: reportlog diff --git a/src/test/regress/sql/reportlog.sql b/src/test/regress/sql/reportlog.sql new file mode 100644 index ...2ac4bd5 *** a/src/test/regress/sql/reportlog.sql --- b/src/test/regress/sql/reportlog.sql *************** *** 0 **** --- 1,70 ---- + -- + -- Test for Report Log With WARNING + -- + SELECT pg_catalog.pg_report_log('WARNING', 'Custom Message'); --OK + + -- + -- Test for ERROR with default ishidestmt + -- + SELECT pg_catalog.pg_report_log('ERROR', 'Custom Message'); --ERROR + + -- + -- Test for ERROR with ishidestmt + -- + SELECT pg_catalog.pg_report_log('ERROR', 'Custom Message', true); --ERROR + + -- + -- Test for anyelement + -- + SELECT pg_catalog.pg_report_log('WARNING', -1234.34); --OK + + -- + -- Test for denial of FATAL + -- + SELECT pg_catalog.pg_report_log('FATAL', 'Fatal Message'); -- ERROR + + -- + -- Test for optional arguements + -- + SELECT pg_catalog.pg_report_log('WARNING', 'Warning Message', true, 'WARNING DETAIL'); --OK + + -- + -- Test for NULL log level + -- + SELECT pg_catalog.pg_report_log(NULL, NULL); --ERROR + + + -- + -- Test for NULL Message + -- + SELECT pg_catalog.pg_report_log('NOTICE', NULL); --OK + + -- + -- Test for SQLSTATE. The below test should print P0001, + -- which is an error code for ERRCODE_RAISE_EXCEPTION + -- + DO $$ + BEGIN + BEGIN + PERFORM pg_catalog.pg_report_log('ERROR', NULL, NULL, NULL, NULL); + EXCEPTION WHEN SQLSTATE 'P0001' THEN + RAISE NOTICE 'handled exception with SQLSTATE: %', SQLSTATE; + END; + + BEGIN + PERFORM pg_catalog.pg_report_log('ERROR', 'custom error', sqlstate => 'P1234'); + EXCEPTION WHEN SQLSTATE 'P1234' THEN + RAISE NOTICE 'handled exception with SQLSTATE: %', SQLSTATE; + END; + + END $$; + + -- + -- Test for all NULL inputs, except elevel + -- + SELECT pg_catalog.pg_report_log('WARNING', NULL, NULL, NULL, NULL, NULL); --OK + + -- + -- Test for all NOT NULL arguments + -- + SELECT pg_catalog.pg_report_log('WARNING', 'Warning Message', true, 'some detail', 'some hint', 'P1234'); --OK