CREATE OR REPLACE PACKAGE BODY xxbbr AS

advertisement

CREATE OR REPLACE PACKAGE BODY xxbbr AS

/******************************************************************************************************

* *

* Name : xxbbr.plb *

* Version : 1.1 *

* *

* Module Description *

* ================== *

* This package provides calls to standarise code logging. *

* *

* Incorporated in this, is an approach for handling unexpected errors in production systems by *

* acting like an airline Black Box Recorder where the complete detail is stored in a "black box" in *

* case an unexpected event occurs. *

* *

* Essentially, the Black Box Recorder is a respository for low level debug and log messages which *

* can then be printed out in the event of an unexpected error. Most of the code is here just to *

* force a consistant way to interact with the Black Box Recorder and to minimise the amount of code *

* which needs to be added to the actual program code (i.e. the code being monitored). *

* *

* The key enhancement is to have an output mechanism which provides all of the log detail in the *

* circumstances where the process ends in error. Usually this would require the process to be rerun *

* with the debug level changed but in a production environment this often isn't possible. Therefore,*

* this logging approach stores everything temporarily in a plsql table so that if a exception is *

* raised by the calling program, then a call can be placed in the exception handler to produce all *

* the detail if required. *

* *

* To get the optimum benefit from the Black Box Recorder feature, it is necessary to record every *

* parameter passed into each function / procedure and every return value. It is also suggested not *

* to comment out low level debug messages which are often created during the development cycle. *

* Instead, these messages should be assigned a high log_level value so that the messages only *

* appear in the log file when the reporting level is set to high or when an expected event occurs. *

* *

* The log_level parameter is used to record the log level of a particular message record which in *

* turn determines whether that message will be displayed in the log file depending on the level of *

* detail requested by the user querying the log file. If a log_level hasn't been set for a *

* particular message record, then the log_level is inherited from the parent. *

* *

* The hierarchy_num parameter is used to map out the code flow (e.g. one function calls another *

* function which calls another function). *

* *

* *

* *

* Change History *

* ============== *

* *

* *

* *

* Name Date Description *

* -------------------- --------- ----------------------------------------------------------------- *

* Fergal Grist 04-MAR-08 Module Created. *

* Fergal Grist 02-JUL-08 Added the current_log_level function to avoid ORA-01403 errors *

* under certain circumstances (e.g. when the users of this package *

* miss out an end_point call which can result in the code hierarchy *

* getting out of sequence). *

* Fergal Grist 04-JUL-08 Renamed the code_level variable to hierarchy_num because it was *

* easy to confuse it with log_level. Renamed the *

* reference_point_rectype/tab to hierarchy_rectype/tab. *

* Fergal Grist 06-AUG-08 Put a SUBSTR around the l_indent_str variable to prevent a *

* 'character string buffer too small' error. *

* Fergal Grist 15-AUG-08 Added the end_point_exception function. *

* Fergal Grist 11-JAN-09 Changed the names of some of the functions, added in functionality*

* to only store g_history_size records. *

* Fergal Grist 11-JAN-09 Added the reset_log procedure. *

* Fergal Grist 11-JAN-09 Changed the name of the 'start_point' procedure to 'begin_point' *

* (which is a bit more intuitive). *

* Fergal Grist 11-JAN-09 Added the module field name and changed the size of the message *

* field to 4000 (in line with FND Debug log). *

* Changed exception handling in the end_point_exception function. *

* *

* *

* *

* *

*****************************************************************************************************/

/*

|| The log collection below is the main data structure associated with this package.

|| It holds every log message, which code unit it relates to, where it comes in the processing

|| hierarchy, etc.

||

|| Only a certain number of records are maintained. This is to avoid a situation where so much detail

|| is collected that a memory error is generated. Once the size of the collection gets to the number

|| of records indicated by g_black_box_max_storage, the structure cycles back to position 1 and cycles

|| back to the maximum position; repeating this cycle as required.

*/

TYPE log_rectype IS RECORD (module VARCHAR2(4000),

-- this is for compatibility with the Oracle Apps FND Debug Log approach

reference_point VARCHAR2(255),

-- this is the current proc/function name or a some user defined ref

hierarchy_num NUMBER,

-- this is used for indenting the output to make it more readable

log_level NUMBER,

-- this allows a selective extraction from the collection if required

type VARCHAR2(10), -- BEGIN, END, PARAM, RETVAL, NOTE, ERROR

message VARCHAR2(4000),

timestamp DATE);

TYPE log_tabtype IS TABLE OF log_rectype INDEX BY BINARY_INTEGER;

g_log_tab log_tabtype; -- this holds each of the log lines

g_current_log_index NUMBER := 0;

g_black_box_max_storage NUMBER := 500;

g_black_box_lapped BOOLEAN := FALSE; -- to record whether the max record count has been exceeded

g_current_module VARCHAR2(4000) := 'xxbbr'; -- for compatibility with the FND Debug Log

/*

|| The hierarchy collection below holds a record of the procedure/function names and their default

|| log_levels. This allows us to deduce a log level for a log_msg within a procedure if no log level

|| has been specified i.e. a message within a procedure takes the log level associated with the start

|| of the procedure unless overrided.

*/

TYPE hierarchy_rectype IS RECORD (reference_point VARCHAR2(255), log_level NUMBER);

TYPE hierarchy_tabtype IS TABLE OF hierarchy_rectype INDEX BY BINARY_INTEGER;

g_hierarchy_tab hierarchy_tabtype;

g_current_hierarchy_num NUMBER := 0; -- this records the current level in the code hierarchy

/*

|| The following are wrapper functions around the g_hierarchy_tab plsql table.

||

|| They return the relevant values (e.g. current log level), if the record has been initialized.

|| Otherwise, a default value is returned.

||

|| This has been set up as a function to avoid having to check whether it exists before it is

|| referenced

|| ---------------------------------------------------------------------------------------------------

*/

FUNCTION current_log_level RETURN NUMBER IS

BEGIN

RETURN NVL(g_hierarchy_tab(g_current_hierarchy_num).log_level, 0);

EXCEPTION

WHEN OTHERS THEN RETURN 0;

END current_log_level;

FUNCTION current_reference_point RETURN VARCHAR2 IS

BEGIN

RETURN g_hierarchy_tab(g_current_hierarchy_num).reference_point;

EXCEPTION

WHEN OTHERS THEN RETURN 'NULL';

END current_reference_point;

/*

|| The following dumps a line of text to the log location.

|| -------------------------------------------------------

*/

PROCEDURE write_log_line(p_text VARCHAR2 := NULL) IS

BEGIN

-- Use the following code if running in an Oracle Applications (eBusiness Suite) environment

IF fnd_global.conc_request_id = -1 -- i.e. it's not via the concurrent manager

THEN dbms_output.put_line(p_text);

ELSE fnd_file.put_line(fnd_file.log, p_text);

END IF;

----

-- The following call links in with Oracle Applications standard debug logging.

-- In order to keep the hierarchy indenting, the call is made within the

-- write_log_line function which also means that only the messages deemed

-- relevant by the BBR logging level are passed onto the OraApps debug handling.

----

fnd_log.string(log_level => 1, module => SUBSTR(g_current_module,1,255), message => p_text);

EXCEPTION

WHEN OTHERS THEN raise_application_error (-20000, 'bbr.write_log_line: ' || SQLERRM);

END write_log_line;

------------------------------------------------------------------------------------------

-- Overloaded version: --

-- The following takes the timestamp and the indent and formats the message accordingly --

------------------------------------------------------------------------------------------

PROCEDURE write_log_line(p_timestamp DATE,

p_message_type VARCHAR2,

p_hierarchy_num NUMBER,

p_text VARCHAR2) IS

l_indent NUMBER;

BEGIN

-- Determine by how much the line should be indented

IF p_message_type IN ('BEGIN', 'END')

THEN l_indent := p_hierarchy_num*2;

ELSE l_indent := p_hierarchy_num*2 + 2;

END IF;

-- Pass the formatted line onto the base write_log_line function to be sent to the output log file

write_log_line(TO_CHAR(p_timestamp, 'HH24:MI:SS') || SUBSTR(NVL(LPAD(' ', l_indent, ' '), ' '),1,250) || p_text);

EXCEPTION

WHEN OTHERS THEN raise_application_error (-20000, 'bbr.write_log_line: ' || SQLERRM);

END write_log_line;

/*

|| The following procedure does 2 things:

||

|| 1) It records the log message in the Black Box Recorder.

|| 2) If the log level is less than the global debug level, it sends the message to the log output.

|| ------------------------------------------------------------------------------------------------

*/

PROCEDURE log_msg(p_msg VARCHAR2, p_level NUMBER := NULL, p_type VARCHAR2 := 'NOTE') IS

BEGIN

-- Get the sequence number for the next Black Box record.

-- Reset it if it is greater than the g_black_box_max_storage value.

IF g_current_log_index >= g_black_box_max_storage

THEN g_black_box_lapped := TRUE;

g_current_log_index := 1;

ELSE g_current_log_index := g_current_log_index + 1;

END IF;

g_log_tab(g_current_log_index).module := g_current_module;

g_log_tab(g_current_log_index).reference_point := current_reference_point;

g_log_tab(g_current_log_index).hierarchy_num := g_current_hierarchy_num;

g_log_tab(g_current_log_index).type := p_type;

g_log_tab(g_current_log_index).message := SUBSTR(p_msg,1,4000);

g_log_tab(g_current_log_index).timestamp := SYSDATE;

-- If the level parameter is null, then use the level from the current parent

IF p_level IS NULL

THEN g_log_tab(g_current_log_index).log_level := current_log_level;

ELSE g_log_tab(g_current_log_index).log_level := p_level;

END IF;

-- Finally, if the log_level is less than the global display level, then output the message

IF g_log_tab(g_current_log_index).log_level <= g_log_display_level

THEN write_log_line(p_timestamp => g_log_tab(g_current_log_index).timestamp,

p_message_type => g_log_tab(g_current_log_index).type,

p_hierarchy_num => g_log_tab(g_current_log_index).hierarchy_num,

p_text => g_log_tab(g_current_log_index).message);

END IF;

EXCEPTION

WHEN OTHERS THEN raise_application_error (-20000, 'bbr.log_msg: ' || SQLERRM);

END log_msg;

/*

|| The following displays the contents of the Black Box Recorder.

|| --------------------------------------------------------------

*/

PROCEDURE display_log IS

i NUMBER;

l_indent NUMBER;

BEGIN

write_log_line; -- just a blank line

write_log_line('DUMPING OUT THE CONTENTS OF THE BLACK BOX RECORDER');

-- Depending on whether all of the slots in the log have been used up and

-- it's cycled back on itself or the number of records in the log are still

-- less than the max number (as indicated by g_black_box_max_storage), 2

-- different approaches are required to list the contents.

--

--

IF g_black_box_lapped

THEN -- In order to get the sequence of the records correct (i.e. earliest first),

-- list the records in the following order:

--

-- 1) From the record after the current record to the end

-- 2) From 1 to the current record

--

-- i.e. as the current record is the most record, this needs to be displayed last.

FOR i IN g_current_log_index + 1 .. g_black_box_max_storage

LOOP

write_log_line(p_timestamp => g_log_tab(i).timestamp,

p_message_type => g_log_tab(i).type,

p_hierarchy_num => g_log_tab(i).hierarchy_num,

p_text => g_log_tab(i).message);

END LOOP;

FOR i IN 1 .. g_current_log_index

LOOP

write_log_line(p_timestamp => g_log_tab(i).timestamp,

p_message_type => g_log_tab(i).type,

p_hierarchy_num => g_log_tab(i).hierarchy_num,

p_text => g_log_tab(i).message);

END LOOP;

ELSE FOR i IN 1 .. g_current_log_index

LOOP

write_log_line(p_timestamp => g_log_tab(i).timestamp,

p_message_type => g_log_tab(i).type,

p_hierarchy_num => g_log_tab(i).hierarchy_num,

p_text => g_log_tab(i).message);

END LOOP;

END IF;

write_log_line; -- just output a blank line

reset_log;

EXCEPTION

WHEN OTHERS THEN raise_application_error (-20000, 'xxbbr.display_log: ' || SQLERRM);

END display_log;

/*

|| The following displays the contents of the Black Box Recorder.

|| --------------------------------------------------------------

*/

PROCEDURE reset_log IS

BEGIN

g_log_tab.DELETE;

g_hierarchy_tab.DELETE;

g_current_hierarchy_num := 0;

g_current_log_index := 0;

g_current_module := 'xxbbr';

EXCEPTION

WHEN OTHERS THEN raise_application_error (-20000, 'xxbbr.reset_log: ' || SQLERRM);

END reset_log;

/*

|| The following records the start of a procedure, function

|| or just a component within a code block such as a loop.

|| --------------------------------------------------------

*/

PROCEDURE begin_point(p_reference VARCHAR2, p_level NUMBER := NULL) IS

l_level NUMBER;

BEGIN

-- If this is the top level start, we need to default in a value for the level parameter

-- if one hasn't already been entered. This is because the parent level gets inherited by

-- by the child processes if they're also null.

IF p_level IS NULL

THEN IF g_current_hierarchy_num = 0

THEN l_level := 0;

ELSE l_level := current_log_level;

END IF;

ELSE l_level := p_level;

END IF;

-- Increment the code level, record the new reference point/level and write details to the log table.

g_current_hierarchy_num := g_current_hierarchy_num + 1;

g_current_module := SUBSTR(g_current_module || '.' || p_reference,1,4000);

g_hierarchy_tab(g_current_hierarchy_num).reference_point := SUBSTR(p_reference,1,255);

g_hierarchy_tab(g_current_hierarchy_num).log_level := l_level;

log_msg(p_msg => 'Entering <' || p_reference || '>', p_level => l_level, p_type => 'BEGIN');

EXCEPTION

WHEN OTHERS THEN raise_application_error (-20000, 'xxbbr.begin_point: ' || SQLERRM);

END begin_point;

/*

|| The following records the end of a procedure, function

|| or just a component within a code block such as a loop.

|| -------------------------------------------------------

*/

PROCEDURE end_point(p_reference VARCHAR2) IS

l_level NUMBER;

BEGIN

-- As an integrity check, make sure that the end_point reference matches the current reference point.

IF p_reference <> current_reference_point

THEN log_msg('WARNING: Exit point descrepancy - expecting <' || current_reference_point || '>' || ' instead of <' || p_reference || '>');

END IF;

log_msg(p_msg => 'Exiting <' || p_reference || '>', p_type => 'END');

IF g_current_hierarchy_num > 0 -- As we're at an 'end' point, we need to decrement the hierarchy_num

THEN g_current_hierarchy_num := g_current_hierarchy_num - 1;

END IF;

-- Update the g_current_module name to remove the reference from the end of it

g_current_module := SUBSTR(g_current_module, 1, INSTR(g_current_module, '.', -1)-1);

EXCEPTION

WHEN OTHERS THEN raise_application_error (-20000, 'xxbbr.end_point: ' || SQLERRM);

END end_point;

/*

|| The following raises an error (but logs the error message first).

|| -----------------------------------------------------------------

*/

PROCEDURE raise_error(p_errmsg VARCHAR2) IS

BEGIN

log_msg('ERROR: ' || p_errmsg);

raise_application_error (-20001, p_errmsg);

EXCEPTION

WHEN OTHERS THEN raise_application_error (-20000, 'xxbbr.raise_error: ' || SQLERRM);

END raise_error;

/*

|| The following groups together a couple of the function calls which should be called

|| by the exception handler. As it was easy to forget one, these have been group into

|| a single function for consistancy.

|| -----------------------------------------------------------------------------------

*/

PROCEDURE end_point_exception(p_reference VARCHAR2) IS

l_sqlerrm VARCHAR2(10000);

BEGIN

-- Tidy up the SQLERRM contents to avoid a lot of repetition and spurious data

l_sqlerrm := SUBSTR(REPLACE(REPLACE(REPLACE(SQLERRM, 'ORA-20000: '), 'ORA-20001: '), 'xxbbr.raise_error: '),1,10000);

log_msg(l_sqlerrm);

-- Although this was raised in the exception handler, we still need to highlight that the function is being exited

end_point(p_reference);

-- If we're at the top level and encounter an unhandle exception,

-- then we want to dump out the complete detail from the Black Box

IF g_current_hierarchy_num = 0

THEN display_log;

END IF;

-- Pass the exception up the chain

IF sqlerrm = 'ORA-01403: no data found'

THEN RAISE NO_DATA_FOUND;

ELSE raise_application_error(-20001, l_sqlerrm);

END IF;

END end_point_exception;

/*

|| The following displays the return value(s) from the functions or procedures.

|| ----------------------------------------------------------------------------

*/

PROCEDURE show_retval(p_return_value VARCHAR2) IS

BEGIN

log_msg('Returning: ' || p_return_value);

EXCEPTION

WHEN OTHERS THEN raise_application_error (-20000, 'xxbbr.show_retval: ' || SQLERRM);

END show_retval;

------------------------

-- overloaded version --

------------------------

PROCEDURE show_retval(p_return_value BOOLEAN) IS

BEGIN

IF p_return_value = TRUE

THEN log_msg('Returning: TRUE');

ELSE log_msg('Returning: FALSE');

END IF;

EXCEPTION

WHEN OTHERS THEN raise_application_error (-20000, 'xxbbr.show_retval: ' || SQLERRM);

END show_retval;

------------------------

-- overloaded version --

------------------------

PROCEDURE show_retval(p_return_name VARCHAR2, p_return_value VARCHAR2) IS

BEGIN

log_msg('Returning value (' || p_return_name || '): ' || p_return_value);

EXCEPTION

WHEN OTHERS THEN raise_application_error (-20000, 'xxbbr.show_retval: ' || SQLERRM);

END show_retval;

/*

|| The following displays the parameters to a function or procedure.

|| -----------------------------------------------------------------

*/

PROCEDURE show_parameter(p_param_name VARCHAR2, p_param_value VARCHAR2) IS

BEGIN

log_msg('Parameter ' || p_param_name || ': ' || p_param_value);

EXCEPTION

WHEN OTHERS THEN raise_application_error (-20000, 'xxbbr.show_parameter: ' || SQLERRM);

END show_parameter;

END xxbbr;

Download