Skip to Main Content

SQL & PL/SQL

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

Logging in PL/SQL, part 1

Scott SwankMay 19 2016 — edited May 27 2016

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

Comments
Locked Post
New comments cannot be posted to this locked post.
Post Details
Locked on Jun 17 2016
Added on May 19 2016
3 comments
1,165 views