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