pg_op_log is a PostgreSQL extension for saving data changes to a history log table when transactions are committed.
This extension is based on the following conventions:
- Each business table should contain the following fields:
modified_at: Records the timestamp of INSERT or UPDATE operationsmodified_by: The user who performed the operation, provided by the business systemdeleted: Flag indicating whether the record is soft-deleted
- A temporary table
temp_op_metaexists in the database to store metadata about the current transaction operation. This table contains only one record and is automatically cleaned up after the transaction commits.- This table has the following fields:
op_type: Operation type, provided by the business systemop_note: Operation notes, provided by the business systemrevision_id: Auto-generated UUID for marking operation batches
- This table has the following fields:
git clone https://github.com/hank-cp/pg_op_log.git
cd pg_op_log
make install- This step is typically executed by the business system when starting a transaction
CREATE EXTENSION IF NOT EXISTS op_log SCHEMA "public" CASCADE;
CREATE TEMP TABLE temp_op_meta (
op_type text,
op_note text,
revision_id text
);
INSERT INTO temp_op_meta (op_type, op_note, revision_id)
VALUES ('CREATE_USER', 'Create new user', gen_random_uuid()::text);SELECT op_log_enable(
'your_table_name'::regclass,
ARRAY['field_to_exclude']::text[],
'{
"json_field_1": {"type": "object"},
"json_field_2": {"type": "array"},
"json_field_3": {"type": "objectArray", "keyFields": ["id", "name"]},
"json_field_4": {"type": "other"}
}'::jsonb,
ARRAY['array_field_1', 'array_field_2']::text[]
);Parameter descriptions:
- 1st parameter: The table name to monitor
- 2nd parameter: Array of fields to exclude from comparison (optional)
- 3rd parameter: Configuration for JSON/JSONB fields (optional)
object: JSON object, compares differences in each fieldarray: JSON array, compares added/removed elementsobjectArray: JSON object array, requires keyFields to match objectsother: Other types, compares overall differences
- 4th parameter: Regular array type fields (optional)
INSERT INTO users (name, email, modified_by)
VALUES ('Alice', '[email protected]', 'admin');SELECT
id,
schema_name,
table_name,
data_id,
op_type,
op_note,
action,
modified_by,
modified_at,
raw_data,
diff,
json_diff
FROM data_op_log
WHERE table_name = 'users'
ORDER BY id DESC;id: Log record IDschema_name: Database schema nametable_name: Table namedata_id: Primary key value (composite keys separated by ∆)modified_at: Record modification timestampmodified_by: User who modified the recordop_type: Operation type (from temp_op_meta)op_note: Operation notes (from temp_op_meta)revision_id: Batch ID (from temp_op_meta)raw_data: Complete data for INSERT/DELETE (JSONB)diff: Regular field differences for UPDATE (JSONB)json_diff: JSON/array field differences for UPDATE (JSONB)action: Operation type (I=INSERT, U=UPDATE, D=DELETE)- Other audit fields...
{
"diffType": "normal",
"name": "\"Alice\" -> \"Alice Wang\"",
"age": "30 -> 31",
"email": "null -> \"[email protected]\""
}{
"metadata": {
"diffType": "object",
"city": "\"Beijing\" -> \"Shanghai\"",
"age": "30 -> 31"
}
}{
"tags": {
"diffType": "array",
"newItems": ["premium", "vip"],
"removedItems": ["trial"]
}
}{
"items": {
"diffType": "objectArray",
"newItems": [
{"id": 3, "name": "item3", "value": 300}
],
"removedItems": [
{"id": 1, "name": "item1", "value": 100}
],
"modifiedItems": [
{
"key": "2",
"value": "200 -> 250",
"name": "\"item2\" -> \"item2_updated\""
}
]
}
}SELECT op_log_disable('your_table_name'::regclass);- Tables must contain
modified_atandmodified_byfields - The
temp_op_metatable can be non-existent or empty; related fields will be recorded as NULL - If there is no primary key, the
idfield is used as data_id by default - Composite primary keys are concatenated with the
∆character - Configuration information is saved in database parameters and requires ALTER DATABASE permission