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.layout; 018 019import java.util.HashMap; 020import java.util.List; 021import java.util.Map; 022 023import javax.script.SimpleBindings; 024 025import org.apache.logging.log4j.Logger; 026import org.apache.logging.log4j.core.LogEvent; 027import org.apache.logging.log4j.core.config.Configuration; 028import org.apache.logging.log4j.core.config.Node; 029import org.apache.logging.log4j.core.config.plugins.Plugin; 030import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; 031import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; 032import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; 033import org.apache.logging.log4j.core.config.plugins.PluginElement; 034import org.apache.logging.log4j.core.impl.LocationAware; 035import org.apache.logging.log4j.core.pattern.PatternFormatter; 036import org.apache.logging.log4j.core.pattern.PatternParser; 037import org.apache.logging.log4j.core.script.AbstractScript; 038import org.apache.logging.log4j.core.script.ScriptRef; 039import org.apache.logging.log4j.status.StatusLogger; 040 041/** 042 * Selects the pattern to use based on the result of executing a Script. The returned value will be used as the "key" 043 * to choose between one of the configured patterns. If no key is returned or there is no match the default 044 * pattern will be used. 045 */ 046@Plugin(name = "ScriptPatternSelector", category = Node.CATEGORY, elementType = PatternSelector.ELEMENT_TYPE, printObject = true) 047public class ScriptPatternSelector implements PatternSelector, LocationAware { 048 049 /** 050 * Custom ScriptPatternSelector builder. Use the {@link #newBuilder() builder factory method} to create this. 051 */ 052 public static class Builder implements org.apache.logging.log4j.core.util.Builder<ScriptPatternSelector> { 053 054 @PluginElement("Script") 055 private AbstractScript script; 056 057 @PluginElement("PatternMatch") 058 private PatternMatch[] properties; 059 060 @PluginBuilderAttribute("defaultPattern") 061 private String defaultPattern; 062 063 @PluginBuilderAttribute("alwaysWriteExceptions") 064 private boolean alwaysWriteExceptions = true; 065 066 @PluginBuilderAttribute("disableAnsi") 067 private boolean disableAnsi; 068 069 @PluginBuilderAttribute("noConsoleNoAnsi") 070 private boolean noConsoleNoAnsi; 071 072 @PluginConfiguration 073 private Configuration configuration; 074 075 private Builder() { 076 // nothing 077 } 078 079 @Override 080 public ScriptPatternSelector build() { 081 if (script == null) { 082 LOGGER.error("A Script, ScriptFile or ScriptRef element must be provided for this ScriptFilter"); 083 return null; 084 } 085 if (script instanceof ScriptRef) { 086 if (configuration.getScriptManager().getScript(script.getName()) == null) { 087 LOGGER.error("No script with name {} has been declared.", script.getName()); 088 return null; 089 } 090 } 091 if (defaultPattern == null) { 092 defaultPattern = PatternLayout.DEFAULT_CONVERSION_PATTERN; 093 } 094 if (properties == null || properties.length == 0) { 095 LOGGER.warn("No marker patterns were provided"); 096 return null; 097 } 098 return new ScriptPatternSelector(script, properties, defaultPattern, alwaysWriteExceptions, disableAnsi, 099 noConsoleNoAnsi, configuration); 100 } 101 102 public Builder setScript(final AbstractScript script) { 103 this.script = script; 104 return this; 105 } 106 107 public Builder setProperties(final PatternMatch[] properties) { 108 this.properties = properties; 109 return this; 110 } 111 112 public Builder setDefaultPattern(final String defaultPattern) { 113 this.defaultPattern = defaultPattern; 114 return this; 115 } 116 117 public Builder setAlwaysWriteExceptions(final boolean alwaysWriteExceptions) { 118 this.alwaysWriteExceptions = alwaysWriteExceptions; 119 return this; 120 } 121 122 public Builder setDisableAnsi(final boolean disableAnsi) { 123 this.disableAnsi = disableAnsi; 124 return this; 125 } 126 127 public Builder setNoConsoleNoAnsi(final boolean noConsoleNoAnsi) { 128 this.noConsoleNoAnsi = noConsoleNoAnsi; 129 return this; 130 } 131 132 public Builder setConfiguration(final Configuration config) { 133 this.configuration = config; 134 return this; 135 } 136 } 137 138 private final Map<String, PatternFormatter[]> formatterMap = new HashMap<>(); 139 140 private final Map<String, String> patternMap = new HashMap<>(); 141 142 private final PatternFormatter[] defaultFormatters; 143 144 private final String defaultPattern; 145 146 private static Logger LOGGER = StatusLogger.getLogger(); 147 private final AbstractScript script; 148 private final Configuration configuration; 149 private final boolean requiresLocation; 150 151 /** 152 * @deprecated Use {@link #newBuilder()} instead. This will be private in a future version. 153 */ 154 @Deprecated 155 public ScriptPatternSelector(final AbstractScript script, final PatternMatch[] properties, final String defaultPattern, 156 final boolean alwaysWriteExceptions, final boolean disableAnsi, 157 final boolean noConsoleNoAnsi, final Configuration config) { 158 this.script = script; 159 this.configuration = config; 160 if (!(script instanceof ScriptRef)) { 161 config.getScriptManager().addScript(script); 162 } 163 final PatternParser parser = PatternLayout.createPatternParser(config); 164 boolean needsLocation = false; 165 for (final PatternMatch property : properties) { 166 try { 167 final List<PatternFormatter> list = parser.parse(property.getPattern(), alwaysWriteExceptions, disableAnsi, noConsoleNoAnsi); 168 PatternFormatter[] formatters = list.toArray(new PatternFormatter[0]); 169 formatterMap.put(property.getKey(), formatters); 170 patternMap.put(property.getKey(), property.getPattern()); 171 for (int i = 0; !needsLocation && i < formatters.length; ++i) { 172 needsLocation = formatters[i].requiresLocation(); 173 } 174 } catch (final RuntimeException ex) { 175 throw new IllegalArgumentException("Cannot parse pattern '" + property.getPattern() + "'", ex); 176 } 177 } 178 try { 179 final List<PatternFormatter> list = parser.parse(defaultPattern, alwaysWriteExceptions, disableAnsi, noConsoleNoAnsi); 180 defaultFormatters = list.toArray(new PatternFormatter[0]); 181 this.defaultPattern = defaultPattern; 182 for (int i = 0; !needsLocation && i < defaultFormatters.length; ++i) { 183 needsLocation = defaultFormatters[i].requiresLocation(); 184 } 185 } catch (final RuntimeException ex) { 186 throw new IllegalArgumentException("Cannot parse pattern '" + defaultPattern + "'", ex); 187 } 188 this.requiresLocation = needsLocation; 189 } 190 191 @Override 192 public boolean requiresLocation() { 193 return requiresLocation; 194 } 195 196 @Override 197 public PatternFormatter[] getFormatters(final LogEvent event) { 198 final SimpleBindings bindings = new SimpleBindings(); 199 bindings.putAll(configuration.getProperties()); 200 bindings.put("substitutor", configuration.getStrSubstitutor()); 201 bindings.put("logEvent", event); 202 final Object object = configuration.getScriptManager().execute(script.getName(), bindings); 203 if (object == null) { 204 return defaultFormatters; 205 } 206 final PatternFormatter[] patternFormatter = formatterMap.get(object.toString()); 207 208 return patternFormatter == null ? defaultFormatters : patternFormatter; 209 } 210 211 212 /** 213 * Creates a builder for a custom ScriptPatternSelector. 214 * 215 * @return a ScriptPatternSelector builder. 216 */ 217 @PluginBuilderFactory 218 public static Builder newBuilder() { 219 return new Builder(); 220 } 221 222 /** 223 * Deprecated, use {@link #newBuilder()} instead. 224 * 225 * @param script 226 * @param properties 227 * @param defaultPattern 228 * @param alwaysWriteExceptions 229 * @param noConsoleNoAnsi 230 * @param configuration 231 * @return a new ScriptPatternSelector 232 * @deprecated Use {@link #newBuilder()} instead. 233 */ 234 @Deprecated 235 public static ScriptPatternSelector createSelector( 236 final AbstractScript script, 237 final PatternMatch[] properties, 238 final String defaultPattern, 239 final boolean alwaysWriteExceptions, 240 final boolean noConsoleNoAnsi, 241 final Configuration configuration) { 242 final Builder builder = newBuilder(); 243 builder.setScript(script); 244 builder.setProperties(properties); 245 builder.setDefaultPattern(defaultPattern); 246 builder.setAlwaysWriteExceptions(alwaysWriteExceptions); 247 builder.setNoConsoleNoAnsi(noConsoleNoAnsi); 248 builder.setConfiguration(configuration); 249 return builder.build(); 250 } 251 252 @Override 253 public String toString() { 254 final StringBuilder sb = new StringBuilder(); 255 boolean first = true; 256 for (final Map.Entry<String, String> entry : patternMap.entrySet()) { 257 if (!first) { 258 sb.append(", "); 259 } 260 sb.append("key=\"").append(entry.getKey()).append("\", pattern=\"").append(entry.getValue()).append("\""); 261 first = false; 262 } 263 if (!first) { 264 sb.append(", "); 265 } 266 sb.append("default=\"").append(defaultPattern).append("\""); 267 return sb.toString(); 268 } 269}