Tuesday, May 31, 2011

Gain access to UCM log messages

The Problem
If you have implemented the LogWriter interface in UCM 10g (or before) you may have noticed it does not quite work in UCM 11g as expected (most error log messages never seem to come through the LogWriter implementation, though some low level stuff does). An example of the LogWriter use can be found in the RssFeeds component that is freely available on OTN (for 7.5 and 10g). This post will discuss the UCM 11g (and probably also UCM 10g) approach for gaining access to the messages that are written to your logs.

UCM 10g - LogWriter interface
The LogWriter interface is a very simple interface with just one method. The code below shows a very typical LogWriter implementation. The RssFeeds component contains a more detailed implementation of how you could use this interface.

public class SampleLogWriter implements LogWriter {
 
 public SampleLogWriter () {
  // do your initialization here
 }

 @Override
 public void doMessageAppend (
  final int logLevel,
  final String message,
  final LogDirInfo context,
  final Throwable exception
 ) {
  try {
   // do something with your logging here 
   // note: don't log anything from here on! (trace is safe)
  } catch ( final Throwable someException ) {
   someException.printStackTrace();
  }
 }

}

And yes, you want to catch ALL possible RunTimeExceptions/Errors, otherwise you are very likely to end up in an infinite loop if your code for some reason throws something (like a NullPointerException or an AssertionError). Obviously, don't log an error in your log message handling code either.

To configure its use you had to create an entry in your content server configuration (config.cfg) as described below and it would work.

ExtraLogWriter=my.SampleLogWriter

UCM 11g - ReportHandler interface
As described in the introduction, the LogWriter no longer functions propertly in UCM 11g. Luckily there is a good alternative that gives you access to all messages written to the logs... and even more! Lets show you a very typical implementation of the ReportHandler interface.

public class SampleReportHandler implements ReportHandler {

 public SampleReportHandler() {
  // you may do some initiation of your report handler here
 }

 @Override
 public void init ( final Map settings ) {
  // you can use some settings here to initialize (further)
  // (called prior to the first call to the message() method) 
 }

 @Override
 public boolean isActiveSection ( final String traceSection ) {
  // whether or not the given tracing section is active in this
  // report handler
  return true;
 }

 @Override
 public void message (
  final String application,
  final String section,
  final int logLevel,
  final IdcMessage message,
  final byte[] bytes,
  final int offset,
  final int length,
  final Throwable throwable,
  final Date date
 ) {
  try {
   // do something with your logging here
   // note: don't log OR trace anything here!
   if ( logLevel >= Report.LEVEL_TRACE ) {
    return;
   }
   // after the above if statement tracing in your custom code is safe
  } catch ( final Throwable someException ) {
   someException.printStackTrace();
  }
 }
}

Again, catch all to avoid infinite loops. And again, don't log any error in your code. But this time... also don't trace (even verbose) because the ReportHandler handles all messages.

Configuring the content server to pick up your ReportHandler implementation and use that, there is - unfortunately - no simple config parameter. The good thing is that you can set this up in your component by editing your component hda and adding the following result set at the end of your <component_name>.hda file.
@ResultSet StateCfgExportedVars
2
name
value
JAVA_OPTIONS_sample_reporthandler
${DEFINE_PREFIX}idc.report.sample.implementor=my.SampleReportHandler
JAVA_CLASSPATH_sample
$COMPONENT_DIR/classes/
@end
The first entry in the result set adds a java option setting up your report handler class as a report implementation. The second entry makes sure the classes directory in your component is part of the java classpath. This last entry is needed because this will be setup before all components are loaded (thus try not to have any dependencies on libraries or code outside of the classes folder and the default loaded libraries such as IdcServer.jar).

Important Differences
  • The log levels use different backing integers, don't mix them up; LogWriter uses the constants setup in intradoc.common.Log (ie. Log.m_warningType = 3) whereas ReportHandler uses the constants setup in intradoc.common.Report (ie. Report.LEVEL_WARNING = 4000).
  • The ReportHandler method message contains more parameters than the doMessageAppend method in the LogWriter interface (luckily all parameters from the LogWriter have an (almost) equivalent parameter in the ReportHandler).
  • As said before, the ReportHandler has access to much more than the logging messages, this includes but is not limited to trace messages.
  • And the most important difference is ofcourse that LogWriter does not function properly in UCM 11g, whilst the ReportHandler does work in UCM 11g and also seems available in UCM 10g (although I have not tested if this works correctly).

Homework
Rewrite the RssFeeds component to work in 11g with the ReportHandler interface.

No comments:

Post a Comment