You are viewing a plain text version of this content. The canonical link for it is here.
Posted to log4j-cvs@jakarta.apache.org by ob...@apache.org on 2002/03/23 08:51:26 UTC
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
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:oliver@puppycrawl.com">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:oliver@puppycrawl.com">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:oliver@puppycrawl.com">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:oliver@puppycrawl.com">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:oliver@puppycrawl.com">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:oliver@puppycrawl.com">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:oliver@puppycrawl.com">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:oliver@puppycrawl.com">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:oliver@puppycrawl.com">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:oliver@puppycrawl.com">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:oliver@puppycrawl.com">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: <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>
Re: 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
Posted by Ceki Gülcü <ce...@qos.ch>.
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:oliver@puppycrawl.com">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:oliver@puppycrawl.com">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:oliver@puppycrawl.com">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:oliver@puppycrawl.com">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:oliver@puppycrawl.com">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:oliver@puppycrawl.com">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:oliver@puppycrawl.com">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:oliver@puppycrawl.com">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:oliver@puppycrawl.com">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:oliver@puppycrawl.com">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:oliver@puppycrawl.com">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: <ma...@jakarta.apache.org>
>For additional commands, e-mail: <ma...@jakarta.apache.org>
--
Ceki
My link of the month: http://java.sun.com/aboutJava/standardization/
--
To unsubscribe, e-mail: <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>