pgelog

This Release
pgelog 1.0.2
Date
Status
Stable
Abstract
Enhanced PostgreSQL error logging extension
Description
pgelog enables logging into a database table using pseudo-autonomous transactions via dblink. Logs survives even on rollback. To optimize performance, dblink connection is cached to pg_variables.
Released By
anfiau
License
PostgreSQL
Resources
Special Files
Tags

Extensions

pgelog 1.0.2

README

pgelog - extended logging by pseudo-autonomous transactions via dblink into log table

Introduction

The pgelog extension enables reliable and non-intrusive logging into a database table using pseudo-autonomous transactions via the dblink extension. Logs are written in a way that survives even if the calling transaction is rolled back.

To optimize performance, the dblink connection is cached using the pg_variables extension and reused throughout the session.

⚠️ Note: Each session may open up to 1 additional connection (due to dblink). Adjust your max_connections setting accordingly.


License

The pgelog extension available under the license similar to PostgreSQL.


Prerequisites

  • PostgreSQL or PostgresPro 11+
  • Extensions:

  • Passwordless dblink to localhost

For pgelog to work, regular users must be able to connect to localhost via dblink without a password.

Update pg_hba.conf:

# TYPE  DATABASE  USER  ADDRESS  METHOD
local   all       all            peer

Reload configuration:

SELECT pg_reload_conf();

Test dblink():

SELECT * FROM dblink(
    'host=localhost port=5432 dbname=' || current_database() || ' user=' || current_user,
    $$SELECT 'It works!'$$
) AS t(result text);

Should return:

It works!

Installation

Typical installation procedure may look like this:

  1. Download and extract:
$ wget https://github.com/anfiau/pgelog/archive/refs/tags/v1.0.2.tar.gz
$ tar -xzf pgelog-1.0.2.tar.gz
$ cd pgelog-1.0.2
$ chmod +x find-pg_config.sh
  1. Install for the latest PostgreSQL version detected:
$ sudo make install

Or install for a specific version (for example, set path to pg_config of 11):

$ sudo make PG_CONFIG=/usr/pgsql-11/bin/pg_config install
  1. Enable in your database:
CREATE EXTENSION IF NOT EXISTS dblink;
CREATE EXTENSION IF NOT EXISTS pg_variables;
CREATE EXTENSION pgelog;

Objects

Table: pgelog_params

Stores configuration parameters

Column Type Description
param_name TEXT Name of parameter
param_value TEXT Value of parameter

Table: pgelog_logs

Main log storage

Column Type Description
log_stamp TIMESTAMP Timestamp (clock_timestamp())
log_type TEXT Log level: FAIL, WARN, INFO, etc.
log_func TEXT Source function or context
phase TEXT Phase label (e.g., 1, 2.a)
log_info TEXT Message body
xact_id TEXT Transaction ID by pg_current_xact_id() or txid_current()
sqlstate TEXT SQLSTATE code
sqlerrm TEXT Error message (SQLERRM)
conn_name TEXT dblink connection name used

View: pgelog_vw_logs

Main log storage with timing

Column Type Description
log_stamp TIMESTAMP Timestamp (clock_timestamp())
log_type TEXT Log level: FAIL, WARN, INFO, etc.
log_func TEXT Source function or context
phase TEXT Phase label (e.g., 1, 2.a)
log_info TEXT Message body
xact_id TEXT Transaction ID by pg_current_xact_id() or txid_current()
time_s INTEGER Time in seconds since the previous log entry for the same xact_id
delta_t INTEGER Time in seconds since first log entry for the same xact_id (~ time from function call to this phase)
sqlstate TEXT SQLSTATE code
sqlerrm TEXT Error message (SQLERRM)
conn_name TEXT dblink connection name used

Usage Examples

Simple

  • Write a log entry
SELECT pgelog_to_log('SQL', 'standalone', 'Test of logging by pgelog', '1');
  • Read latest entry
SELECT log_stamp, log_info
FROM pgelog_logs
ORDER BY log_stamp DESC
LIMIT 1;
  • Result:
log_stamp log_info
2025-09-15 10:54:41.907 Test of logging by pgelog

Logging Exception in PL/pgSQL

  • Execute PL/pgSQL block raising an exception
DO $$
DECLARE
  v_Result   FLOAT;
  v_Divisor  INTEGER := 0;
  v_Log_Func TEXT := 'PL/pgSQL block';
  v_Phase    TEXT;
  v_Exc_1    TEXT;
  v_Exc_2    TEXT;
  v_Exc_3    TEXT;
  v_Exc_4    TEXT;
  v_SQLSTATE TEXT;
  v_SQLERRM  TEXT;
BEGIN
 -- 1) First phase
 v_Phase := '1)';
 v_Result := 1.0 / v_Divisor;
 -- 2) Second phase
 v_Phase := '2)';
 PERFORM pgelog_to_log('INFO', v_Log_Func,
   'v_Divisor='||COALESCE(v_Divisor::TEXT,'NULL'), v_Phase);
 -- Catch exceptions
 EXCEPTION
    WHEN OTHERS THEN
       GET STACKED DIAGNOSTICS
           v_Exc_1    = MESSAGE_TEXT
          ,v_Exc_2    = PG_EXCEPTION_DETAIL
          ,v_Exc_3    = PG_EXCEPTION_HINT
          ,v_Exc_4    = PG_EXCEPTION_CONTEXT
          ,v_SQLSTATE = RETURNED_SQLSTATE
       ;
       v_SQLERRM := format(
                           '%s%s%s%s%s'
                           ,COALESCE(NULLIF(v_Exc_1,'')||'; ','')
                           ,COALESCE(NULLIF(v_Exc_2,'')||'; ','')
                           ,COALESCE(NULLIF(v_Exc_3,'')||'; ','')
                           ,COALESCE(NULLIF(v_Exc_4,'')||'; ','')
                           ,COALESCE(NULLIF(SQLERRM::TEXT,'')||'; ','')
                          );
       PERFORM pgelog_to_log('FAIL', v_Log_Func
         ,format(
                 '%s failed for v_Divisor=%s'
                 ,v_Log_Func
                 ,COALESCE(v_Divisor::TEXT,'NULL')
                )
         ,v_Phase, v_SQLERRM, v_SQLSTATE);
       RAISE EXCEPTION '%', v_SQLERRM
       USING ERRCODE = SQLSTATE;
END $$;
  • Read latest entry with ‘FAIL’ log_type
SELECT L.log_info, L.sqlerrm
FROM   pgelog_logs L
WHERE  L.log_type = 'FAIL'
ORDER BY L.log_stamp DESC
LIMIT 1;
  • Result:
log_info sqlerrm
PL/pgSQL block failed for v_Divisor=0 division by zero PL/pgSQL; function inline_code_block line 16 at assignment; division by zero¶

Configuration Parameters

Use pgelog_set_param() and pgelog_get_param():

SELECT pgelog_get_param('pgelog_ttl_minutes'); -- get old value = 1440
SELECT pgelog_set_param('pgelog_ttl_minutes', '2880'); -- set new value = 2880
SELECT pgelog_get_param('pgelog_ttl_minutes'); -- get new value = 2880
Parameter Default Description
pgelog_port ‘5432’ Database port
pgelog_pgv_transactional ‘y’ Use pg_variables in transactional mode
pgelog_assign_xact_id ‘n’ Force xact ID assignment for read-only tx
pgelog_is_active ‘y’ Global logging toggle
pgelog_pgv_package ‘pgelog’ pg_variables package name
pgelog_ttl_minutes ‘1440’ Retention time in minutes (default: 1 day)
pgelog_log_clean_call ‘y’ Log calls to pgelog_clean_log()?
pgelog_log_init_call ‘n’ Log calls to pgelog_init()?

You can store your own custom parameters in pgelog_params by pgelog_set_param() — they persist across backups.


Session-Level Control of Logging

Override global logging settings per session:

  • pgelog_enable_locally() – Enable logging only in this session
  • pgelog_disable_locally() – Disable logging only in this session

Example:

SELECT pgelog_disable_locally(); -- Turn off logging for current session

Clean Up Old Logs

Remove logs older than N minutes:

  • Delete logs older than 60 minutes:
SELECT pgelog_clean_log(60);
  • Or use default TTL:
SELECT pgelog_clean_log();
  • Schedule it via cron (for example run daily cleanup at 2:00 AM):
0 2 * * * psql -U postgres -d mydb -c "SELECT pgelog_clean_log();"

Functions

Function Returns Description
pgelog_to_log(log_type,log_func,log_info,phase,sqlerrm,sqlstate) BOOLEAN Write a log entry
pgelog_init() BOOLEAN Initialize dblink (auto-called)
pgelog_close() BOOLEAN Close dblink manually
pgelog_clean_log(minutes) BOOLEAN Remove old records
pgelog_set_param(name,val) VOID Set config param
pgelog_get_param(name) TEXT Get config param
pgelog_delete_param(name) VOID Delete config param
pgelog_enable() / disable() BOOLEAN Toggle global logging
pgelog_enable_locally() / disable_locally() BOOLEAN Toggle session logging

Exception Handling

All exceptions in pgelog functions are caught silently to avoid disrupting calling code. A NOTICE is raised with error details.

This ensures logging never causes unintended rollbacks.


PGXN

This extension is available on PGXN. Install it with:

pgxn install pgelog