diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b3b78d2..682164e 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17925,6 +17925,15 @@ postgres=# SELECT * FROM pg_xlogfile_name_offset(pg_stop_backup()); Return information about a file. + + + pg_report_log(eleveltext, message anyelement[, ishidestmt boolean, detail text, hint text, iserrstate boolean]) + + void + + Write message to log destination. + + @@ -17993,6 +18002,25 @@ SELECT (pg_stat_file('filename')).modification; + + 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 index ccc030f..f1ddc35 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -940,3 +940,18 @@ RETURNS jsonb LANGUAGE INTERNAL STRICT IMMUTABLE AS 'jsonb_set'; + +CREATE OR REPLACE FUNCTION pg_report_log(elevel TEXT, message TEXT, ishidestmt BOOLEAN DEFAULT TRUE, detail TEXT DEFAULT NULL, hint TEXT DEFAULT NULL, iserrstate BOOLEAN DEFAULT TRUE) +RETURNS VOID +LANGUAGE INTERNAL +VOLATILE +AS 'pg_report_log'; + +CREATE OR REPLACE FUNCTION pg_report_log(elevel TEXT, message anyelement, ishidestmt BOOLEAN DEFAULT TRUE, detail TEXT DEFAULT NULL, hint TEXT DEFAULT NULL, iserrstate BOOLEAN DEFAULT TRUE) +RETURNS VOID +VOLATILE +AS +$$ +SELECT pg_report_log($1::pg_catalog.text, $2::pg_catalog.text, $3::pg_catalog.bool, $4::pg_catalog.text, $5::pg_catalog.text, $6::pg_catalog.bool) +$$ +LANGUAGE SQL; diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c index c0495d9..d2f10c9 100644 --- a/src/backend/utils/adt/misc.c +++ b/src/backend/utils/adt/misc.c @@ -75,6 +75,98 @@ current_query(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } + +/* + * Parsing error levels + */ +typedef struct +{ + int ecode; + char *level; +} errorlevels; + +static int parse_error_level(const char* elevel) +{ + errorlevels elevels[]={ + {DEBUG5, "DEBUG5"}, {DEBUG4, "DEBUG4"}, {DEBUG3, "DEBUG3"}, + {DEBUG2, "DEBUG2"}, {DEBUG1, "DEBUG1"}, {LOG, "LOG"}, + {COMMERROR, "COMMERROR"}, {INFO, "INFO"}, {NOTICE, "NOTICE"}, + {WARNING, "WARNING"}, {ERROR, "ERROR"}, {FATAL, "FATAL"}, {PANIC, "PANIC"} + /* + * Adding PGERROR to elevels if WIN32 + */ + #ifdef WIN32 + ,{PGERROR, "PGERROR"} + #endif + }; + int noelevel = (int) sizeof(elevels)/sizeof(*elevels); + int itr = 0; + + while (itr < noelevel) + { + if (pg_strcasecmp(elevels[itr].level, elevel) == 0) + break; + itr++; + } + + if (itr != noelevel) + return elevels[itr].ecode; + /* Invalid log level */ + else + return 0; +} + +/* + * pg_report_log() + * + * Printing custom log messages in log file. + */ + +Datum +pg_report_log(PG_FUNCTION_ARGS) +{ + int elevel; + bool ishidestmt = false, iserrstate = false; + char *loglevel, *detail, *hint; + + loglevel = PG_ARGISNULL(0) ? NULL : text_to_cstring(PG_GETARG_TEXT_P(0)); + ishidestmt = PG_ARGISNULL(2) ? false : PG_GETARG_BOOL(2); + detail = PG_ARGISNULL(3) ? NULL : text_to_cstring(PG_GETARG_TEXT_P(3)); + hint = PG_ARGISNULL(4) ? NULL : text_to_cstring(PG_GETARG_TEXT_P(4)); + iserrstate = PG_ARGISNULL(5) ? false : PG_GETARG_BOOL(5); + + if(!loglevel) + ereport(ERROR, + (errmsg("NULL is an unsupported report log level."))); + + elevel = parse_error_level(loglevel); + + /* + * Do not expose FATAL, PANIC log levels to outer world. + */ + if(elevel && elevel==FATAL) + ereport(ERROR, + (errmsg("%s is an unsupported report log level.", loglevel))); + + else if(elevel && elevel==PANIC) + ereport(ERROR, + (errmsg("%s is an unsupported report log level.", loglevel))); + + else if(elevel) + ereport(elevel, + ( ((elevel>=ERROR) && iserrstate ) ? errcode(ERRCODE_RAISE_EXCEPTION) : 0, + (errmsg("%s", PG_ARGISNULL(1) ? "" : text_to_cstring(PG_GETARG_TEXT_P(1))), + detail ? errdetail("%s", detail) : 0, + hint ? errhint("%s", hint) : 0, + errhidestmt(ishidestmt) + ))); + else + ereport(ERROR, + (errmsg("%s is an unknown report log level.", loglevel))); + + 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 ddf7c67..0474b2a 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -5349,6 +5349,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 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 16" _null_ _null_ "{elevel, message, ishidestmt, detail, hint, iserrstate}" _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 16" _null_ _null_ "{elevel, message, ishidestmt, detail, hint, iserrstate}" _null_ _null_ "SELECT pg_report_log($1::pg_catalog.text, $2::pg_catalog.text, $3::pg_catalog.bool, $4::pg_catalog.text, $5::pg_catalog.text, $6::pg_catalog.bool)" _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 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 7684717..4ec2eb1 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/expected/reportlog.out b/src/test/regress/expected/reportlog.out new file mode 100644 index 0000000..8811c12 --- /dev/null +++ b/src/test/regress/expected/reportlog.out @@ -0,0 +1,88 @@ +-- +-- 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'); --OK +ERROR: FATAL is an unsupported report log level. +-- +-- 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 elevel +-- +SELECT pg_catalog.pg_report_log(NULL, NULL); --ERROR +ERROR: NULL is an unsupported report log level. +-- +-- Test for NULL Message +-- +SELECT pg_catalog.pg_report_log('NOTICE', NULL); --OK +NOTICE: + pg_report_log +--------------- + +(1 row) + +-- +-- Test for iserrstate. The below test should print P0001, which is an error code for ERRCODE_RAISE_EXCEPTION +-- +DO $$ BEGIN PERFORM pg_catalog.pg_report_log('ERROR', NULL, NULL, NULL, NULL, true); EXCEPTION WHEN OTHERS THEN RAISE NOTICE '%', SQLSTATE; END $$; +NOTICE: P0001 +-- +-- Test for all NULL inputs, except elevel +-- +SELECT pg_catalog.pg_report_log('WARNING', NULL, NULL, NULL, NULL, NULL); --OK +WARNING: + pg_report_log +--------------- + +(1 row) + +-- +-- Test for all NOT NULL arguments +-- +SELECT pg_catalog.pg_report_log('WARNING', 'Warning Message', true, 'DETAIL', 'HINT', true); --OK +WARNING: Warning Message +DETAIL: DETAIL +HINT: HINT + pg_report_log +--------------- + +(1 row) + diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 6fc5d1e..4cd193d 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 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 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 2ae51cf..6c9f5c3 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -158,3 +158,4 @@ test: with 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 0000000..a838fc7 --- /dev/null +++ b/src/test/regress/sql/reportlog.sql @@ -0,0 +1,55 @@ +-- +-- 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'); --OK + +-- +-- Test for optional arguements +-- +SELECT pg_catalog.pg_report_log('WARNING', 'Warning Message', true, 'WARNING DETAIL'); --OK + +-- +-- Test for NULL elevel +-- +SELECT pg_catalog.pg_report_log(NULL, NULL); --ERROR + + +-- +-- Test for NULL Message +-- +SELECT pg_catalog.pg_report_log('NOTICE', NULL); --OK + +-- +-- Test for iserrstate. The below test should print P0001, which is an error code for ERRCODE_RAISE_EXCEPTION +-- +DO $$ BEGIN PERFORM pg_catalog.pg_report_log('ERROR', NULL, NULL, NULL, NULL, true); EXCEPTION WHEN OTHERS THEN RAISE NOTICE '%', SQLSTATE; 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, 'DETAIL', 'HINT', true); --OK