|

Reprinted with Permission by Quest
Software Nov. 2003
|
CREATE OR REPLACE PACKAGE demo_pkg IS -- -- Purpose: Demonstration package for
call stack and dump logging -- -- Methods: PROC1() -- PROC2() --
instantiate_error_context() -- -- MODIFICATION HISTORY -- Person Date Comments
-- --------- -------- ------------------------------------------- -- DKK
01/02/01 creation PROCEDURE proc1; PROCEDURE proc2; PROCEDURE
instantiate_error_context; END; / CREATE OR REPLACE PACKAGE error_pkg IS -- --
Purpose: Package to handle errors raised during execution of other DB packages
-- -- Methods: set_module_name() -- remove_module_name() -- -- MODIFICATION
HISTORY -- Person Date Comments -- --------- --------
------------------------------------------- -- DKK 02/01/01 creation -- Add the
module name to the call stack, so that we know where -- any errors occurred.
PROCEDURE set_module_name ( module_name_in IN VARCHAR2 ); -- Add the module name
to the call stack, so that we know where -- any errors occurred. PROCEDURE
set_err_msg ( msg_id_in IN VARCHAR2 ); -- As modules finish correctly the module
name is dropped from -- the stack. When the last module completes correctly the
stack is -- discarded. PROCEDURE remove_module_name; -- Called when we want to
raise a specific error from the message table PROCEDURE raise_error (
comments_in IN VARCHAR2 DEFAULT NULL ); -- Called by obj_% packages to add
context to error logs PROCEDURE add_context ( context_domain_in VARCHAR2 DEFAULT
NULL, context_item_in VARCHAR2, context_value_in VARCHAR2 ); END; -- error_pkg /
CREATE OR REPLACE PACKAGE BODY demo_pkg IS -- -- Purpose: Demonstration package
for call stack and dump logging -- -- Methods: PROC1() -- PROC2() --
instantiate_error_context() -- -- MODIFICATION HISTORY -- Person Date Comments
-- --------- -------- ------------------------------------------- -- DKK
01/02/01 creation -- The following variables represent the attributes of the
client object -- They are only modified and examined by the GAS routines of the
package. -- The appropriate variables must be loaded before other DB packages
are called -- that operate on the client object. TYPE application_rectype IS
RECORD ( field_1 VARCHAR2 (12) := NULL, field_2 INTEGER := NULL, field_3 INTEGER
:= NULL); application application_rectype;
-------------------------------------------------------------------- -- The
following are the GAS routines for all of the attributes associate with -- the
application object
-------------------------------------------------------------------- PROCEDURE
proc1 IS BEGIN --announce entry into this module error_pkg.set_module_name
('demo_pkg.proc1'); -- do your processing here. application.field_1 := 'test
string'; proc2; error_pkg.remove_module_name; EXCEPTION WHEN OTHERS THEN
error_pkg.set_err_msg ('DAT023'); error_pkg.raise_error ('Failed Operation');
END; PROCEDURE proc2 IS BEGIN --announce entry into this module
error_pkg.set_module_name ('demo_pkg.proc2'); -- do your processing here.
application.field_2 := -37; -- force an error to occur here so we can see the
resulting dump RAISE NO_DATA_FOUND; error_pkg.remove_module_name; EXCEPTION WHEN
OTHERS THEN error_pkg.set_err_msg ('DAT027'); error_pkg.raise_error ('Failed
Operation'); END;
-------------------------------------------------------------------- -- Add dump
information to the trace record PROCEDURE instantiate_error_context IS BEGIN
error_pkg.add_context ('DEMO_PKG', 'Field #1', application.field_1);
error_pkg.add_context ('DEMO_PKG', 'Field #2', application.field_2);
error_pkg.add_context ('DEMO_PKG', 'Field #3', application.field_3); END; END; /
CREATE OR REPLACE PACKAGE BODY error_pkg IS -- -- Purpose: Package to handle
errors raised during execution of other DB packages -- -- -- MODIFICATION
HISTORY -- Person Date Comments -- --------- --------
------------------------------------------- -- DKK 01/02/01 creation
------------------------------------------------------------ -- Private type
definitions ------------------------------------------------------------ --
SUBTYPE message_type is messages.msgtext%TYPE; TYPE stack IS TABLE OF VARCHAR2
(100) INDEX BY BINARY_INTEGER; TYPE error_context_rectype IS RECORD (
context_domain VARCHAR2 (30) := NULL, context_item VARCHAR2 (25) := NULL,
context_value VARCHAR2 (250) := NULL); TYPE error_context_type IS TABLE OF
error_context_rectype INDEX BY BINARY_INTEGER;
------------------------------------------------------------ -- Private Package
variables ------------------------------------------------------------
error_context error_context_type; empty_error_context error_context_type; -- to
clear error_context max_context_id PLS_INTEGER := 0; -- index for error_context
call_stack stack; -- Stack of calling modules empty_stack stack; -- to clear
call_stack top_of_stack PLS_INTEGER := 0; -- call_stack index stack_is_empty
BOOLEAN := TRUE; call_stack_text VARCHAR2 (2000) := NULL; -- text representation
of call_stack, created on first call to raise_error err_msg_id VARCHAR2(100) :=
NULL; err_msg_type VARCHAR2 (3) := NULL; -- DAT or APP, passed in by set_err_msg
err_comments VARCHAR2 (2000) := NULL; -- passed in by raise_error
----------------------------------------------------------- -- Private Interface
-- -----------------------------------------------------------
-------------------------------------------------------------------------- --
Clear the stack and release the memory associated with it. -- This is done when
either -- a) the whole sequence of calls completed successfully -- b) an
exception has been raised, and we have already dumped out the call stack
PROCEDURE clear_stack IS BEGIN call_stack := empty_stack; top_of_stack := 0;
stack_is_empty := TRUE; END;
-------------------------------------------------------------- -- Return TRUE if
one of the elements in the call stack was from the package -- identified by the
value passed in. Since the call stack elements are formatted -- like
<PACKAGE_NAME>.<MODULE> we will then look for any element containing --
<ELEMENT_IN>. -- This only works because of standards that were set early on in
development (e.g. -- absolutely NO code outside of packages etc) FUNCTION
package_exists ( element_in IN VARCHAR2 ) RETURN BOOLEAN IS retval BOOLEAN :=
FALSE; BEGIN FOR cur_index IN NVL (call_stack.FIRST, 0) .. NVL (call_stack.LAST,
0) LOOP BEGIN IF INSTR (UPPER (call_stack (cur_index)), UPPER (element_in || '.'
) ) > 0 THEN retval := TRUE; END IF; EXCEPTION WHEN NO_DATA_FOUND THEN NULL;
END; END LOOP; RETURN retval; END;
-------------------------------------------------------------- -- Clears error
context (error_context) PROCEDURE clear_context IS BEGIN error_context :=
empty_error_context; max_context_id := 0; END;
-------------------------------------------------------------- -- Called by
raise_error to create a log file describing the error PROCEDURE log_error IS
ldomain VARCHAR2 (25); BEGIN clear_context; add_context ('ERROR', 'Error Log
Time', TO_CHAR (SYSDATE, 'HH24:MI:SS')); add_context ('ERROR', 'Call Stack',
call_stack_text); add_context ('ERROR', 'Comments', err_comments); add_context
('ERROR', 'CFRS Error No', err_msg_id); add_context ('ERROR', 'Oracle Error',
SQLERRM); -- call the same method on each package that I am interested in --
This was originally written using Oracle 7.3.4, and I'm sure that with -- NDS it
could be enhanced to dynamically build a PLSQL block for each of these... -- The
following if - end if section should be repeated for each package of interest.
-- We only had about three that were critical in troubleshooting, so it wasn't
worth the -- effort to make it any more elaborate at the time. IF package_exists
('DEMO_PKG') THEN demo_pkg.instantiate_error_context; END IF; FOR iloop IN 0 ..
max_context_id - 1 LOOP IF ldomain != error_context (iloop).context_domain THEN
pl (RPAD ('----------' || error_context (iloop).context_domain, 40, '-' ) ); END
IF; pl (RPAD (error_context (iloop).context_item || ': ', 26) || error_context
(iloop).context_value ); ldomain := error_context (iloop).context_domain; END
LOOP; EXCEPTION WHEN OTHERS THEN pl ('Problem in error_pkg.log_error: ' ||
SQLERRM); END; ----------------------------------------------------------- --
Public Interface -- -----------------------------------------------------------
-------------------------------------------------------------- -- Called by
obj_% packages to add context to error logs PROCEDURE add_context (
context_domain_in VARCHAR2 DEFAULT NULL, context_item_in VARCHAR2,
context_value_in VARCHAR2 ) IS BEGIN error_context
(max_context_id).context_domain := SUBSTR (context_domain_in, 1, 30);
error_context (max_context_id).context_item := SUBSTR (context_item_in, 1, 25);
error_context (max_context_id).context_value := SUBSTR (NVL (context_value_in,
'NULL'), 1, 250); max_context_id := max_context_id + 1; END;
-------------------------------------------------------------- -- Set the active
error message for retrieval by the calling procedure PROCEDURE set_err_msg (
msg_id_in IN VARCHAR2 ) IS BEGIN -- validate the format of the message number IF
UPPER (SUBSTR (msg_id_in, 1, 3)) NOT IN ('DAT', 'APP') OR TO_NUMBER (SUBSTR
(msg_id_in, 4, 3)) NOT BETWEEN 0 AND 499 THEN -- invalid message type
raise_application_error (-20000, 'Invalid message number'); END IF; -- only set
the message id if it isn't already defined IF err_msg_id IS NULL THEN
err_msg_type := UPPER (SUBSTR (msg_id_in, 1, 3)); err_msg_id := msg_id_in; END
IF; END; -------------------------------------------------------------- -- Add
the module name to the call stack, so that we know where -- any errors occurred.
PROCEDURE set_module_name ( module_name_in IN VARCHAR2 ) IS BEGIN pl ('Adding '
|| module_name_in || ' to stack'); IF stack_is_empty THEN call_stack (1) :=
SUBSTR (module_name_in, 1, 100); top_of_stack := 1; stack_is_empty := FALSE;
ELSE top_of_stack := top_of_stack + 1; call_stack (top_of_stack) := SUBSTR
(module_name_in, 1, 100); END IF; -- clear out the msg nbr from any previous
unsuccessful run err_msg_id := NULL; err_comments := NULL; call_stack_text :=
NULL; EXCEPTION -- something really bad must happen to get here, probably some
PL/SQL table error WHEN OTHERS THEN pl ('********************* STACK FAULT
************************'); NULL; END;
---------------------------------------------------------------- -- As modules
finish correctly the module name is dropped from -- the list. When the last
module completes correctly the array is -- discarded. PROCEDURE
remove_module_name IS empty_stack EXCEPTION; BEGIN -- check if the user is
trying to pop off an element that doesn't exist IF stack_is_empty THEN RAISE
empty_stack; END IF; pl ('removing ' || call_stack (top_of_stack) || ' from
stack....'); -- clear the contents of the stack element call_stack
(top_of_stack) := NULL; -- move the stack pointer down to the next element
top_of_stack := top_of_stack - 1; -- if there are no more elements in the stack,
set our boolean variable stack_is_empty := (top_of_stack = 0); -- if the stack
doesn't contain any elements, release the memory associated with it. IF
stack_is_empty THEN clear_stack; END IF; EXCEPTION WHEN empty_stack THEN pl
('********************* STACK FAULT ************************'); NULL; WHEN
OTHERS THEN set_err_msg ('DAT005'); raise_error; END;
-------------------------------------------------------------- -- Return a text
representation of the call stack FUNCTION get_call_stack RETURN VARCHAR2 IS
l_call_stack_text VARCHAR2 (2000); BEGIN IF stack_is_empty THEN
l_call_stack_text := l_call_stack_text || '<EMPTY>'; ELSE FOR stack_index IN 1
.. top_of_stack LOOP IF stack_index = 1 THEN l_call_stack_text :=
l_call_stack_text || call_stack (stack_index); ELSE l_call_stack_text :=
l_call_stack_text || ' --> ' || call_stack (stack_index ); END IF; END LOOP;
END IF; RETURN (l_call_stack_text); EXCEPTION WHEN OTHERS THEN set_err_msg
('DAT073'); raise_error; END;
-------------------------------------------------------------- -- Called when we
determine that the required parameters aren't available -- The module names in
the error stack are displayed with the module which has -- the error indicated
PROCEDURE raise_error ( comments_in IN VARCHAR2 DEFAULT NULL ) IS msg_nbr
INTEGER; BEGIN msg_nbr := TO_NUMBER (SUBSTR (err_msg_id, 4, 3)); IF
call_stack_text IS NULL THEN call_stack_text := SUBSTR (get_call_stack, 1,
2000); err_comments := comments_in; log_error; END IF; clear_stack; IF
err_msg_type = 'DAT' THEN raise_application_error ( (-20000 - msg_nbr),
call_stack_text || ' failed to Complete!!!' || err_comments ); ELSIF
err_msg_type = 'APP' THEN raise_application_error ( (-20500 - msg_nbr),
call_stack_text || ' failed to Complete!!!' || err_comments ); END IF; END; END;
/ -- End of DDL Script for Package Body BOOK.ERROR_PKG -- End of DDL Script for
Package Body BOOK.error_pkg