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