    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements. See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache license, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License. You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the license for the specific language governing permissions and
    015     * limitations under the license.
    016     */
    017
    018    package org.apache.logging.log4j.core.filter;
    019
    020    import java.util.Iterator;
    021    import java.util.Queue;
    022    import java.util.concurrent.ConcurrentLinkedQueue;
    023    import java.util.concurrent.DelayQueue;
    024    import java.util.concurrent.Delayed;
    025    import java.util.concurrent.TimeUnit;
    026
    027    import org.apache.logging.log4j.Level;
    028    import org.apache.logging.log4j.Marker;
    029    import org.apache.logging.log4j.core.Filter;
    030    import org.apache.logging.log4j.core.LogEvent;
    031    import org.apache.logging.log4j.core.Logger;
    032    import org.apache.logging.log4j.core.config.Node;
    033    import org.apache.logging.log4j.core.config.plugins.Plugin;
    034    import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
    035    import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
    036    import org.apache.logging.log4j.message.Message;
    037
    038    /**
    039     * The <code>BurstFilter</code> is a logging filter that regulates logging traffic.
    040     *
    041     * <p>
    042     * Use this filter when you want to control the maximum burst of log statements that can be sent to an appender. The
    043     * filter is configured in the log4j configuration file. For example, the following configuration limits the number of
    044     * INFO level (as well as DEBUG and TRACE) log statements that can be sent to the console to a burst of 100 with an
    045     * average rate of 16 per second. WARN, ERROR and FATAL messages would continue to be delivered.
    046     * </p>
    047     * <code>
    048     * &lt;Console name="console"&gt;<br>
    049     * &nbsp;&lt;PatternLayout pattern="%-5p %d{dd-MMM-yyyy HH:mm:ss} %x %t %m%n"/&gt;<br>
    050     * &nbsp;&lt;filters&gt;<br>
    051     * &nbsp;&nbsp;&lt;Burst level="INFO" rate="16" maxBurst="100"/&gt;<br>
    052     * &nbsp;&lt;/filters&gt;<br>
    053     * &lt;/Console&gt;<br>
    054     * </code><br>
    055     */
    056
    057    @Plugin(name = "BurstFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true)
    058    public final class BurstFilter extends AbstractFilter {
    059
    060        private static final long serialVersionUID = 1L;
    061
    062        private static final long NANOS_IN_SECONDS = 1000000000;
    063
    064        private static final int DEFAULT_RATE = 10;
    065
    066        private static final int DEFAULT_RATE_MULTIPLE = 100;
    067
    068        private static final int HASH_SHIFT = 32;
    069
    070        /**
    071         * Level of messages to be filtered. Anything at or below this level will be
    072         * filtered out if <code>maxBurst</code> has been exceeded. The default is
    073         * WARN meaning any messages that are higher than warn will be logged
    074         * regardless of the size of a burst.
    075         */
    076        private final Level level;
    077
    078        private final long burstInterval;
    079
    080        private final DelayQueue<LogDelay> history = new DelayQueue<LogDelay>();
    081
    082        private final Queue<LogDelay> available = new ConcurrentLinkedQueue<LogDelay>();
    083
    084        static LogDelay createLogDelay(long expireTime) {
    085            return new LogDelay(expireTime);
    086        }
    087
    088        private BurstFilter(final Level level, final float rate, final long maxBurst, final Result onMatch,
    089                            final Result onMismatch) {
    090            super(onMatch, onMismatch);
    091            this.level = level;
    092            this.burstInterval = (long) (NANOS_IN_SECONDS * (maxBurst / rate));
    093            for (int i = 0; i < maxBurst; ++i) {
    094                available.add(createLogDelay(0));
    095            }
    096        }
    097
    098        @Override
    099        public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
    100                             final Object... params) {
    101            return filter(level);
    102        }
    103
    104        @Override
    105        public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg,
    106                             final Throwable t) {
    107            return filter(level);
    108        }
    109
    110        @Override
    111        public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg,
    112                             final Throwable t) {
    113            return filter(level);
    114        }
    115
    116        @Override
    117        public Result filter(final LogEvent event) {
    118            return filter(event.getLevel());
    119        }
    120
    121        /**
    122         * Decide if we're going to log <code>event</code> based on whether the
    123         * maximum burst of log statements has been exceeded.
    124         *
    125         * @param level The log level.
    126         * @return The onMatch value if the filter passes, onMismatch otherwise.
    127         */
    128        private Result filter(final Level level) {
    129            if (this.level.isMoreSpecificThan(level)) {
    130                LogDelay delay = history.poll();
    131                while (delay != null) {
    132                    available.add(delay);
    133                    delay = history.poll();
    134                }
    135                delay = available.poll();
    136                if (delay != null) {
    137                    delay.setDelay(burstInterval);
    138                    history.add(delay);
    139                    return onMatch;
    140                }
    141                return onMismatch;
    142            }
    143            return onMatch;
    144
    145        }
    146
    147        /**
    148         * Returns the number of available slots. Used for unit testing.
    149         * @return The number of available slots.
    150         */
    151        public int getAvailable() {
    152            return available.size();
    153        }
    154
    155        /**
    156         * Clear the history. Used for unit testing.
    157         */
    158        public void clear() {
    159            final Iterator<LogDelay> iter = history.iterator();
    160            while (iter.hasNext()) {
    161                final LogDelay delay = iter.next();
    162                history.remove(delay);
    163                available.add(delay);
    164            }
    165        }
    166
    167        @Override
    168        public String toString() {
    169            return "level=" + level.toString() + ", interval=" + burstInterval + ", max=" + history.size();
    170        }
    171
    172        /**
    173         * Delay object to represent each log event that has occurred within the timespan.
    174         *
    175         * Consider this class private, package visibility for testing.
    176         */
    177        private static class LogDelay implements Delayed {
    178
    179            LogDelay(long expireTime) {
    180                this.expireTime = expireTime;
    181            }
    182
    183            private long expireTime;
    184
    185            public void setDelay(final long delay) {
    186                this.expireTime = delay + System.nanoTime();
    187            }
    188
    189            @Override
    190            public long getDelay(final TimeUnit timeUnit) {
    191                return timeUnit.convert(expireTime - System.nanoTime(), TimeUnit.NANOSECONDS);
    192            }
    193
    194            @Override
    195            public int compareTo(final Delayed delayed) {
    196                final long diff = this.expireTime - ((LogDelay) delayed).expireTime;
    197                return Long.signum(diff);
    198            }
    199
    200            @Override
    201            public boolean equals(final Object o) {
    202                if (this == o) {
    203                    return true;
    204                }
    205                if (o == null || getClass() != o.getClass()) {
    206                    return false;
    207                }
    208
    209                final LogDelay logDelay = (LogDelay) o;
    210
    211                if (expireTime != logDelay.expireTime) {
    212                    return false;
    213                }
    214
    215                return true;
    216            }
    217
    218            @Override
    219            public int hashCode() {
    220                return (int) (expireTime ^ (expireTime >>> HASH_SHIFT));
    221            }
    222        }
    223
    224        @PluginBuilderFactory
    225        public static Builder newBuilder() {
    226            return new Builder();
    227        }
    228
    229        public static class Builder implements org.apache.logging.log4j.core.util.Builder<BurstFilter> {
    230
    231            @PluginBuilderAttribute
    232            private Level level = Level.WARN;
    233
    234            @PluginBuilderAttribute
    235            private float rate = DEFAULT_RATE;
    236
    237            @PluginBuilderAttribute
    238            private long maxBurst;
    239
    240            @PluginBuilderAttribute
    241            private Result onMatch = Result.NEUTRAL;
    242
    243            @PluginBuilderAttribute
    244            private Result onMismatch = Result.DENY;
    245
    246            /**
    247             * Sets the logging level to use.
    248             */
    249            public Builder setLevel(final Level level) {
    250                this.level = level;
    251                return this;
    252            }
    253
    254            /**
    255             * Sets the average number of events per second to allow. This must be a positive number.
    256             */
    257            public Builder setRate(final float rate) {
    258                this.rate = rate;
    259                return this;
    260            }
    261
    262            /**
    263             * Sets the maximum number of events that can occur before events are filtered for exceeding the average rate.
    264             * The default is 10 times the rate.
    265             */
    266            public Builder setMaxBurst(final long maxBurst) {
    267                this.maxBurst = maxBurst;
    268                return this;
    269            }
    270
    271            /**
    272             * Sets the Result to return when the filter matches. Defaults to Result.NEUTRAL.
    273             */
    274            public Builder setOnMatch(final Result onMatch) {
    275                this.onMatch = onMatch;
    276                return this;
    277            }
    278
    279            /**
    280             * Sets the Result to return when the filter does not match. The default is Result.DENY.
    281             */
    282            public Builder setOnMismatch(final Result onMismatch) {
    283                this.onMismatch = onMismatch;
    284                return this;
    285            }
    286
    287            @Override
    288            public BurstFilter build() {
    289                if (this.rate <= 0) {
    290                    this.rate = DEFAULT_RATE;
    291                }
    292                if (this.maxBurst <= 0) {
    293                    this.maxBurst = (long) (this.rate * DEFAULT_RATE_MULTIPLE);
    294                }
    295                return new BurstFilter(this.level, this.rate, this.maxBurst, this.onMatch, this.onMismatch);
    296            }
    297        }
    298    }