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]