View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.layout;
18  
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  
23  import javax.script.SimpleBindings;
24  
25  import org.apache.logging.log4j.Logger;
26  import org.apache.logging.log4j.core.LogEvent;
27  import org.apache.logging.log4j.core.config.Configuration;
28  import org.apache.logging.log4j.core.config.Node;
29  import org.apache.logging.log4j.core.config.plugins.Plugin;
30  import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
31  import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
32  import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
33  import org.apache.logging.log4j.core.config.plugins.PluginElement;
34  import org.apache.logging.log4j.core.impl.LocationAware;
35  import org.apache.logging.log4j.core.pattern.PatternFormatter;
36  import org.apache.logging.log4j.core.pattern.PatternParser;
37  import org.apache.logging.log4j.core.script.AbstractScript;
38  import org.apache.logging.log4j.core.script.ScriptRef;
39  import org.apache.logging.log4j.status.StatusLogger;
40  
41  /**
42   * Selects the pattern to use based on the result of executing a Script. The returned value will be used as the "key"
43   * to choose between one of the configured patterns. If no key is returned or there is no match the default
44   * pattern will be used.
45   */
46  @Plugin(name = "ScriptPatternSelector", category = Node.CATEGORY, elementType = PatternSelector.ELEMENT_TYPE, printObject = true)
47  public class ScriptPatternSelector implements PatternSelector, LocationAware {
48  
49      /**
50       * Custom ScriptPatternSelector builder. Use the {@link #newBuilder() builder factory method} to create this.
51       */
52      public static class Builder implements org.apache.logging.log4j.core.util.Builder<ScriptPatternSelector> {
53  
54          @PluginElement("Script")
55          private AbstractScript script;
56  
57          @PluginElement("PatternMatch")
58          private PatternMatch[] properties;
59  
60          @PluginBuilderAttribute("defaultPattern")
61          private String defaultPattern;
62  
63          @PluginBuilderAttribute("alwaysWriteExceptions")
64          private boolean alwaysWriteExceptions = true;
65  
66          @PluginBuilderAttribute("disableAnsi")
67          private boolean disableAnsi;
68  
69          @PluginBuilderAttribute("noConsoleNoAnsi")
70          private boolean noConsoleNoAnsi;
71  
72          @PluginConfiguration
73          private Configuration configuration;
74  
75          private Builder() {
76              // nothing
77          }
78  
79          @Override
80          public ScriptPatternSelector build() {
81              if (script == null) {
82                  LOGGER.error("A Script, ScriptFile or ScriptRef element must be provided for this ScriptFilter");
83                  return null;
84              }
85              if (script instanceof ScriptRef) {
86                  if (configuration.getScriptManager().getScript(script.getName()) == null) {
87                      LOGGER.error("No script with name {} has been declared.", script.getName());
88                      return null;
89                  }
90              }
91              if (defaultPattern == null) {
92                  defaultPattern = PatternLayout.DEFAULT_CONVERSION_PATTERN;
93              }
94              if (properties == null || properties.length == 0) {
95                  LOGGER.warn("No marker patterns were provided");
96                  return null;
97              }
98              return new ScriptPatternSelector(script, properties, defaultPattern, alwaysWriteExceptions, disableAnsi,
99                      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 }