[logback-dev] svn commit: r2082 - in logback/trunk/logback-classic/src: main/java/ch/qos/logback/classic/hoard test/input/joran/hoard test/java/ch/qos/logback/classic test/java/ch/qos/logback/classic/hoard test/java/ch/qos/logback/classic/hoard/tracker

noreply.ceki at qos.ch noreply.ceki at qos.ch
Wed Dec 17 18:46:02 CET 2008


Author: ceki
Date: Wed Dec 17 18:46:01 2008
New Revision: 2082

Added:
   logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/hoard/AppenderTracker.java
   logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/hoard/AppenderTrackerImpl.java
   logback/trunk/logback-classic/src/test/input/joran/hoard/smoke.xml
   logback/trunk/logback-classic/src/test/input/joran/hoard/unsetDefaultValueProperty.xml
   logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/AppenderTrackerTest.java
   logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/PackageTest.java
   logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/ScenarioBasedAppenderTrackerTest.java
   logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/Simulator.java
   logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/tracker/
   logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/tracker/AppenderTrackerTImpl.java
   logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/tracker/SimulationEvent.java
   logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/tracker/TEntry.java
Modified:
   logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/hoard/HoardingAppender.java
   logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/hoard/HoardingJoranConfigurator.java
   logback/trunk/logback-classic/src/test/input/joran/hoard/hoard0.xml
   logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/AllClassicTest.java
   logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/HoardingAppenderTest.java

Log:
Code to track and clean up appenders. Related to LBCLASSIC-94
 	 

Added: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/hoard/AppenderTracker.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/hoard/AppenderTracker.java	Wed Dec 17 18:46:01 2008
@@ -0,0 +1,19 @@
+package ch.qos.logback.classic.hoard;
+
+import java.util.List;
+
+import ch.qos.logback.core.Appender;
+
+public interface AppenderTracker<E> {
+
+  static int MILLIS_IN_ONE_SECOND = 1000;
+  static int THRESHOLD = 30 * 60 * MILLIS_IN_ONE_SECOND; // 30 minutes
+
+  void put(String key, Appender<E> value, long timestamp);
+  Appender<E> get(String key, long timestamp);
+  void stopStaleAppenders(long now);
+  List<String> keyList();
+  List<Appender<E>> valueList();
+
+
+}
\ No newline at end of file

Added: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/hoard/AppenderTrackerImpl.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/hoard/AppenderTrackerImpl.java	Wed Dec 17 18:46:01 2008
@@ -0,0 +1,216 @@
+/**
+ * Logback: the generic, reliable, fast and flexible logging framework.
+ * 
+ * Copyright (C) 2000-2008, QOS.ch
+ * 
+ * This library is free software, you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation.
+ */
+package ch.qos.logback.classic.hoard;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import ch.qos.logback.core.Appender;
+
+/**
+ * Track appenders by a key. When an appender is not used for
+ * longer than THRESHOLD, stop it.
+ * @author Ceki Gulcu
+ */
+public class AppenderTrackerImpl<E> implements AppenderTracker<E> {
+
+  Map<String, Entry> map = new HashMap<String, Entry>();
+ 
+  Entry head; // least recently used entries are towards the head
+  Entry tail; // most recently used entries are towards the tail
+
+  long lastCheck = 0;
+
+  AppenderTrackerImpl() {
+    head = new Entry(null, null, 0);
+    tail = head;
+  }
+
+  /* (non-Javadoc)
+   * @see ch.qos.logback.classic.hoard.AppenderTracker#put(java.lang.String, ch.qos.logback.core.Appender, long)
+   */
+  public synchronized void put(String key, Appender<E> value, long timestamp) {
+    Entry entry = map.get(key);
+    if (entry == null) {
+      entry = new Entry(key, value, timestamp);
+      map.put(key, entry);
+    }
+    moveToTail(entry);
+  }
+
+  /* (non-Javadoc)
+   * @see ch.qos.logback.classic.hoard.AppenderTracker#get(java.lang.String, long)
+   */
+  public synchronized Appender<E> get(String key, long timestamp) {
+    Entry existing = map.get(key);
+    if (existing == null) {
+      return null;
+    } else {
+      existing.setTimestamp(timestamp);
+      moveToTail(existing);
+      return existing.value;
+    }
+  }
+
+  
+  /* (non-Javadoc)
+   * @see ch.qos.logback.classic.hoard.AppenderTracker#stopStaleAppenders(long)
+   */
+  public synchronized void stopStaleAppenders(long now) {
+    if (lastCheck + MILLIS_IN_ONE_SECOND > now) {
+      return;
+    }
+    lastCheck = now;
+    while (head.value != null && isEntryStale(head,now)) {
+      Appender appender = head.value;
+      //System.out.println("  stopping "+appender);
+      appender.stop();
+      removeHead();
+    }
+  } 
+
+  public List<String> keyList() {
+    List<String> result = new LinkedList<String>();
+    Entry e = head;
+    while (e != tail) {
+      result.add(e.key);
+      e = e.next;
+    }
+    return result;
+  }
+  
+  
+  final private boolean isEntryStale(Entry entry, long now) {
+    return ((entry.timestamp + THRESHOLD) < now);
+  }
+
+  
+  private void removeHead() {
+    // System.out.println("RemoveHead called");
+    map.remove(head.key);
+    head = head.next;
+    head.prev = null;
+  }
+
+  private void moveToTail(Entry e) {
+    rearrangePreexistingLinks(e);
+    rearrangeTailLinks(e);
+  }
+
+  private void rearrangePreexistingLinks(Entry e) {
+    if (e.prev != null) {
+      e.prev.next = e.next;
+    }
+    if (e.next != null) {
+      e.next.prev = e.prev;
+    }
+    if (head == e) {
+      head = e.next;
+    }
+  }
+
+  private void rearrangeTailLinks(Entry e) {
+    if (head == tail) {
+      head = e;
+    }
+    Entry preTail = tail.prev;
+    if (preTail != null) {
+      preTail.next = e;
+    }
+    e.prev = preTail;
+    e.next = tail;
+    tail.prev = e;
+  }
+
+  public void dump() {
+    Entry e = head;
+    System.out.print("N:");
+    while (e != null) {
+      // System.out.print(e+"->");
+      System.out.print(e.key + ", ");
+      e = e.next;
+    }
+    System.out.println();
+  }
+
+
+
+  public List<Appender<E>> valueList() {
+    List<Appender<E>> result = new LinkedList<Appender<E>>();
+    Entry e = head;
+    while (e != tail) {
+      result.add(e.value);
+      e = e.next;
+    }
+    return result;
+  }
+  
+  // ================================================================
+  private class Entry {
+    Entry next;
+    Entry prev;
+
+    String key;
+    Appender<E> value;
+    long timestamp;
+
+    Entry(String k, Appender<E> v, long timestamp) {
+      this.key = k;
+      this.value = v;
+      this.timestamp = timestamp;
+    }
+
+    public long getTimestamp() {
+      return timestamp;
+    }
+
+    public void setTimestamp(long timestamp) {
+      this.timestamp = timestamp;
+    }
+
+    @Override
+    public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + ((key == null) ? 0 : key.hashCode());
+      return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj)
+        return true;
+      if (obj == null)
+        return false;
+      if (getClass() != obj.getClass())
+        return false;
+      final Entry other = (Entry) obj;
+      if (key == null) {
+        if (other.key != null)
+          return false;
+      } else if (!key.equals(other.key))
+        return false;
+      if (value == null) {
+        if (other.value != null)
+          return false;
+      } else if (!value.equals(other.value))
+        return false;
+      return true;
+    }
+
+    @Override
+    public String toString() {
+      return "(" + key + ", " + value + ")";
+    }
+  }
+
+}

Modified: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/hoard/HoardingAppender.java
==============================================================================
--- logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/hoard/HoardingAppender.java	(original)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/hoard/HoardingAppender.java	Wed Dec 17 18:46:01 2008
@@ -1,22 +1,39 @@
+/**
+ * Logback: the generic, reliable, fast and flexible logging framework.
+ * 
+ * Copyright (C) 2000-2008, QOS.ch
+ * 
+ * This library is free software, you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation.
+ */
 package ch.qos.logback.classic.hoard;
 
-import java.util.Hashtable;
-import java.util.Map;
-
 import org.slf4j.MDC;
 
 import ch.qos.logback.classic.spi.LoggingEvent;
 import ch.qos.logback.core.Appender;
 import ch.qos.logback.core.UnsynchronizedAppenderBase;
 import ch.qos.logback.core.joran.spi.JoranException;
+import ch.qos.logback.core.util.OptionHelper;
 
+/**
+ * This appender can contains other appenders which it can build dynamically
+ * depending on MDC values. The built appender is specified as part of a
+ * configuration file.
+ * 
+ * <p>See the logback manual for further details.
+ * 
+ * 
+ * @author Ceki Gulcu
+ */
 public class HoardingAppender extends UnsynchronizedAppenderBase<LoggingEvent> {
 
-  static String DEFAULT = "default";
-
-  Map<String, Appender<LoggingEvent>> appenderMap = new Hashtable<String, Appender<LoggingEvent>>();
+  AppenderTracker<LoggingEvent> appenderTracker = new AppenderTrackerImpl<LoggingEvent>();
+  //Map<String, Appender<LoggingEvent>> appenderMap = new Hashtable<String, Appender<LoggingEvent>>();
 
   String mdcKey;
+  String defaultValue;
 
   AppenderFactory appenderFactory;
 
@@ -24,26 +41,57 @@
     this.appenderFactory = appenderFactory;
   }
 
-  
-  
+  @Override
+  public void start() {
+    int errors = 0;
+    if (OptionHelper.isEmpty(mdcKey)) {
+      errors++;
+      addError("The \"mdcKey\" property must be set");
+    }
+    if (OptionHelper.isEmpty(defaultValue)) {
+      errors++;
+      addError("The \"defaultValue\" property must be set");
+    }
+    if (errors == 0) {
+      super.start();
+    }
+  }
+
+  @Override
+  public void stop() {
+    for (Appender<LoggingEvent> appender : appenderTracker.valueList()) {
+      appender.stop();
+    }
+  }
+
   @Override
   protected void append(LoggingEvent loggingEvent) {
+    if (!isStarted()) {
+      return;
+    }
+
     String mdcValue = MDC.get(mdcKey);
 
     if (mdcValue == null) {
-      mdcValue = DEFAULT;
+      mdcValue = defaultValue;
     }
 
-    Appender<LoggingEvent> appender = appenderMap.get(mdcValue);
+    long timestamp = loggingEvent.getTimeStamp();
+    
+    Appender<LoggingEvent> appender = appenderTracker.get(mdcValue, timestamp);
 
     if (appender == null) {
       try {
         appender = appenderFactory.buildAppender(context, mdcKey, mdcValue);
+        if (appender != null) {
+          appenderTracker.put(mdcValue, appender, timestamp);
+        }
       } catch (JoranException e) {
         addError("Failed to build appender for " + mdcKey + "=" + mdcValue, e);
         return;
       }
     }
+    appenderTracker.stopStaleAppenders(timestamp);
     appender.doAppend(loggingEvent);
   }
 
@@ -54,6 +102,29 @@
   public void setMdcKey(String mdcKey) {
     this.mdcKey = mdcKey;
   }
-  
-  
+
+  /**
+   * @see #setDefaultValue(String)
+   * @return
+   */
+  public String getDefaultValue() {
+    return defaultValue;
+  }
+
+  /**
+   * The default MDC value in case the MDC is not set for
+   * {@link #setMdcKey(String) mdcKey}.
+   * 
+   * <p> For example, if {@link #setMdcKey(String) mdcKey} is set to the value
+   * "someKey", and the MDC is not set for "someKey", then this appender will
+   * use the default value, which you can set with the help of method.
+   * 
+   * <p>The "defaultValue" property is set to the value "DEFAULT" by default.
+   * 
+   * @param defaultValue
+   */
+  public void setDefaultValue(String defaultValue) {
+    this.defaultValue = defaultValue;
+  }
+
 }

Modified: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/hoard/HoardingJoranConfigurator.java
==============================================================================
--- logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/hoard/HoardingJoranConfigurator.java	(original)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/hoard/HoardingJoranConfigurator.java	Wed Dec 17 18:46:01 2008
@@ -4,6 +4,7 @@
 import java.util.HashMap;
 import java.util.Map;
 
+import ch.qos.logback.classic.spi.LoggingEvent;
 import ch.qos.logback.core.Appender;
 import ch.qos.logback.core.joran.GenericConfigurator;
 import ch.qos.logback.core.joran.action.ActionConst;
@@ -55,10 +56,11 @@
     interpreter.setInterpretationContextPropertiesMap(propertiesMap);
   }
 
-  public Appender getAppender() {
+  @SuppressWarnings("unchecked")
+  public Appender<LoggingEvent> getAppender() {
     Map<String, Object> omap = interpreter.getInterpretationContext().getObjectMap();
     HashMap map = (HashMap) omap.get(ActionConst.APPENDER_BAG);
     Collection values = map.values();
-    return (Appender) values.iterator().next();
+    return (Appender<LoggingEvent>) values.iterator().next();
   }
 }

Modified: logback/trunk/logback-classic/src/test/input/joran/hoard/hoard0.xml
==============================================================================
--- logback/trunk/logback-classic/src/test/input/joran/hoard/hoard0.xml	(original)
+++ logback/trunk/logback-classic/src/test/input/joran/hoard/hoard0.xml	Wed Dec 17 18:46:01 2008
@@ -7,10 +7,9 @@
     class="ch.qos.logback.classic.hoard.HoardingAppender">
 
     <mdcKey>userid</mdcKey>
-    
-
+    <default>asdad</default>
     <hoard>
-      <appender name="FILE" class="ch.qos.logback.core.FileAppender">
+      <appender name="FILE-${userid}" class="ch.qos.logback.core.FileAppender">
         <File>${userid}.log</File>
         <Append>true</Append>
         <layout class="ch.qos.logback.classic.PatternLayout">
@@ -18,7 +17,6 @@
         </layout>
       </appender>
     </hoard>
-
   </appender>
 
   <root level="DEBUG">

Added: logback/trunk/logback-classic/src/test/input/joran/hoard/smoke.xml
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/test/input/joran/hoard/smoke.xml	Wed Dec 17 18:46:01 2008
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE configuration>
+
+<configuration debug="true">
+
+  <appender name="HOARD"
+    class="ch.qos.logback.classic.hoard.HoardingAppender">
+
+    <mdcKey>userid</mdcKey>
+    <defaultValue>smoke</defaultValue>
+    <hoard>
+      <appender name="list-${userid}" class="ch.qos.logback.core.read.ListAppender"/>
+    </hoard>
+  </appender>
+
+  <root level="DEBUG">
+    <appender-ref ref="HOARD" />
+  </root>
+
+</configuration>

Added: logback/trunk/logback-classic/src/test/input/joran/hoard/unsetDefaultValueProperty.xml
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/test/input/joran/hoard/unsetDefaultValueProperty.xml	Wed Dec 17 18:46:01 2008
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE configuration>
+
+<configuration debug="true">
+
+  <appender name="HOARD"
+    class="ch.qos.logback.classic.hoard.HoardingAppender">
+
+    <mdcKey>userid</mdcKey>
+    <hoard>
+      <appender name="list-${userid}" class="ch.qos.logback.core.read.ListAppender"/>
+    </hoard>
+  </appender>
+
+  <root level="DEBUG">
+    <appender-ref ref="HOARD" />
+  </root>
+
+</configuration>

Modified: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/AllClassicTest.java
==============================================================================
--- logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/AllClassicTest.java	(original)
+++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/AllClassicTest.java	Wed Dec 17 18:46:01 2008
@@ -20,20 +20,23 @@
     TestSuite suite = new TestSuite();
 
     suite.addTest(org.slf4j.impl.PackageTest.suite());
-    suite.addTest(new JUnit4TestAdapter(ch.qos.logback.classic.PackageTest.class));
+    suite.addTest(new JUnit4TestAdapter(
+        ch.qos.logback.classic.PackageTest.class));
     suite.addTest(ch.qos.logback.classic.util.PackageTest.suite());
     suite.addTest(ch.qos.logback.classic.control.PackageTest.suite());
     suite.addTest(ch.qos.logback.classic.joran.PackageTest.suite());
-    suite.addTest(ch.qos.logback.classic.jmx.PackageTest.suite()); 
+    suite.addTest(ch.qos.logback.classic.jmx.PackageTest.suite());
     suite.addTest(ch.qos.logback.classic.boolex.PackageTest.suite());
-    suite.addTest(ch.qos.logback.classic.selector.PackageTest.suite()); 
-    suite.addTest(ch.qos.logback.classic.html.PackageTest.suite()); 
+    suite.addTest(ch.qos.logback.classic.selector.PackageTest.suite());
+    suite.addTest(ch.qos.logback.classic.html.PackageTest.suite());
     suite.addTest(ch.qos.logback.classic.net.PackageTest.suite());
-    suite.addTest(ch.qos.logback.classic.pattern.PackageTest.suite()); 
-    suite.addTest(ch.qos.logback.classic.db.PackageTest.suite()); 
-    suite.addTest(ch.qos.logback.classic.spi.PackageTest.suite()); 
-    suite.addTest(ch.qos.logback.classic.turbo.PackageTest.suite()); 
-    
+    suite.addTest(ch.qos.logback.classic.pattern.PackageTest.suite());
+    suite.addTest(ch.qos.logback.classic.db.PackageTest.suite());
+    suite.addTest(ch.qos.logback.classic.spi.PackageTest.suite());
+    suite.addTest(ch.qos.logback.classic.turbo.PackageTest.suite());
+    suite.addTest(new JUnit4TestAdapter(
+        ch.qos.logback.classic.hoard.PackageTest.class));
+
     return suite;
   }
 }

Added: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/AppenderTrackerTest.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/AppenderTrackerTest.java	Wed Dec 17 18:46:01 2008
@@ -0,0 +1,53 @@
+package ch.qos.logback.classic.hoard;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import ch.qos.logback.core.Context;
+import ch.qos.logback.core.ContextBase;
+import ch.qos.logback.core.read.ListAppender;
+
+public class AppenderTrackerTest {
+
+  
+  Context context = new ContextBase();
+  AppenderTracker<Object> appenderTracker = new AppenderTrackerImpl<Object>();
+  ListAppender<Object> la = new ListAppender<Object>();
+  
+  @Before
+  public void setUp() {
+    la.setContext(context);
+    la.start();
+  }
+  
+  @Test
+  public void empty() {
+    long now = 3000;
+    assertNull(appenderTracker.get("a", now++));
+    now += AppenderTrackerImpl.THRESHOLD+1000;
+    appenderTracker.stopStaleAppenders(now);
+    assertNull(appenderTracker.get("a", now++));
+  }
+  
+  @Test
+  public void smoke() {
+    assertTrue(la.isStarted());
+    long now = 3000;
+    appenderTracker.put("a", la, now);
+    assertEquals(la, appenderTracker.get("a", now++));
+    now += AppenderTrackerImpl.THRESHOLD+1000;
+    appenderTracker.stopStaleAppenders(now);
+    assertFalse(la.isStarted());
+    assertNull(appenderTracker.get("a", now++));
+  }
+  
+  @Test
+  public void scenarioBased() {
+    
+  }
+}

Modified: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/HoardingAppenderTest.java
==============================================================================
--- logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/HoardingAppenderTest.java	(original)
+++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/HoardingAppenderTest.java	Wed Dec 17 18:46:01 2008
@@ -9,13 +9,21 @@
  */
 package ch.qos.logback.classic.hoard;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.List;
+
 import org.junit.Test;
 
 import ch.qos.logback.classic.Logger;
 import ch.qos.logback.classic.LoggerContext;
 import ch.qos.logback.classic.joran.JoranConfigurator;
+import ch.qos.logback.classic.spi.LoggingEvent;
 import ch.qos.logback.classic.util.TeztConstants;
 import ch.qos.logback.core.joran.spi.JoranException;
+import ch.qos.logback.core.read.ListAppender;
 import ch.qos.logback.core.util.StatusPrinter;
 
 public class HoardingAppenderTest {
@@ -32,6 +40,29 @@
     jc.doConfigure(file);
   }
 
+  @Test
+  public void unsetDefaultValueProperty() throws JoranException {
+    configure(PREFIX + "unsetDefaultValueProperty.xml");
+    logger.debug("hello");
+    HoardingAppender ha = (HoardingAppender) root.getAppender("HOARD");
+    assertFalse(ha.isStarted());
+    
+  }
+
+  @Test
+  public void smoke() throws JoranException {
+    configure(PREFIX + "smoke.xml");
+    logger.debug("smoke");
+    long timestamp = 0;
+    HoardingAppender ha = (HoardingAppender) root.getAppender("HOARD");
+    ListAppender<LoggingEvent> listAppender = (ListAppender<LoggingEvent>) ha.appenderTracker.get("smoke", timestamp);
+    StatusPrinter.print(loggerContext);
+    
+    assertNotNull(listAppender);
+    List<LoggingEvent> eventList = listAppender.list;
+    assertEquals(1, listAppender.list.size());
+    assertEquals("smoke", eventList.get(0).getMessage());
+  }
 
   @Test
   public void testLevel() throws JoranException {

Added: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/PackageTest.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/PackageTest.java	Wed Dec 17 18:46:01 2008
@@ -0,0 +1,19 @@
+/**
+ * Logback: the generic, reliable, fast and flexible logging framework.
+ * 
+ * Copyright (C) 2000-2008, QOS.ch
+ * 
+ * This library is free software, you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation.
+ */
+package ch.qos.logback.classic.hoard;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+ at RunWith(Suite.class)
+ at SuiteClasses({HoardingAppenderTest.class})
+public class PackageTest  {
+}
\ No newline at end of file

Added: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/ScenarioBasedAppenderTrackerTest.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/ScenarioBasedAppenderTrackerTest.java	Wed Dec 17 18:46:01 2008
@@ -0,0 +1,53 @@
+/**
+ * Logback: the generic, reliable, fast and flexible logging framework.
+ * 
+ * Copyright (C) 2000-2008, QOS.ch
+ * 
+ * This library is free software, you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation.
+ */
+package ch.qos.logback.classic.hoard;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class ScenarioBasedAppenderTrackerTest {
+
+  Simulator simulator;
+
+  void verify() {
+    AppenderTracker at = simulator.appenderTracker;
+    AppenderTracker t_at = simulator.t_appenderTracker;
+    //List<String> resultKeys = at.keyList();
+    //List<String> witnessKeys = t_at.keyList();
+    assertEquals(t_at.keyList(), at.keyList());
+  }
+
+  @Test
+  public void shortTest() {
+    simulator = new Simulator(20, AppenderTracker.THRESHOLD / 2);
+    simulator.buildScenario(200);
+    simulator.simulate();
+    verify();
+  }
+
+  @Test
+  public void mediumTest() {
+    simulator = new Simulator(100, AppenderTracker.THRESHOLD / 2);
+    simulator.buildScenario(20000);
+    simulator.simulate();
+    verify();
+  }
+
+  @Test
+  @Ignore
+  public void longetTest() {
+    simulator = new Simulator(100, AppenderTracker.THRESHOLD / 200);
+    simulator.buildScenario(2000000);
+    simulator.simulate();
+    verify();
+  }
+}

Added: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/Simulator.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/Simulator.java	Wed Dec 17 18:46:01 2008
@@ -0,0 +1,92 @@
+/**
+ * Logback: the generic, reliable, fast and flexible logging framework.
+ * 
+ * Copyright (C) 2000-2008, QOS.ch
+ * 
+ * This library is free software, you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation.
+ */
+package ch.qos.logback.classic.hoard;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+import ch.qos.logback.classic.hoard.tracker.SimulationEvent;
+import ch.qos.logback.classic.hoard.tracker.AppenderTrackerTImpl;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.appender.NOPAppender;
+
+/**
+ * Simulate use of AppenderTracker by HoardAppender.
+ * 
+ * @author ceki
+ *
+ */
+public class Simulator {
+
+  AppenderTrackerImpl<Object> appenderTracker = new AppenderTrackerImpl<Object>();
+  AppenderTrackerTImpl t_appenderTracker = new AppenderTrackerTImpl();
+
+  List<String> keySpace = new ArrayList<String>();
+  List<SimulationEvent> scenario = new ArrayList<SimulationEvent>();
+  Random randomKeyGen = new Random(100);
+
+  Random random = new Random(11234);
+
+  final int maxTimestampInc;
+  long timestamp = 30000;
+
+  Simulator(int keySpaceLen, int maxTimestampInc) {
+    this.maxTimestampInc = maxTimestampInc;
+    Map<String, String> checkMap = new HashMap<String, String>();
+    for (int i = 0; i < keySpaceLen; i++) {
+      String k = getRandomKeyStr();
+      if (checkMap.containsKey(k)) {
+        System.out.println("random key collision occured");
+        k += "" + i;
+      }
+      keySpace.add(k);
+      checkMap.put(k, k);
+    }
+
+  }
+
+  private String getRandomKeyStr() {
+    int ri = randomKeyGen.nextInt();
+    String s = String.format("%X", ri);
+    return s;
+  }
+
+  void buildScenario(int simLen) {
+    int keySpaceLen = keySpace.size();
+    for (int i = 0; i < simLen; i++) {
+      int index = random.nextInt(keySpaceLen);
+      timestamp += random.nextInt(maxTimestampInc);
+      String key = keySpace.get(index);
+      scenario.add(new SimulationEvent(key, timestamp));
+    }
+  }
+
+  public void simulate() {
+    for (SimulationEvent simeEvent : scenario) {
+      play(simeEvent, appenderTracker);
+      play(simeEvent, t_appenderTracker);
+    }
+  }
+
+  void play(SimulationEvent simulationEvent,
+      AppenderTracker<Object> appenderTracker) {
+    String mdcValue = simulationEvent.key;
+    long timestamp = simulationEvent.timestamp;
+    Appender<Object> appender = appenderTracker.get(mdcValue, timestamp);
+    if (appender == null) {
+      appender = new NOPAppender<Object>();
+      appenderTracker.put(mdcValue, appender, timestamp);
+    }
+    appenderTracker.stopStaleAppenders(timestamp);
+  }
+}

Added: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/tracker/AppenderTrackerTImpl.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/tracker/AppenderTrackerTImpl.java	Wed Dec 17 18:46:01 2008
@@ -0,0 +1,98 @@
+/**
+ * Logback: the generic, reliable, fast and flexible logging framework.
+ * 
+ * Copyright (C) 2000-2008, QOS.ch
+ * 
+ * This library is free software, you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation.
+ */
+package ch.qos.logback.classic.hoard.tracker;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import ch.qos.logback.classic.hoard.AppenderTracker;
+import ch.qos.logback.core.Appender;
+
+/**
+ * This is an alternative (slower) implementation of AppenderTracker for testing
+ * purposes.
+ * 
+ * @author Ceki Gulcu
+ */
+public class AppenderTrackerTImpl implements AppenderTracker<Object> {
+
+  List<TEntry> entryList = new LinkedList<TEntry>();
+  long lastCheck = 0;
+
+  public AppenderTrackerTImpl() {
+  }
+
+  @SuppressWarnings("unchecked")
+  synchronized public void put(String k, Appender<Object> appender,
+      long timestamp) {
+    TEntry te = getEntry(k);
+    if (te != null) {
+      te.timestamp = timestamp;
+    } else {
+      te = new TEntry(k, appender, timestamp);
+      entryList.add(te);
+    }
+    Collections.sort(entryList);
+  }
+
+  @SuppressWarnings("unchecked")
+  synchronized public Appender<Object> get(String k, long timestamp) {
+    TEntry te = getEntry(k);
+    if (te == null) {
+      return null;
+    } else {
+      te.timestamp = timestamp;
+      Collections.sort(entryList);
+      return te.appender;
+    }
+  }
+
+  synchronized public void stopStaleAppenders(long timestamp) {
+    if (lastCheck + MILLIS_IN_ONE_SECOND > timestamp) {
+      return;
+    }
+    lastCheck = timestamp;
+    while (entryList.size() != 0 && isEntryStale(entryList.get(0), timestamp)) {
+      entryList.remove(0);
+    }
+  }
+
+  final private boolean isEntryStale(TEntry entry, long now) {
+    return ((entry.timestamp + THRESHOLD) < now);
+  }
+
+  synchronized public List<String> keyList() {
+    List<String> keyList = new ArrayList<String>();
+    for (TEntry e : entryList) {
+      keyList.add(e.key);
+    }
+    return keyList;
+  }
+
+  synchronized public List<Appender<Object>> valueList() {
+    List<Appender<Object>> appenderList = new ArrayList<Appender<Object>>();
+    for (TEntry e : entryList) {
+      appenderList.add(e.appender);
+    }
+    return appenderList;
+  }
+
+  private TEntry getEntry(String k) {
+    for (int i = 0; i < entryList.size(); i++) {
+      TEntry te = entryList.get(i);
+      if (te.key.equals(k)) {
+        return te;
+      }
+    }
+    return null;
+  }
+}

Added: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/tracker/SimulationEvent.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/tracker/SimulationEvent.java	Wed Dec 17 18:46:01 2008
@@ -0,0 +1,17 @@
+package ch.qos.logback.classic.hoard.tracker;
+
+
+public class SimulationEvent {
+
+  public String key;
+  public long timestamp;
+
+  public SimulationEvent(String key, long timestamp) {
+    this.key = key;
+    this.timestamp = timestamp;
+  }
+
+  public String toString() {
+      return "Event: k=" + key +", timestamp=" + timestamp;
+  }
+}

Added: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/tracker/TEntry.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/hoard/tracker/TEntry.java	Wed Dec 17 18:46:01 2008
@@ -0,0 +1,45 @@
+/**
+ * Logback: the generic, reliable, fast and flexible logging framework.
+ * 
+ * Copyright (C) 2000-2008, QOS.ch
+ * 
+ * This library is free software, you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation.
+ */
+package ch.qos.logback.classic.hoard.tracker;
+
+import ch.qos.logback.core.Appender;
+
+public class TEntry implements Comparable {
+
+  String key;
+  long timestamp;
+  Appender<Object> appender;
+  
+  TEntry(String key, Appender<Object> appender, long timestamp) {
+    this.key = key;
+    this.appender = appender;
+    this.timestamp = timestamp;
+  }
+
+  public int compareTo(Object o) {
+    if(!(o instanceof TEntry)) {
+      throw new IllegalArgumentException("arguments must be of type "+TEntry.class);
+    }
+    
+    TEntry other = (TEntry) o;
+    if(timestamp > other.timestamp) {
+      return 1;
+    }
+    if(timestamp == other.timestamp) {
+      return 0;
+    }
+    return -1;
+  }
+  
+  @Override
+  public String toString() {
+    return "("+key+","+timestamp+")";
+  }
+}


More information about the logback-dev mailing list