All code is available at:
***Modeerator action (Timo): Scott, as the admins have informed you already, the link to the repository where users can get the code is not allowed here. I removed the link. The community, I'm sure, would like to get more of your content. My advice would be to write a blog entry on your personal blog site. A link to this blog site can be added to your profile.***
I'll start out by describing logging itself. Logging is the process of writing text (somewhere!) that describes details of an application's execution.
Examples might look like:
"[2016-05-19 10:03] created customer 2134, last name: Doe, first name: John"
"retrieved 40000 orders in 140ms"
etc.
A key detail is that logging can be quite voluminous, and is only occasionally interesting. So then an important detail is simply managing what is logged. The common approach is to provide an API that allows data to be written at varying degrees of granularity. E.g.
logger.info('This is generally considered the default level of output.');
logger.debug('This would typically be displayed in a development environment, but not in production unless a specific issue was being investigated');
logger.error('This should be reserved for significant application problems');
logger.fatal('A potentially unrecoverable error has occurred.');
To that end I've created an object type called simply a log_level. It has 6 member functions, corresponding to each of the 6 most common logging granularities:
CREATE OR REPLACE TYPE log_level AS OBJECT (
value INTEGER,
...
MEMBER FUNCTION is_trace_enabled RETURN BOOLEAN,
MEMBER FUNCTION is_debug_enabled RETURN BOOLEAN,
MEMBER FUNCTION is_info_enabled RETURN BOOLEAN,
MEMBER FUNCTION is_warn_enabled RETURN BOOLEAN,
MEMBER FUNCTION is_error_enabled RETURN BOOLEAN,
MEMBER FUNCTION is_fatal_enabled RETURN BOOLEAN
)
The range for log_level.value then comes from a package (logger_pkg):
off CONSTANT INTEGER := 0;
fatal CONSTANT INTEGER := 1;
error CONSTANT INTEGER := 2;
warn CONSTANT INTEGER := 3;
info CONSTANT INTEGER := 4;
debug CONSTANT INTEGER := 5;
trace CONSTANT INTEGER := 6;
We're now in a position to do remarkably simple things, such as:
DECLARE
lvl log_level := log_level();
BEGIN
IF lvl.is_info_enabled()
THEN
dbms_output.put_line('info is enabled by default');
END IF;
IF lvl.is_debug_enabled()
THEN
dbms_output.put_line('UNEXPECTED: debug should not be enable');
END IF;
lvl.value := logger_pkg.debug;
IF lvl.is_debug_enabled()
THEN
dbms_output.put_line('now debug is enabled');
END IF;
END;
/
info is enabled by default
now debug is enabled
This is in fact so simple as to border on pointless. So let's consider a more interesting sub-type:
CREATE OR REPLACE TYPE dynamic_log_level
UNDER log_level (
context VARCHAR2(200 CHAR),
next_update DATE,
update_seconds NUMBER(4, 0),
CONSTRUCTOR FUNCTION dynamic_log_level(p_context IN VARCHAR2, p_update_seconds IN PLS_INTEGER DEFAULT 30)
RETURN SELF AS RESULT,
...
)
/
This will retrieve its log level, allowing it to be varied by updating the corresponding row of our configuration table.
DECLARE
l_lvl log_level := dynamic_log_level(p_context => 'demo_namespace', p_update_seconds => 0);
BEGIN
IF l_lvl.is_info_enabled()
THEN
DBMS_OUTPUT.put_line('by default info is enabled');
END IF;
IF l_lvl.is_debug_enabled()
THEN
DBMS_OUTPUT.put_line('UNEXPECTED: however debug is not');
END IF;
UPDATE swx_log_config
SET log_level = logger_pkg.debug
WHERE module = 'demo_namespace';
IF l_lvl.is_debug_enabled()
THEN
DBMS_OUTPUT.put_line('and now debug is enabled');
END IF;
DELETE FROM swx_log_config
WHERE module = 'demo_namespace';
COMMIT;
END;
/
by default info is enabled
and now debug is enabled
Note that in this example we hard-coded a named context for our dynamic log_level. This will instead come from utl_callstack once we pull everything together. That allows logging to be enabled at a default level for a given instance, as well as at the granularity of a given package or package/procedure.
UPDATE swx_log_config
SET log_level = logger_pkg.debug
WHERE module = 'my_package.troublesome_function';
COMMIT;
And then, within that function, calls to logger.debug('customer number: '||p_cust_num) will be written to the log in our production code without actually touching the application itself.
More to come.
Scott