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 @PluginFactory 060 public static DynamicThresholdFilter createFilter( 061 @PluginAttribute("key") final String key, 062 @PluginElement("Pairs") final KeyValuePair[] pairs, 063 @PluginAttribute("defaultThreshold") final Level defaultThreshold, 064 @PluginAttribute("onMatch") final Result onMatch, 065 @PluginAttribute("onMismatch") final Result onMismatch) { 066 final Map<String, Level> map = new HashMap<>(); 067 for (final KeyValuePair pair : pairs) { 068 map.put(pair.getKey(), Level.toLevel(pair.getValue())); 069 } 070 final Level level = defaultThreshold == null ? Level.ERROR : defaultThreshold; 071 return new DynamicThresholdFilter(key, map, level, onMatch, onMismatch); 072 } 073 074 private Level defaultThreshold = Level.ERROR; 075 private final String key; 076 private final ContextDataInjector injector = ContextDataInjectorFactory.createInjector(); 077 private Map<String, Level> levelMap = new HashMap<>(); 078 079 private DynamicThresholdFilter(final String key, final Map<String, Level> pairs, final Level defaultLevel, 080 final Result onMatch, final Result onMismatch) { 081 super(onMatch, onMismatch); 082 Objects.requireNonNull(key, "key cannot be null"); 083 this.key = key; 084 this.levelMap = pairs; 085 this.defaultThreshold = defaultLevel; 086 } 087 088 @Override 089 public boolean equals(final Object obj) { 090 if (this == obj) { 091 return true; 092 } 093 if (!super.equalsImpl(obj)) { 094 return false; 095 } 096 if (getClass() != obj.getClass()) { 097 return false; 098 } 099 final DynamicThresholdFilter other = (DynamicThresholdFilter) obj; 100 if (defaultThreshold == null) { 101 if (other.defaultThreshold != null) { 102 return false; 103 } 104 } else if (!defaultThreshold.equals(other.defaultThreshold)) { 105 return false; 106 } 107 if (key == null) { 108 if (other.key != null) { 109 return false; 110 } 111 } else if (!key.equals(other.key)) { 112 return false; 113 } 114 if (levelMap == null) { 115 if (other.levelMap != null) { 116 return false; 117 } 118 } else if (!levelMap.equals(other.levelMap)) { 119 return false; 120 } 121 return true; 122 } 123 124 private Result filter(final Level level, final ReadOnlyStringMap contextMap) { 125 final String value = contextMap.getValue(key); 126 if (value != null) { 127 Level ctxLevel = levelMap.get(value); 128 if (ctxLevel == null) { 129 ctxLevel = defaultThreshold; 130 } 131 return level.isMoreSpecificThan(ctxLevel) ? onMatch : onMismatch; 132 } 133 return Result.NEUTRAL; 134 135 } 136 137 @Override 138 public Result filter(final LogEvent event) { 139 return filter(event.getLevel(), event.getContextData()); 140 } 141 142 @Override 143 public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg, 144 final Throwable t) { 145 return filter(level, currentContextData()); 146 } 147 148 @Override 149 public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg, 150 final Throwable t) { 151 return filter(level, currentContextData()); 152 } 153 154 @Override 155 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 156 final Object... params) { 157 return filter(level, currentContextData()); 158 } 159 160 private ReadOnlyStringMap currentContextData() { 161 return injector.rawContextData(); 162 } 163 164 @Override 165 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 166 final Object p0) { 167 return filter(level, currentContextData()); 168 } 169 170 @Override 171 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 172 final Object p0, final Object p1) { 173 return filter(level, currentContextData()); 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 @Override 183 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 184 final Object p0, final Object p1, final Object p2, final Object p3) { 185 return filter(level, currentContextData()); 186 } 187 188 @Override 189 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 190 final Object p0, final Object p1, final Object p2, final Object p3, 191 final Object p4) { 192 return filter(level, currentContextData()); 193 } 194 195 @Override 196 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 197 final Object p0, final Object p1, final Object p2, final Object p3, 198 final Object p4, final Object p5) { 199 return filter(level, currentContextData()); 200 } 201 202 @Override 203 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 204 final Object p0, final Object p1, final Object p2, final Object p3, 205 final Object p4, final Object p5, final Object p6) { 206 return filter(level, currentContextData()); 207 } 208 209 @Override 210 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 211 final Object p0, final Object p1, final Object p2, final Object p3, 212 final Object p4, final Object p5, final Object p6, 213 final Object p7) { 214 return filter(level, currentContextData()); 215 } 216 217 @Override 218 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 219 final Object p0, final Object p1, final Object p2, final Object p3, 220 final Object p4, final Object p5, final Object p6, 221 final Object p7, final Object p8) { 222 return filter(level, currentContextData()); 223 } 224 225 @Override 226 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 227 final Object p0, final Object p1, final Object p2, final Object p3, 228 final Object p4, final Object p5, final Object p6, 229 final Object p7, final Object p8, final Object p9) { 230 return filter(level, currentContextData()); 231 } 232 233 public String getKey() { 234 return this.key; 235 } 236 237 public Map<String, Level> getLevelMap() { 238 return levelMap; 239 } 240 241 @Override 242 public int hashCode() { 243 final int prime = 31; 244 int result = super.hashCodeImpl(); 245 result = prime * result + ((defaultThreshold == null) ? 0 : defaultThreshold.hashCode()); 246 result = prime * result + ((key == null) ? 0 : key.hashCode()); 247 result = prime * result + ((levelMap == null) ? 0 : levelMap.hashCode()); 248 return result; 249 } 250 251 @Override 252 public String toString() { 253 final StringBuilder sb = new StringBuilder(); 254 sb.append("key=").append(key); 255 sb.append(", default=").append(defaultThreshold); 256 if (levelMap.size() > 0) { 257 sb.append('{'); 258 boolean first = true; 259 for (final Map.Entry<String, Level> entry : levelMap.entrySet()) { 260 if (!first) { 261 sb.append(", "); 262 first = false; 263 } 264 sb.append(entry.getKey()).append('=').append(entry.getValue()); 265 } 266 sb.append('}'); 267 } 268 return sb.toString(); 269 } 270}