
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; + +@RunWith(Suite.class) +@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+")"; + } +}