cvs commit: jakarta-log4j/src/java/org/apache/log4j/chainsaw XMLFileHandler.java MyTableModel.java Main.java LoggingReceiver.java LoadXMLAction.java ExitAction.java EventDetails.java DetailPanel.java ControlPanel.java
[email protected] 23 Mar 2002 07:51:26 -0000
| Newsgroups | gmane.comp.jakarta.log4j.cvs |
|---|---|
| Message-ID | <[email protected]> |
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]>