The following is a very simple sample of a Handler class that writes formatted events to a file. This class is functional, but is intended solely to demonstrate concepts. For simplicity and clarity, much code (including appropriate boundary condition checking logic) has been ignored. This sample is not intended to be an example of good programming practice.
package com.ibm.ws.ras.test.user; import com.ibm.ras.*; import java.io.*; import java.util.*; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedExceptionAction; import java.security.PrivilegedActionException; /** * The <code>SimpleFileHandler</code> is a class that * implements the {{@link RASIHandler} interface. It is a simple * Handler that writes to a file. The name of the file * must be specified in the constructor. * <p> * If the file includes a path, the path separator * may be a front-slash ('/') or the * platform-specific path separator character. For example: * * /Dir1/Dir2/Dir3/MyStuff.log * */ public class SimpleFileHandler implements RASIHandler { /** * A public boolean that can be inspected by the caller to determine * if an error has occurred during an operation. * This boolean can only be changed when the device synchronizer is held. */ public boolean errorHasOccurred = false; /** * The name of the Handler */ private String ivName = ""; /** * The message mask which determines the types of messages * that will be processed. */ private long ivMessageMask; /** * The trace mask which determines the types of trace points * that will be processed. */ private long ivTraceMask; /** * The names of the message event classes which this object processes. */ private Vector ivMessageEventClasses; /** * The names of the trace event classes which this object processes. */ private Vector ivTraceEventClasses; /** * The set of {@link RASIMaskChangeListener} which want to be informed of changes to the * <code>RASIMaskChangeGenerator</code> message * or trace mask configuration. */ private Vector ivMaskChangeListeners; /** * The fully-qualified, normalized name of the file to which the log entries are written. */ private String ivFqFileName; /** * A boolean flag which indicates whether the device to which this handler sends log * entries is open. It is set to true when the device is open and false otherwise. */ private boolean ivDeviceOpen = false; /** * A Hashtable of RASIFormatters keyed by the name of the event class they format. * Each event type can have exactly * one formatter. Different event classes can have different formatters. */ private Hashtable ivFormatters; /** * An object on which the {@link #closeDevice closeDevice} and * {@link #writeEvent writeEvent} methods can synchronize. */ private Object ivDeviceLock = new Object(); /** * The stream to which formatted log events are written. This stream will wrap a file. */ private PrintWriter ivWriter = null; /** * Create a SimpleFileHandler. * <p> * The constructor will attempt to open a stream in append mode over the * specified file. If the operation does not complete * successfully, the errorHasOccurred boolean is set to true. If no exceptions are * thrown by this constructor and the * errorHasOccurred booleans state is false, the stream is open * and the handler is usable. * <p> * @param name the name assigned to this handler object. Null is tolerated. * @param fileName a non-null file name. Caller must guarantee this name is not null. * A fully qualified file name is preferred. */ public SimpleFileHandler(String name, String fileName) throws Exception { setName(name); ivMessageMask = RASIMessageEvent.DEFAULT_MESSAGE_MASK; ivTraceMask = RASITraceEvent.DEFAULT_TRACE_MASK; // Allocate the Hashtables and Vectors required. ivMaskChangeListeners = new Vector(); ivMessageEventClasses = new Vector(); ivTraceEventClasses = new Vector(); ivFormatters = new Hashtable(); // Add the default event classes that this handler will process addMessageEventClass("com.ibm.ras.RASMessageEvent"); addTraceEventClass("com.ibm.ras.RASTraceEvent"); // Get the fully qualified, normalized file name. Open the stream File x = new File(fileName); ivFqFileName = x.getAbsolutePath(); openDevice(); } /////////////////////////////////////////////////////////////////////// // // The following methods are required by the RASIObject interface // /////////////////////////////////////////////////////////////////////// /** * Return this objects configuration as a set of Properties in a Hashtable. * <p> * This handler does not support properties-based configuration. * Therefore a call to this method always returns null * @return null is always returned. */ public Hashtable getConfig() { return null; } /** * Set this objects configuration from the properties in the specified Hashtable. * <p> * This handler does not support properties-based configuration. * This method is a no-operation. * @param hashTable a Hashtable containing the properties. Input is ignored. */ public void setConfig(Hashtable ht) { return; } /** * Return the name by which this object is known. * <p> * @return a String containing the name of this object, or an * empty string ("") if the name has not been set. */ public String getName() { return ivName; } /** * Set the name by which this object is known. * If the specified name is <code>null</code>, the current name is not changed. * <p> * @param name The new name for this object. Null is tolerated. */ public void setName(String name) { if (name != null) ivName = name; } /** * Return the description field of this object. * <p> * This handler does not use a description field. * An empty String is always returned. * <p> * @return an empty String. */ public String getDescription() { return ""; } /** * Set the description field for this object. * <p> * This handler does not use a description field. * Input is ignored and this method does nothing. * <p> * @param desc The description of this object. Input is ignored. */ public void setDescription(String desc) { return; } /** * Return the name of the {@link com.ibm.ras.mgr.RASManager RASManager} group * with which this object is associated. This method is only used by the RAS Manager. * <p> * This object does not support RASManager configuration. Null is always returned. * @return null is always returned. */ public String getGroup() { return null; } /////////////////////////////////////////////////////////////// // // Methods required by the RASIMaskChangeGenerator interface // ////////////////////////////////////////////////////////////// /** * Return the message mask which defines the set of message * types that will be processed by this Handler. The set of possible * types is identified in the {@link RASIMessageEvent} * <code>TYPE_XXXX</code> constants. * <p> * @return The current message mask. */ public long getMessageMask() { return ivMessageMask; } /** * Set the message mask which defines the set of message types * that will be processed by this Handler. The set of possible * types is identified in the {@link RASIMessageEvent} * <code>TYPE_XXXX</code> constants. * The mask value is not validated against these types. * <p> * @param mask The message mask. */ public void setMessageMask(long mask) { RASMaskChangeEvent mc = new RASMaskChangeEvent(this, ivMessageMask, mask, true); ivMessageMask = mask; fireMaskChangedEvent(mc); } /** * Return the trace mask which defines the set of trace types that will be * processed by this Handler. The set of possible * types is identified in the {@link RASITraceEvent} * <code>TYPE_XXXX</code> constants. * <p> * @return The current trace mask. */ public long getTraceMask() { return ivTraceMask; } /** * Set the trace mask which defines the set of trace types that will * be processed by this Handler. The set of possible types * is identified in the {@link RASITraceEvent} * <code>TYPE_XXXX</code> constants. The specified trace mask value is not validated. * <p> * @param mask The trace mask. */ public void setTraceMask(long mask) { RASMaskChangeEvent mc = new RASMaskChangeEvent(this, ivTraceMask, mask, false); ivTraceMask = mask; fireMaskChangedEvent(mc); } /** * Add a {@link RASIMaskChangeListener} object to the set of listeners * which wish to be identified of a change in the message * or trace mask configuration. If the specified listener is * <code>null</code> or is already registered, this method does nothing. * <p> * @param listener The mask change listener. */ public void addMaskChangeListener(RASIMaskChangeListener listener) { if (listener != null && (!ivMaskChangeListeners.contains(listener))) ivMaskChangeListeners.addElement(listener); } /** * Remove a {@link RASIMaskChangeListener} object from * the list of registered listeners that wish to be informed of changes * in the message or trace mask configuration. If the listener is * <code>null</code> or is not registered, this method does nothing. * <p> * @param listener The mask change listener. */ public void removeMaskChangeListener(RASIMaskChangeListener listener) { if (listener != null && ivMaskChangeListeners.contains(listener)) ivMaskChangeListeners.removeElement(listener); } /** * Return an enumeration over the set of listeners currently registered * to be informed of changes in the message or trace mask configuration. * <p> * @return An Enumeration of mask change listeners. If no listeners * are registered, the Enumeration is empty. */ public Enumeration getMaskChangeListeners() { return ivMaskChangeListeners.elements(); } /** * Inform all registered <code>RASIMaskChangeListener</code>s * that the message or trace mask has been changed. *<p> * @param mc A mask change event, indicating what has changed. */ public void fireMaskChangedEvent(RASMaskChangeEvent mc) { RASIMaskChangeListener c; Enumeration e = getMaskChangeListeners(); while (e.hasMoreElements()) { c = (RASIMaskChangeListener) e.nextElement(); c.maskValueChanged(mc); } } /** * Add the name of a message event class to the list of message * event classes which this handler processes. If the specified * event class is null or is already registered, this method does nothing. * <p> * @param name The event class name. */ public void addMessageEventClass(String name) { if (name != null && (! ivMessageEventClasses.contains(name))) ivMessageEventClasses.addElement(name); } /** * Remove the name of a message event class from the list of names * of classes which this handler processes. If the specified event * class is null or is not registered, this method does nothing. * <p> * @param name The event class name. */ public void removeMessageEventClass(String name) { if ((name != null) && (ivMessageEventClasses.contains(name))) ivMessageEventClasses.removeElement(name); } /** * Return an enumeration over the set of names of the message event * classes which this handler processes. * <p> * @return An Enumeration of RAS event class names. * If no event classes are registered, the Enumeration is empty. */ public Enumeration getMessageEventClasses() { return ivMessageEventClasses.elements(); } /** * Add the name of a trace event class to the list of trace event classes * which this handler processes. If the specified event * class is null or is already registered, this method does nothing. * <p> * @param name The event class name. */ public void addTraceEventClass(String name) { if ((name != null) && (!ivTraceEventClasses.contains(name))) ivTraceEventClasses.addElement(name); } /** * Remove the name of a trace event class from the list of names * of classes which this handler processes. If the * specified event class is null or is not registered, this method does nothing. * <p> * @param name The event class name. */ public void removeTraceEventClass(String name) { if ((name != null) && (ivTraceEventClasses.contains(name))) ivTraceEventClasses.removeElement(name); } /** * Return an enumeration over the set of names of the trace * event classes which this handler processes * <p> * @return An Enumeration of RAS event class names. * If no event classes are registered, the Enumeration is empty. */ public Enumeration getTraceEventClasses() { return ivTraceEventClasses.elements(); } /////////////////////////////////////////////////////////// // // Methods required by the RASIHandler interface // ////////////////////////////////////////////////////////// /** * Return the maximum number of {@link RASIEvent RASIEvents} * which this handler will queue before writing. * <p> * In the WebSphere Application Server environment, handlers * may not start threads. All writes will be done * synchonously and never queued. This handler does not queue * events for later retry if a write operation fails. * <p> * @return zero is always returned. */ public int getMaximumQueueSize() { return 0; } /** * Set the maximum number of {@link RASIEvent RASIEvents} * which the handler will queue before writing. * <p> * This handler does not queue events. This method is a no-operation * <p> * @param size The maximum queue size. Input is ignored. */ public void setMaximumQueueSize(int size) throws IllegalStateException { return; } /** * Return the amount of time (in milliseconds) that this * handler will wait before retrying a failed write * <p> * This handler does not retry or queue failed writes. If a write operation * fails, the event is simply discarded. * <p> * @return The retry interval. Zero is always returned. */ public int getRetryInterval() { return 0; } /** * Set the amount of time (in milliseconds) that this handler will * wait before retrying a failed write. * <p> * This handler does not queue or retry failed writes. This method is a no-operation. * <p> * @param interval The retry interval. Input is ignored. */ public void setRetryInterval(int interval) { return; } /** * Return the current number of {@link RASIEvent RASIEvents} in the handler's queue. * <p> * This handler does not queue events. Zero is always returned. * <p> * @return The current queue size. Zero is always returned. */ public int getQueueSize() { return 0; } /** * Add a RASIFormatter to the set of formatters which are currently * registered to this handler. The specified formatter * must be fully configured. Specifically, the formatter must be configured * with the set of {@link RASIEvent} classes which it * knows how to format. * <p> * @param formatter The event formatter. Null is tolerated. If the specified * formatter supports formatting an event class which * already has an associated formatter, the existing formatter * is replaced with this one. **/ public void addFormatter(RASIFormatter formatter) { if (formatter != null) { Enumeration e = formatter.getEventClasses(); while (e.hasMoreElements()) { String name = (String) e.nextElement(); ivFormatters.put(name, formatter); } } } /** * Remove a RASIFormatter from the set of formatters currently * registered with this handler. * <p> * @param formatter The event formatter. If the specified formatter is * null or is not registered, this method does nothing. */ public void removeFormatter(RASIFormatter formatter) { if (formatter != null) { Enumeration e = formatter.getEventClasses(); while (e.hasMoreElements()) { String name = (String) e.nextElement(); ivFormatters.remove(name); } } } /** * Return an enumeration over the set of RASIFormatters currently * registered with this handler. * <p> * @return An Enumeration over the set of registered formatters. If no formatters are * currently registered, the Enumeration is empty. **/ public Enumeration getFormatters() { return ivFormatters.elements(); } /** * Close the stream to which this handler is currently writing its entries, * if the stream is currently open. */ public void closeDevice() { synchronized(ivDeviceLock) { if (ivWriter == null) return; ivWriter.flush(); ivWriter.close(); ivWriter = null; } } /** * Stop the handler, closing the stream to which this handler is * currently writing its entries * <p> * This method must be called when a handler is no longer needed. * Be careful not to call * this method if other loggers may still be using this handler. */ public void stop() { // This handler does not have any queues to flush or preprocessing to do. // Simply call closeDevice(). closeDevice(); } /** * Asynchronously process a RAS event passed from a logger to this handler. * <p> * WebSphere Application Server loggers always operate synchronously. * It is expected that no events will be delivered via this method. This * handler also only supports synchronous operations. If events are * delivered via this method, simply process them synchronously * <p> * @param event A RAS event. Null is tolerated */ public void logEvent(RASIEvent event) { writeEvent(event); } /** * Synchronously process a RAS event passed from a logger to this handler. * <p> * WebSphere Application Server loggers always operate synchronously. * It is expected that all * events will be delivered via this method. This handler also only supports * synchronous operations. * <p> * @param event A RAS event. Null is tolerated */ public void writeEvent(RASIEvent event) { if (event == null) return; synchronized(ivDeviceLock) { if (ivWriter == null) return; RASIFormatter formatter = findFormatter(event); if (formatter != null) { String msg = formatter.format(event); ivWriter.println(msg); // If an error occurs, simply set the boolean that caller can check if (ivWriter.checkError()) errorHasOccurred = true; } } } ///////////////////////////////////////////////////////// // // Methods introduced by this implementation // ///////////////////////////////////////////////////////// /** * Return the fully-qualified, normalized name of the file which this handler is * currently configured to write events to. * <p> * @return The fully-qualified, normalized name of the output file. */ public String getFileName() { return ivFqFileName; } /** * Set this handler to write to a file other than the file it is currently writing to. * <p> * The current stream that the handler is writing to is closed. * A new stream is opened over the specified file. * <p> * @param name name of the file. May not be null. A fully-qualified file * name is recommended. * @exception An exception is thrown if the specified name is null, the file * cannot be created or some other error * occurs. If an exception is thrown, the handlers state is indeterminate. */ public void setFileName(String name) throws Exception { if (name == null) throw new Exception("Null passed for name"); synchronized(ivDeviceLock) { closeDevice(); File x = new File(name); ivFqFileName = x.getAbsolutePath(); openDevice(); } } /** * Open a stream over the file to which this handler will write formatted log entries. * The stream will always be opened in append mode. * <p> * If a stream is already open over the file, the current stream is closed. * If an error occurs during this operation, the errorHasOccurred boolean is set to true * and a plain text error message is written to System.err along with the exception * stack trace, if any. If the operation is successful, the errorHasOccurred boolean is * set to false. * <p> */ public void openDevice() { synchronized(ivDeviceLock) { try { closeDevice(); errorHasOccurred = false; // The file name may have been changed.Create the directory for the file // if it doesn't already exist. File x = new File(ivFqFileName); String dir = x.getParent(); File dirs = new File(dir); if (fileExists(dirs) == false) { boolean result = makeDirectories(dirs); if (result == false) { errorHasOccurred = true; return; } } // Open a file output stream over the file in append mode. // Wrap the FileOutputStream in an // OutputStreamWriter. Finally wrap the OutputStreamWriter in a // BufferedPrintWriter with line flushing enabled. FileOutputStream fos = createFileOutputStream(ivFqFileName, true); OutputStreamWriter osw = new OutputStreamWriter(fos); ivWriter = new PrintWriter(new BufferedWriter(osw), true); } catch (Throwable t) { // not much we can do here except set the error boolean. errorHasOccurred = true; System.err.println("Error occurred in openDevice() for handler "+ivName); t.printStackTrace(); } } } /** * Return a reference to the formatter associated with the specified * event class. If the specified event class is not * registered, the superclasses of the event class will be checked * for a registered formatter. * <p> * @param event A RAS event. Must not be null. * @return formatter The formatter associated with the specified event class. * Null is returned if the event class is not registered. */ private RASIFormatter findFormatter(RASIEvent event) { Class eventClass = event.getClass(); RASIFormatter formatter = null; while (eventClass != null) { String className = eventClass.getName(); if (ivFormatters.containsKey(className)) { return (RASIFormatter) ivFormatters.get(className); } else eventClass = eventClass.getSuperclass(); } return null; } /** * A worker method that wraps the creation of a * FileOutputStream in a doPrivileged block. * <p> * @param fileName the name of the file to create the stream over. * @param append a boolean, when true indicates the file should be opened in append * mode * @ return the FileOutputStream. * @exception SecurityException A security violation has occurred. * This class is not authorized to access the specified file. * @exception PrivilegedActionException a checked exception was * thrown in the course of running the privileged action. * The checked exception is contained within the * PrivilegedActionException. Most likely the wrapped exception is a FileNotFound. */ private FileOutputStream createFileOutputStream(String fileName, boolean append) throws PrivilegedActionException { final String tempFileName = fileName; final boolean tempAppend = append; FileOutputStream fs = (FileOutputStream) AccessController.doPrivileged( new PrivilegedExceptionAction() { public Object run() throws IOException { return new FileOutputStream(tempFileName, tempAppend); } } ); return fs; } /** * A worker method that wraps the check for the existence of a file in a * doPrivileged block. * <p> * @param fileToCheck a <code>File</code> object whose abstract pathname corresponds * to the physical file whose existence is to be checked. * @return true if and only if the file exists. Otherwise false. * @exception SecurityException A security violation has occurred. * This class is not authorized to access the specified file. */ private boolean fileExists(File fileToCheck) throws SecurityException { final File tempFileToCheck = fileToCheck; Boolean exists = (Boolean) AccessController.doPrivileged( new PrivilegedAction() { public Object run() { return new Boolean(tempFileToCheck.exists()); } } ); return exists.booleanValue(); } /** * A worker method that wraps the creation of directories in a doPrivileged block. * <p> * @param dirToMake a non-null <code>File</code> object whose * abstract pathname represents * the fully qualified directory to create. * @return true is returned if and only if all necessary directories were created. * false is returned. * @exception SecurityException A security violation has occurred. This class * Otherwise is not authorized * to access at leas one of the specified directories. */ private boolean makeDirectories(File dirToMake) throws SecurityException { final File tempDirToMake = dirToMake; Boolean result = (Boolean) AccessController.doPrivileged( new PrivilegedAction() { public Object run() { return new Boolean(tempDirToMake.mkdirs()); } } ); return result.booleanValue(); } }