\echo Use "CREATE EXTENSION pgelog" to load this file. \quit -- 1) Tables of pgelog -- -- 1.1) Table to store extended logging data CREATE TABLE IF NOT EXISTS pgelog_logs ( log_stamp TIMESTAMP -- 1; Date and time of log record (got by clock_timestamp()) ,log_type TEXT -- 2; Kind of log record - FAIL, WARN, INFO etc ,log_func TEXT -- 3; Name of log record source - stored function, trigger, SQL clause etc ,phase TEXT -- 4; Serial number or label of current phase of logical operation inside above mentioned log_func, for example 1, 2, 2.a ,log_info TEXT -- 5; Body of log record ,xact_id TEXT -- 6; txid::text from txid_current() or txid_current_if_assigned() for PG < 13 or txid8::text from pg_current_xact_id() or pg_current_xact_id_if_assigned() for PG >= 13 ,sqlstate TEXT -- 7; Current Postgres SQLSTATE ,sqlerrm TEXT -- 8; Current Postgres SQLERRM ,conn_name TEXT -- 9; Name of dblink used for pseudo-autonomous transaction execution ); COMMENT ON TABLE pgelog_logs IS 'pgelog extension table to store extended logging data'; COMMENT ON COLUMN pgelog_logs.log_stamp IS 'Date and time of log record (got by clock_timestamp())'; COMMENT ON COLUMN pgelog_logs.log_type IS 'Kind of log record - FAIL, WARN, INFO etc'; COMMENT ON COLUMN pgelog_logs.log_func IS 'Name of log record source - stored function, trigger, SQL clause etc'; COMMENT ON COLUMN pgelog_logs.phase IS 'Serial number or label of current phase of logical operation inside above mentioned log_func, for example 1, 2, 2.a'; COMMENT ON COLUMN pgelog_logs.log_info IS 'Body of log record'; COMMENT ON COLUMN pgelog_logs.xact_id IS 'txid::text from txid_current() or txid_current_if_assigned() for PG < 13 or txid8::text from pg_current_xact_id() or pg_current_xact_id_if_assigned() for PG >= 13'; COMMENT ON COLUMN pgelog_logs.sqlstate IS 'Current Postgres SQLSTATE'; COMMENT ON COLUMN pgelog_logs.sqlerrm IS 'Current Postgres SQLERRM'; COMMENT ON COLUMN pgelog_logs.conn_name IS 'Name of dblink used for pseudo-autonomous transaction execution'; GRANT SELECT ON pgelog_logs TO PUBLIC; SELECT pg_catalog.pg_extension_config_dump('pgelog_logs', ''); ----------------- -- -- 1.2) Table to store pgelog control parameters CREATE TABLE IF NOT EXISTS pgelog_params ( param_name TEXT NOT NULL -- 1; Name of pgelog control parameter ,param_value TEXT -- 2; Value of pgelog control parameter ,CONSTRAINT pk_pgelog_params PRIMARY KEY(param_name) ); COMMENT ON TABLE pgelog_params IS 'pgelog extension table to store pgelog control parameters'; COMMENT ON COLUMN pgelog_params.param_name IS 'Name of pgelog control parameter'; COMMENT ON COLUMN pgelog_params.param_value IS 'Value of pgelog control parameter'; GRANT SELECT ON pgelog_params TO PUBLIC; SELECT pg_catalog.pg_extension_config_dump('pgelog_params', ''); INSERT INTO pgelog_params(param_name, param_value) VALUES ('pgelog_pgv_package' ,'pgelog') ,('pgelog_is_active' ,'y') ,('pgelog_port' ,'5432') ,('pgelog_assign_xact_id' ,'n') ,('pgelog_timestamp_format' ,'YYYY-MM-DD HH24:MI:SS.US') ,('pgelog_pgv_transactional','y') ,('pgelog_ttl_minutes' ,'1440') ,('pgelog_log_clean_call' ,'y') ,('pgelog_log_init_call' ,'n') ; ----------------- -- -- 2) Functions of pgelog -- -- SQL functions ------------------ -- -- 2.1) Read pgelog control parameter CREATE OR REPLACE FUNCTION pgelog_get_param -- Read pgelog control parameter ( p_name TEXT -- 1; Name of pgelog control parameter ) RETURNS TEXT -- pgelog control parameter LANGUAGE SQL STRICT ----------------------------------- AS $BODY$ SELECT P.param_value FROM pgelog_params P WHERE P.param_name = p_name $BODY$; GRANT EXECUTE ON FUNCTION pgelog_get_param(TEXT) TO PUBLIC; ----------------------------------- -- 2.2) Set pgelog control parameter CREATE OR REPLACE FUNCTION pgelog_set_param -- Set pgelog control parameter ( p_name TEXT -- 1; Name of pgelog control parameter ,p_value TEXT -- 2; Value of pgelog control parameter ) RETURNS VOID LANGUAGE SQL ----------------------------------- AS $BODY$ INSERT INTO pgelog_params(param_name, param_value) SELECT p_name, p_value WHERE p_name IS NOT NULL -- STRICT only to p_name ON CONFLICT ON CONSTRAINT pk_pgelog_params DO UPDATE SET param_value = EXCLUDED.param_value $BODY$; GRANT EXECUTE ON FUNCTION pgelog_set_param(TEXT, TEXT) TO PUBLIC; ----------------------------------- -- 2.3) Delete pgelog control parameter CREATE OR REPLACE FUNCTION pgelog_delete_param -- Delete pgelog control parameter ( p_name TEXT -- 1; Name of pgelog control parameter ) RETURNS VOID LANGUAGE SQL STRICT ----------------------------------- AS $BODY$ DELETE FROM pgelog_params WHERE param_name = p_name $BODY$; GRANT EXECUTE ON FUNCTION pgelog_delete_param(TEXT) TO PUBLIC; ----------------------------------- -- -- PL/pgSQL functions ------------- -- -- 2.4) Initialize pgelog logger and set required pg_variables CREATE OR REPLACE FUNCTION pgelog_init() RETURNS BOOLEAN -- TRUE if logger initialized ----------------------------------- AS $BODY$ DECLARE v_Phase TEXT; v_Proc_Name TEXT := 'pgelog_init'; v_Port TEXT; v_Package TEXT; v_Conn_Str TEXT; v_Conn_Name TEXT; v_Pg_Version INTEGER; v_Is_Active TEXT; v_Assign_XID TEXT; v_Time_Fmt TEXT; v_Is_Trans TEXT; v_Transact BOOLEAN; v_Log_Call TEXT; v_Log_Type TEXT; v_Log_Func TEXT; v_Log_Info TEXT; ----------------------------------- BEGIN -- 1) Get information about logging mode -- 1.1) Get pg_variables package name v_Phase := '1.1)'; v_Package := COALESCE(pgelog_get_param('pgelog_pgv_package'),'pgelog'); -- 1.2) Read pgelog parameters for transactional mode v_Phase := '1.2)'; v_Is_Trans := COALESCE(lower(pgelog_get_param('pgelog_pgv_transactional')),'y'); v_Transact := (v_Is_Trans = 'y'); -- 1.3) Check if 'pgelog_is_active' pg_variable already set for current session by pgelog_enable_locally()/pgelog_disable_localy() call v_Phase := '1.3)'; IF (pgv_exists(v_Package,'pgelog_is_active')) THEN -- 1.3.1) 'pgelog_is_active' pg_variable already set, read it from pg_variable 'pgelog_is_active' v_Phase := '1.3.1)'; v_Is_Active := pgv_get(v_Package,'pgelog_is_active' ,NULL::TEXT); ELSE -- 1.3.2) 'pgelog_is_active' pg_variable NOT set, read it from table pgelog_params v_Phase := '1.3.2)'; v_Is_Active := COALESCE(lower(pgelog_get_param('pgelog_is_active')),'n'); -- 1.3.3) Set pg_variable 'pgelog_is_active' v_Phase := '1.3.3)'; PERFORM pgv_set(v_Package, 'pgelog_is_active', v_Is_Active, v_Transact); END IF; -- 1.4) Read pgelog parameters from table pgelog_params v_Phase := '1.4)'; v_Assign_XID := COALESCE(lower(pgelog_get_param('pgelog_assign_xact_id')),'n'); v_Time_Fmt := COALESCE(pgelog_get_param('pgelog_timestamp_format'),'YYYY-MM-DD HH24:MI:SS.US'); -- 1.5) Process case of inactive logging IF (v_Is_Active <> 'y') THEN -- do not initialize if logging is NOT active -- 1.5.1) Set NULL to pg_variables to create it v_Phase := '1.5.1)'; PERFORM pgv_set(v_Package, 'pgelog_conn_string' , v_Conn_Str , v_Transact); PERFORM pgv_set(v_Package, 'pgelog_conn_name' , v_Conn_Name , v_Transact); PERFORM pgv_set(v_Package, 'pgelog_pg_version' , v_Pg_Version, v_Transact); PERFORM pgv_set(v_Package, 'pgelog_assign_xact_id' , v_Assign_XID, v_Transact); PERFORM pgv_set(v_Package, 'pgelog_timestamp_format', v_Time_Fmt , v_Transact); RETURN FALSE; -- do not connect and exit END IF; -- 2) Get Postgres port and major version v_Phase := '2)'; v_Port := COALESCE(pgelog_get_param('pgelog_port'),'5432'); v_Pg_Version := COALESCE(split_part(current_setting('server_version'),'.', 1)::INTEGER, 0); -- 3) Compose connection string (with search_path security issue) v_Phase := '3)'; v_Conn_Str := format('host=localhost port=%s dbname=%s user=%s options=-csearch_path=%s', v_Port, current_database(), current_user, current_schema); -- 4) Get random unique connection name for current session v_Phase := '4)'; v_Conn_Name := gen_random_uuid(); -- 5) Open connection as unprivileged user v_Phase := '5)'; PERFORM dblink_connect_u(v_Conn_Name,v_Conn_Str); -- 6) Save connection name, connection string, Postgres major version etc (with is_transactional = v_Transact) v_Phase := '6)'; PERFORM pgv_set(v_Package, 'pgelog_conn_string' , v_Conn_Str , v_Transact); PERFORM pgv_set(v_Package, 'pgelog_conn_name' , v_Conn_Name , v_Transact); PERFORM pgv_set(v_Package, 'pgelog_pg_version' , v_Pg_Version, v_Transact); PERFORM pgv_set(v_Package, 'pgelog_assign_xact_id' , v_Assign_XID, v_Transact); PERFORM pgv_set(v_Package, 'pgelog_timestamp_format', v_Time_Fmt , v_Transact); -- 7) Write report to pgelog_logs if enabled v_Phase := '7)'; v_Log_Call := COALESCE(lower(pgelog_get_param('pgelog_log_init_call')),'y'); IF (v_Log_Call = 'y') THEN v_Log_Type := 'pgelog'; v_Log_Func := format('%s.pgelog_init',current_schema); v_Phase := '7)'; v_Log_Info := format('v_Conn_Name=%s',COALESCE(v_Conn_Name,'NULL')); PERFORM pgelog_to_log( v_Log_Type -- 1; Kind of log record - FAIL, WARN, INFO etc ,v_Log_Func -- 2; Name of log record source - stored function, trigger, SQL clause etc ,v_Log_Info -- 3; Body of log record ,v_Phase -- 4; Serial number or label of current phase of logical operation inside above mentioned log_func, for example 1, 2, 2.a ); END IF; -- 8) Return positive result RETURN TRUE; -- 9) Swallow exceptions to prevent influence to the caller EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'FAIL of %() at phase %',v_Proc_Name,v_Phase; RETURN FALSE; END; $BODY$ LANGUAGE plPGSQL VOLATILE; GRANT EXECUTE ON FUNCTION pgelog_init() TO PUBLIC; ----------------------------------- -- 2.5) Close logger dblink connection and delete pg_variables CREATE OR REPLACE FUNCTION pgelog_close() RETURNS BOOLEAN -- TRUE if logger closed ----------------------------------- AS $BODY$ DECLARE v_Phase TEXT; v_Proc_Name TEXT := 'pgelog_close'; v_Package TEXT; v_Conn_Name TEXT; ----------------------------------- BEGIN -- 1) Get pg_variables package name used in pgelog v_Phase := '1)'; v_Package := COALESCE(pgelog_get_param('pgelog_pgv_package'),'pgelog'); -- 2) Check if connection exists IF (pgv_exists(v_Package,'pgelog_conn_name')) THEN -- 2.1) Get connection name v_Phase := '2.1)'; v_Conn_Name := pgv_get(v_Package,'pgelog_conn_name',NULL::TEXT); -- 2.3) Close connection v_Phase := '2.1)'; PERFORM dblink_disconnect(v_Conn_Name); -- 2.3) Delete all pg_variables for package v_Package v_Phase := '2.3)'; IF (pgv_exists(v_Package)) THEN v_Phase := '2.3.1)'; PERFORM pgv_remove(v_Package); END IF; -- 2.4) Success RETURN TRUE; ELSE RETURN TRUE; END IF; -- 3) Swallow exceptions to prevent influence to the caller EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'FAIL of %() at phase %',v_Proc_Name,v_Phase; RETURN FALSE; END; $BODY$ LANGUAGE plPGSQL VOLATILE; GRANT EXECUTE ON FUNCTION pgelog_close() TO PUBLIC; ----------------------------------- -- 2.6) Delete records of logger table pgelog_logs records older than TTL in minutes and writes report to log if 'pgelog_log_clean_call'='y' CREATE OR REPLACE FUNCTION pgelog_clean_log ( p_ttl_minutes INTEGER = NULL -- 1; TTL (time to live) of pgelog_logs records in minutes; NULL means value set by 'pgelog_ttl_minutes' parameter ) RETURNS BOOLEAN -- TRUE if pgelog_logs ceaned ----------------------------------- AS $BODY$ DECLARE v_Phase TEXT; v_Proc_Name TEXT := 'pgelog_clean_log'; v_TTL_Minutes INTEGER; v_Log_Call TEXT; v_Num_Deleted INTEGER; v_Boundary TIMESTAMP; v_Log_Type TEXT; v_Log_Func TEXT; v_Log_Info TEXT; ----------------------------------- BEGIN -- 1) Get TTL (time to live) of pgelog_logs records in minutes v_Phase := '1)'; v_TTL_Minutes := COALESCE(p_ttl_minutes, pgelog_get_param('pgelog_ttl_minutes')::INTEGER, 1); v_Log_Call := COALESCE(lower(pgelog_get_param('pgelog_log_clean_call')),'y'); -- 2) Delete obsolete pgelog_logs records v_Phase := '2)'; v_Boundary := clock_timestamp() - interval '1 minute'*v_TTL_Minutes; DELETE FROM pgelog_logs WHERE log_stamp < v_Boundary; GET DIAGNOSTICS v_Num_Deleted = ROW_COUNT; -- 3) Write report to pgelog_logs if enabled IF (v_Log_Call = 'y') THEN v_Phase := '3)'; v_Log_Type := 'pgelog'; v_Log_Func := format('%s.pgelog_clean_log',current_schema); v_Log_Info := format('p_ttl_minutes=%s, v_TTL_Minutes=%s, deleted %s rows of pgelog_logs' ,COALESCE(p_ttl_minutes::TEXT,'NULL') ,COALESCE(v_TTL_Minutes::TEXT,'NULL') ,COALESCE(v_Num_Deleted::TEXT,'NULL') ); PERFORM pgelog_to_log( v_Log_Type -- 1; Kind of log record - FAIL, WARN, INFO etc ,v_Log_Func -- 2; Name of log record source - stored function, trigger, SQL clause etc ,v_Log_Info -- 3; Body of log record ,v_Phase -- 4; Serial number or label of current phase of logical operation inside above mentioned log_func, for example 1, 2, 2.a ); END IF; -- 4) Success RETURN TRUE; -- 5) Swallow exceptions to prevent influence to the caller EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'FAIL of %() at phase %',v_Proc_Name,v_Phase; RETURN FALSE; END; $BODY$ LANGUAGE plPGSQL VOLATILE; GRANT EXECUTE ON FUNCTION pgelog_clean_log(INTEGER) TO PUBLIC; ----------------------------------- -- 2.7) Disable logging permanently but can be overrided for current session by pgelog_enable_locally() call CREATE OR REPLACE FUNCTION pgelog_disable() RETURNS BOOLEAN -- TRUE if logging permanently disabled ----------------------------------- AS $BODY$ DECLARE v_Phase TEXT; v_Proc_Name TEXT := 'pgelog_disable'; v_Is_Active TEXT := 'n'; ----------------------------------- BEGIN -- 1) Set pg_variable package name v_Phase := '1)'; PERFORM pgelog_set_param('pgelog_is_active', v_Is_Active); -- 2) Disable right now! v_Phase := '2)'; PERFORM pgelog_disable_locally(); -- 3) Return positive result RETURN TRUE; -- 4) Swallow exceptions to prevent influence to the caller EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'FAIL of %() at phase %',v_Proc_Name,v_Phase; RETURN FALSE; END; $BODY$ LANGUAGE plPGSQL VOLATILE; GRANT EXECUTE ON FUNCTION pgelog_disable() TO PUBLIC; ----------------------------------- -- 2.8) Disable logging for current session only CREATE OR REPLACE FUNCTION pgelog_disable_locally() RETURNS BOOLEAN -- TRUE if logging locally disabled ----------------------------------- AS $BODY$ DECLARE v_Phase TEXT; v_Proc_Name TEXT := 'pgelog_disable_locally'; v_Package TEXT; v_Is_Active TEXT := 'n'; v_Is_Trans TEXT; v_Transact BOOLEAN; v_Conn_Name TEXT; ----------------------------------- BEGIN -- 1) Get pg_variable package name v_Phase := '1)'; v_Package := COALESCE(pgelog_get_param('pgelog_pgv_package'),'pgelog'); -- 2) Read pgelog parameters for transactional mode v_Phase := '2)'; v_Is_Trans := COALESCE(lower(pgelog_get_param('pgelog_pgv_transactional')),'y'); v_Transact := (v_Is_Trans = 'y'); -- 3) Set pg_variable pgelog_is_active v_Phase := '3)'; IF (pgv_exists(v_Package, 'pgelog_is_active')) THEN -- 3.1) Delete pgelog_is_active pg_variable v_Phase := '3.1)'; PERFORM pgv_remove(v_Package, 'pgelog_is_active'); END IF; -- 3.2) Set pgelog_is_active pg_variable v_Phase := '3.2)'; PERFORM pgv_set(v_Package, 'pgelog_is_active', v_Is_Active, v_Transact); -- 4) Check if pgelog_conn_name pg_variable already set for current session v_Phase := '4)'; IF (pgv_exists(v_Package, 'pgelog_conn_name')) THEN -- 4.1) Get connection name v_Phase := '4.1)'; v_Conn_Name := pgv_get(v_Package, 'pgelog_conn_name', NULL::TEXT); -- 4.2) Close connection v_Phase := '4.2)'; PERFORM dblink_disconnect(v_Conn_Name); -- 4.3) Delete pgelog_conn_string pg_variable to invoke pgelog_init() later in required v_Phase := '4.3)'; PERFORM pgv_remove(v_Package, 'pgelog_conn_name'); END IF; -- 5) Return positive result RETURN TRUE; -- 6) Swallow exceptions to prevent influence to the caller EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'FAIL of %() at phase %',v_Proc_Name,v_Phase; RETURN FALSE; END; $BODY$ LANGUAGE plPGSQL VOLATILE; GRANT EXECUTE ON FUNCTION pgelog_disable_locally() TO PUBLIC; ----------------------------------- -- 2.9) Enable logging permanently but can be overrided for current session by pgelog_disable_locally() call CREATE OR REPLACE FUNCTION pgelog_enable() RETURNS BOOLEAN -- TRUE if logging permanently enabled ----------------------------------- AS $BODY$ DECLARE v_Phase TEXT; v_Proc_Name TEXT := 'pgelog_enable'; v_Is_Active TEXT := 'y'; ----------------------------------- BEGIN -- 1) Set pg_variable package name v_Phase := '1)'; PERFORM pgelog_set_param('pgelog_is_active', v_Is_Active); -- 2) Enable right now! v_Phase := '2)'; PERFORM pgelog_enable_locally(); -- 3) Return positive result RETURN TRUE; -- 4) Swallow exceptions to prevent influence to the caller EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'FAIL of %() at phase %',v_Proc_Name,v_Phase; RETURN FALSE; END; $BODY$ LANGUAGE plPGSQL VOLATILE; GRANT EXECUTE ON FUNCTION pgelog_enable() TO PUBLIC; ----------------------------------- -- 2.10) Enable logging for current session only CREATE OR REPLACE FUNCTION pgelog_enable_locally() RETURNS BOOLEAN -- TRUE if logging locally enabled ----------------------------------- AS $BODY$ DECLARE v_Phase TEXT; v_Proc_Name TEXT := 'pgelog_enable_locally'; v_Package TEXT; v_Is_Active TEXT := 'y'; v_Is_Trans TEXT; v_Transact BOOLEAN; ----------------------------------- BEGIN -- 1) Get pg_variable package name v_Phase := '1)'; v_Package := COALESCE(pgelog_get_param('pgelog_pgv_package'),'pgelog'); -- 2) Read pgelog parameters for transactional mode v_Phase := '2)'; v_Is_Trans := COALESCE(lower(pgelog_get_param('pgelog_pgv_transactional')),'y'); v_Transact := (v_Is_Trans = 'y'); -- 3) Set pg_variable pgelog_is_active v_Phase := '3)'; IF (pgv_exists(v_Package, 'pgelog_is_active')) THEN -- 3.1) Delete pgelog_is_active pg_variable v_Phase := '3.1)'; PERFORM pgv_remove(v_Package, 'pgelog_is_active'); END IF; -- 3.2) Set pgelog_is_active pg_variable v_Phase := '3.2)'; PERFORM pgv_set(v_Package, 'pgelog_is_active', v_Is_Active, v_Transact); -- 4) Return positive result RETURN TRUE; -- 5) Swallow exceptions to prevent influence to the caller EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'FAIL of %() at phase %',v_Proc_Name,v_Phase; RETURN FALSE; END; $BODY$ LANGUAGE plPGSQL VOLATILE; GRANT EXECUTE ON FUNCTION pgelog_enable_locally() TO PUBLIC; ----------------------------------- -- 2.11) Write log info into pgelog_logs table CREATE OR REPLACE FUNCTION pgelog_to_log ( p_log_type TEXT -- 1; Kind of log record - FAIL, WARN, INFO etc ,p_log_func TEXT -- 2; Name of log record source - stored function, trigger, SQL clause etc ,p_log_info TEXT -- 3; Body of log record ,p_phase TEXT -- 4; Serial number or label of current phase of logical operation inside above mentioned log_func, for example 1, 2, 2.a ,p_sqlerrm TEXT = NULL -- 5; Current Postgres SQLSTATE ,p_sqlstate TEXT = NULL -- 6; Current Postgres SQLERRM ) RETURNS BOOLEAN -- Returns TRUE on success and FALSE on any exception ----------------------------------- AS $BODY$ DECLARE v_Phase TEXT; v_Proc_Name TEXT := 'pgelog_to_log'; v_Log_DT TIMESTAMP := clock_timestamp(); v_Log_Stamp TEXT; v_ShouldLog BOOLEAN := TRUE; v_Package TEXT; v_Conn_Name TEXT; v_Pg_Version INTEGER; v_Set_XID BOOLEAN := TRUE; v_Xact_ID TEXT; v_Time_Fmt TEXT; v_SQL TEXT; v_Is_Active TEXT; v_Need_Init BOOLEAN := TRUE; ----------------------------------- BEGIN -- 1) Check environment and call pgelog_init() if required -- 1.1) Get pg_variables package name from table v_Phase := '1.1)'; v_Package := COALESCE(pgelog_get_param('pgelog_pgv_package'),'pgelog'); -- 1.2) Check if pg_variable pgelog_is_active exist v_Phase := '1.2)'; IF (pgv_exists(v_Package,'pgelog_is_active')) THEN -- 1.2.1) Read pg_variable pgelog_is_active v_Phase := '1.2.1)'; v_Is_Active := pgv_get(v_Package,'pgelog_is_active',NULL::TEXT); -- 1.2.2) Exit if pgelog_is_active = 'n' (i.e., logging is disabled) v_Phase := '1.2.2)'; IF (v_Is_Active = 'n') THEN RETURN TRUE; END IF; END IF; -- 1.3) Check if pg_variable pgelog_conn_name exist v_Phase := '1.3)'; IF (pgv_exists(v_Package,'pgelog_conn_name')) THEN -- 1.3.1) If pgelog_conn_name esixt it's not need to call pgelog_init() v_Need_Init := FALSE; END IF; -- 1.4) Call pgelog_init() if required v_Phase := '1.4)'; IF (v_Need_Init) THEN v_ShouldLog := pgelog_init(); END IF; -- 2) Get prepared connection and other parameters from prepared pg_variables IF (v_ShouldLog) THEN -- 2.1) Get connection name, Postgres major version and xact_id processing type v_Phase := '2.1)'; v_Conn_Name := pgv_get(v_Package,'pgelog_conn_name' ,NULL::TEXT); v_Pg_Version := pgv_get(v_Package,'pgelog_pg_version',NULL::INTEGER); v_Set_XID := (lower(COALESCE(pgv_get(v_Package,'pgelog_assign_xact_id',NULL::TEXT),'n')) = 'y'); -- 2.2) Ask a transaction's ID or assign a new transaction's ID if the current transaction does not have one already IF (v_Set_XID) THEN -- 2.2.1) Assign xact_id IF (v_Pg_Version < 13) THEN v_Phase := '2.2.1.1)'; v_Xact_ID := txid_current()::TEXT; ELSE v_Phase := '2.2.1.2)'; v_Xact_ID := pg_current_xact_id()::TEXT; END IF; ELSE -- 2.2.2) Ask xact_id IF (v_Pg_Version < 13) THEN v_Phase := '2.2.2.1)'; v_Xact_ID := txid_current_if_assigned()::TEXT; ELSE v_Phase := '2.2.2.2)'; v_Xact_ID := pg_current_xact_id_if_assigned()::TEXT; END IF; END IF; ELSE -- 2.3) Logging is not activated, exiting... RETURN TRUE; END IF; -- 3) Prepare SQL and execute it via dblink() -- 3.1) Get other parameters v_Phase := '3.1)'; v_Time_Fmt := pgv_get(v_Package,'pgelog_timestamp_format',NULL::TEXT); v_Log_Stamp := to_char(v_Log_DT, v_Time_Fmt); -- 3.2) Prepare SQL v_Phase := '3.2)'; v_SQL := format($DYNSQL$ INSERT INTO pgelog_logs ( log_stamp ,log_type ,log_func ,phase ,log_info ,xact_id ,sqlstate ,sqlerrm ,conn_name ) VALUES ( to_timestamp(%L,%L) ,%L ,%L ,%L ,%L ,%L ,%L ,%L ,%L )$DYNSQL$ ,v_Log_Stamp,v_Time_Fmt ,p_log_type ,p_log_func ,p_phase ,p_log_info ,v_Xact_ID ,p_sqlstate ,p_sqlerrm ,v_Conn_Name ); -- end of format() -- 3.3) Execute SQL via dblink() v_Phase := '3.3)'; PERFORM dblink_exec(v_Conn_Name, v_SQL); -- 4) Exit RETURN TRUE; -- 5) Swallow exceptions to prevent influence to the caller EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'FAIL of %() at phase %',v_Proc_Name,v_Phase; RETURN FALSE; END; $BODY$ LANGUAGE plPGSQL VOLATILE; GRANT EXECUTE ON FUNCTION pgelog_to_log(TEXT, TEXT, TEXT, TEXT, TEXT, TEXT) TO PUBLIC; ----------------------------------- -- -- 3) Views of pgelog -- -- 3.1) View to show stored extended logging data with times CREATE OR REPLACE VIEW pgelog_vw_logs AS SELECT L.log_stamp AS log_stamp -- 1; Date and time of log record (got by LOCALTIMESTAMP) ,L.log_type AS log_type -- 2; Kind of log record - FAIL, WARN, INFO etc ,L.log_func AS log_func -- 3; Name of log record source - stored function, trigger, SQL clause etc ,L.phase AS phase -- 4; Serial number or label of current phase of logical operation inside above mentioned log_func, for example 1, 2, 2.a ,CASE WHEN (L.xact_id = LEAD(L.xact_id) OVER W1) THEN ROUND(EXTRACT(EPOCH FROM (L.log_stamp - LEAD(L.log_stamp) OVER W1))::NUMERIC,3) ELSE NULL END AS time_s -- 5; Time in seconds since the previous log entry for the same xact_id ,CASE WHEN (L.xact_id IS NOT NULL) THEN ROUND(EXTRACT(EPOCH FROM (L.log_stamp - MIN(L.log_stamp) OVER W2))::NUMERIC,3) ELSE NULL END AS delta_t -- 6; Time in seconds since first log entry for the same xact_id (~ time from function call to this phase) ,L.log_info AS log_info -- 7; Body of log record ,L.xact_id AS xact_id -- 8; txid::text from txid_current() or txid_current_if_assigned() for PG < 13 or txid8::text from pg_current_xact_id() or pg_current_xact_id_if_assigned() for PG >= 13 ,L.sqlstate AS sqlstate -- 9; Current Postgres SQLSTATE ,L.sqlerrm AS sqlerrm --10; Current Postgres SQLERRM ,L.conn_name AS conn_name --11; Name of dblink used for pseudo-autonomous transaction execution FROM pgelog_logs L WINDOW W1 AS (PARTITION BY L.log_func, L.xact_id ORDER BY L.log_stamp DESC) ,W2 AS (PARTITION BY L.log_func, L.xact_id ORDER BY L.log_stamp ASC) ; COMMENT ON VIEW pgelog_vw_logs IS 'pgelog extension view to show stored extended logging data with times'; COMMENT ON COLUMN pgelog_vw_logs.log_stamp IS 'Date and time of log record (got by LOCALTIMESTAMP)'; COMMENT ON COLUMN pgelog_vw_logs.log_type IS 'Kind of log record - FAIL, WARN, INFO etc'; COMMENT ON COLUMN pgelog_vw_logs.log_func IS 'Name of log record source - stored function, trigger, SQL clause etc'; COMMENT ON COLUMN pgelog_vw_logs.phase IS 'Serial number or label of current phase of logical operation inside above mentioned log_func, for example 1, 2, 2.a'; COMMENT ON COLUMN pgelog_vw_logs.time_s IS 'Time in seconds since the previous log entry for the same xact_id'; COMMENT ON COLUMN pgelog_vw_logs.delta_t IS 'Time in seconds since first log entry for the same xact_id (~ time from function call to this phase)'; COMMENT ON COLUMN pgelog_vw_logs.log_info IS 'Body of log record'; COMMENT ON COLUMN pgelog_vw_logs.xact_id IS 'txid::text from txid_current() or txid_current_if_assigned() for PG < 13 or txid8::text from pg_current_xact_id() or pg_current_xact_id_if_assigned() for PG >= 13'; COMMENT ON COLUMN pgelog_vw_logs.sqlstate IS 'Current Postgres SQLSTATE'; COMMENT ON COLUMN pgelog_vw_logs.sqlerrm IS 'Current Postgres SQLERRM'; COMMENT ON COLUMN pgelog_vw_logs.conn_name IS 'Name of dblink used for pseudo-autonomous transaction execution'; GRANT SELECT ON pgelog_vw_logs TO PUBLIC; -----------------