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 Marker in the LogEvent.
43   */
44  @Plugin(name = "ScriptPatternSelector", category = Node.CATEGORY, elementType = PatternSelector.ELEMENT_TYPE, printObject = true)
45  public class ScriptPatternSelector implements PatternSelector, LocationAware {
46  
47      /**
48       * Custom ScriptPatternSelector builder. Use the {@link #newBuilder() builder factory method} to create this.
49       */
50      public static class Builder implements org.apache.logging.log4j.core.util.Builder<ScriptPatternSelector> {
51  
52          @PluginElement("Script")
53          private AbstractScript script;
54  
55          @PluginElement("PatternMatch")
56          private PatternMatch[] properties;
57  
58          @PluginBuilderAttribute("defaultPattern")
59          private String defaultPattern;
60  
61          @PluginBuilderAttribute("alwaysWriteExceptions")
62          private boolean alwaysWriteExceptions = true;
63  
64          @PluginBuilderAttribute("disableAnsi")
65          private boolean disableAnsi;
66  
67          @PluginBuilderAttribute("noConsoleNoAnsi")
68          private boolean noConsoleNoAnsi;
69  
70          @PluginConfiguration
71          private Configuration configuration;
72  
73          private Builder() {
74              // nothing
75          }
76  
77          @Override
78          public ScriptPatternSelector build() {
79              if (script == null) {
80                  LOGGER.error("A Script, ScriptFile or ScriptRef element must be provided for this ScriptFilter");
81                  return null;
82              }
83              if (script instanceof ScriptRef) {
84                  if (configuration.getScriptManager().getScript(script.getName()) == null) {
85                      LOGGER.error("No script with name {} has been declared.", script.getName());
86                      return null;
87                  }
88              }
89              if (defaultPattern == null) {
90                  defaultPattern = PatternLayout.DEFAULT_CONVERSION_PATTERN;
91              }
92              if (properties == null || properties.length == 0) {
93                  LOGGER.warn("No marker patterns were provided");
94                  return null;
95              }
96              return new ScriptPatternSelector(script, properties, defaultPattern, alwaysWriteExceptions, disableAnsi,
97                      noConsoleNoAnsi, configuration);
98          }
99  
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 }