Creating a Generic Audit Framework Using PL/SQL
Introduction
Auditing is a critical requirement in modern database applications. Organizations need visibility into who changed data, when the change occurred, what values were modified, and why the modification happened. Whether driven by regulatory compliance, security requirements, troubleshooting needs, or operational governance, a robust auditing solution is essential.
Many applications implement auditing separately for each table, resulting in duplicated code, inconsistent audit records, and increased maintenance overhead. A better approach is to develop a generic audit framework using PL/SQL that can be reused across the database.
In this article, we'll explore how to design and implement a reusable audit framework using PL/SQL, discuss key architectural considerations, and review best practices for building scalable auditing solutions.
Why Build a Generic Audit Framework?
Traditional auditing implementations often involve:
Custom triggers for each table
Duplicate logging logic
Inconsistent audit structures
Difficult maintenance
Increased development effort
A generic framework provides:
Standardized auditing
Centralized management
Reduced code duplication
Easier maintenance
Consistent reporting
Audit Requirements
A comprehensive audit framework should capture:
| Information | Description |
|---|---|
| User | Who made the change |
| Timestamp | When the change occurred |
| Table Name | Which table was affected |
| Operation | INSERT, UPDATE, DELETE |
| Primary Key | Which row was modified |
| Old Value | Previous data |
| New Value | Updated data |
| Session Information | Optional session metadata |
These elements provide a complete history of database activity.
Framework Architecture
A generic audit solution typically consists of:
Application
│
▼
DML Operations
│
▼
Audit Trigger
│
▼
Audit Package
│
▼
Audit Tables
The trigger captures row changes, while a reusable PL/SQL package handles logging and storage.
Creating the Audit Table
A central audit repository stores all change records.
Example:
CREATE TABLE audit_log (
audit_id NUMBER GENERATED ALWAYS AS IDENTITY,
table_name VARCHAR2(128),
operation_type VARCHAR2(10),
column_name VARCHAR2(128),
primary_key VARCHAR2(4000),
old_value VARCHAR2(4000),
new_value VARCHAR2(4000),
changed_by VARCHAR2(100),
change_date DATE,
session_id NUMBER
);
This table can support auditing across multiple application tables.
Building the Audit Package
The audit package centralizes logging functionality.
Package Specification
CREATE OR REPLACE PACKAGE audit_pkg AS
PROCEDURE log_change(
p_table_name VARCHAR2,
p_operation VARCHAR2,
p_column_name VARCHAR2,
p_primary_key VARCHAR2,
p_old_value VARCHAR2,
p_new_value VARCHAR2
);
END audit_pkg;
/
Package Body
CREATE OR REPLACE PACKAGE BODY audit_pkg AS
PROCEDURE log_change(
p_table_name VARCHAR2,
p_operation VARCHAR2,
p_column_name VARCHAR2,
p_primary_key VARCHAR2,
p_old_value VARCHAR2,
p_new_value VARCHAR2
) IS
BEGIN
INSERT INTO audit_log (
table_name,
operation_type,
column_name,
primary_key,
old_value,
new_value,
changed_by,
change_date,
session_id
)
VALUES (
p_table_name,
p_operation,
p_column_name,
p_primary_key,
p_old_value,
p_new_value,
USER,
SYSDATE,
SYS_CONTEXT('USERENV','SID')
);
END;
END audit_pkg;
/
This procedure becomes the foundation of the auditing framework.
Auditing INSERT Operations
Example trigger:
CREATE OR REPLACE TRIGGER trg_emp_ins
AFTER INSERT ON employees
FOR EACH ROW
BEGIN
audit_pkg.log_change(
p_table_name => 'EMPLOYEES',
p_operation => 'INSERT',
p_column_name => 'ROW',
p_primary_key => :NEW.employee_id,
p_old_value => NULL,
p_new_value => 'New Record'
);
END;
/
This records newly created rows.
Auditing UPDATE Operations
For updates, both old and new values are important.
Example:
IF NVL(:OLD.salary,0) <> NVL(:NEW.salary,0) THEN
audit_pkg.log_change(
p_table_name => 'EMPLOYEES',
p_operation => 'UPDATE',
p_column_name => 'SALARY',
p_primary_key => :NEW.employee_id,
p_old_value => :OLD.salary,
p_new_value => :NEW.salary
);
END IF;
This captures only actual changes.
Auditing DELETE Operations
Example:
CREATE OR REPLACE TRIGGER trg_emp_del
AFTER DELETE ON employees
FOR EACH ROW
BEGIN
audit_pkg.log_change(
p_table_name => 'EMPLOYEES',
p_operation => 'DELETE',
p_column_name => 'ROW',
p_primary_key => :OLD.employee_id,
p_old_value => 'Deleted Record',
p_new_value => NULL
);
END;
/
This preserves deletion history.
Using Autonomous Transactions
Audit records should survive transaction rollbacks.
Example:
PROCEDURE log_change IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO audit_log (...);
COMMIT;
END;
Benefits include:
Guaranteed audit persistence
Independent transaction handling
Reliable compliance reporting
However, organizations should carefully evaluate whether audit entries should remain when business transactions fail.
Capturing Session Metadata
Additional context can improve audit usefulness.
Examples:
SYS_CONTEXT('USERENV','SESSION_USER')
SYS_CONTEXT('USERENV','OS_USER')
SYS_CONTEXT('USERENV','IP_ADDRESS')
SYS_CONTEXT('USERENV','MODULE')
These values help identify the source of database activity.
Generic Trigger Generation
Creating triggers manually for hundreds of tables is impractical.
A metadata-driven approach can automate trigger generation.
Example:
SELECT table_name
FROM user_tables;
A PL/SQL utility can dynamically generate audit triggers for each table.
Benefits:
Faster deployment
Consistent implementation
Reduced maintenance
Auditing Multiple Columns
For detailed change tracking:
| Employee ID | Column | Old Value | New Value |
|---|---|---|---|
| 100 | SALARY | 5000 | 6000 |
| 100 | JOB_ID | SA_REP | SA_MGR |
This structure enables precise reporting and historical analysis.
Reporting on Audit Data
Example query:
SELECT
table_name,
operation_type,
changed_by,
change_date
FROM audit_log
ORDER BY change_date DESC;
Common reports include:
User activity history
Data change history
Compliance reports
Security investigations
Performance Considerations
Auditing introduces additional database activity.
To minimize impact:
Audit Only Necessary Tables
Avoid auditing every table indiscriminately.
Audit Only Significant Columns
Exclude frequently changing, low-value columns.
Archive Old Audit Data
Move historical records to archive tables.
Index Audit Tables
Improve reporting performance.
Example:
CREATE INDEX idx_audit_date
ON audit_log(change_date);
Batch Purging
Regularly remove obsolete audit data according to retention policies.
Advanced Enhancements
A mature audit framework may include:
JSON-Based Change Storage
Store complete row changes:
{
"salary": {
"old": 5000,
"new": 6000
}
}
Audit Policies
Enable or disable auditing dynamically.
Change Reason Tracking
Require users to provide justification for modifications.
Notification Integration
Generate alerts for sensitive data changes.
Dashboard Reporting
Provide real-time audit visibility.
Best Practices
Centralize Audit Logic
Use a single package whenever possible.
Avoid Business Logic in Triggers
Keep triggers lightweight.
Capture Only Meaningful Changes
Reduce unnecessary audit volume.
Protect Audit Data
Restrict access to audit tables.
Monitor Framework Performance
Track storage growth and execution overhead.
Test Under Load
Validate scalability before production deployment.
Conclusion
A generic audit framework built with PL/SQL provides a scalable, maintainable, and consistent approach to tracking database changes. By centralizing audit logic within reusable packages and combining it with metadata-driven triggers, organizations can significantly reduce development effort while improving visibility into application activity.
Whether supporting regulatory compliance, security investigations, operational monitoring, or historical reporting, a well-designed audit framework becomes a valuable component of any enterprise Oracle environment. Investing in a reusable auditing solution today can save countless hours of maintenance and troubleshooting in the future.
Comments
Post a Comment