sdeboy 2003/06/09 21:45:52
Modified: src/java/org/apache/log4j/jdbc JDBCReceiver.java
src/java/org/apache/log4j/chainsaw
ChainsawCyclicBufferTableModel.java
Log:
- ChainsawCyclicBufferTableModel now ignores duplicate events (they must be
identical, including properties -specifically a log4jid must be provided- and
timestamp).
- Also updated jdbcreceiver to support a refreshMillis param which, if set, will
re-execute the SQL retrieve statement every refreshMillis milliseconds.
- The combined benefit of these two changes makes JDBCReceiver a valuable receiver
for use with Chainsaw.
- Events can be directly logged to a database and Chainsaw will only insert new
events into the tablemodel.
Revision Changes Path
1.3 +144 -119
jakarta-log4j-sandbox/src/java/org/apache/log4j/jdbc/JDBCReceiver.java
Index: JDBCReceiver.java
===================================================================
RCS file:
/home/cvs/jakarta-log4j-sandbox/src/java/org/apache/log4j/jdbc/JDBCReceiver.java,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- JDBCReceiver.java 6 Jun 2003 05:33:04 -0000 1.2
+++ JDBCReceiver.java 10 Jun 2003 04:45:51 -0000 1.3
@@ -65,7 +65,6 @@
import java.text.ParseException;
import java.util.Calendar;
-import java.util.Date;
import java.util.Hashtable;
import java.util.StringTokenizer;
@@ -76,39 +75,60 @@
* This receiver executes the SQL statement defined in the plugin configuration
once,
* converting the rows it finds into LoggingEvents, and then ends.
*
- * The configuration of this plugin is very similar to the JDBCAppender, however a
+ * The configuration of this plugin is very similar to the JDBCAppender, however a
* SELECT statement must be provided instead of an INSERT statement.
*
- * The select statement must provide all fields which define a LoggingEvent, with
- * the column names matching this list: LOGGER, TIMESTAMP, LEVEL, THREAD, MESSAGE,
+ * The select statement must provide all fields which define a LoggingEvent, with
+ * the column names matching this list: LOGGER, TIMESTAMP, LEVEL, THREAD, MESSAGE,
* NDC, MDC, CLASS, METHOD, FILE, LINE, PROPERTIES, EXCEPTION
*
- * If the source table doesn't provide a column for any of the fields, the field
must
- * still be provided in the SELECT statement. For example, if a JDBCAppender was
used
- * to write only a timestamp, level and patternlayout combination of message and
other
+ * If the source table doesn't provide a column for any of the fields, the field
must
+ * still be provided in the SELECT statement. For example, if a JDBCAppender was
used
+ * to write only a timestamp, level and patternlayout combination of message and
other
* fields, here is a sample SQL statement you would provide as the plugin 'sql'
param:
*
- * param name="sql" value='select "" as LOGGER, timestamp as TIMESTAMP, level as
LEVEL,
- * "" as THREAD, message as MESSAGE, "" as NDC, "" as MDC, "" as CLASS, "" as
METHOD,
- * "" as FILE, "" as LINE, "{{log4japp,databaselogs,log4jmachinename,mymachine}}"
- * as PROPERTIES, "" as EXCEPTION from logtable'
+ * EXAMPLE MYSQL SELECT STATEMENT WHICH CAN BE USED TO PROVIDE EVENTS TO CHAINSAW -
+ * (counter is an autoincrement int column and timestamp is a datetime column):
+ *
+ * param name="sql" value='select logger as LOGGER, timestamp as TIMESTAMP,
+ * level as LEVEL, thread as THREAD, message as MESSAGE, ndc as NDC, mdc as MDC,
+ * class as CLASS, method as METHOD, file as FILE, line as LINE,
+ * concat("{{log4japp,databaselogs,log4jmachinename,mymachine,log4jid,", COUNTER,
"}}")
+ * as PROPERTIES, "" as EXCEPTION from logtable'
*
- * In other words, if a number of LoggingEvent properties were combined into one
field
- * in the database, the combined field should be set as the MESSAGE column in the
+ * In other words, if a number of LoggingEvent properties were combined into one
field
+ * in the database, the combined field should be set as the MESSAGE column in the
* SELECT statement. Missing columns should be set to "".
- *
+ *
* Make sure to alias the column names if needed to match the list provided above.
*
- * NOTE: Patternlayout doesn't support Properties and JDBCAppender doesn't support
- * exceptions, but the fields can be defined in the SQL statement and included in
+ * NOTE: Patternlayout doesn't support Properties and JDBCAppender doesn't support
+ * exceptions, but the fields can be defined in the SQL statement and included in
* the event.
- *
- * This means that log4japp and/or log4jmachinename properties can be provided and
+ *
+ * This means that log4japp and/or log4jmachinename properties can be provided and
* the properties may be used to create a unique tab for the events.
- *
- * Both {{name, value, name2, value2}} formats and formats without the double
braces
- * are supported for MDC and properties fields.
*
+ * Both {{name, value, name2, value2}} formats and formats without the double braces
+ * are supported for MDC and properties fields.
+ *
+ * NOTE: If refreshMillis is not set, the receiver will run the SQL ONCE. If it is
set,
+ * the SQL will be ran every refreshMillis milliseconds.
+ *
+ * WARNING: Using refreshMillis with an event processing tool that doesn't know how
+ * to ignore duplicate events will result in duplicate events being processed.
+ *
+ * CREATING EVENTS USABLE BY CHAINSAW:
+ * Chainsaw's event reception ignores duplicate event delivery, so refreshMillis
can be
+ * set and JDBCReceiver can be used as a primary receiver with Chainsaw - allowing
+ * a timed re-retrieve of events from a database into the UI for analysis of events.
+ *
+ * Include the properties as provided in the example SQL above to successfully get
+ * events to be delivered into Chainsaw. The log4jid property must be provided by
the
+ * database and the timestamp field must be a datetime. The log4jmachinename and
log4japp
+ * properties are specific to your application and define which unique tab the
events are
+ * delivered to.
+ *
* @author Scott Deboy <[EMAIL PROTECTED]>
*
*/
@@ -131,6 +151,7 @@
protected String databasePassword = "mypassword";
protected Connection connection = null;
protected String sqlStatement = "";
+ protected String refreshMillis = null;
public JDBCReceiver() {
}
@@ -182,6 +203,14 @@
setActive(false);
}
+ public void setRefreshMillis(String s) {
+ refreshMillis = s;
+ }
+
+ public String getRefreshMillis() {
+ return refreshMillis;
+ }
+
public void setSql(String s) {
sqlStatement = s;
}
@@ -234,113 +263,109 @@
public void run() {
setActive(true);
- try {
- Logger logger = null;
- long timeStamp = 0L;
- String level = null;
- String threadName = null;
- Object message = null;
- String ndc = null;
- Hashtable mdc = null;
- String[] exception = null;
- String className = null;
- String methodName = null;
- String fileName = null;
- String lineNumber = null;
- Hashtable properties = null;
-
- Statement statement = getConnection().createStatement();
- ResultSet rs = statement.executeQuery(sqlStatement);
- rs.beforeFirst();
-
- while (rs.next()) {
- logger = Logger.getLogger(rs.getString("LOGGER"));
-
- //TIMESTAMP PARSING IS NOT WORKING for the default format.
-
- //The default timestamp format, ISO8601DateFormat, doesn't have a
- //parse method to convert the format back to a date.
- //This parse process just tries to use the default dateformat parse
- //methods set to Lenient to convert it back to a date.
- //if that fails, it will use the current time as the timestamp
- DateFormat df = DateFormat.getDateInstance(DateFormat.LONG);
- df.setLenient(true);
-
- try {
- timeStamp = df.parse(rs.getString("TIMESTAMP")).getTime();
- } catch (ParseException pe) {
- timeStamp = new Date().getTime();
- }
-
- level = rs.getString("LEVEL");
- threadName = rs.getString("THREAD");
- message = rs.getString("MESSAGE");
- ndc = rs.getString("NDC");
-
- String mdcString = rs.getString("MDC");
- mdc = new Hashtable();
-
- if (mdcString != null) {
- //support MDC being wrapped in {{name, value}} or just name, value
- if (
- (mdcString.indexOf("{{") > -1) && (mdcString.indexOf("}}") > -1)) {
- mdcString =
- mdcString.substring(
- mdcString.indexOf("{{") + 2, mdcString.indexOf("}}"));
+ do {
+ try {
+ Logger logger = null;
+ long timeStamp = 0L;
+ String level = null;
+ String threadName = null;
+ Object message = null;
+ String ndc = null;
+ Hashtable mdc = null;
+ String[] exception = null;
+ String className = null;
+ String methodName = null;
+ String fileName = null;
+ String lineNumber = null;
+ Hashtable properties = null;
+
+ Statement statement = getConnection().createStatement();
+ ResultSet rs = statement.executeQuery(sqlStatement);
+ rs.beforeFirst();
+
+ while (rs.next()) {
+ logger = Logger.getLogger(rs.getString("LOGGER"));
+ timeStamp = rs.getTimestamp("TIMESTAMP").getTime();
+
+ level = rs.getString("LEVEL");
+ threadName = rs.getString("THREAD");
+ message = rs.getString("MESSAGE");
+ ndc = rs.getString("NDC");
+
+ String mdcString = rs.getString("MDC");
+ mdc = new Hashtable();
+
+ if (mdcString != null) {
+ //support MDC being wrapped in {{name, value}} or just name, value
+ if (
+ (mdcString.indexOf("{{") > -1)
+ && (mdcString.indexOf("}}") > -1)) {
+ mdcString =
+ mdcString.substring(
+ mdcString.indexOf("{{") + 2, mdcString.indexOf("}}"));
+ }
+
+ StringTokenizer tok = new StringTokenizer(mdcString, ",");
+
+ while (tok.countTokens() > 1) {
+ mdc.put(tok.nextToken(), tok.nextToken());
+ }
}
- StringTokenizer tok = new StringTokenizer(mdcString, ",");
-
- while (tok.countTokens() > 1) {
- mdc.put(tok.nextToken(), tok.nextToken());
+ //although exception is not supported by jdbcappender, it needs to be
provided in the SQL string
+ exception = new String[] { rs.getString("EXCEPTION") };
+ className = rs.getString("CLASS");
+ methodName = rs.getString("METHOD");
+ fileName = rs.getString("FILE");
+ lineNumber = rs.getString("LINE");
+
+ //although properties are not supported by JDBCAppender, if they are
provided in the
+ //SQL they can be used here (for example, to route events to a unique
tab if
+ //the machinename and/or appname property are set)
+ String propertiesString = rs.getString("PROPERTIES");
+ properties = new Hashtable();
+
+ if (propertiesString != null) {
+ //support properties being wrapped in {{name, value}} or just name,
value
+ if (
+ (propertiesString.indexOf("{{") > -1)
+ && (propertiesString.indexOf("}}") > -1)) {
+ propertiesString =
+ propertiesString.substring(
+ propertiesString.indexOf("{{") + 2,
+ propertiesString.indexOf("}}"));
+ }
+
+ StringTokenizer tok2 =
+ new StringTokenizer(propertiesString, ",");
+
+ while (tok2.countTokens() > 1) {
+ properties.put(tok2.nextToken(), tok2.nextToken());
+ }
}
- }
- //although exception is not supported by jdbcappender, it needs to be
provided in the SQL string
- exception = new String[] {rs.getString("EXCEPTION")};
- className = rs.getString("CLASS");
- methodName = rs.getString("METHOD");
- fileName = rs.getString("FILE");
- lineNumber = rs.getString("LINE");
-
- //although properties are not supported by JDBCAppender, if they are
provided in the
- //SQL they can be used here (for example, to route events to a unique tab
if
- //the machinename and/or appname property are set)
- String propertiesString = rs.getString("PROPERTIES");
- properties = new Hashtable();
-
- if (propertiesString != null) {
- //support properties being wrapped in {{name, value}} or just name,
value
- if (
- (propertiesString.indexOf("{{") > -1)
- && (propertiesString.indexOf("}}") > -1)) {
- propertiesString =
- propertiesString.substring(
- propertiesString.indexOf("{{") + 2,
- propertiesString.indexOf("}}"));
- }
+ Level levelImpl = Level.toLevel(level);
- StringTokenizer tok2 = new StringTokenizer(propertiesString, ",");
+ LoggingEvent event =
+ new LoggingEvent(
+ logger.getName(), logger, timeStamp, levelImpl, threadName,
+ message, ndc, mdc, exception,
+ new LocationInfo(fileName, className, methodName, lineNumber),
+ properties);
- while (tok2.countTokens() > 1) {
- properties.put(tok2.nextToken(), tok2.nextToken());
- }
+ doPost(event);
}
+ } catch (SQLException se) {
+ se.printStackTrace();
+ }
- Level levelImpl = Level.toLevel(level);
-
- LoggingEvent event =
- new LoggingEvent(
- logger.getName(), logger, timeStamp, levelImpl, threadName,
- message, ndc, mdc, exception,
- new LocationInfo(fileName, className, methodName, lineNumber),
- properties);
-
- doPost(event);
+ if (refreshMillis != null) {
+ try {
+ Thread.sleep(Integer.parseInt(refreshMillis));
+ } catch (InterruptedException ie) {
+ }
}
- } catch (SQLException se) {
- se.printStackTrace();
- }
+ } while (refreshMillis != null);
}
}
}
1.12 +14 -1
jakarta-log4j-sandbox/src/java/org/apache/log4j/chainsaw/ChainsawCyclicBufferTableModel.java
Index: ChainsawCyclicBufferTableModel.java
===================================================================
RCS file:
/home/cvs/jakarta-log4j-sandbox/src/java/org/apache/log4j/chainsaw/ChainsawCyclicBufferTableModel.java,v
retrieving revision 1.11
retrieving revision 1.12
diff -u -r1.11 -r1.12
--- ChainsawCyclicBufferTableModel.java 9 Jun 2003 03:23:26 -0000 1.11
+++ ChainsawCyclicBufferTableModel.java 10 Jun 2003 04:45:51 -0000 1.12
@@ -61,7 +61,13 @@
/**
- * A CyclicBuffer implementation of the EventContainer.
+ * A CyclicBuffer implementation of the EventContainer.
+ *
+ * NOTE: This implementation prevents duplicate rows from being added to the model.
+ *
+ * Ignoring duplicates was added to support receivers which may attempt to deliver
the same
+ * event more than once but can be safely ignored (for example, the database
receiver
+ * when set to retrieve in a loop).
*
* @author Paul Smith <[EMAIL PROTECTED]>
* @author Scott Deboy <[EMAIL PROTECTED]>
@@ -266,6 +272,7 @@
boolean rowAdded = false;
synchronized (syncLock) {
+
//set the last field to the 'unfilteredevents size + 1 - an ID based on
reception order
int propertiesIndex =
ChainsawColumns.getColumnsNames().indexOf(
@@ -303,6 +310,12 @@
}
row.add(thisInt);
+
+ //prevent duplicate rows
+ if(unfilteredList.contains(row)) {
+ return false;
+ }
+
addRow(row);
if (
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]