Author: rgoers
Date: Sun Oct 2 07:13:19 2011
New Revision: 1178134
URL: http://svn.apache.org/viewvc?rev=1178134&view=rev
Log:
Add BurstFilter
Added:
logging/log4j/branches/BRANCH_2_0_EXPERIMENTAL/rgoers/log4j2-core/src/main/java/org/apache/logging/log4j/core/filter/BurstFilter.java
logging/log4j/branches/BRANCH_2_0_EXPERIMENTAL/rgoers/log4j2-core/src/test/java/org/apache/logging/log4j/core/filter/BurstFilterTest.java
logging/log4j/branches/BRANCH_2_0_EXPERIMENTAL/rgoers/log4j2-core/src/test/resources/log4j-burst.xml
Added:
logging/log4j/branches/BRANCH_2_0_EXPERIMENTAL/rgoers/log4j2-core/src/main/java/org/apache/logging/log4j/core/filter/BurstFilter.java
URL:
http://svn.apache.org/viewvc/logging/log4j/branches/BRANCH_2_0_EXPERIMENTAL/rgoers/log4j2-core/src/main/java/org/apache/logging/log4j/core/filter/BurstFilter.java?rev=1178134&view=auto
==============================================================================
---
logging/log4j/branches/BRANCH_2_0_EXPERIMENTAL/rgoers/log4j2-core/src/main/java/org/apache/logging/log4j/core/filter/BurstFilter.java
(added)
+++
logging/log4j/branches/BRANCH_2_0_EXPERIMENTAL/rgoers/log4j2-core/src/main/java/org/apache/logging/log4j/core/filter/BurstFilter.java
Sun Oct 2 07:13:19 2011
@@ -0,0 +1,228 @@
+/* Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.logging.log4j.core.filter;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttr;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.message.Message;
+
+import java.util.Iterator;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.DelayQueue;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * The <code>BurstFilter</code> is a logging filter that regulates logging
+ * traffic. Use this filter when you want to control the maximum burst of log
+ * statements that can be sent to an appender. The filter is configured in the
+ * log4j configuration file. For example, the following configuration limits
the
+ * number of INFO level (as well as DEBUG and TRACE) log statements that can
be sent to the
+ * console to a burst of 100 within 6 seconds. WARN, ERROR and FATAL messages
would continue to
+ * be delivered.<br>
+ * <br>
+ * <p/>
+ * <code>
+ * <Console name="console"><br>
+ * <PatternLayout pattern="%-5p %d{dd-MMM-yyyy HH:mm:ss} %x %t
%m%n"/><br>
+ * $lt;filters><br>
+ * <Burst level="INFO" burstInterval="6" maxBurst="100"/><br>
+ * </filters><br>
+ * </Console><br>
+ * </code><br>
+ */
+
+@Plugin(name = "Burst", type = "Core", elementType = "filter")
+public class BurstFilter extends FilterBase {
+
+ private static final long NANOS_IN_SECONDS = 1000000000;
+ /**
+ * Level of messages to be filtered. Anything at or below this level will
be
+ * filtered out if <code>maxBurst</code> has been exceeded. The default is
+ * WARN meaning any messages that are higher than warn will be logged
+ * regardless of the size of a burst.
+ */
+ private final Level level;
+
+ private final long burstInterval;
+
+ private final DelayQueue<LogDelay> history = new DelayQueue<LogDelay>();
+
+ private final Queue<LogDelay> available = new
ConcurrentLinkedQueue<LogDelay>();
+
+ /**
+ * Time of last token removal.
+ */
+ private long lastTokenRemovedTime;
+
+ private BurstFilter(Level level, long burstInterval, long maxBurst,
+ Result onMatch, Result onMismatch) {
+ super(onMatch, onMismatch);
+ this.level = level;
+ this.burstInterval = burstInterval;
+ for (int i = 0; i < maxBurst; ++i) {
+ available.add(new LogDelay());
+ }
+ }
+
+ public Result filter(Logger logger, Level level, Marker marker, String
msg, Object[] params) {
+ return filter(level);
+ }
+
+ public Result filter(Logger logger, Level level, Marker marker, Object
msg, Throwable t) {
+ return filter(level);
+ }
+
+ public Result filter(Logger logger, Level level, Marker marker, Message
msg, Throwable t) {
+ return filter(level);
+ }
+
+ @Override
+ public Result filter(LogEvent event) {
+ return filter(event.getLevel());
+ }
+
+ /**
+ * Decide if we're going to log <code>event</code> based on whether the
+ * maximum burst of log statements has been exceeded.
+ *
+ * @param level The log level.
+ * @return The onMatch value if the filter passes, onMismatch otherwise.
+ */
+ private Result filter(Level level) {
+ if (this.level.isAtLeastAsSpecificAs(level)) {
+ LogDelay delay = history.poll();
+ while (delay != null) {
+ available.add(delay);
+ delay = history.poll();
+ }
+ delay = available.poll();
+ if (delay != null) {
+ delay.setDelay(burstInterval);
+ history.add(delay);
+ return onMatch;
+ }
+ return onMismatch;
+ }
+ return onMatch;
+
+ }
+
+ public int getAvailable() {
+ return available.size();
+ }
+
+ public void clear() {
+ Iterator<LogDelay> iter = history.iterator();
+ while (iter.hasNext()) {
+ LogDelay delay = iter.next();
+ history.remove(delay);
+ available.add(delay);
+ }
+ }
+
+ private class LogDelay implements Delayed {
+
+ private long expireTime;
+
+ public LogDelay() {
+ }
+
+ public void setDelay(long delay) {
+ this.expireTime = (delay * NANOS_IN_SECONDS) + System.nanoTime();
+ }
+
+ public long getDelay(TimeUnit timeUnit) {
+ return timeUnit.convert(expireTime - System.nanoTime(),
TimeUnit.NANOSECONDS);
+ }
+
+ public int compareTo(Delayed delayed) {
+ if (this.expireTime < ((LogDelay) delayed).expireTime) {
+ return -1;
+ } else if (this.expireTime > ((LogDelay) delayed).expireTime) {
+ return 1;
+ }
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ LogDelay logDelay = (LogDelay) o;
+
+ if (expireTime != logDelay.expireTime) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return (int) (expireTime ^ (expireTime >>> 32));
+ }
+ }
+
+ /**
+ * @param level The logging level.
+ * @param burstInterval Interval, in seconds, at which to add to the
number of log statements
+ * that will be allowed following a burst.
This value specifies how often
+ * <code>burstRecoverAmount</code> statements
will be added to the total number
+ * allowed for every
<code>burstRecoveryInterval</code> that passes
+ * following a burst, up to but not exceeding
<code>maxBurst</code>.
+ * @param maxBurst This value dictates the maximum traffic
burst that can be logged to any appender
+ * that uses the <code>BurstFilter</code>,
i.e. there can never be more than
+ * <code>maxBurst</code> log statements sent
to an appender in
+ * <code>burstRecoveryInterval</code> seconds.
+ * @param match
+ * @param mismatch
+ * @return
+ */
+ @PluginFactory
+ public static BurstFilter createFilter(@PluginAttr("level") String level,
+ @PluginAttr("burstInterval") String
burstInterval,
+ @PluginAttr("maxBurst") String
maxBurst,
+ @PluginAttr("onmatch") String match,
+ @PluginAttr("onmismatch") String
mismatch) {
+ Result onMatch = match == null ? null : Result.valueOf(match);
+ Result onMismatch = mismatch == null ? null : Result.valueOf(mismatch);
+ Level lvl = Level.toLevel(level, Level.WARN);
+ long brInterval = burstInterval == null ? 0 :
Long.parseLong(burstInterval);
+ long max = maxBurst == null ? 0 : Long.parseLong(maxBurst);
+ if (onMatch == null) {
+ onMatch = Result.NEUTRAL;
+ }
+ if (onMismatch == null) {
+ onMismatch = Result.DENY;
+ }
+ return new BurstFilter(lvl, brInterval, max, onMatch, onMismatch);
+ }
+}
\ No newline at end of file
Added:
logging/log4j/branches/BRANCH_2_0_EXPERIMENTAL/rgoers/log4j2-core/src/test/java/org/apache/logging/log4j/core/filter/BurstFilterTest.java
URL:
http://svn.apache.org/viewvc/logging/log4j/branches/BRANCH_2_0_EXPERIMENTAL/rgoers/log4j2-core/src/test/java/org/apache/logging/log4j/core/filter/BurstFilterTest.java?rev=1178134&view=auto
==============================================================================
---
logging/log4j/branches/BRANCH_2_0_EXPERIMENTAL/rgoers/log4j2-core/src/test/java/org/apache/logging/log4j/core/filter/BurstFilterTest.java
(added)
+++
logging/log4j/branches/BRANCH_2_0_EXPERIMENTAL/rgoers/log4j2-core/src/test/java/org/apache/logging/log4j/core/filter/BurstFilterTest.java
Sun Oct 2 07:13:19 2011
@@ -0,0 +1,164 @@
+/* Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.logging.log4j.core.filter;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.appender.ListAppender;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.XMLConfigurationFactory;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+/**
+ * Unit test for <code>BurstFilter</code>.
+ */
+public class BurstFilterTest {
+
+ private static final String CONFIG = "log4j-burst.xml";
+
+ private static Configuration config;
+ private static ListAppender app;
+ private static BurstFilter filter;
+ private static LoggerContext ctx;
+
+ @BeforeClass
+ public static void setupClass() {
+
System.setProperty(XMLConfigurationFactory.CONFIGURATION_FILE_PROPERTY, CONFIG);
+ ctx = (LoggerContext) LogManager.getContext(false);
+ config = ctx.getConfiguration();
+ for (Map.Entry<String, Appender> entry :
config.getAppenders().entrySet()) {
+ if (entry.getKey().equals("ListAppender")) {
+ app = (ListAppender) entry.getValue();
+ Iterator<Filter> iter = app.getFilters();
+ while (iter.hasNext()) {
+ Filter f = iter.next();
+ if (f instanceof BurstFilter) {
+ filter = (BurstFilter) f;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ private Logger logger =
LogManager.getLogger(BurstFilterTest.class.getName());
+
+ /**
+ * Test BurstFilter by surpassing maximum number of log messages allowed
by filter and
+ * making sure only the maximum number are indeed logged, then wait for
while and make
+ * sure the filter allows the appropriate number of messages to be logged.
+ */
+ @Test
+ public void test() throws Exception {
+ assertNotNull("No ListAppender", app);
+ assertNotNull("No BurstFilter", filter);
+ // exceed the burst limit and make sure no more than 100 errors get
logged
+ long start = System.nanoTime();
+ for (int i = 0; i < 110; i++) {
+ if (i % 10 == 0) {
+ Thread.sleep(200);
+ }
+ logger.info("Logging 110 messages, should only see 100 logs # " +
(i + 1));
+ assertTrue("Incorrect number of available slots",
filter.getAvailable() < 100);
+ }
+ List<String> msgs = app.getMessages();
+ assertTrue("Incorrect message count. Should be 100, actual " +
msgs.size(), msgs.size() == 100);
+ app.clear();
+
+ assertTrue("Incorrect number of available slots",
filter.getAvailable() < 100);
+ // Allow some of the events to clear
+ Thread.sleep(1500);
+
+ for (int i = 0; i < 110; i++) {
+ logger.info("Waited 1.5 seconds and trying to log again, should
see more than 0 and less than 100" + (i + 1));
+ }
+
+ msgs = app.getMessages();
+ assertTrue("Incorrect message count. Should be > 0 and < 100, actual "
+ msgs.size(),
+ msgs.size() > 0 && msgs.size() < 100);
+ app.clear();
+
+ filter.clear();
+
+ for (int i = 0; i < 110; i++) {
+ logger.info("Waited 1.5 seconds and trying to log again, should
see more than 0 and less than 100" + (i + 1));
+ }
+ assertTrue("", filter.getAvailable() == 0);
+ app.clear();
+
+
+ // now log 100 debugs, they shouldn't get through because there are no
available slots.
+ for (int i = 0; i < 110; i++) {
+ logger.debug(
+ "TEST FAILED! Logging 110 debug messages, shouldn't see any of
them because they are debugs #" + (i + 1));
+ }
+
+ msgs = app.getMessages();
+ assertTrue("Incorrect message count. Should be 0, actual " +
msgs.size(), msgs.size() == 0);
+ app.clear();
+
+ // now log 100 warns, they should all get through because the filter's
level is set at info
+ for (int i = 0; i < 110; i++) {
+ logger.warn("Logging 110 warn messages, should see all of them
because they are warns #" + (i + 1));
+ }
+
+ msgs = app.getMessages();
+ assertTrue("Incorrect message count. Should be 110, actual " +
msgs.size(), msgs.size() == 110);
+ app.clear();
+
+ // now log 100 errors, they should all get through because the filter
level is set at info
+ for (int i = 0; i < 110; i++) {
+ logger.error("Logging 110 error messages, should see all of them
because they are errors #" + (i + 1));
+ }
+
+ msgs = app.getMessages();
+ assertTrue("Incorrect message count. Should be 110, actual " +
msgs.size(), msgs.size() == 110);
+ app.clear();
+
+ // now log 100 fatals, they should all get through because the filter
level is set at info
+ for (int i = 0; i < 110; i++) {
+ logger.fatal("Logging 110 fatal messages, should see all of them
because they are fatals #" + (i + 1));
+ }
+
+ msgs = app.getMessages();
+ assertTrue("Incorrect message count. Should be 110, actual " +
msgs.size(), msgs.size() == 110);
+ app.clear();
+
+ // wait and make sure we can log messages again despite the fact we
just logged a bunch of warns, errors, fatals
+ Thread.sleep(3100);
+
+ for (int i = 0; i < 110; i++) {
+ logger.debug("Waited 3+ seconds, should see 100 logs #" + (i + 1));
+ }
+ msgs = app.getMessages();
+ assertTrue("Incorrect message count. Should be 100, actual " +
msgs.size(), msgs.size() == 100);
+ app.clear();
+
+ }
+}
Added:
logging/log4j/branches/BRANCH_2_0_EXPERIMENTAL/rgoers/log4j2-core/src/test/resources/log4j-burst.xml
URL:
http://svn.apache.org/viewvc/logging/log4j/branches/BRANCH_2_0_EXPERIMENTAL/rgoers/log4j2-core/src/test/resources/log4j-burst.xml?rev=1178134&view=auto
==============================================================================
---
logging/log4j/branches/BRANCH_2_0_EXPERIMENTAL/rgoers/log4j2-core/src/test/resources/log4j-burst.xml
(added)
+++
logging/log4j/branches/BRANCH_2_0_EXPERIMENTAL/rgoers/log4j2-core/src/test/resources/log4j-burst.xml
Sun Oct 2 07:13:19 2011
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration status="warn" name="BurstTest">
+ <appenders>
+
+ <List name="ListAppender">
+ <PatternLayout pattern="%-5p %d{dd-MMM-yyyy HH:mm:ss} %t %m%n"/>
+ <filters>
+ <Burst level="INFO" burstInterval="3" maxBurst="100"/>
+ </filters>
+ </List>
+ </appenders>
+
+ <loggers>
+ <root level="TRACE">
+ <appender-ref ref="ListAppender"/>
+ </root>
+ </loggers>
+</configuration>
\ No newline at end of file