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:

InformationDescription
UserWho made the change
TimestampWhen the change occurred
Table NameWhich table was affected
OperationINSERT, UPDATE, DELETE
Primary KeyWhich row was modified
Old ValuePrevious data
New ValueUpdated data
Session InformationOptional 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 IDColumnOld ValueNew Value
100SALARY50006000
100JOB_IDSA_REPSA_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

Popular posts from this blog

Oracle APEX Development Tips I Wish I Knew Earlier

The Evolution of Software Engineering: From Writing Code to Solving Business Problems

Advanced PL/SQL Performance Tuning: Processing Millions of Records Efficiently