View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.filter;
18  
19  import org.apache.logging.log4j.Level;
20  import org.apache.logging.log4j.Marker;
21  import org.apache.logging.log4j.core.LogEvent;
22  import org.apache.logging.log4j.core.Logger;
23  import org.apache.logging.log4j.core.config.plugins.Plugin;
24  import org.apache.logging.log4j.core.config.plugins.PluginAttr;
25  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
26  import org.apache.logging.log4j.message.Message;
27  
28  import java.util.Iterator;
29  import java.util.Queue;
30  import java.util.concurrent.ConcurrentLinkedQueue;
31  import java.util.concurrent.DelayQueue;
32  import java.util.concurrent.Delayed;
33  import java.util.concurrent.TimeUnit;
34  
35  /**
36   * The <code>BurstFilter</code> is a logging filter that regulates logging
37   * traffic. Use this filter when you want to control the maximum burst of log
38   * statements that can be sent to an appender. The filter is configured in the
39   * log4j configuration file. For example, the following configuration limits the
40   * number of INFO level (as well as DEBUG and TRACE) log statements that can be sent to the
41   * console to a burst of 100 with an average rate of 16 per second. WARN, ERROR and FATAL messages would continue to
42   * be delivered.<br>
43   * <br>
44   * <p/>
45   * <code>
46   * &lt;Console name="console"&gt;<br>
47   * &nbsp;&lt;PatternLayout pattern="%-5p %d{dd-MMM-yyyy HH:mm:ss} %x %t %m%n"/&gt;<br>
48   * &nbsp;&lt;filters&gt;<br>
49   * &nbsp;&nbsp;&lt;Burst level="INFO" rate="16" maxBurst="100"/&gt;<br>
50   * &nbsp;&lt;/filters&gt;<br>
51   * &lt;/Console&gt;<br>
52   * </code><br>
53   */
54  
55  @Plugin(name = "BurstFilter", type = "Core", elementType = "filter", printObject = true)
56  public final class BurstFilter extends FilterBase {
57  
58      private static final long NANOS_IN_SECONDS =  1000000000;
59  
60      private static final int DEFAULT_RATE = 10;
61  
62      private static final int DEFAULT_RATE_MULTIPLE = 100;
63  
64      private static final int HASH_SHIFT = 32;
65  
66      /**
67       * Level of messages to be filtered. Anything at or below this level will be
68       * filtered out if <code>maxBurst</code> has been exceeded. The default is
69       * WARN meaning any messages that are higher than warn will be logged
70       * regardless of the size of a burst.
71       */
72      private final Level level;
73  
74      private final long burstInterval;
75  
76      private final DelayQueue<LogDelay> history = new DelayQueue<LogDelay>();
77  
78      private final Queue<LogDelay> available = new ConcurrentLinkedQueue<LogDelay>();
79  
80      private BurstFilter(Level level, float rate, long maxBurst, Result onMatch, Result onMismatch) {
81          super(onMatch, onMismatch);
82          this.level = level;
83          this.burstInterval = (long) (NANOS_IN_SECONDS * (maxBurst / rate));
84          for (int i = 0; i < maxBurst; ++i) {
85              available.add(new LogDelay());
86          }
87      }
88  
89      public Result filter(Logger logger, Level level, Marker marker, String msg, Object[] params) {
90          return filter(level);
91      }
92  
93      public Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) {
94          return filter(level);
95      }
96  
97      public Result filter(Logger logger, Level level, Marker marker, Message msg, Throwable t) {
98          return filter(level);
99      }
100 
101     @Override
102     public Result filter(LogEvent event) {
103         return filter(event.getLevel());
104     }
105 
106     /**
107      * Decide if we're going to log <code>event</code> based on whether the
108      * maximum burst of log statements has been exceeded.
109      *
110      * @param level The log level.
111      * @return The onMatch value if the filter passes, onMismatch otherwise.
112      */
113     private Result filter(Level level) {
114         if (this.level.isAtLeastAsSpecificAs(level)) {
115             LogDelay delay = history.poll();
116             while (delay != null) {
117                 available.add(delay);
118                 delay = history.poll();
119             }
120             delay = available.poll();
121             if (delay != null) {
122                 delay.setDelay(burstInterval);
123                 history.add(delay);
124                 return onMatch;
125             }
126             return onMismatch;
127         }
128         return onMatch;
129 
130     }
131 
132     /**
133      * Returns the number of available slots. Used for unit testing.
134      * @return The number of available slots.
135      */
136     public int getAvailable() {
137         return available.size();
138     }
139 
140     /**
141      * Clear the history. Used for unit testing.
142      */
143     public void clear() {
144         Iterator<LogDelay> iter = history.iterator();
145         while (iter.hasNext()) {
146             LogDelay delay = iter.next();
147             history.remove(delay);
148             available.add(delay);
149         }
150     }
151 
152     public String toString() {
153         return "level=" + level.toString() + ", interval=" + burstInterval + ", max=" + history.size();
154     }
155 
156     /**
157      * Delay object to represent each log event that has occurred within the timespan.
158      */
159     private class LogDelay implements Delayed {
160 
161         private long expireTime;
162 
163         public LogDelay() {
164         }
165 
166         public void setDelay(long delay) {
167             this.expireTime = delay + System.nanoTime();
168         }
169 
170         public long getDelay(TimeUnit timeUnit) {
171             return timeUnit.convert(expireTime - System.nanoTime(), TimeUnit.NANOSECONDS);
172         }
173 
174         public int compareTo(Delayed delayed) {
175             if (this.expireTime < ((LogDelay) delayed).expireTime) {
176                 return -1;
177             } else if (this.expireTime > ((LogDelay) delayed).expireTime) {
178                 return 1;
179             }
180             return 0;
181         }
182 
183         @Override
184         public boolean equals(Object o) {
185             if (this == o) {
186                 return true;
187             }
188             if (o == null || getClass() != o.getClass()) {
189                 return false;
190             }
191 
192             LogDelay logDelay = (LogDelay) o;
193 
194             if (expireTime != logDelay.expireTime) {
195                 return false;
196             }
197 
198             return true;
199         }
200 
201         @Override
202         public int hashCode() {
203             return (int) (expireTime ^ (expireTime >>> HASH_SHIFT));
204         }
205     }
206 
207     /**
208      * @param level  The logging level.
209      * @param rate   The average number of events per second to allow.
210      * @param maxBurst  The maximum number of events that can occur before events are filtered for exceeding the
211      * average rate. The default is 10 times the rate.
212      * @param match  The Result to return when the filter matches. Defaults to Result.NEUTRAL.
213      * @param mismatch The Result to return when the filter does not match. The default is Result.DENY.
214      * @return A BurstFilter.
215      */
216     @PluginFactory
217     public static BurstFilter createFilter(@PluginAttr("level") String level,
218                                            @PluginAttr("rate") String rate,
219                                            @PluginAttr("maxBurst") String maxBurst,
220                                            @PluginAttr("onmatch") String match,
221                                            @PluginAttr("onmismatch") String mismatch) {
222         Result onMatch = match == null ? null : Result.valueOf(match.toUpperCase());
223         Result onMismatch = mismatch == null ? null : Result.valueOf(mismatch.toUpperCase());
224         Level lvl = Level.toLevel(level, Level.WARN);
225         float eventRate = rate == null ? DEFAULT_RATE : Float.parseFloat(rate);
226         if (eventRate <= 0) {
227             eventRate = DEFAULT_RATE;
228         }
229         long max = maxBurst == null ? (long) (eventRate * DEFAULT_RATE_MULTIPLE) : Long.parseLong(maxBurst);
230         if (onMatch == null) {
231             onMatch = Result.NEUTRAL;
232         }
233         if (onMismatch == null) {
234             onMismatch = Result.DENY;
235         }
236         return new BurstFilter(lvl, eventRate, max, onMatch, onMismatch);
237     }
238 }