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.util.KeyValuePair;
035import org.apache.logging.log4j.message.Message;
036
037/**
038 * Compare against a log level that is associated with an MDC value.
039 */
040@Plugin(name = "DynamicThresholdFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true)
041public final class DynamicThresholdFilter extends AbstractFilter {
042
043    /**
044     * Create the DynamicThresholdFilter.
045     * @param key The name of the key to compare.
046     * @param pairs An array of value and Level pairs.
047     * @param defaultThreshold The default Level.
048     * @param onMatch The action to perform if a match occurs.
049     * @param onMismatch The action to perform if no match occurs.
050     * @return The DynamicThresholdFilter.
051     */
052    @PluginFactory
053    public static DynamicThresholdFilter createFilter(
054            @PluginAttribute("key") final String key,
055            @PluginElement("Pairs") final KeyValuePair[] pairs,
056            @PluginAttribute("defaultThreshold") final Level defaultThreshold,
057            @PluginAttribute("onMatch") final Result onMatch,
058            @PluginAttribute("onMismatch") final Result onMismatch) {
059        final Map<String, Level> map = new HashMap<>();
060        for (final KeyValuePair pair : pairs) {
061            map.put(pair.getKey(), Level.toLevel(pair.getValue()));
062        }
063        final Level level = defaultThreshold == null ? Level.ERROR : defaultThreshold;
064        return new DynamicThresholdFilter(key, map, level, onMatch, onMismatch);
065    }
066    private Level defaultThreshold = Level.ERROR;
067    private final String key;
068
069    private Map<String, Level> levelMap = new HashMap<>();
070
071    private DynamicThresholdFilter(final String key, final Map<String, Level> pairs, final Level defaultLevel,
072                                   final Result onMatch, final Result onMismatch) {
073        super(onMatch, onMismatch);
074        Objects.requireNonNull(key, "key cannot be null");
075        this.key = key;
076        this.levelMap = pairs;
077        this.defaultThreshold = defaultLevel;
078    }
079
080    @Override
081    public boolean equals(final Object obj) {
082        if (this == obj) {
083            return true;
084        }
085        if (!super.equalsImpl(obj)) {
086            return false;
087        }
088        if (getClass() != obj.getClass()) {
089            return false;
090        }
091        final DynamicThresholdFilter other = (DynamicThresholdFilter) obj;
092        if (defaultThreshold == null) {
093            if (other.defaultThreshold != null) {
094                return false;
095            }
096        } else if (!defaultThreshold.equals(other.defaultThreshold)) {
097            return false;
098        }
099        if (key == null) {
100            if (other.key != null) {
101                return false;
102            }
103        } else if (!key.equals(other.key)) {
104            return false;
105        }
106        if (levelMap == null) {
107            if (other.levelMap != null) {
108                return false;
109            }
110        } else if (!levelMap.equals(other.levelMap)) {
111            return false;
112        }
113        return true;
114    }
115
116    private Result filter(final Level level, final Map<String, String> contextMap) {
117        final String value = contextMap.get(key);
118        if (value != null) {
119            Level ctxLevel = levelMap.get(value);
120            if (ctxLevel == null) {
121                ctxLevel = defaultThreshold;
122            }
123            return level.isMoreSpecificThan(ctxLevel) ? onMatch : onMismatch;
124        }
125        return Result.NEUTRAL;
126
127    }
128
129    @Override
130    public Result filter(final LogEvent event) {
131        return filter(event.getLevel(), event.getContextMap());
132    }
133
134    @Override
135    public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg,
136                         final Throwable t) {
137        return filter(level, ThreadContext.getContext());
138    }
139
140    @Override
141    public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg,
142                         final Throwable t) {
143        return filter(level, ThreadContext.getContext());
144    }
145
146    @Override
147    public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
148                         final Object... params) {
149        return filter(level, ThreadContext.getContext());
150    }
151
152    public String getKey() {
153        return this.key;
154    }
155
156    public Map<String, Level> getLevelMap() {
157        return levelMap;
158    }
159
160    @Override
161    public int hashCode() {
162        final int prime = 31;
163        int result = super.hashCodeImpl();
164        result = prime * result + ((defaultThreshold == null) ? 0 : defaultThreshold.hashCode());
165        result = prime * result + ((key == null) ? 0 : key.hashCode());
166        result = prime * result + ((levelMap == null) ? 0 : levelMap.hashCode());
167        return result;
168    }
169
170    @Override
171    public String toString() {
172        final StringBuilder sb = new StringBuilder();
173        sb.append("key=").append(key);
174        sb.append(", default=").append(defaultThreshold);
175        if (levelMap.size() > 0) {
176            sb.append('{');
177            boolean first = true;
178            for (final Map.Entry<String, Level> entry : levelMap.entrySet()) {
179                if (!first) {
180                    sb.append(", ");
181                    first = false;
182                }
183                sb.append(entry.getKey()).append('=').append(entry.getValue());
184            }
185            sb.append('}');
186        }
187        return sb.toString();
188    }
189}