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 */
017package org.apache.logging.log4j.core.filter;
018
019import java.util.HashMap;
020import java.util.Map;
021import java.util.Objects;
022
023import org.apache.logging.log4j.Level;
024import org.apache.logging.log4j.Marker;
025import org.apache.logging.log4j.ThreadContext;
026import org.apache.logging.log4j.core.Filter;
027import org.apache.logging.log4j.core.LogEvent;
028import org.apache.logging.log4j.core.Logger;
029import org.apache.logging.log4j.core.config.Node;
030import org.apache.logging.log4j.core.config.plugins.Plugin;
031import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
032import org.apache.logging.log4j.core.config.plugins.PluginElement;
033import org.apache.logging.log4j.core.config.plugins.PluginFactory;
034import org.apache.logging.log4j.core.ContextDataInjector;
035import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory;
036import org.apache.logging.log4j.core.util.KeyValuePair;
037import org.apache.logging.log4j.message.Message;
038import org.apache.logging.log4j.util.PerformanceSensitive;
039import org.apache.logging.log4j.util.ReadOnlyStringMap;
040
041/**
042 * Compares against a log level that is associated with a context value. By default the context is the
043 * {@link ThreadContext}, but users may {@linkplain ContextDataInjectorFactory configure} a custom
044 * {@link ContextDataInjector} which obtains context data from some other source.
045 */
046@Plugin(name = "DynamicThresholdFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true)
047@PerformanceSensitive("allocation")
048public final class DynamicThresholdFilter extends AbstractFilter {
049
050    /**
051     * Creates a DynamicThresholdFilter.
052     * @param key The name of the key to compare.
053     * @param pairs An array of value and Level pairs.
054     * @param defaultThreshold The default Level.
055     * @param onMatch The action to perform if a match occurs.
056     * @param onMismatch The action to perform if no match occurs.
057     * @return The DynamicThresholdFilter.
058     */
059    // TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder
060    @PluginFactory
061    public static DynamicThresholdFilter createFilter(
062            @PluginAttribute("key") final String key,
063            @PluginElement("Pairs") final KeyValuePair[] pairs,
064            @PluginAttribute("defaultThreshold") final Level defaultThreshold,
065            @PluginAttribute("onMatch") final Result onMatch,
066            @PluginAttribute("onMismatch") final Result onMismatch) {
067        final Map<String, Level> map = new HashMap<>();
068        for (final KeyValuePair pair : pairs) {
069            map.put(pair.getKey(), Level.toLevel(pair.getValue()));
070        }
071        final Level level = defaultThreshold == null ? Level.ERROR : defaultThreshold;
072        return new DynamicThresholdFilter(key, map, level, onMatch, onMismatch);
073    }
074
075    private Level defaultThreshold = Level.ERROR;
076    private final String key;
077    private final ContextDataInjector injector = ContextDataInjectorFactory.createInjector();
078    private Map<String, Level> levelMap = new HashMap<>();
079
080    private DynamicThresholdFilter(final String key, final Map<String, Level> pairs, final Level defaultLevel,
081                                   final Result onMatch, final Result onMismatch) {
082        super(onMatch, onMismatch);
083        Objects.requireNonNull(key, "key cannot be null");
084        this.key = key;
085        this.levelMap = pairs;
086        this.defaultThreshold = defaultLevel;
087    }
088
089    @Override
090    public boolean equals(final Object obj) {
091        if (this == obj) {
092            return true;
093        }
094        if (!super.equalsImpl(obj)) {
095            return false;
096        }
097        if (getClass() != obj.getClass()) {
098            return false;
099        }
100        final DynamicThresholdFilter other = (DynamicThresholdFilter) obj;
101        if (!Objects.equals(defaultThreshold, other.defaultThreshold)) {
102            return false;
103        }
104        if (!Objects.equals(key, other.key)) {
105            return false;
106        }
107        if (!Objects.equals(levelMap, other.levelMap)) {
108            return false;
109        }
110        return true;
111    }
112
113    private Result filter(final Level level, final ReadOnlyStringMap contextMap) {
114        final String value = contextMap.getValue(key);
115        if (value != null) {
116            Level ctxLevel = levelMap.get(value);
117            if (ctxLevel == null) {
118                ctxLevel = defaultThreshold;
119            }
120            return level.isMoreSpecificThan(ctxLevel) ? onMatch : onMismatch;
121        }
122        return Result.NEUTRAL;
123
124    }
125
126    @Override
127    public Result filter(final LogEvent event) {
128        return filter(event.getLevel(), event.getContextData());
129    }
130
131    @Override
132    public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg,
133                         final Throwable t) {
134        return filter(level, currentContextData());
135    }
136
137    @Override
138    public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg,
139                         final Throwable t) {
140        return filter(level, currentContextData());
141    }
142
143    @Override
144    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
145                         final Object... params) {
146        return filter(level, currentContextData());
147    }
148
149    private ReadOnlyStringMap currentContextData() {
150        return injector.rawContextData();
151    }
152
153    @Override
154    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
155            final Object p0) {
156        return filter(level, currentContextData());
157    }
158
159    @Override
160    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
161            final Object p0, final Object p1) {
162        return filter(level, currentContextData());
163    }
164
165    @Override
166    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
167            final Object p0, final Object p1, final Object p2) {
168        return filter(level, currentContextData());
169    }
170
171    @Override
172    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
173            final Object p0, final Object p1, final Object p2, final Object p3) {
174        return filter(level, currentContextData());
175    }
176
177    @Override
178    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
179            final Object p0, final Object p1, final Object p2, final Object p3,
180            final Object p4) {
181        return filter(level, currentContextData());
182    }
183
184    @Override
185    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
186            final Object p0, final Object p1, final Object p2, final Object p3,
187            final Object p4, final Object p5) {
188        return filter(level, currentContextData());
189    }
190
191    @Override
192    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
193            final Object p0, final Object p1, final Object p2, final Object p3,
194            final Object p4, final Object p5, final Object p6) {
195        return filter(level, currentContextData());
196    }
197
198    @Override
199    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
200            final Object p0, final Object p1, final Object p2, final Object p3,
201            final Object p4, final Object p5, final Object p6,
202            final Object p7) {
203        return filter(level, currentContextData());
204    }
205
206    @Override
207    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
208            final Object p0, final Object p1, final Object p2, final Object p3,
209            final Object p4, final Object p5, final Object p6,
210            final Object p7, final Object p8) {
211        return filter(level, currentContextData());
212    }
213
214    @Override
215    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
216            final Object p0, final Object p1, final Object p2, final Object p3,
217            final Object p4, final Object p5, final Object p6,
218            final Object p7, final Object p8, final Object p9) {
219        return filter(level, currentContextData());
220    }
221
222    public String getKey() {
223        return this.key;
224    }
225
226    public Map<String, Level> getLevelMap() {
227        return levelMap;
228    }
229
230    @Override
231    public int hashCode() {
232        final int prime = 31;
233        int result = super.hashCodeImpl();
234        result = prime * result + ((defaultThreshold == null) ? 0 : defaultThreshold.hashCode());
235        result = prime * result + ((key == null) ? 0 : key.hashCode());
236        result = prime * result + ((levelMap == null) ? 0 : levelMap.hashCode());
237        return result;
238    }
239
240    @Override
241    public String toString() {
242        final StringBuilder sb = new StringBuilder();
243        sb.append("key=").append(key);
244        sb.append(", default=").append(defaultThreshold);
245        if (levelMap.size() > 0) {
246            sb.append('{');
247            boolean first = true;
248            for (final Map.Entry<String, Level> entry : levelMap.entrySet()) {
249                if (!first) {
250                    sb.append(", ");
251                    first = false;
252                }
253                sb.append(entry.getKey()).append('=').append(entry.getValue());
254            }
255            sb.append('}');
256        }
257        return sb.toString();
258    }
259}