Output modules form the interface between the messages and any output channel. Their task is to receive the message, format it accordingly and write it to the desired location. In order to be able to work with any number of output modules without changing the core implementation of PIT
, a concept of an abstract object type named PIT_MODULE
and inheritance is used. Beeing an abstract object means that you can't instantiate it. But it is not final, so it's allowed to create a new object that inherits from PIT_MODULE
. By this, two goals are achieved:
-
PIT
is able to recognize new modules automatically as they can be found in viewUSER_TYPES
. They share the same attributePIT_MODULE
as their supertype. -
As all output module inherit from
PIT_MODULE
, they all have the methods defined atPIT_MODULE
. This way, all output modules automatically implement all methods required byPIT
. But only those methods you require are overwritten by you to make sure your output module works as expected. All other methods fall back to a default behaviour which is an empty stub: They simply do nothing. This is called an interface, a language construct that is not possible in PL/SQL without using objects.
PIT_MODULE
implements the following methods:
LOG_VALIDATION
withMESSAGE_TYPE
parameter, used to handle user input validation without which normally are not persisted but only shown on the respective GUI.LOG_EXCEPTION
withMESSAGE_TYPE
parameter, used to handle fatal, severe, error, warn, info or debug messages if an exception occurred.PANIC
withMESSAGE_TYPE
parameter, used as a final resort. Intended use is to catch unforeseen, unrecoverable errors such as in thewhen others
branch of an exception handler.LOG_STATE
withMSG_PARAMS
parameter, used to log state of arbitrary key-value-pairs. Can be used to trace the actual status of any number of variables within a method.NOTIFY
, used to signal changes. Normal use would be to report progress of long running actions etc. The implementation may require a websocket server to send the messages to.TWEET
with a message type parameter. It uses a generic message with only plain text and is meant to output code debug information quick and dirty. Not meant to be part of the final code.PRINT
, used to pass messages without any severity etc. Useful to print output to a GUI or similar.ENTER
, used to log entering a method, including parameters etc.LEAVE
, used to log leaving a method, including parameters, timing information etc.PURGE_LOG
, used to clean the log based on severity and/or date settingsCONTEXT_CHANGED
, used to signal that the log settings were changed
As all of them are implemented as stubs doing nothing, you're free to overload only those methods you require in your inherited object. Each output module may have any number of parameters and supporting objects like tables, but at the bare minimum it should define a parameter setting the fire threshold of the output module. By defining this threshold, message of a severity less than this threshold will be ignored.
All output modules require a parameterless constructor method. This method should check whether the module is available for use. A mail output module should check whether the mail server is available and working, a file output module may check whether the directory is in place and an APEX module will check whether there is a living APEX environment.
PIT
ships with some predefined output modules. They cover a range of widely used output channels, such as the console, a trace file or a log table. But they are by no means »special« and you're free to change them without affecting any functionality of PIT
. Use them as a blue print for your own output modules.
The most basic output module probably is PIT_CONSOLE
. It implements almost any method of the abstract supertype with the exception of the purge method which does not seem to make too much sense when writing to a console window.
Only task of this module is to format the messages, call stack objects and message parameter objects to strings and print them to the console.
In a project, I created an alternative solutioin that writes to a package to overcome the length limitations of dbms_output
. This is according to Tom Kyte's blog, Option 3. This is just an example on how flexible the idea of PIT
is.
This module writes messages and call stack information to log tables. A total of three tables are created for this output module: PIT_TABLE_LOG
, PIT_TABLE_CALL_STACK
and PIT_TABLE_CALL_PARAMS
. Recall that any message contains its own unique ID generated by PIT
. These IDs are taken from a sequence that is ordered
, meaning that it will create IDs in the correct order even in RAC environments. Therefore this ID is used as the primary key for the log tables. It can also be used to order your log entries in time correctly.
PIT_TABLE
only implements the LOG
, ENTER
, LEAVE
and PURGE
methods.
This module writes messages and call stack information to a trace file. In order to use this output module, you need to create a directory object for it where it may write the trace files to. In essence, the duty of this package is comparable to the PIT_CONSOLE
package in that it has to cast the messages etc. to string.
This module sends mail messages containing the messages received. As this output module has to create message texts with salutations etc. it depends on helper package UTL_TEXT
see here. The dependency is mild though, so if you don't want to utilize this package it will not take too long to replace it with your own utility.
This module create a table named PIT_MAIL_QUEUE
. It implements the LOG
method only and differs between fatal and »normal« errors in that it sends a mail directly if a fatal error occurs whereas it stores message of level pit.LEVEL_ERROR
at the mail queue. There is a functionality in the accompanying package that allows to send a report with all error messages once a day, controlled by a scheduler job.
This module integrates PIT
into an APEX environment. Main task of the package is to convert messages to APEX error messages and integrate them into the APEX error or debug stack. Together with this module, a special session adapter called PIT_APEX_ADAPTER
is created that replaces the normally used PIT_DEFAULT_ADAPTER
. This is because in APEX the session information may not be taken from the database session but the APEX session identified by the session id contained in USERENV
. Plus, this adapter checks whether APEX is set to debug mode. If so, the adatper recommends a context switch to a context named CONTEXT_APEX
that switches debugging on and chooses PIT_APEX
as the only output module.
This module implements the LOG
, PRINT
, NOTIFY
and ENTER/LEAVE
methods. Method NOTIFY
is experimental and not fully implemented yet, as the plan is to deliver messages to APEX via a WebSocket server.
Setting APEX to debug does emit any ENTER/LEAVE
call stack entries. This is bound to LEVEL5
and upward. So to see those entries, set the debug level accordingly.
The constructor will check whether APEX is usable, i.e. whether there is a living APEX session context. If not, the module will be installed, but not available.
Writing a new output module is easy. There are two steps to take: Create a new object under PIT_MODULE
and implement overwrites of the methods you need within that new module.
In order to create your own output module, you start by examining PIT_MODULE
. Make yourself familiar with the methods available. Here is a short overview:
context_changed
This method is called if a context switch was detected. It is rarely used, but I implement it in a test output module to document that changing the context does call those methods.log
This method is the core logging methods for all errors, warnings, debug messages and so on. If called, it passes the actual instance ofMESSAGE_TYPE
to allow you to work with the message.log
This overloaded method accepts a parameter of typeMSG_PARAMS
and is used to log state information.MSG_PARAMS
is a list ofMSG_PARAM
instances, each containing a name/value pair.print
This method is called if a message is passed to the UI.notify
This method is used to pass status messages and notifications. For web application output modules, it was planned to make use of WebSocketrs to pass small information chunks to the browser. ThePIT_APEX
module does not implement this feature yet though.enter
andleave
These methods trace entering and leaving a method. They pass an instance ofCALL_STACK_TYPE
with further information on the method.purge
Use this to purge to log. Two parameters, a date up to which all log entries are to be purged and a severity filter allow for fine grained purging.
To create your own output module, you only overwrite the methods you need. Examine this example to understand how this is done:
create or replace type pit_file under pit_module(
overriding member procedure log_exception(
self in out nocopy pit_file,
p_message in message_type),
overriding member procedure purge_log(
self in out nocopy pit_file,
p_purge_date in date,
p_severity_greater_equal in integer default null),
overriding member procedure enter(
self in out nocopy pit_file,
p_call_stack in call_stack_type),
overriding member procedure leave(
self in out nocopy pit_file,
p_call_stack in call_stack_type),
constructor function pit_file(
self in out nocopy pit_file)
return self as result)
final instantiable;
/
Simply replace the module name with your name and make sure that the constructor method returns the correct object type.
My approach to implement functionality for output modules is to write wrapper methods that simply call a package with the respective functionality. I do this because the programming features of object bodies are not as strong as PL/SQL package features. No private methods or attributes are allowed and other restrictions apply. Therefore, delegating the implementation to a separate package does make sense.
So this is the implementation of the PIT_FILE
output module:
create or replace type body pit_file
as
overriding member procedure log_exception(
self in out nocopy pit_file,
p_message in message_type)
as
begin
if p_message.severity <= fire_threshold then
pit_file_pkg.log(self, p_message);
end if;
end log_exception;
overriding member procedure purge_log(
self in out nocopy pit_file,
p_purge_date in date,
p_severity_greater_equal in integer default null)
as
begin
pit_file_pkg.purge;
end purge_log;
overriding member procedure enter(
self in out nocopy pit_file,
p_call_stack in call_stack_type)
as
begin
pit_file_pkg.enter(self, p_call_stack);
end enter;
overriding member procedure leave(
self in out nocopy pit_file,
p_call_stack in call_stack_type)
as
begin
pit_file_pkg.leave(self, p_call_stack);
end leave;
constructor function pit_file (
self in out nocopy pit_file)
return self as result
as
begin
pit_file_pkg.initialize_module(self);
return;
end pit_file;
end;
/
Implementing package PIT_FILE
is not special by any means. It's a simple PL/SQL package working with the messages. There's only one thing to obey: PIT
takes care of the transaction context, as it spans an autonomous transaction for each call. So you don't have to and shouldn't include any transaction control within this output module. Write to a table if you like and PIT
will take care of the commit
for you.
I delegated the constructor functionality to PIT_FILE_PKG
as well. It may be interesting to see the implementation of this method:
procedure initialize_module(
self in out pit_file)
as
begin
g_dir := param.get_string(C_OUT_DIRECTORY, C_PARAM_GROUP);
g_filename := param.get_string(C_FILE_NAME, C_PARAM_GROUP);
-- Test
open_file(C_WRITE_APPEND);
self.fire_threshold := param.get_integer(C_FIRE_THRESHOLD, C_PARAM_GROUP);
self.status := msg.PIT_MODULE_INSTANTIATED;
exception
when others then
-- Do NOT throw any exceptions during initalization phase!
self.status := msg.PIT_FAIL_MODULE_INIT;
self.stack := pit_util.get_error_stack;
end initialize_module;
As the bare minimum, I define a fire threshold and set my status to msg.PIT_MODULE_INSTANTIATED
. Here, I also try to open a file at the directory defined at a parameter for that output module. If this fails, I catch this error and mark the instance as UNUSABLE
, using message msg.PIT_FAIL_MODULE_INIT
. Please keep in mind that it is not possible to pass any arguments to the constructor method. It is required that there is a constructor without parameters in order to use it from PIT
.
Any output module should have at least one parameter that controls a fire threshold for that module. Create this parameter by calling admin_param.merge_parameter
method. But you're free to add any number of parameters to your output module. There is no restriction of what you do with the message. Write an incident in your ticket system, send mails, do whatever you like. The only thing to keep in mind is performance. But if a severe error occurs, performance is probable your least problem.
If you want to learn about how output modules are created, simply review the ones delivered with PIT
. You will quickly get the idea and understand that there's nothing special about it.