Listening for Log Messages
There are two ways to listen for log messages. You can either take advantage of the JMX infrastructure and register a notification listener with the LogBroadcasterRuntime MBean, or you can attach a custom log handler (using the JDK 1.4 Logging API) to the server's Logger directly using a startup class. Using the Logging API is simpler and lets you benefit from other features of the API, such as filtering and message formatting.
21.2.1 JMX Listeners
WebLogic distributes log messages as JMX notifications. Thus, writing an application that listens and responds to log events is no different from writing any other notification listener, which we covered in Chapter 20. As Figure 21-1 illustrates, each server hosts a log broadcaster. This is implemented by the LogBroadcasterRuntime MBean. The following command retrieves the name of the LogBroadcasterRuntime MBean on a Managed Server:
java weblogic.Admin -url t3://servera.x:7001/ -username system -password pssst GET -pretty -type LogBroadcasterRuntime
In this case, the name of the MBean on ServerA is:
myClusterDomain:Location=ServerA,Name=TheLogBroadcaster,Type=LogBroadcasterRuntime
You can use the MBean's name to register a NotificationListener with the LogBroadcasterRuntime MBean:
WebLogicObjectName name = new WebLogicObjectName("TheLogBroadcaster", "LogBroadcasterRuntime", "myClusterDomain", "ServerA"); MyLogListener mll = new MyLogListener( ); ah.getMBeanServer( ).addNotificationListener(name, mll, null /*no filter*/, null);
Here we used the MBeanServer interface to register the log listener with the desired Log Broadcaster MBean. All notifications that are generated by the log system are of type weblogic.management.logging.WebLogicLogNotification. This interface lets you access crucial attributes about the nature of the log event. Example 21-1 shows how the log listener can extract the attributes of the generated log notification.
Example 21-1. Listening for log notifications
public class MyLogListener implements RemoteNotificationListener { public void handleNotification(Notification notification, Object hb) { WebLogicLogNotification wln = (WebLogicLogNotification) notification; System.out.println("Message ID = " + wln.getMessageId( )); System.out.println("Server name = " + wln.getServername( )); System.out.println("Machine name = " + wln.getMachineName( )); System.out.println("Severity = " + wln.getSeverity( )); System.out.println("Type = " + wln.getType( )); System.out.println("Timestamp = " + wln.getTimeStamp( )); System.out.println("Message = " + wln.getMessage( )); System.out.println("Thread ID = " + wln.getThreadId( )); System.out.println("User ID = " + wln.getUserId( )); System.out.println("Transaction ID = " + wln.getTransactionId( )); } }
If the client that registers the listener object is still running, and we fire a log message, the listener generates the following output to the console window:
Message ID = 500000 Server name = ServerA Machine name = xena Severity = 16 Type = weblogic.log.OREILLY.500000 Timestamp = 1044201543290 Message = I am hungry: 123 Thread ID = ExecuteThread: '8' for queue: 'default' User ID = kernel identity Transaction ID =
Here, the value for the Type field will be of the format weblogic.logMessage.subSystem.messageID, where subsystem refers to the subsystem that generated the log notification. This data is useful particularly if you intend to use a filter in combination with your log event listener.
21.2.2 JDK Logging
In WebLogic 8.1, a java.util.logging.Logger object is responsible for publishing the log messages generated by each server. Each logger can have a number of handlers registered with it. For example, the ConsoleHandler is used to print log messages to the console. If you want to implement a custom listener, you simply can create your own log handler and register it with the server's logger, using the standard JDK Logging API. This approach bypasses JMX altogether. There are three WebLogic classes that you should be aware of:
- Use WebLogic's subclass of LogRecord, WLLogRecord, to access information on any incoming log messages.
- Use the weblogic.logging.WLLevel class to access the logging levels that can be specified for a logger or handler.
- Use the helper class, weblogic.logging.LoggingHelper, to access the appropriate logger instances. An external client should rely on the getClientLogger( ) method. A client that runs within a Managed Server should invoke the getServerLogger( ) method to reference the server's Logger, whereas a client on the Administration Server should use the getDomainLogger( ) method to access the Domain Logger.
The following example, which can be executed on a server, shows how easy it is to access the logger and manipulate one of its handlers. It finds the console handler and sets its logging level and filter:
Logger serverlogger = LoggingHelper.getServerLogger( ); Handler[] handlerArray = serverlogger.getHandlers( ); for (int i=0; i < handlerArray.length; i++) { Handler h = handlerArray[i]; if(h.getClass( ).getName( ).equals("weblogic.logging.ConsoleHandler")){ h.setLevel(weblogic.logging.WLLevel.INFO); h.setFilter(new InfoFilter( )); } }
Filters are very straightforward. Example 21-2 lists the source for a log filter that permits only log messages with severity level of INFO.
Example 21-2. A simple log filter
package com.oreilly.wlguide.logging; import java.util.logging.Filter; import java.util.logging.LogRecord; import weblogic.logging.WLLevel; import weblogic.logging.WLLogRecord; public class InfoFilter implements Filter { public boolean isLoggable(LogRecord record) { return ((WLLogRecord)record).getLevel( ).equals(WLLevel.INFO); } }
Remember, filters and log levels can be assigned to both the loggers and the handlers. If they're assigned to loggers, they determine the set of messages that the logger will publish. If they're assigned to handlers, they determine the set of messages that the handler will accept.
Writing a handler is easy, and Example 21-3 provides a simple implementation.
Example 21-3. A simple handler using the standard JDK 1.4 Logging API
package com.oreilly.wlguide.logging; import java.util.logging.*; import weblogic.logging.WLLogRecord; public class WLLogHandler extends Handler { public void publish(LogRecord record) { WLLogRecord wr = (WLLogRecord) record; System.err.println(wr.getServerName( )+":" + wr.getLevel( )+ ":" + wr.getMessage( )); } // flush should flush buffered output public void flush( ) { } // close should close resources. Will be called at shutdown. public void close( ) throws SecurityException { } }
Now you can simply access the logger and register your log handler:
Logger l = LoggingHelper.getDomainLogger( ); l.addHandler(new MyHandler( ));
Often, a WebLogic startup class provides a convenient point for registering the handler with the loggers. Example 21-4 lists the source code for a startup class that registers a custom handler with the server's Logger.
Example 21-4. A startup class registering a handler
package com.oreilly.wlguide.logging; import java.util.Hashtable; import javax.naming.*; import weblogic.common.T3ServicesDef; import weblogic.common.T3StartupDef; import java.util.logging.*; import weblogic.logging.*; public class InstallLogHandler implements T3StartupDef { private T3ServicesDef services; public void setServices(T3ServicesDef services) { this.services = services; } public String startup(String name, Hashtable args) throws Exception { try { Handler h = new WLLogHandler( ); h.setLevel(WLLevel.INFO); // level that handler accepts Logger l = LoggingHelper.getServerLogger( ); l.setLevel(WLLevel.INFO); // level that logger publishes l.addHandler(h); // let's rummage around for the console logger Handler[] handlerArray = l.getHandlers( ); for (int i=0; i < handlerArray.length; i++) { Handler hh= handlerArray[i]; if (hh instanceof ConsoleHandler) ((ConsoleHandler)hh).setLevel(WLLevel.ERROR); } } catch (Exception e) { e.printStackTrace( ); } return ""; } }
Notice how the startup class also modifies the severity level of the ConsoleHandler attached to the server Logger.