Welcome to chainsaw!
At 07:51 23.03.2002 +0000, you wrote: >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]> -- Ceki My link of the month: http://java.sun.com/aboutJava/standardization/ -- To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]> For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>