oburn 02/03/22 23:51:26 Added: src/java/org/apache/log4j/chainsaw XMLFileHandler.java MyTableModel.java Main.java LoggingReceiver.java LoadXMLAction.java ExitAction.java EventDetails.java DetailPanel.java ControlPanel.java Log: First version of Chainsaw based on version 1.1. The changes made were: - Change the package name - Change the license to Apache - Remove the startup sound - Remove the test generator class - Change the email address for the author. Revision Changes Path 1.1 jakarta-log4j/src/java/org/apache/log4j/chainsaw/XMLFileHandler.java Index: XMLFileHandler.java =================================================================== /* * Copyright (C) The Apache Software Foundation. All rights reserved. * * This software is published under the terms of the Apache Software * License version 1.1, a copy of which has been included with this * distribution in the LICENSE.txt file. */ package org.apache.log4j.chainsaw; import java.util.StringTokenizer; import org.apache.log4j.Priority; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; /** * A content handler for document containing Log4J events logged using the * XMLLayout class. It will create events and add them to a supplied model. * * @author <a href="mailto:[EMAIL PROTECTED]">Oliver Burn</a> * @version 1.0 */ class XMLFileHandler extends DefaultHandler { /** represents the event tag **/ private static final String TAG_EVENT = "log4j:event"; /** represents the message tag **/ private static final String TAG_MESSAGE = "log4j:message"; /** represents the ndc tag **/ private static final String TAG_NDC = "log4j:NDC"; /** represents the throwable tag **/ private static final String TAG_THROWABLE = "log4j:throwable"; /** represents the location info tag **/ private static final String TAG_LOCATION_INFO = "log4j:locationInfo"; /** where to put the events **/ private final MyTableModel mModel; /** the number of events in the document **/ private int mNumEvents; /** the current element being parsed **/ private String mCurrentElement; /** the time of the event **/ private long mTimeStamp; /** the priority of the event **/ private Priority mPriority; /** the category of the event **/ private String mCategoryName; /** the NDC for the event **/ private String mNDC; /** the thread for the event **/ private String mThreadName; /** the msg for the event **/ private String mMessage; /** the throwable details the event **/ private String[] mThrowableStrRep; /** the location details for the event **/ private String mLocationDetails; /** * Creates a new <code>XMLFileHandler</code> instance. * * @param aModel where to add the events */ XMLFileHandler(MyTableModel aModel) { mModel = aModel; } /** @see DefaultHandler **/ public void startDocument() throws SAXException { mNumEvents = 0; } /** @see DefaultHandler **/ public void characters(char[] aChars, int aStart, int aLength) { if (mCurrentElement == TAG_NDC) { mNDC = new String(aChars, aStart, aLength); } else if (mCurrentElement == TAG_MESSAGE) { mMessage = new String(aChars, aStart, aLength); } else if (mCurrentElement == TAG_THROWABLE) { final StringTokenizer st = new StringTokenizer(new String(aChars, aStart, aLength), "\t"); mThrowableStrRep = new String[st.countTokens()]; if (mThrowableStrRep.length > 0) { mThrowableStrRep[0] = st.nextToken(); for (int i = 1; i < mThrowableStrRep.length; i++) { mThrowableStrRep[i] = "\t" + st.nextToken(); } } } } /** @see DefaultHandler **/ public void endElement(String aNamespaceURI, String aLocalName, String aQName) { if (TAG_EVENT.equals(aQName)) { addEvent(); resetData(); } else if (mCurrentElement != TAG_EVENT) { mCurrentElement = TAG_EVENT; // hack - but only thing I care about } } /** @see DefaultHandler **/ public void startElement(String aNamespaceURI, String aLocalName, String aQName, Attributes aAtts) { if (TAG_EVENT.equals(aQName)) { mThreadName = aAtts.getValue("thread"); mTimeStamp = Long.parseLong(aAtts.getValue("timestamp")); mCategoryName = aAtts.getValue("category"); mPriority = Priority.toPriority(aAtts.getValue("priority")); } else if (TAG_LOCATION_INFO.equals(aQName)) { mLocationDetails = aAtts.getValue("class") + "." + aAtts.getValue("method") + "(" + aAtts.getValue("file") + ":" + aAtts.getValue("line") + ")"; } else if (TAG_NDC.equals(aQName)) { mCurrentElement = TAG_NDC; } else if (TAG_MESSAGE.equals(aQName)) { mCurrentElement = TAG_MESSAGE; } else if (TAG_THROWABLE.equals(aQName)) { mCurrentElement = TAG_THROWABLE; } } /** @return the number of events in the document **/ int getNumEvents() { return mNumEvents; } //////////////////////////////////////////////////////////////////////////// // Private methods //////////////////////////////////////////////////////////////////////////// /** Add an event to the model **/ private void addEvent() { mModel.addEvent(new EventDetails(mTimeStamp, mPriority, mCategoryName, mNDC, mThreadName, mMessage, mThrowableStrRep, mLocationDetails)); mNumEvents++; } /** Reset the data for an event **/ private void resetData() { mTimeStamp = 0; mPriority = null; mCategoryName = null; mNDC = null; mThreadName = null; mMessage = null; mThrowableStrRep = null; mLocationDetails = null; } } 1.1 jakarta-log4j/src/java/org/apache/log4j/chainsaw/MyTableModel.java Index: MyTableModel.java =================================================================== /* * Copyright (C) The Apache Software Foundation. All rights reserved. * * This software is published under the terms of the Apache Software * License version 1.1, a copy of which has been included with this * distribution in the LICENSE.txt file. */ package org.apache.log4j.chainsaw; import java.text.DateFormat; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; import javax.swing.table.AbstractTableModel; import org.apache.log4j.Priority; import org.apache.log4j.Category; /** * Represents a list of <code>EventDetails</code> objects that are sorted on * logging time. Methods are provided to filter the events that are visible. * * @author <a href="mailto:[EMAIL PROTECTED]">Oliver Burn</a> */ class MyTableModel extends AbstractTableModel { /** used to log messages **/ private static final Category LOG = Category.getInstance(MyTableModel.class); /** use the compare logging events **/ private static final Comparator MY_COMP = new Comparator() { /** @see Comparator **/ public int compare(Object aObj1, Object aObj2) { if ((aObj1 == null) && (aObj2 == null)) { return 0; // treat as equal } else if (aObj1 == null) { return -1; // null less than everything } else if (aObj2 == null) { return 1; // think about it. :-> } // will assume only have LoggingEvent final EventDetails le1 = (EventDetails) aObj1; final EventDetails le2 = (EventDetails) aObj2; if (le1.getTimeStamp() < le2.getTimeStamp()) { return 1; } // assume not two events are logged at exactly the same time return -1; } }; /** * Helper that actually processes incoming events. * @author <a href="mailto:[EMAIL PROTECTED]">Oliver Burn</a> */ private class Processor implements Runnable { /** loops getting the events **/ public void run() { while (true) { try { Thread.sleep(1000); } catch (InterruptedException e) { // ignore } synchronized (mLock) { if (mPaused) { continue; } boolean toHead = true; // were events added to head boolean needUpdate = false; final Iterator it = mPendingEvents.iterator(); while (it.hasNext()) { final EventDetails event = (EventDetails) it.next(); mAllEvents.add(event); toHead = toHead && (event == mAllEvents.first()); needUpdate = needUpdate || matchFilter(event); } mPendingEvents.clear(); if (needUpdate) { updateFilteredEvents(toHead); } } } } } /** names of the columns in the table **/ private static final String[] COL_NAMES = { "Time", "Priority", "Trace", "Category", "NDC", "Message"}; /** definition of an empty list **/ private static final EventDetails[] EMPTY_LIST = new EventDetails[] {}; /** used to format dates **/ private static final DateFormat DATE_FORMATTER = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM); /** the lock to control access **/ private final Object mLock = new Object(); /** set of all logged events - not filtered **/ private final SortedSet mAllEvents = new TreeSet(MY_COMP); /** events that are visible after filtering **/ private EventDetails[] mFilteredEvents = EMPTY_LIST; /** list of events that are buffered for processing **/ private final List mPendingEvents = new ArrayList(); /** indicates whether event collection is paused to the UI **/ private boolean mPaused = false; /** filter for the thread **/ private String mThreadFilter = ""; /** filter for the message **/ private String mMessageFilter = ""; /** filter for the NDC **/ private String mNDCFilter = ""; /** filter for the category **/ private String mCategoryFilter = ""; /** filter for the priority **/ private Priority mPriorityFilter = Priority.DEBUG; /** * Creates a new <code>MyTableModel</code> instance. * */ MyTableModel() { final Thread t = new Thread(new Processor()); t.setDaemon(true); t.start(); } //////////////////////////////////////////////////////////////////////////// // Table Methods //////////////////////////////////////////////////////////////////////////// /** @see TableModel **/ public int getRowCount() { synchronized (mLock) { return mFilteredEvents.length; } } /** @see TableModel **/ public int getColumnCount() { // does not need to be synchronized return COL_NAMES.length; } /** @see TableModel **/ public String getColumnName(int aCol) { // does not need to be synchronized return COL_NAMES[aCol]; } /** @see TableModel **/ public Class getColumnClass(int aCol) { // does not need to be synchronized return (aCol == 2) ? Boolean.class : Object.class; } /** @see TableModel **/ public Object getValueAt(int aRow, int aCol) { synchronized (mLock) { final EventDetails event = mFilteredEvents[aRow]; if (aCol == 0) { return DATE_FORMATTER.format(new Date(event.getTimeStamp())); } else if (aCol == 1) { return event.getPriority(); } else if (aCol == 2) { return (event.getThrowableStrRep() == null) ? Boolean.FALSE : Boolean.TRUE; } else if (aCol == 3) { return event.getCategoryName(); } else if (aCol == 4) { return event.getNDC(); } return event.getMessage(); } } //////////////////////////////////////////////////////////////////////////// // Public Methods //////////////////////////////////////////////////////////////////////////// /** * Sets the priority to filter events on. Only events of equal or higher * property are now displayed. * * @param aPriority the priority to filter on */ public void setPriorityFilter(Priority aPriority) { synchronized (mLock) { mPriorityFilter = aPriority; updateFilteredEvents(false); } } /** * Set the filter for the thread field. * * @param aStr the string to match */ public void setThreadFilter(String aStr) { synchronized (mLock) { mThreadFilter = aStr.trim(); updateFilteredEvents(false); } } /** * Set the filter for the message field. * * @param aStr the string to match */ public void setMessageFilter(String aStr) { synchronized (mLock) { mMessageFilter = aStr.trim(); updateFilteredEvents(false); } } /** * Set the filter for the NDC field. * * @param aStr the string to match */ public void setNDCFilter(String aStr) { synchronized (mLock) { mNDCFilter = aStr.trim(); updateFilteredEvents(false); } } /** * Set the filter for the category field. * * @param aStr the string to match */ public void setCategoryFilter(String aStr) { synchronized (mLock) { mCategoryFilter = aStr.trim(); updateFilteredEvents(false); } } /** * Add an event to the list. * * @param aEvent a <code>EventDetails</code> value */ public void addEvent(EventDetails aEvent) { synchronized (mLock) { mPendingEvents.add(aEvent); } } /** * Clear the list of all events. */ public void clear() { synchronized (mLock) { mAllEvents.clear(); mFilteredEvents = new EventDetails[0]; mPendingEvents.clear(); fireTableDataChanged(); } } /** Toggle whether collecting events **/ public void toggle() { synchronized (mLock) { mPaused = !mPaused; } } /** @return whether currently paused collecting events **/ public boolean isPaused() { synchronized (mLock) { return mPaused; } } /** * Get the throwable information at a specified row in the filtered events. * * @param aRow the row index of the event * @return the throwable information */ public EventDetails getEventDetails(int aRow) { synchronized (mLock) { return mFilteredEvents[aRow]; } } //////////////////////////////////////////////////////////////////////////// // Private methods //////////////////////////////////////////////////////////////////////////// /** * Update the filtered events data structure. * @param aInsertedToFront indicates whether events were added to front of * the events. If true, then the current first event must still exist * in the list after the filter is applied. */ private void updateFilteredEvents(boolean aInsertedToFront) { final List filtered = new ArrayList(); final Iterator it = mAllEvents.iterator(); while (it.hasNext()) { final EventDetails event = (EventDetails) it.next(); if (matchFilter(event)) { filtered.add(event); } } final EventDetails lastFirst = (mFilteredEvents.length == 0) ? null : mFilteredEvents[0]; mFilteredEvents = (EventDetails[]) filtered.toArray(EMPTY_LIST); if (aInsertedToFront && (lastFirst != null)) { final int index = filtered.indexOf(lastFirst); if (index < 1) { LOG.warn("In strange state"); fireTableDataChanged(); } else { fireTableRowsInserted(0, index - 1); } } else { fireTableDataChanged(); } } /** * Returns whether an event matches the filters. * * @param aEvent the event to check for a match * @return whether the event matches */ private boolean matchFilter(EventDetails aEvent) { if (aEvent.getPriority().isGreaterOrEqual(mPriorityFilter) && (aEvent.getThreadName().indexOf(mThreadFilter) >= 0) && (aEvent.getCategoryName().indexOf(mCategoryFilter) >= 0) && ((mNDCFilter.length() == 0) || ((aEvent.getNDC() != null) && (aEvent.getNDC().indexOf(mNDCFilter) >= 0)))) { final String rm = aEvent.getMessage(); if (rm == null) { // only match if we have not filtering in place return (mMessageFilter.length() == 0); } else { return (rm.indexOf(mMessageFilter) >= 0); } } return false; // by default not match } } 1.1 jakarta-log4j/src/java/org/apache/log4j/chainsaw/Main.java Index: Main.java =================================================================== /* * Copyright (C) The Apache Software Foundation. All rights reserved. * * This software is published under the terms of the Apache Software * License version 1.1, a copy of which has been included with this * distribution in the LICENSE.txt file. */ package org.apache.log4j.chainsaw; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.IOException; import java.util.Properties; import javax.swing.BorderFactory; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTable; import javax.swing.ListSelectionModel; import org.apache.log4j.Category; import org.apache.log4j.PropertyConfigurator; /** * The main application. * * @author <a href="mailto:[EMAIL PROTECTED]">Oliver Burn</a> */ public class Main extends JFrame { /** the default port number to listen on **/ private static final int DEFAULT_PORT = 4445; /** name of property for port name **/ public static final String PORT_PROP_NAME = "chainsaw.port"; /** use to log messages **/ private static final Category LOG = Category.getInstance(Main.class); /** * Creates a new <code>Main</code> instance. */ private Main() { super("CHAINSAW - Log4J Log Viewer"); // create the all important model final MyTableModel model = new MyTableModel(); //Create the menu bar. final JMenuBar menuBar = new JMenuBar(); setJMenuBar(menuBar); final JMenu menu = new JMenu("File"); menuBar.add(menu); try { final LoadXMLAction lxa = new LoadXMLAction(this, model); final JMenuItem loadMenuItem = new JMenuItem("Load file..."); menu.add(loadMenuItem); loadMenuItem.addActionListener(lxa); } catch (Exception e) { LOG.info("Unable to create the action to load XML files", e); JOptionPane.showMessageDialog( this, "Unable to create a XML parser - unable to load XML events.", "CHAINSAW", JOptionPane.ERROR_MESSAGE); } final JMenuItem exitMenuItem = new JMenuItem("Exit"); menu.add(exitMenuItem); exitMenuItem.addActionListener(ExitAction.INSTANCE); // Add control panel final ControlPanel cp = new ControlPanel(model); getContentPane().add(cp, BorderLayout.NORTH); // Create the table final JTable table = new JTable(model); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); final JScrollPane scrollPane = new JScrollPane(table); scrollPane.setBorder(BorderFactory.createTitledBorder("Events: ")); scrollPane.setPreferredSize(new Dimension(900, 300)); // Create the details final JPanel details = new DetailPanel(table, model); details.setPreferredSize(new Dimension(900, 300)); // Add the table and stack trace into a splitter final JSplitPane jsp = new JSplitPane(JSplitPane.VERTICAL_SPLIT, scrollPane, details); getContentPane().add(jsp, BorderLayout.CENTER); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent aEvent) { ExitAction.INSTANCE.actionPerformed(null); } }); pack(); setVisible(true); setupReceiver(model); } /** * Setup recieving messages. * * @param aModel a <code>MyTableModel</code> value */ private void setupReceiver(MyTableModel aModel) { int port = DEFAULT_PORT; final String strRep = System.getProperty(PORT_PROP_NAME); if (strRep != null) { try { port = Integer.parseInt(strRep); } catch (NumberFormatException nfe) { LOG.fatal("Unable to parse " + PORT_PROP_NAME + " property with value " + strRep + "."); JOptionPane.showMessageDialog( this, "Unable to parse port number from '" + strRep + "', quitting.", "CHAINSAW", JOptionPane.ERROR_MESSAGE); System.exit(1); } } try { final LoggingReceiver lr = new LoggingReceiver(aModel, port); lr.start(); } catch (IOException e) { LOG.fatal("Unable to connect to socket server, quiting", e); JOptionPane.showMessageDialog( this, "Unable to create socket on port " + port + ", quitting.", "CHAINSAW", JOptionPane.ERROR_MESSAGE); System.exit(1); } } //////////////////////////////////////////////////////////////////////////// // static methods //////////////////////////////////////////////////////////////////////////// /** initialise log4j **/ private static void initLog4J() { final Properties props = new Properties(); props.setProperty("log4j.rootCategory", "DEBUG, A1"); props.setProperty("log4j.appender.A1", "org.apache.log4j.ConsoleAppender"); props.setProperty("log4j.appender.A1.layout", "org.apache.log4j.TTCCLayout"); PropertyConfigurator.configure(props); } /** * The main method. * * @param aArgs ignored */ public static void main(String[] aArgs) { initLog4J(); new Main(); } } 1.1 jakarta-log4j/src/java/org/apache/log4j/chainsaw/LoggingReceiver.java Index: LoggingReceiver.java =================================================================== /* * Copyright (C) The Apache Software Foundation. All rights reserved. * * This software is published under the terms of the Apache Software * License version 1.1, a copy of which has been included with this * distribution in the LICENSE.txt file. */ package org.apache.log4j.chainsaw; import java.io.EOFException; import java.io.IOException; import java.io.ObjectInputStream; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import org.apache.log4j.Category; import org.apache.log4j.spi.LoggingEvent; /** * A daemon thread the processes connections from a * <code>org.apache.log4j.net.SocketAppender.html</code>. * * @author <a href="mailto:[EMAIL PROTECTED]">Oliver Burn</a> */ class LoggingReceiver extends Thread { /** used to log messages **/ private static final Category LOG = Category.getInstance(LoggingReceiver.class); /** * Helper that actually processes a client connection. It receives events * and adds them to the supplied model. * * @author <a href="mailto:[EMAIL PROTECTED]">Oliver Burn</a> */ private class Slurper implements Runnable { /** socket connection to read events from **/ private final Socket mClient; /** * Creates a new <code>Slurper</code> instance. * * @param aClient socket to receive events from */ Slurper(Socket aClient) { mClient = aClient; } /** loops getting the events **/ public void run() { LOG.debug("Starting to get data"); try { final ObjectInputStream ois = new ObjectInputStream(mClient.getInputStream()); while (true) { final LoggingEvent event = (LoggingEvent) ois.readObject(); mModel.addEvent(new EventDetails(event)); } } catch (EOFException e) { LOG.info("Reached EOF, closing connection"); } catch (SocketException e) { LOG.info("Caught SocketException, closing connection"); } catch (IOException e) { LOG.warn("Got IOException, closing connection", e); } catch (ClassNotFoundException e) { LOG.warn("Got ClassNotFoundException, closing connection", e); } try { mClient.close(); } catch (IOException e) { LOG.warn("Error closing connection", e); } } } /** where to put the events **/ private final MyTableModel mModel; /** server for listening for connections **/ private final ServerSocket mSvrSock; /** * Creates a new <code>LoggingReceiver</code> instance. * * @param aModel model to place put received into * @param aPort port to listen on * @throws IOException if an error occurs */ LoggingReceiver(MyTableModel aModel, int aPort) throws IOException { setDaemon(true); mModel = aModel; mSvrSock = new ServerSocket(aPort); } /** Listens for client connections **/ public void run() { LOG.info("Thread started"); try { while (true) { LOG.debug("Waiting for a connection"); final Socket client = mSvrSock.accept(); LOG.debug("Got a connection from " + client.getInetAddress().getHostName()); final Thread t = new Thread(new Slurper(client)); t.setDaemon(true); t.start(); } } catch (IOException e) { LOG.error("Error in accepting connections, stopping.", e); } } } 1.1 jakarta-log4j/src/java/org/apache/log4j/chainsaw/LoadXMLAction.java Index: LoadXMLAction.java =================================================================== /* * Copyright (C) The Apache Software Foundation. All rights reserved. * * This software is published under the terms of the Apache Software * License version 1.1, a copy of which has been included with this * distribution in the LICENSE.txt file. */ package org.apache.log4j.chainsaw; import java.awt.event.ActionEvent; import java.io.File; import java.io.IOException; import java.io.StringReader; import javax.swing.AbstractAction; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParserFactory; import org.apache.log4j.Category; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; /** * Encapsulates the action to load an XML file. * * @author <a href="mailto:[EMAIL PROTECTED]">Oliver Burn</a> * @version 1.0 */ class LoadXMLAction extends AbstractAction { /** use to log messages **/ private static final Category LOG = Category.getInstance(LoadXMLAction.class); /** the parent frame **/ private final JFrame mParent; /** * the file chooser - configured to allow only the selection of a * single file. */ private final JFileChooser mChooser = new JFileChooser(); { mChooser.setMultiSelectionEnabled(false); mChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); } /** parser to read XML files **/ private final XMLReader mParser; /** the content handler **/ private final XMLFileHandler mHandler; /** * Creates a new <code>LoadXMLAction</code> instance. * * @param aParent the parent frame * @param aModel the model to add events to * @exception SAXException if an error occurs * @throws ParserConfigurationException if an error occurs */ LoadXMLAction(JFrame aParent, MyTableModel aModel) throws SAXException, ParserConfigurationException { mParent = aParent; mHandler = new XMLFileHandler(aModel); mParser = SAXParserFactory.newInstance().newSAXParser().getXMLReader(); mParser.setContentHandler(mHandler); } /** * Prompts the user for a file to load events from. * @param aIgnore an <code>ActionEvent</code> value */ public void actionPerformed(ActionEvent aIgnore) { LOG.info("load file called"); if (mChooser.showOpenDialog(mParent) == JFileChooser.APPROVE_OPTION) { LOG.info("Need to load a file"); final File chosen = mChooser.getSelectedFile(); LOG.info("loading the contents of " + chosen.getAbsolutePath()); try { final int num = loadFile(chosen.getAbsolutePath()); JOptionPane.showMessageDialog( mParent, "Loaded " + num + " events.", "CHAINSAW", JOptionPane.INFORMATION_MESSAGE); } catch (Exception e) { LOG.warn("caught an exception loading the file", e); JOptionPane.showMessageDialog( mParent, "Error parsing file - " + e.getMessage(), "CHAINSAW", JOptionPane.ERROR_MESSAGE); } } } /** * Loads the contents of file into the model * * @param aFile the file to extract events from * @return the number of events loaded * @throws SAXException if an error occurs * @throws IOException if an error occurs */ private int loadFile(String aFile) throws SAXException, IOException { synchronized (mParser) { // Create a dummy document to parse the file final StringBuffer buf = new StringBuffer(); buf.append("<?xml version=\"1.0\" standalone=\"yes\"?>\n"); buf.append("<!DOCTYPE log4j:eventSet "); buf.append("[<!ENTITY data SYSTEM \"file:///"); buf.append(aFile); buf.append("\">]>\n"); buf.append("<log4j:eventSet xmlns:log4j=\"Claira\">\n"); buf.append("&data;\n"); buf.append("</log4j:eventSet>\n"); final InputSource is = new InputSource(new StringReader(buf.toString())); mParser.parse(is); return mHandler.getNumEvents(); } } } 1.1 jakarta-log4j/src/java/org/apache/log4j/chainsaw/ExitAction.java Index: ExitAction.java =================================================================== /* * Copyright (C) The Apache Software Foundation. All rights reserved. * * This software is published under the terms of the Apache Software * License version 1.1, a copy of which has been included with this * distribution in the LICENSE.txt file. */ package org.apache.log4j.chainsaw; import java.awt.event.ActionEvent; import javax.swing.AbstractAction; import org.apache.log4j.Category; /** * Encapsulates the action to exit. * * @author <a href="mailto:[EMAIL PROTECTED]">Oliver Burn</a> * @version 1.0 */ class ExitAction extends AbstractAction { /** use to log messages **/ private static final Category LOG = Category.getInstance(ExitAction.class); /** The instance to share **/ public static final ExitAction INSTANCE = new ExitAction(); /** Stop people creating instances **/ private ExitAction() {} /** * Will shutdown the application. * @param aIgnore ignored */ public void actionPerformed(ActionEvent aIgnore) { LOG.info("shutting down"); System.exit(0); } } 1.1 jakarta-log4j/src/java/org/apache/log4j/chainsaw/EventDetails.java Index: EventDetails.java =================================================================== /* * Copyright (C) The Apache Software Foundation. All rights reserved. * * This software is published under the terms of the Apache Software * License version 1.1, a copy of which has been included with this * distribution in the LICENSE.txt file. */ package org.apache.log4j.chainsaw; import org.apache.log4j.Priority; import org.apache.log4j.spi.LoggingEvent; /** * Represents the details of a logging event. It is intended to overcome the * problem that a LoggingEvent cannot be constructed with purely fake data. * * @author <a href="mailto:[EMAIL PROTECTED]">Oliver Burn</a> * @version 1.0 */ class EventDetails { /** the time of the event **/ private final long mTimeStamp; /** the priority of the event **/ private final Priority mPriority; /** the category of the event **/ private final String mCategoryName; /** the NDC for the event **/ private final String mNDC; /** the thread for the event **/ private final String mThreadName; /** the msg for the event **/ private final String mMessage; /** the throwable details the event **/ private final String[] mThrowableStrRep; /** the location details for the event **/ private final String mLocationDetails; /** * Creates a new <code>EventDetails</code> instance. * @param aTimeStamp a <code>long</code> value * @param aPriority a <code>Priority</code> value * @param aCategoryName a <code>String</code> value * @param aNDC a <code>String</code> value * @param aThreadName a <code>String</code> value * @param aMessage a <code>String</code> value * @param aThrowableStrRep a <code>String[]</code> value * @param aLocationDetails a <code>String</code> value */ EventDetails(long aTimeStamp, Priority aPriority, String aCategoryName, String aNDC, String aThreadName, String aMessage, String[] aThrowableStrRep, String aLocationDetails) { mTimeStamp = aTimeStamp; mPriority = aPriority; mCategoryName = aCategoryName; mNDC = aNDC; mThreadName = aThreadName; mMessage = aMessage; mThrowableStrRep = aThrowableStrRep; mLocationDetails = aLocationDetails; } /** * Creates a new <code>EventDetails</code> instance. * * @param aEvent a <code>LoggingEvent</code> value */ EventDetails(LoggingEvent aEvent) { this(aEvent.timeStamp, aEvent.level, aEvent.categoryName, aEvent.getNDC(), aEvent.getThreadName(), aEvent.getRenderedMessage(), aEvent.getThrowableStrRep(), (aEvent.getLocationInformation() == null) ? null : aEvent.getLocationInformation().fullInfo); } /** @see #mTimeStamp **/ long getTimeStamp() { return mTimeStamp; } /** @see #mPriority **/ Priority getPriority() { return mPriority; } /** @see #mCategoryName **/ String getCategoryName() { return mCategoryName; } /** @see #mNDC **/ String getNDC() { return mNDC; } /** @see #mThreadName **/ String getThreadName() { return mThreadName; } /** @see #mMessage **/ String getMessage() { return mMessage; } /** @see #mLocationDetails **/ String getLocationDetails(){ return mLocationDetails; } /** @see #mThrowableStrRep **/ String[] getThrowableStrRep() { return mThrowableStrRep; } } 1.1 jakarta-log4j/src/java/org/apache/log4j/chainsaw/DetailPanel.java Index: DetailPanel.java =================================================================== /* * Copyright (C) The Apache Software Foundation. All rights reserved. * * This software is published under the terms of the Apache Software * License version 1.1, a copy of which has been included with this * distribution in the LICENSE.txt file. */ package org.apache.log4j.chainsaw; import java.awt.BorderLayout; import java.text.MessageFormat; import java.util.Date; import javax.swing.BorderFactory; import javax.swing.JEditorPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.apache.log4j.Category; /** * A panel for showing a stack trace. * * @author <a href="mailto:[EMAIL PROTECTED]">Oliver Burn</a> */ class DetailPanel extends JPanel implements ListSelectionListener { /** used to log events **/ private static final Category LOG = Category.getInstance(DetailPanel.class); /** used to format the logging event **/ private static final MessageFormat FORMATTER = new MessageFormat( "<b>Time:</b> <code>{0,time,medium}</code>" + " <b>Priority:</b> <code>{1}</code>" + " <b>Thread:</b> <code>{2}</code>" + " <b>NDC:</b> <code>{3}</code>" + "<br><b>Category:</b> <code>{4}</code>" + "<br><b>Location:</b> <code>{5}</code>" + "<br><b>Message:</b>" + "<pre>{6}</pre>" + "<b>Throwable:</b>" + "<pre>{7}</pre>"); /** the model for the data to render **/ private final MyTableModel mModel; /** pane for rendering detail **/ private final JEditorPane mDetails; /** * Creates a new <code>DetailPanel</code> instance. * * @param aTable the table to listen for selections on * @param aModel the model backing the table */ DetailPanel(JTable aTable, final MyTableModel aModel) { mModel = aModel; setLayout(new BorderLayout()); setBorder(BorderFactory.createTitledBorder("Details: ")); mDetails = new JEditorPane(); mDetails.setEditable(false); mDetails.setContentType("text/html"); add(new JScrollPane(mDetails), BorderLayout.CENTER); final ListSelectionModel rowSM = aTable.getSelectionModel(); rowSM.addListSelectionListener(this); } /** @see ListSelectionListener **/ public void valueChanged(ListSelectionEvent aEvent) { //Ignore extra messages. if (aEvent.getValueIsAdjusting()) { return; } final ListSelectionModel lsm = (ListSelectionModel) aEvent.getSource(); if (lsm.isSelectionEmpty()) { mDetails.setText("Nothing selected"); } else { final int selectedRow = lsm.getMinSelectionIndex(); final EventDetails e = mModel.getEventDetails(selectedRow); final Object[] args = { new Date(e.getTimeStamp()), e.getPriority(), escape(e.getThreadName()), escape(e.getNDC()), escape(e.getCategoryName()), escape(e.getLocationDetails()), escape(e.getMessage()), escape(getThrowableStrRep(e)) }; mDetails.setText(FORMATTER.format(args)); mDetails.setCaretPosition(0); } } //////////////////////////////////////////////////////////////////////////// // Private methods //////////////////////////////////////////////////////////////////////////// /** * Returns a string representation of a throwable. * * @param aEvent contains the throwable information * @return a <code>String</code> value */ private static String getThrowableStrRep(EventDetails aEvent) { final String[] strs = aEvent.getThrowableStrRep(); if (strs == null) { return null; } final StringBuffer sb = new StringBuffer(); for (int i = 0; i < strs.length; i++) { sb.append(strs[i]).append("\n"); } return sb.toString(); } /** * Escape <, > & and " as their entities. It is very * dumb about & handling. * @param aStr the String to escape. * @return the escaped String */ private String escape(String aStr) { if (aStr == null) { return null; } final StringBuffer buf = new StringBuffer(); for (int i = 0; i < aStr.length(); i++) { char c = aStr.charAt(i); switch (c) { case '<': buf.append("<"); break; case '>': buf.append(">"); break; case '\"': buf.append("""); break; case '&': buf.append("&"); break; default: buf.append(c); break; } } return buf.toString(); } } 1.1 jakarta-log4j/src/java/org/apache/log4j/chainsaw/ControlPanel.java Index: ControlPanel.java =================================================================== /* * Copyright (C) The Apache Software Foundation. All rights reserved. * * This software is published under the terms of the Apache Software * License version 1.1, a copy of which has been included with this * distribution in the LICENSE.txt file. */ package org.apache.log4j.chainsaw; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import org.apache.log4j.Category; import org.apache.log4j.Priority; /** * Represents the controls for filtering, pausing, exiting, etc. * * @author <a href="mailto:[EMAIL PROTECTED]">Oliver Burn</a> */ class ControlPanel extends JPanel { /** use the log messages **/ private static final Category LOG = Category.getInstance(ControlPanel.class); /** * Creates a new <code>ControlPanel</code> instance. * * @param aModel the model to control */ ControlPanel(final MyTableModel aModel) { setBorder(BorderFactory.createTitledBorder("Controls: ")); final GridBagLayout gridbag = new GridBagLayout(); final GridBagConstraints c = new GridBagConstraints(); setLayout(gridbag); // Pad everything c.ipadx = 5; c.ipady = 5; // Add the 1st column of labels c.gridx = 0; c.anchor = GridBagConstraints.EAST; c.gridy = 0; JLabel label = new JLabel("Filter Level:"); gridbag.setConstraints(label, c); add(label); c.gridy++; label = new JLabel("Filter Thread:"); gridbag.setConstraints(label, c); add(label); c.gridy++; label = new JLabel("Filter Category:"); gridbag.setConstraints(label, c); add(label); c.gridy++; label = new JLabel("Filter NDC:"); gridbag.setConstraints(label, c); add(label); c.gridy++; label = new JLabel("Filter Message:"); gridbag.setConstraints(label, c); add(label); // Add the 2nd column of filters c.weightx = 1; //c.weighty = 1; c.gridx = 1; c.anchor = GridBagConstraints.WEST; c.gridy = 0; final Priority[] allPriorities = Priority.getAllPossiblePriorities(); final JComboBox priorities = new JComboBox(allPriorities); final Priority lowest = allPriorities[allPriorities.length - 1]; priorities.setSelectedItem(lowest); aModel.setPriorityFilter(lowest); gridbag.setConstraints(priorities, c); add(priorities); priorities.setEditable(false); priorities.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent aEvent) { aModel.setPriorityFilter( (Priority) priorities.getSelectedItem()); } }); c.fill = GridBagConstraints.HORIZONTAL; c.gridy++; final JTextField threadField = new JTextField(""); threadField.getDocument().addDocumentListener(new DocumentListener () { public void insertUpdate(DocumentEvent aEvent) { aModel.setThreadFilter(threadField.getText()); } public void removeUpdate(DocumentEvent aEvente) { aModel.setThreadFilter(threadField.getText()); } public void changedUpdate(DocumentEvent aEvent) { aModel.setThreadFilter(threadField.getText()); } }); gridbag.setConstraints(threadField, c); add(threadField); c.gridy++; final JTextField catField = new JTextField(""); catField.getDocument().addDocumentListener(new DocumentListener () { public void insertUpdate(DocumentEvent aEvent) { aModel.setCategoryFilter(catField.getText()); } public void removeUpdate(DocumentEvent aEvent) { aModel.setCategoryFilter(catField.getText()); } public void changedUpdate(DocumentEvent aEvent) { aModel.setCategoryFilter(catField.getText()); } }); gridbag.setConstraints(catField, c); add(catField); c.gridy++; final JTextField ndcField = new JTextField(""); ndcField.getDocument().addDocumentListener(new DocumentListener () { public void insertUpdate(DocumentEvent aEvent) { aModel.setNDCFilter(ndcField.getText()); } public void removeUpdate(DocumentEvent aEvent) { aModel.setNDCFilter(ndcField.getText()); } public void changedUpdate(DocumentEvent aEvent) { aModel.setNDCFilter(ndcField.getText()); } }); gridbag.setConstraints(ndcField, c); add(ndcField); c.gridy++; final JTextField msgField = new JTextField(""); msgField.getDocument().addDocumentListener(new DocumentListener () { public void insertUpdate(DocumentEvent aEvent) { aModel.setMessageFilter(msgField.getText()); } public void removeUpdate(DocumentEvent aEvent) { aModel.setMessageFilter(msgField.getText()); } public void changedUpdate(DocumentEvent aEvent) { aModel.setMessageFilter(msgField.getText()); } }); gridbag.setConstraints(msgField, c); add(msgField); // Add the 3rd column of buttons c.weightx = 0; c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.EAST; c.gridx = 2; c.gridy = 0; final JButton exitButton = new JButton("Exit"); exitButton.setMnemonic('x'); exitButton.addActionListener(ExitAction.INSTANCE); gridbag.setConstraints(exitButton, c); add(exitButton); c.gridy++; final JButton clearButton = new JButton("Clear"); clearButton.setMnemonic('c'); clearButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent aEvent) { aModel.clear(); } }); gridbag.setConstraints(clearButton, c); add(clearButton); c.gridy++; final JButton toggleButton = new JButton("Pause"); toggleButton.setMnemonic('p'); toggleButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent aEvent) { aModel.toggle(); toggleButton.setText( aModel.isPaused() ? "Resume" : "Pause"); } }); gridbag.setConstraints(toggleButton, c); add(toggleButton); } }
-- To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]> For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>