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]

Reply via email to