Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / SQL

Logging information in Oracle

5.00/5 (5 votes)
1 May 2012CPOL5 min read 38.1K   293  
This article describes techniques such as autonomous transactions and action information that can be used when logging information about operations in Oracle database.

Introduction

While logging can be done in numerous ways on the client side when using a database, it's often useful to also log information on the database side. For example when executing a stored procedure, running a batch etc, it's good to know what steps are done. This article describes few useful things to know when building a logging mechanism: Autonomous transactions and module/action information. 

Setting up the test bench 

First we'll need a few tables. One to add data to and another one where log information is going to be stored. The test table is very simple:

SQL
-------------------------------------------------
-- Definition for Test table
-------------------------------------------------
CREATE TABLE Test (
   SomeColumn VARCHAR2(30)
);

Just a single column to put some text into. 

The log table looks like this:

SQL
-------------------------------------------------
-- Definition for LogEntry table
-------------------------------------------------
CREATE TABLE LogEntry (
   Created    DATE           DEFAULT SYSDATE CONSTRAINT nn_LogEntry_Created NOT NULL,
   CreatedBy  VARCHAR2(30)   DEFAULT USER    CONSTRAINT nn_LogEntry_CreatedBy NOT NULL,
   Sid        NUMBER                         CONSTRAINT nn_LogEntry_Sid NOT NULL,
   OsUser     VARCHAR(20)                    CONSTRAINT nn_LogEntry_OsUser NOT NULL,
   Machine    VARCHAR(20)                    CONSTRAINT nn_LogEntry_Machine NOT NULL,
   Process    VARCHAR(12)                    CONSTRAINT nn_LogEntry_Process NOT NULL,
   Program    VARCHAR(100)                   CONSTRAINT nn_LogEntry_Program NOT NULL,
   Module     VARCHAR(100),
   Action     VARCHAR(100),
   Text       VARCHAR2(2000)                 CONSTRAINT nn_LogEntry_Text NOT NULL
); 

The idea is to store the following information into the LogEntry table:

  • Created, when the log entry is created. 
  • CreatedBy, the user name that added the log entry. 
  • Sid, session identifier for the session that inserted the log entry. 
  • OsUser, the actual operating system user. 
  • Machine, name of the computer from the statement was executed. 
  • Process, process identifier from the statement was executed.  
  • Program, program from the statement was executed.  
  • Module, descriptive information about the module. 
  • Action, descriptive information about the action. 
  • Text, the actual log entry text. 

Package for logging

To actually insert rows into the LogEntry table I created a package called Logging. The definition of the package looks like following.

SQL
-------------------------------------------------
-- Package definition for Logging
-------------------------------------------------
CREATE OR REPLACE PACKAGE Logging AUTHID DEFINER
AS
   -- Writes a single entry to LogEntry-table
   PROCEDURE LogEntry           (vText varchar2);

   -- Writes a single entry to LogEntry-table using autonomous transaction
   PROCEDURE LogEntryAutonomous (vText varchar2);

END logging;
/ 

Let's concentrate on the first procedure. The procedure looks like (partial definition of the body):  

SQL
   -----------------------------------------------------------------------
   -- Writes a single entry to LogEntry-table
   -----------------------------------------------------------------------
   PROCEDURE LogEntry (vText varchar2) AS
   BEGIN
      INSERT INTO LogEntry (
         Sid,
         OsUser,
         Machine,
         Process,
         Program,
         Module,
         Action,
         Text)
      SELECT vs.Sid,
             vs.OsUser,
             vs.Machine,
             vs.Process,
             vs.Program,
             vs.Module,
             vs.Action,
             vText
      FROM v$session vs
      WHERE sid = sys_context('USERENV','SID');
      
   END LogEntry;  

The procedure inserts the text passed to it to the LogEntry table (what a big surprise :)). But also some information from the v$session view is stored. This information helps to track log entries, what operation came from which user or which workstation, were the operations done from the same session etc.

Normally a user doesn't have the privilege to use v$session inside a procedure. Because of this the user who owns the package must be granted a select privilege to the underlying view. The following statement grants the necessary privileges, and it must be run with SYSDBA privileges:

SQL
GRANT SELECT ON sys.v_$session TO <user name goes here>;
/   

Also to get the session information from the correct session the sys_context is used to fetch the SID from the USERENV namespace. 

Simple tests 

Add data with a commit 

Now let's do a small test to see that everything is working.

SQL
--------------------------------------------
-- Simple test
--------------------------------------------
-- Add a log entry
EXECUTE Logging.LogEntry('Entering test 1');

-- Add a test row
INSERT INTO Test VALUES ('Test row 1');

-- Add a log entry
EXECUTE Logging.LogEntry('Test 1 ended');

-- Commit the transaction
COMMIT

The script adds a log entry, inserts a row, adds a second log entry and then commits the transaction. If we look at the data in the Test table, it looks like 

SQL
-- Select the data from the test table
SELECT * FROM Test;  
SOMECOLUMN     
----------
Test row 1  

And the data in the LogEntry table is as foreseeable 

SQL
-- Select the data from the log table
SELECT TO_CHAR(le.Created, 'dd.mm.yyyy hh24.mi.ss') as Created,
       le.Sid,
       le.OsUser,
       le.Machine,
       le.Process,
       le.Program,
       le.Text
FROM LogEntry le
ORDER BY le.Created DESC;

The results: 

CREATED              SID  OSUSER  MACHINE  PROCESS  PROGRAM        TEXT
-------------------  ---  ------  -------  -------  -------------  ---------------
29.04.2012 23.00.12  94   mika    MyBox    2056     SQL Developer  Test 1 ended 
29.04.2012 23.00.10  94   mika    MyBox    2056     SQL Developer  Entering test 1 

So everything is as expected.

Add data and roll back the operation 

Let's do another test: 

SQL
--------------------------------------------
-- Test inside a single transaction
--------------------------------------------
-- Add a log entry
EXECUTE Logging.LogEntry('Entering test 2');

-- Add a test row
INSERT INTO Test VALUES ('Test row 2');

-- Add a log entry
EXECUTE Logging.LogEntry('Test 2 ended');

-- Roll back the transaction
ROLLBACK

Basically this is the same thing as the first test, only this time the transaction isn't committed but rolled back. And if we have a look at the data in the Test table, nothing has been changed (as supposed). 

SQL
-- Select the data from the test table
SELECT * FROM Test;  
SOMECOLUMN     
----------
Test row 1  

Now, if we list the contents of the LogEntry table, we'll see that there's no changes in this table either. 

SQL
-- Select the data from the log table
SELECT TO_CHAR(le.Created, 'dd.mm.yyyy hh24.mi.ss') as Created,
       le.Sid,
       le.OsUser,
       le.Machine,
       le.Process,
       le.Program,
       le.Text
FROM LogEntry le
ORDER BY le.Created DESC;

The results: 

CREATED              SID  OSUSER  MACHINE  PROCESS  PROGRAM        TEXT
-------------------  ---  ------  -------  -------  -------------  ---------------
29.04.2012 23.00.12  94   mika    MyBox    2056     SQL Developer  Test 1 ended 
29.04.2012 23.00.10  94   mika    MyBox    2056     SQL Developer  Entering test 1 

However, it would have been nice to know that something has been done even though it has been rolled back. For example if the rolling back was done because something went wrong, we could have wrote a log entry about the situation but all interesting information would have been gone because of the rollback.

Using an autonomous transaction

First, what is an autonomous transaction? An autonomous transaction shouldn't be mixed with a nested transaction, it's a completely different beast. While a nested transaction shares the same context as the outer transaction, an autonomous transaction does not. Perhaps the easiest way is to consider an autonomous transaction as a parallel, separate transaction.

When an autonomous transaction is called, the original transaction context is left and a 'new' one begins. When this transaction ends the execution comes back to the original transaction. This means:

  • If the autonomous transaction executes a commit, the modified data from the autonomous transaction is visible to all transactions immediately. 
  • The two transactions share no locks. 
  • Even if the actual transaction executes a rollback, the data committed in an autonomous transaction remains. 
Let's have a look at the other procedure in the package:

SQL
   -----------------------------------------------------------------------
   -- Writes a single entry to LogEntry-table using autonomous transaction
   -----------------------------------------------------------------------
   PROCEDURE LogEntryAutonomous (vText varchar2) AS
      PRAGMA AUTONOMOUS_TRANSACTION;
   BEGIN
      LogEntry(vText);
      COMMIT;
      
   END LogEntryAutonomous; 

Basically this procedure just calls the previously defined procedure, but there are two things to note.

PRAGMA AUTONOMOUS_TRANSACTION, this pragma defines that the procedure uses an autonomous transaction. The scope is the whole procedure. 

Also note the commit in the end of the procedure. Even though the first procedure didn't do a commit or rollback but the decision was left to the caller of the procedure, now the decision must be done inside the autonomous block before returning to the actual transaction. 

If the transaction isn't committed or rolled back in the end of the block, an ORA-06519 will be raised.  The explanation for this error is: 

06519. 00000 -  "active autonomous transaction detected and rolled back"
*Cause:    Before returning from an autonomous PL/SQL block, all autonomous
           transactions started within the block must be completed (either
           committed or rolled back). If not, the active autonomous
           transaction is implicitly rolled back and this error is raised.
*Action:   Ensure that before returning from an autonomous PL/SQL block,
           any active autonomous transactions are explicitly committed
           or rolled back. 

Now let's run a test.

SQL
--------------------------------------------
-- Test using autonomous transaction
--------------------------------------------
-- Add a log entry
EXECUTE Logging.LogEntryAutonomous('Entering test 3');

-- Add a test row
INSERT INTO Test VALUES ('Test row 3');

-- Add a log entry
EXECUTE Logging.LogEntryAutonomous('Test 3 ended');

-- Roll back the transaction
ROLLBACK

As earlier some modifications are done and the transaction is rolled back. The data in the test table remains unmodified but what about the data in the LogEntry table.

SQL
-- Select the data from the log table
SELECT TO_CHAR(le.Created, 'dd.mm.yyyy hh24.mi.ss') as Created,
       le.Sid,
       le.OsUser,
       le.Machine,
       le.Process,
       le.Program,
       le.Text
FROM LogEntry le
ORDER BY le.Created DESC

The data looks now like following: 

CREATED              SID  OSUSER  MACHINE  PROCESS  PROGRAM        TEXT
-------------------  ---  ------  -------  -------  -------------  ---------------
29.04.2012 23.46.26  94   mika    MyBox    2056     SQL Developer  Test 3 ended
29.04.2012 23.46.24  94   mika    MyBox    2056     SQL Developer  Entering test 3
29.04.2012 23.00.12  94   mika    MyBox    2056     SQL Developer  Test 1 ended
29.04.2012 23.00.10  94   mika    MyBox    2056     SQL Developer  Entering test 1  
So even if the transaction was rolled back, now the log entries are shown since the autonomous transaction was committed.

Adding extra information 

The previous examples gathered some useful information from the client side. However, if the log entries are recorded for example from a stored procedure, it may not be enough to know the calling program or session.

Oracle has a DBMS_APPLICATION_INFO package which can be used to set more information also from the calling client. In the scope of this article the interesting procedures are:

  • SET_MODULE, sets the current module and action 
  • SET_ACTION, sets the current action  

By using these procedures the client application can set more information about the context on the client side.

A small test using the DBMS_APPLICATION_INFO

SQL
--------------------------------------------
-- Setting module and action with autonomous logging
--------------------------------------------
-- Set module and action
EXECUTE DBMS_APPLICATION_INFO.SET_MODULE('Test module', 'First action');

-- Add a log entry
EXECUTE Logging.LogEntryAutonomous('Entering test 4');

-- Set action
EXECUTE DBMS_APPLICATION_INFO.SET_ACTION('Some insert sequence');

-- Add a log entry
EXECUTE Logging.LogEntryAutonomous('Insert row 4.1');

-- Add a test row
INSERT INTO Test VALUES ('Test row 4.1');

-- Add a log entry
EXECUTE Logging.LogEntryAutonomous('Insert row 4.2');

-- Add a test row
INSERT INTO Test VALUES ('Test row 4.2');

-- Add a log entry
EXECUTE Logging.LogEntryAutonomous('Test 4 ended');

-- Roll back the transaction
ROLLBACK;

First the module and the initial action are set and a log entry is created. After this, a new action is set and few inserts are run inside this action. In the end, a log entry is added and the transaction is rolled back.

After running the script above, the data in LogEntry is fetched using 

SQL
-- Select the data from the log table
SELECT TO_CHAR(le.Created, 'dd.mm.yyyy hh24.mi.ss') as Created,
       le.Sid,
       le.Program,
       le.Module,
       le.Action,
       le.Text
FROM LogEntry le
ORDER BY le.Created DESC

And the results are as following. The latest 4 rows are from this batch and the previous ones from previous batches. 

CREATED              SID  PROGRAM        MODULE        ACTION               TEXT
-------------------  ---  -------------  -----------  --------------------  ---------------
30.04.2012 00.01.44  94   SQL Developer  Test module  Some insert sequence  Test 4 ended
30.04.2012 00.01.41  94   SQL Developer  Test module  Some insert sequence  Insert row 4.2
30.04.2012 00.01.37  94   SQL Developer  Test module  Some insert sequence  Insert row 4.1
30.04.2012 00.01.35  94   SQL Developer  Test module  First action          Entering test 4
29.04.2012 23.46.26  94   SQL Developer  Test module  Some insert           Test 3 ended
29.04.2012 23.46.24  94   SQL Developer  Test module  Some insert           Entering test 3
29.04.2012 23.00.12  94   SQL Developer  Test module  Some insert           Test 1 ended
29.04.2012 23.00.10  94   SQL Developer  Test module  Some insert           Entering test 1 

Final words 

Autonomous transactions are very handy in several situations. Logging is one of them. When using them for the first time, remember that they are not nested transactions and don't behave like one.

Also properly using module and action information, the execution context can be more closely monitored from the database side. This gives lots of useful information especially when solving problems. 

History

  • April 30, 2012: Article created. 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)