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 (defaultThreshold == null) { 102 if (other.defaultThreshold != null) { 103 return false; 104 } 105 } else if (!defaultThreshold.equals(other.defaultThreshold)) { 106 return false; 107 } 108 if (key == null) { 109 if (other.key != null) { 110 return false; 111 } 112 } else if (!key.equals(other.key)) { 113 return false; 114 } 115 if (levelMap == null) { 116 if (other.levelMap != null) { 117 return false; 118 } 119 } else if (!levelMap.equals(other.levelMap)) { 120 return false; 121 } 122 return true; 123 } 124 125 private Result filter(final Level level, final ReadOnlyStringMap contextMap) { 126 final String value = contextMap.getValue(key); 127 if (value != null) { 128 Level ctxLevel = levelMap.get(value); 129 if (ctxLevel == null) { 130 ctxLevel = defaultThreshold; 131 } 132 return level.isMoreSpecificThan(ctxLevel) ? onMatch : onMismatch; 133 } 134 return Result.NEUTRAL; 135 136 } 137 138 @Override 139 public Result filter(final LogEvent event) { 140 return filter(event.getLevel(), event.getContextData()); 141 } 142 143 @Override 144 public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg, 145 final Throwable t) { 146 return filter(level, currentContextData()); 147 } 148 149 @Override 150 public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg, 151 final Throwable t) { 152 return filter(level, currentContextData()); 153 } 154 155 @Override 156 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 157 final Object... params) { 158 return filter(level, currentContextData()); 159 } 160 161 private ReadOnlyStringMap currentContextData() { 162 return injector.rawContextData(); 163 } 164 165 @Override 166 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 167 final Object p0) { 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) { 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) { 180 return filter(level, currentContextData()); 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 @Override 190 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 191 final Object p0, final Object p1, final Object p2, final Object p3, 192 final Object p4) { 193 return filter(level, currentContextData()); 194 } 195 196 @Override 197 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 198 final Object p0, final Object p1, final Object p2, final Object p3, 199 final Object p4, final Object p5) { 200 return filter(level, currentContextData()); 201 } 202 203 @Override 204 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 205 final Object p0, final Object p1, final Object p2, final Object p3, 206 final Object p4, final Object p5, final Object p6) { 207 return filter(level, currentContextData()); 208 } 209 210 @Override 211 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 212 final Object p0, final Object p1, final Object p2, final Object p3, 213 final Object p4, final Object p5, final Object p6, 214 final Object p7) { 215 return filter(level, currentContextData()); 216 } 217 218 @Override 219 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 220 final Object p0, final Object p1, final Object p2, final Object p3, 221 final Object p4, final Object p5, final Object p6, 222 final Object p7, final Object p8) { 223 return filter(level, currentContextData()); 224 } 225 226 @Override 227 public Result filter(final Logger logger, final Level level, final Marker marker, final String msg, 228 final Object p0, final Object p1, final Object p2, final Object p3, 229 final Object p4, final Object p5, final Object p6, 230 final Object p7, final Object p8, final Object p9) { 231 return filter(level, currentContextData()); 232 } 233 234 public String getKey() { 235 return this.key; 236 } 237 238 public Map<String, Level> getLevelMap() { 239 return levelMap; 240 } 241 242 @Override 243 public int hashCode() { 244 final int prime = 31; 245 int result = super.hashCodeImpl(); 246 result = prime * result + ((defaultThreshold == null) ? 0 : defaultThreshold.hashCode()); 247 result = prime * result + ((key == null) ? 0 : key.hashCode()); 248 result = prime * result + ((levelMap == null) ? 0 : levelMap.hashCode()); 249 return result; 250 } 251 252 @Override 253 public String toString() { 254 final StringBuilder sb = new StringBuilder(); 255 sb.append("key=").append(key); 256 sb.append(", default=").append(defaultThreshold); 257 if (levelMap.size() > 0) { 258 sb.append('{'); 259 boolean first = true; 260 for (final Map.Entry<String, Level> entry : levelMap.entrySet()) { 261 if (!first) { 262 sb.append(", "); 263 first = false; 264 } 265 sb.append(entry.getKey()).append('=').append(entry.getValue()); 266 } 267 sb.append('}'); 268 } 269 return sb.toString(); 270 } 271}