The're just 3 Prerequisites that must be done before using LEHS by the Application:
PKG_LEHS.log ('<Log Application>','<Log-Level>','<Log Message>');
PKG_LEHS.fatal ('<Log Application>','<Log Message>'); PKG_LEHS.error ('<Log Application>','<Log Message>'); PKG_LEHS.warn ('<Log Application>','<Log Message>'); PKG_LEHS.info ('<Log Application>','<Log Message>'); PKG_LEHS.debug ('<Log Application>','<Log Message>'); PKG_LEHS.trace ('<Log Application>','<Log Message>');
DBMS_UTILITY.format_call_stack
and DBMS_UTILITY.format_error_backtrace
as well as DBMS_UTILITY.format_error_stack
to identify the affected Line-Number of the log-raising Code you have to interpret this Line-Number correctly.
USER_SOURCE
. Because the SQL-Developer shows the first line create or replace
on a separate line it causes to mistakes with the interpretation of the Line.
DECLARE
or with BEGIN
).
The Text CREATE OR REPLACE TRIGGER ...
will be ignored by DBMS_UTILITY.format_call_stack
,
DBMS_UTILITY.format_error_backtrace
or DBMS_UTILITY.format_error_stack
.
Ok, you already know user defined Exceptions and user defined Error Messages:
DECLARE e_not_null EXCEPTION; -- user defined Exception PRAGMA EXCEPTION_INIT (e_not_null, -1401); BEGIN INSERT INTO some_table ( not_null_column ) VALUES ( NULL ); EXCEPTION WHEN e_not_null THEN -- user defined Error Message RAISE_APPLICATION_ERROR (-20000, 'Some userdefined Exception', FALSE); END;For the user defined Error Messages you can take a long range of Exception Codes (from -20999 to -20000). 1000 Error Codes! But on most Applications I have seen only the Code -20000 has been taken for any Exception that has been raised by this Application. That's not good for any Error hunting Session.
CREATE OR REPLACE PROCEDURE test_it ( pi_param1 IN VARCHAR2 ,pi_param2 IN DATE ) IS BEGIN IF pi_param1 IS NULL THEN RAISE_APPLICATION_ERROR (-20000, 'Mandatory Parameter pi_param1 IS NULL'); END IF; IF pi_param2 IS NULL THEN RAISE_APPLICATION_ERROR (-20000, 'Mandatory Parameter pi_param2 IS NULL'); END IF; ... END test_it;And now imagine you have to write your Exception Messages in several Languages (*mega grin*).
Now I tell you what user named Exceptions are. This is a recommendation by Stephen Feuerstein. I really apologize you to book one of his great Courses about PL/SQL Programming. He's just a Genius on this area.
Just define a Name for every Exception you want to raise and join it with any free Exception Code when you raise it. This is the short Version. Just see some Example:
CREATE OR REPLACE PROCEDURE test_it ( pi_param1 IN VARCHAR2 ,pi_param2 IN DATE ) IS c_param1_is_null CONSTANT VARCHAR2(30) := 'PARAM1_IS_NULL'; c_param2_is_null CONSTANT VARCHAR2(30) := 'PARAM2_IS_NULL'; PROCEDURE raise_user_exception ( pi_error_name IN VARCHAR2 ) IS BEGIN IF pi_error_name IN (c_param1_is_null, c_param2_is_null) THEN RAISE_APPLICATION_ERROR (-20000, 'Mandatory Parameter pi_param1 IS NULL'); END IF; END raise_user_exception; BEGIN IF pi_param1 IS NULL THEN raise_user_exception (c_param1_is_null); END IF; IF pi_param2 IS NULL THEN raise_user_exception (c_param2_is_null); END IF; END test_it;Ok, you would say "no Advantage for me as Developer. Far from it I have to code more lines". Yeah, but imagine, if you set this raising Procedure outside of your Program (make it global) then you can take this code for dozens of Procedures just to check the mandatory Parameters. So, this Procedure already exists within LEHS.
The next Point is the flexibility of such Exception Messages. If you can see above the Exception Message itself is for both always the same. Wouldn't it be fine if the Exception Message is for all these Checks (mandatory Parameter) is always the same except of the Name of the checked Parameter itself? Let's do it:
CREATE OR REPLACE PROCEDURE test_it ( pi_param1 IN VARCHAR2 ,pi_param2 IN DATE ) IS c_param1_is_null CONSTANT VARCHAR2(30) := 'PARAM1_IS_NULL'; c_param2_is_null CONSTANT VARCHAR2(30) := 'PARAM2_IS_NULL'; PROCEDURE raise_mandatory_check ( pi_error_name IN VARCHAR2 ,pi_parameter IN VARCHAR2 ) IS v_message VARCHAR2; BEGIN v_message := 'Mandatory Parameter ' || pi_parameter || ' IS NULL'; IF pi_error_name IN (c_param1_is_null, c_param2_is_null) THEN RAISE_APPLICATION_ERROR (-20000, v_message); END IF; END raise_user_exception; BEGIN IF pi_param1 IS NULL THEN raise_mandatory_check (c_param1_is_null, 'pi_param1'); END IF; IF pi_param2 IS NULL THEN raise_mandatory_check (c_param2_is_null, 'pi_param2'); END IF; END test_it;We have defined a flexible user named Exception Message.
So, if we join these all Examples together the Code for LEHS looks like this:
CREATE OR REPLACE PROCEDURE test_it ( pi_param1 IN VARCHAR2 ,pi_param2 IN DATE ) IS c_log_application CONSTANT PKG_LEHS.pst_application_name := 'LOG_APPLICATION'; c_parameter_missing CONSTANT PKG_LEHS.pst_error_name := 'MISSING MANDATORY PARAMETER'; BEGIN -- Initialize the Error Message PKG_LEHS.add_message ( pi_application => c_log_application ,pi_error_name => c_parameter_missing ,pi_error_code => -20000 ,pi_message => 'Mandatory Parameter %1 is NULL' -- %1 will be replaced by the first Entry of the Error-Arguments List ); PKG_LEHS.add_message ( pi_application => c_log_application ,pi_error_name => c_parameter_missing ,pi_error_code => -20000 ,pi_message => 'Zwingender Parameter %1 ist NULL' ,pi_nls_language => 'GERMAN' ); PKG_LEHS.debug (c_log_application, 'Check for mandatory Parameters'); -- Do the Checks (Assertions) PKG_LEHS.assert ( pi_assertion => pi_param1 IS NOT NULL ,pi_application => c_log_application ,pi_error_name => c_parameter_missing ,pi_error_arguments => PKG_LEHS.pt_error_arguments('pi_param1') ); PKG_LEHS.assert ( pi_assertion => pi_param2 IS NOT NULL ,pi_application => c_log_application ,pi_error_name => c_parameter_missing ,pi_error_arguments => PKG_LEHS.pt_error_arguments('pi_param2') ); END test_it;Ok, you might say "That's too much code! What's the advantage for the Developer?". Let' have a look on a short advantage List:
AMERICAN
).NLS_LANG
of the Client it would show the
Message with the correct Language.
PKG_LEHS.assert => pi_log
to FALSE
PKG_LEHS.pt_error_arguments
).Mandatory Parameter %1 is missing
)
The first Argument is a BOOLEAN
-Condition. If this this Condition fails the Assertion will raise the Exception the Developer has specified. In fact PKG_LEHS.assert
is only a Combination of a IF
-Statement with the Procedure PKG_LEHS.handle_exception
or PKG_LEHS.handle_exception_and_log
.
By Default a raised Exception will also be logged with the Log-Level defined for the Log-Application. If you want to supress this Behaviour you can set the Parameter pi_log
to FALSE
. Then only the Exception will be raised without any logging. Except you define a Log-Topic, then the failed Assertion will always be logged (because it makes no sense to give a Log-Topic without any logging).
SELECT INTO
and forget to handle the Exception NO_DATA_FOUND
). For this Oracle gives you a very powerful enhancement to catch all Exceptions:
DECLARE c_log_application CONSTANT PKG_LEHS.pst_application_name := 'LOG_APPLICATION'; v_dummy BINARY_INTEGER; BEGIN -- the following SELECT will cause the Exception NO_DATA_FOUND SELECT 1 INTO v_dummy FROM dual WHERE x = 'Y'; EXCEPTION WHEN OTHERS THEN -- Log the Exception PKG_LEHS.log_exception (c_log_application); RAISE; END;As you can see above LEHS has a simple Solution to log the raised Exception. Simply call
PKG_LEHS.log_exception
and the raised Exception will be logged to the Line where the Exception has been raised. In the Example above the Line-Number is 7. This works also for Exceptions that are raised within Procedures, Functions and Triggers.
Any user defined named Exception, which will also be caught by WHEN OTHERS THEN
, will not be logged because a Developer can decide at the time where the Exception will be raised if he/she also wants to log or not. Except you set the Parameter pi_log_app_exception
to TRUE
. This is useful for some Situations where you want to handle a user defined named Exception by PKG_LEHS.log_excepiton
(see Topic Exception Messages).
Note: You have to re-raise the caught Exception by yourself. Otherwise the Exception will not be propagated to the calling PL/SQL-Block. The general Recommendation is, that you end every Procedure/Function with this general Exception Handling Code:
CREATE OR REPLACE PROCEDURE prc_test IS c_log_application CONSTANT PKG_LEHS.pst_application_name := 'LOG_APPLICATION'; BEGIN -- do some stuff NULL; EXCEPTION WHEN OTHERS THEN PKG_LEHS.log_exception (c_log_application); RAISE; END prc_test;
When you put this Part of Code in nearby every Procedure/Function you automatically build your own Error-Stack with the Logging Routines.
LEHS_LOGS.log_id
, a Sequence for every Log-Entry). Ordering by this Log-ID shows you the Log-Entries in the correct Order.
But this Log-ID can't help if 2 Programs running at the same time to the same Application. Both Programs will fill the Log-Table but you have no chance to see, which Log-Entry has been created by which Program. For this Situation a Log-Transaction will help. Every Procedure creates a new Log-Transaction and the Log-Entries will be logged to this Log-Transaction ID.
DECLARE c_log_application PKG_LEHS.pst_application_name := 'LOG_APPLICATION'; -- Define a Variable to get the Log-Transaction ID v_log_transaction BINARY_INTEGER; BEGIN -- Start a new Log-Transaction v_log_transaction := PKG_LEHS.start_log_transaction (c_log_application); -- do some stuff -- close the Log-Transaction PKG_LEHS.end_log_transaction (c_log_application); EXCEPTION WHEN OTHERS THEN PKG_LEHS.log_exception (c_log_application); PKG_LEHS.end_log_transaction (c_log_application); RAISE; END;
A Log Transaction will be started by PKG_LEHS.start_log_transaction
. The returned Transaction ID will automatically be stored within the Log-Context (Memory where all the needed Variables are stored) and will be logged automatically. This Transaction ID stays at the Log-Context until another call of PKG_LEHS.start_log_transaction
has been done or the Log-Transaction has been closed by PKG_LEHS.end_log_transaction
. Also, if you call another internal Procedure this Transaction ID will be used.
PKG_LEHS.start_log_transaction
the Transaction to the returned ID is in State OPEN
until PKG_LEHS.end_log_transaction
has been called. When you call another Procedure which also starts a new Log-Transaction you must join to the old Log-Transaction after returning to the calling Procedure.
CREATE OR REPLACE PACKAGE BODY pkg_test IS pc_log_application CONSTANT PKG_LEHS.pst_application_name := 'LOG_APPLICATION'; PROCEDURE inner_proc IS v_log_transaction BINARY_INTEGER; BEGIN v_log_transaction := PKG_LEHS.start_log_transaction (pc_log_application); -- do some stuff -- at the end PKG_LEHS.end_log_transaction (pc_log_application); EXCEPTION WHEN OTHERS THEN PKG_LEHS.log_exception (pc_log_application); PKG_LEHS.end_log_transaction (pc_log_application); RAISE; END inner_proc; PROCEDURE public_proc IS v_log_transaction BINARY_INTEGER; BEGIN -- first start the Log Transaction v_log_transaction := PKG_LEHS.start_log_transaction (pc_log_application); -- now call the inner procedure BEGIN inner_proc; PKG_LEHS.join_log_transaction (pc_log_application, v_log_transaction); EXCEPTION WHEN OTHERS THEN PKG_LEHS.join_log_transaction (pc_log_application, v_log_transaction); RAISE; END; -- now end the Log Transaction PKG_LEHS.end_log_transaction; EXCEPTION WHEN OTHERS THEN PKG_LEHS.log_exception (pc_log_application); PKG_LEHS.end_log_transaction (pc_log_application); RAISE; END public_proc; END pkg_test;
Maybe a Developer will now say "What the Hell? Why should I join to a Transaction ID? Why can't I give the Transaction ID by a Parameter to the Log-Call?". The Answer: Simplicity and Performance. It is less complex to focus on the Development than handle complex logging. A Log Transaction is optional, you can use it but it is not obligatory. And: most of the Programs will run within one Log Transaction (also the Sub-Programs). So it is better to save the Transaction ID within the Log Context than giving it all the Time to the Subprograms. Imagine: You always have to give the Log-Transaction as by Parameters to your Sub-Programs. What a mess.
Keep it simple was one of the main focus during the Development of LEHS. LEHS should support the Developer and not embarrass him/her. By the Way: this Conception makes the real Application Code more readable (and maintainable) than giving the Transaction ID by Parameter (or even by setting a Package Variable or something else).
CREATE OR REPLACE PROCEDURE prc_test ( pi_param1 IN VARCHAR2 ,pi_param2 IN DATE DEFAULT NULL ,pi_param3 IN NUMBER DEFAULT 3.1415927 ) IS c_procname CONSTANT VARCHAR2(30) := 'PRC_TEST'; c_log_application CONSTANT PKG_LEHS.pst_application_name := 'LOG_APPLICATION'; v_log_transaction BINARY_INTEGER; BEGIN v_log_transaction := PKG_LEHS.start_log_transaction (c_log_application); -- set some Keywords to the Log-Transaction PKG_LEHS.add_transaction_key (c_log_application, v_log_transaction, 'PROCNAME', c_procname); PKG_LEHS.add_transaction_key (c_log_application, v_log_transaction, 'START_TIME', SYSDATE); PKG_LEHS.add_transaction_key (c_log_application, v_log_transaction, 'PI_PARAM1', pi_param1); PKG_LEHS.add_transaction_key (c_log_application, v_log_transaction, 'PI_PARAM2', pi_param2); PKG_LEHS.add_transaction_key (c_log_application, v_log_transaction, 'PI_PARAM3', pi_param3); -- do the Rest of the Stuff PKG_LEHS.end_log_transaction (c_log_application); EXCEPTION WHEN OTHERS THEN PKG_LEHS.log_exception (c_log_application); PKG_LEHS.end_log_transaction (c_log_application); RAISE; END prc_test;
You can register Keywords to your Log-Transaction. This feature is very powerful because it gives you the Possibility to search for the throughput of your Application. If your Application has been started by calling execute PRC_TEST ('first', TO_DATE('20.09.2009','DD.MM.RRRR'), 5.25);
you can now use the following:
SELECT transaction_id FROM lehs_transactions TRX WHERE TRX.application = 'LOG_APPLICATION' AND TRX.key = 'PROCNAME' AND TRX.value = 'PRC_TEST' AND EXISTS (SELECT 1 FROM lehs_transactions SUBTRX WHERE SUBTRX.transaction_id = TRX.transaction_id AND SUBTRX.key = 'PI_PARAM1' AND SUBTRX.value = 'first' ) AND EXISTS (SELECT 1 FROM lehs_transactions SUBTRX WHERE SUBTRX.transaction_id = TRX.transaction_id AND SUBTRX.key = 'PI_PARAM2' AND SUBTRX.datatype = 'DATE' AND TO_DATE(SUBTRX.value) = TO_DATE('20.09.2009','DD.MM.RRRR') ) AND EXISTS (SELECT 1 FROM lehs_transactions SUBTRX WHERE SUBTRX.transaction_id = TRX.transaction_id AND SUBTRX.key = 'PI_PARAM3' AND SUBTRX.datatype = 'NUMERIC' AND TO_NUMBER(SUBTRX.value) >= 3 );
The Example above shows you all Log Transactions to the Log-Application LOG_APPLICATION
with the following Characteristics:
PROCNAME
with Value PRC_TEST
PI_PARAM1
with the Value first
PI_PARAM2
with the Value 20.9.2009
(given as a Date)PI_PARAM3
with a Value greater or equal to 3
(interpreted as a Number)A Transaction Key can have one of the following Datatypes:
VARCHAR2/CHAR
DATE
NUMBER
TIMESTAMP (up to 9 factional Digits)
LEHS_TRANSACTIONS
all Transaction Keys with either the Datatype DATE
, NUMBER
or TIMESTAMP
will be transformed to the Character Interpretation by simply converting with TO_CHAR
without any formatting Pattern. Therefor it will use the Default of the Session so that you can reconvert this Value simply by calling either TO_DATE
, TO_NUMBER
or TO_TIMESTAMP
(also every Function without a formatting Description so you also use the Default of the Session).
To avoid falling into a Conversion Error you can also query the Parameter Datatype before converting the Value (i.E. AND SUBTRX.datatype = 'NUMERIC'
).
Exception Messages, which will be administered by LEHS can have the following Features:
NLS_LANG
)CLOB
)DECLARE c_application CONSTANT PKG_LEHS.pst_application_name := 'LOG_APPLICATION'; BEGIN -- the following Error Definition would be delivered by a Script -- so this is called the Admin driven Installation of an Exception Message PKG_LEHS.add_message ( pi_application => c_application ,pi_error_name => 'UNANTICIPATED ERROR' ,pi_error_code => -20000 ,pi_message => 'Unanticipated Error %1 has been raised. Reason: %2' ,pi_show_error_stack => TRUE ,pi_nls_language => 'AMERICAN' ); PKG_LEHS.add_message ( pi_application => c_application ,pi_error_name => 'UNANTICIPATED ERROR' ,pi_error_code => -20000 ,pi_message => 'Unerwarteter Fehler %1 wurde ausgelöst. Begründung: %2' ,pi_show_error_stack => TRUE ,pi_nls_language => 'GERMAN' ); END;
CREATE OR REPLACE PACKAGE pkg_constant IS pc_log_application CONSTANT PKG_LEHS.pst_application_name := 'LOG_APPLICATION'; pc_unanticipated_error CONSTANT PKG_LEHS.pst_error_name := 'UNANTICIPATED ERROR'; pc_missing_mandatory_parameter CONSTANT PKG_LEHS.pst_error_name := 'MANDATORY PARAMETER MISSING'; pc_errgrp_entity1 CONSTANT PKG_LEHS.pst_error_name := 'ERROR GROUP ENTITY 1' pc_errgrp_entity2 CONSTANT PKG_LEHS.pst_error_name := 'ERROR GROUP ENTITY 2'; pe_errgrp EXCEPTION; PRAGMA EXCEPTION_INIT (pe_errgrp, -20002); END pkg_constant; CREATE OR REPLACE PACKGE BODY pkg_constant IS BEGIN -- Initialization of the Exception Messages -- the following Exception Messages will be defined by the Application itself -- this is called Developer driven Installation. PKG_LEHS.add_message ( pi_application => pc_log_application ,pi_error_name => pc_unanticipated_error ,pi_error_code => -20000 ,pi_message => 'Unanticipated Error %1 has been raised. Reason: %2' ,pi_show_error_stack => TRUE ,pi_nls_language => 'AMERICAN' ); PKG_LEHS.add_message ( pi_application => pc_log_application ,pi_error_name => pc_unanticipated_error ,pi_error_code => -20000 ,pi_message => 'Unerwarteter Fehler %1 wurde ausgelöst. Begründung: %2' ,pi_show_error_stack => TRUE ,pi_nls_language => 'GERMAN' ); PKG_LEHS.add_message ( pi_application => pc_log_application ,pi_error_name => pc_missing_mandatory_parameter ,pi_error_code => -20001 ,pi_message => 'Mandatory Parameter %1 is missing or NULL' ,pi_show_error_stack => FALSE ,pi_nls_language => 'AMERICAN' ); PKG_LEHS.add_message ( pi_application => pc_log_application ,pi_error_name => pc_missing_mandatory_parameter ,pi_error_code => -20001 ,pi_message => 'Zwingender Parameter %1 wurde nicht angegeben oder ist NULL' ,pi_show_error_stack => FALSE ,pi_nls_language => 'GERMAN' ); PKG_LEHS.add_message ( pi_application => pc_log_application ,pi_error_name => pc_errgrp_entity1 ,pi_error_code => -20002 ,pi_message => 'Error 1 of an Error Group: %1 (only English)' ,pi_show_error_stack => FALSE ,pi_nls_language => 'AMERICAN' ); PKG_LEHS.add_message ( pi_application => pc_log_application ,pi_error_name => pc_errgrp_entity2 ,pi_error_code => -20002 ,pi_message => 'Error 2 of an Error Group: %1 (only English)' ,pi_show_error_stack => FALSE ,pi_nls_language => 'AMERICAN' ); END pkg_constant;
CREATE OR REPLACE PROCEDURE use_lehs ( pi_entity1 IN BOOLEAN ) IS BEGIN IF NVL(pi_entity1, TRUE) THEN PKG_LEHS.handle_exception ( pi_application => PKG_CONSTANT.pc_log_application ,pi_error_name => PKG_CONSTANT.pc_errgrp_entity1 ,pi_error_arguments => PKG_LEHS.pt_error_arguments ('Argument') ); ELSE PKG_LEHS.handle_exception ( pi_application => PKG_CONSTANT.pc_log_application ,pi_error_name => PKG_CONSTANT.pc_errgrp_entity2 ,pi_error_arguments => PKG_LEHS.pt_error_arguments ('Argument') END IF; END use_lehs; CREATE OR REPLACE PROCEDURE use_lehs2 ( pi_entity1 IN BOOLEAN ) IS BEGIN use_lehs (pi_entity1); EXCEPTION WHEN PKG_CONSTANT.pe_errgrp THEN IF PKG_LEHS.is_named_exception (PKG_CONSTANT.pc_log_application, PKG_CONSTANT.pc_errgrp_entity1, SQLCODE) THEN -- When pc_errgrp_entity1 has been raised then only warn with a Log-Message but don't re-raise this Exception PKG_LEHS.warn (PKG_CONSTANT.pc_log_application, SQLERRM(SQLCODE)); ELSE PKG_LEHS.log_exception (PKG_CONSTANT.pc_log_application, TRUE); RAISE; END IF; WHEN OTHERS THEN PKG_LEHS.log_exception (PKG_CONSTANT.pc_log_application); RAISE; END use_lehs2;
Done. Much code, isn't it? But not much ado nothing. This is a Recommendation by myself how to make an Application code:
The best Way to describe Log-Topics is to describe, how a normal Book works. If you plan to write a Book you start with the first Chapter. This Chapter can have several Subchapters (and also these can Subchapters). Then you write the next Chapter. Also with several Subchapters. Finally you might have a Structure like these:
1. Install LEHS 1.1 Download LEHS 1.2 Create a User for LEHS 1.3 Install LEHS 1.4 Do some public Stuff 2. Configure LEHS 2.1 Who am I? 2.2 Concepts 2.3 Log Application 2.4 Log Appender 2.4.1 Appender Parameter 2.4.2 Log Format 2.5 Log Levels 2.6 Programmable Log Levels 2.7 Log Privileges 2.8 Get the Metadata InformationDoes this looks similar for you? Yes, this is called a Hierarchy. With Topics you can sort your Log-Entries by a Hierarchy. But this Chapter-List is just more than a normal Hierarchy. It's a Hierarchy with a strong self-managed Order within the same Hierarchy-Level. So we have a linked List within a Hierarchy-Level.
![]() |
Defining a Topic is very easy. A Topic has always identified by the Name, which is case sensitive. A Top-Topic is a topic with no Parent-Topic. The Top of the Tree!
Within the same Hierarchy you can define the Position of the Topic either by the Position itself or by a Predecessor Topic.
PKG_LEHS.add_topic ( pi_application => 'LOG_APPLICATION' ,pi_topic => 'Top-Topic 3' ,pi_topic_parent => NULL ,pi_position => 3 )
PKG_LEHS.add_topic ( pi_application => 'LOG_APPLICATION' ,pi_topic => 'Top-Topic 3' ,pi_topic_parent => NULL ,pi_topic_predecessor => 'Top-Topic 2' )
"And why is this so powerful?" Let' have a look on another Example:
CREATE OR REPLACE PROCEDURE prc_test IS c_log_application CONSTANT PKG_LEHS.pst_application_name := 'LOG_APPLICATION'; -- Define the Log-Topics for the technical Log c_tl CONSTANT PKG_LEHS.pst_log_topic := 'technical Log'; c_tl_main CONSTANT PKG_LEHS.pst_log_topic := 'Main Routine'; c_tl_sub1 CONSTANT PKG_LEHS.pst_log_topic := 'Sub Routine 1'; c_tl_sub2 CONSTANT PKG_LEHS.pst_log_topic := 'Sub Routine 2'; -- Define the Log-Topics for the Business Log c_bl CONSTANT PKG_LEHS.pst_log_topic := 'Business Log'; PROCEDURE sub1 IS BEGIN PKG_LEHS.debug (c_log_application, c_tl_sub1, 'Entry from Subroutine1'); EXCEPTION WHEN OTHERS THEN PKG_LEHS.log_exception (c_log_application, c_tl_sub1); END sub1; PROCEDURE sub2 IS BEGIN PKG_LEHS.debug (c_log_application, c_tl_sub1, 'Entry from Subroutine2'); EXCEPTION WHEN OTHERS THEN PKG_LEHS.log_exception (c_log_application, c_tl_sub2); END sub2; BEGIN -- define the Log Hierarchy PKG_LEHS.add_topic (c_log_application, c_bl, NULL, TO_CHAR(NULL)); PKG_LEHS.add_topic (c_log_application, c_tl, NULL, c_bl); PKG_LEHS.add_topic (c_log_application, c_tl_main, c_tl, TO_CHAR(NULL)); PKG_LEHS.add_topic (c_log_application, c_tl_sub1, c_tl_main, TO_CHAR(NULL)); PKG_LEHS.add_topic (c_log_application, c_tl_sub2, c_tl_main, c_tl_sub1); PKG_LEHS.info (c_log_application, c_tl_main, 'Start the Application'); PKG_LEHS.info (c_log_application, c_bl, 'Calculation has been started'); PKG_LEHS.debug (c_log_application, c_tl_main, 'Call sub1'); sub1; PKG_LEHS.debug (c_log_application, c_tl_main, 'Call sub2'); sub2; PKG_LEHS.info (c_log_application, c_tl_main, 'End the Application'); PKG_LEHS.info (c_log_application, c_bl, 'Calculation has been ended successfully'); EXCEPTION WHEN OTHERS THEN PKG_LEHS.log_exception (c_log_application, c_tl_main); PKG_LEHS.error (c_log_application, c_bl, 'An Error has been occured. See technical Log for further Information'); RAISE; END prc_test;You defined two main Topics: a technical Log (with some Sub-Topics) and a Business Log. The Business Log is for your Clients. The technical Log is for the Developers when something has gone wrong. Your Clients would only know when the Application has been started and if the Application has been ended successfully or with an Error. But your Clients would not know which Error has been occured. So your Clients will query the Logs with the following Statement:
SELECT * FROM lehs_logs WHERE log_topic = 'Business Log' ORDER BY log_id DESCWhen something has gone wrong it's important that the Analyst (or the Developer) has some good Structure within the Logs to find the Error quickly. So you will go through your technical Log (with all the Sub-Topics) to get the technical Log-Information:
SELECT LOG.* FROM lehs_topics TOP RIGHT OUTER JOIN lehs_logs LOG ON LOG.application = TOP.application AND LOG.log_topic = TOP.topic_name CONNECT BY PRIOR TOP.topic_name = TOP.parent_topic_name AND TOP.application = 'LOG_APPLICATION' START WITH TOP.topic_name = 'technical Log' AND TOP.application = 'LOG_APPLICATION' ORDER BY LOG.log_id DESCThis is the Reason why the Log Topics are really powerful. You have a Solution for both, technical Log and Business Log with one Tool and the Code is maintainable. When something has gone wrong you can get the needed Log with one
SELECT
-Statement! And this can be packed into a View. I think that this has really big Power!
Note: If you ask me for the Reason of the horizontal Order (predecessing) then I will tell you: It's just for fun. Maybe one Day there's neccessary and then you can use it. Until then: Have fun with it. Because LEHS is open Source you can get some Parts as a Design Pattern. I hope so ;-)
SELECT * FROM lehs_logs WHERE application = '<Log Application>' ORDER BY log_id DESC;
SELECT * FROM lehs_transactions WHERE application = '<Log Application>';
SELECT * FROM lehs_topics WHERE application = '<Log Application>';The defined Log-Levels will be available by the View
SELECT * FROM lehs_levels WHERE application = '<Log Application>';If you want to show the whole Topic-Tree (in the correct Order of the Sub-Topics) then you can get this Query:
SELECT * FROM (SELECT level AS top_level ,application ,topic_name ,parent_topic_name ,topic_name_predecessor FROM lehs_topics CONNECT BY PRIOR topic_name = topic_name_predecessor AND application = 'LEHS' START WITH topic_name_predecessor IS NULL AND application = 'LEHS') CONNECT BY PRIOR topic_name = parent_topic_name START WITH parent_topic_name IS NULL ORDER SIBLINGS BY top_level ,topic_name
PKG_LEHS
gets the Position of the Caller from the Stack. The next Caller after PKG_LEHS
will be used to write the Log to the Appender. For a Wrapper this is not the truth. So, if you use your own Wrapper for LEHS then you have to call the Methods of the Package PKG_WRAPPED_LEHS
. All Methods of this Package will just ignore the next Level after PKG_LEHS
and get the Caller of the Wrapper. So your Wrapper-Class will just be ignored and the real Caller will be get.
A simple Example:
CREATE OR REPLACE PACKAGE LEHS_EXAMPLE.pkg_my_lehs_wrapper IS pc_own_log_level VARCHAR2(30) := 'MY OWN LOG LEVEL'; PROCEDURE log ( pi_log_level IN VARCHAR2 ,pi_message IN CLOB ); PROCEDURE start_transaction ( pi_keyword1 IN VARCHAR2 ,pi_keyword2 IN DATE ,pi_keyword3 IN NUMBER ); PROCEDURE end_transaction; PROCEDURE fatal (pi_message IN CLOB); PROCEDURE error (pi_message IN CLOB); PROCEDURE warn (pi_message IN CLOB); PROCEDURE info (pi_message IN CLOB); PROCEDURE debug (pi_message IN CLOB); PROCEDURE trace (pi_message IN CLOB); PROCEDURE mandatory_parameter_missing (pi_parameter_name IN VARCHAR2); PROCEDURE another_user_defined_exception ( pi_text_parameter IN VARCHAR2 ,pi_numeric_parameter IN NUMBER ,pi_date_parameter IN DATE ); END LEHS_EXAMPLE.pkg_my_lehs_wrapper; / CREATE OR REPLACE PACKAGE BODY LEHS_EXAMPLE.pkg_my_lehs_wrapper IS pc_app CONSTANT PKG_LEHS.pst_application_name := 'MY LOG APP'; pc_mandatory_parameter_missing CONSTANT PKG_LEHS.pst_error_name := 'MANDATORY PARAMETER MISSING'; pc_another_user_def_exception CONSTANT PKG_LEHS.pst_error_name := 'ANOTHER USER DEFINED EXCEPTION'; pv_transaction PKG_LEHS.pst_log_transaction := NULL; PROCEDURE log ( pi_log_level IN VARCHAR2 ,pi_message IN CLOB ) IS BEGIN PKG_WRAPPED_LEHS.log ( pi_application => pc_app ,pi_log_level => pi_log_level ,pi_message => pi_message ); END log; PROCEDURE start_transaction ( pi_keyword1 IN VARCHAR2 ,pi_keyword2 IN DATE ,pi_keyword3 IN NUMBER ) IS c_keyword1 CONSTANT VARCHAR2(30) := 'KEYWORD1'; c_keyword2 CONSTANT VARCHAR2(30) := 'KEYWORD2'; c_keyword3 CONSTANT VARCHAR2(30) := 'KEYWORD3'; BEGIN IF pv_transaction IS NOT NULL THEN RETURN; END IF; pv_transaction := PKG_LEHS.start_transaction (pc_app); PKG_LEHS.add_transaction_keyword (pc_app, pv_transaction, c_keyword1, pi_keyword1); PKG_LEHS.add_transaction_keyword (pc_app, pv_transaction, c_keyword2, pi_keyword2); PKG_LEHS.add_transaction_keyword (pc_app, pv_transaction, c_keyword3, pi_keyword3); END start_transaction; PROCEDURE end_transaction IS BEGIN IF pv_transaction IS NULL THEN RETURN; END IF; PKG_LEHS.end_log_transaction (pc_app); END end_transaction; PROCEDURE fatal (pi_message IN CLOB) IS BEGIN PKG_WRAPPED_LEHS.fatal (pc_app, pi_message); END fatal; PROCEDURE error (pi_message IN CLOB) IS BEGIN PKG_WRAPPED_LEHS.error (pc_app, pi_message); END error; PROCEDURE warn (pi_message IN CLOB) IS BEGIN PKG_WRAPPED_LEHS.warn (pc_app, pi_message); END warn; PROCEDURE info (pi_message IN CLOB) IS BEGIN PKG_WRAPPED_LEHS.info (pc_app, pi_message); END info; PROCEDURE debug (pi_message IN CLOB) IS BEGIN PKG_WRAPPED_LEHS.debug (pc_app, pi_message); END debug; PROCEDURE trace (pi_message IN CLOB) IS BEGIN PKG_WRAPPED_LEHS.trace (pc_app, pi_message); END trace; PROCEDURE mandatory_parameter_missing (pi_parameter_name IN VARCHAR2) IS BEGIN PKG_WRAPPED_LEHS.handle_exception_and_log ( pi_application => pc_app ,pi_error_name => pc_mandatory_parameter_missing ,pi_error_arguments => PKG_LEHS.pt_error_arguments (UPPER(pi_parameter_name)) ); END mandatory_parameter_missing; PROCEDURE another_user_defined_exception ( pi_text_parameter IN VARCHAR2 ,pi_numeric_parameter IN NUMBER ,pi_date_parameter IN DATE ) IS BEGIN PKG_WRAPPED_LEHS.handle_exception_and_log ( pi_application => pc_app ,pi_error_name => pc_another_user_def_exception ,pi_error_arguments => PKG_LEHS.pt_error_arguments ( pi_text_parameter ,TO_CHAR(pi_numeric_parameter) ,TO_CHAR(pi_date_parameter) ); ); END another_user_defined_exception; BEGIN PKG_LEHS.add_message ( pi_application => pc_app ,pi_error_name => pc_mandatory_parameter_missing ,pi_error_code => -20000 ,pi_message => 'Mandatory Parameter %1 is missing or NULL' ); PKG_LEHS.add_message ( pi_application => pc_app ,pi_error_name => pc_another_user_def_exception ,pi_error_code => -20999 ,pi_message => 'Another Exception with Text Parameter %1, numeric Parameter %2 and a Date Parameter %3' ); END LEHS_EXAMPLE.pkg_my_lehs_wrapper; /As you can see in the Example (and of course in the API) not all Methods of the Package
PKG_LEHS
would be overwritten in the Package PKG_WRAPPED_LEHS
.PKG_LEHS
have to be ignored so that the Caller of this Wrapper-Package would be recognized as the real Caller.One of the best Reasons by using a Wrapper-Package is, that the real Log-Application can be hidden. So the Developers can use your Package but they don't know anything about the Log-Engine in the Background (that could either be LEHS or Log4PLSQL or another else).