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.pattern.PatternFormatter;
35  import org.apache.logging.log4j.core.pattern.PatternParser;
36  import org.apache.logging.log4j.core.script.AbstractScript;
37  import org.apache.logging.log4j.core.script.ScriptRef;
38  import org.apache.logging.log4j.status.StatusLogger;
39  
40  /**
41   * Selects the pattern to use based on the Marker in the LogEvent.
42   */
43  @Plugin(name = "ScriptPatternSelector", category = Node.CATEGORY, elementType = PatternSelector.ELEMENT_TYPE, printObject = true)
44  public class ScriptPatternSelector implements PatternSelector {
45  
46      /**
47       * Custom ScriptPatternSelector builder. Use the {@link #newBuilder() builder factory method} to create this.
48       */
49      public static class Builder implements org.apache.logging.log4j.core.util.Builder<ScriptPatternSelector> {
50  
51          @PluginElement("Script")
52          private AbstractScript script;
53  
54          @PluginElement("PatternMatch")
55          private PatternMatch[] properties;
56  
57          @PluginBuilderAttribute("defaultPattern")
58          private String defaultPattern;
59  
60          @PluginBuilderAttribute("alwaysWriteExceptions")
61          private boolean alwaysWriteExceptions = true;
62  
63          @PluginBuilderAttribute("disableAnsi")
64          private boolean disableAnsi;
65  
66          @PluginBuilderAttribute("noConsoleNoAnsi")
67          private boolean noConsoleNoAnsi;
68  
69          @PluginConfiguration
70          private Configuration configuration;
71  
72          private Builder() {
73              // nothing
74          }
75  
76          @Override
77          public ScriptPatternSelector build() {
78              if (script == null) {
79                  LOGGER.error("A Script, ScriptFile or ScriptRef element must be provided for this ScriptFilter");
80                  return null;
81              }
82              if (script instanceof ScriptRef) {
83                  if (configuration.getScriptManager().getScript(script.getName()) == null) {
84                      LOGGER.error("No script with name {} has been declared.", script.getName());
85                      return null;
86                  }
87              }
88              if (defaultPattern == null) {
89                  defaultPattern = PatternLayout.DEFAULT_CONVERSION_PATTERN;
90              }
91              if (properties == null || properties.length == 0) {
92                  LOGGER.warn("No marker patterns were provided");
93                  return null;
94              }
95              return new ScriptPatternSelector(script, properties, defaultPattern, alwaysWriteExceptions, disableAnsi,
96                      noConsoleNoAnsi, configuration);
97          }
98  
99          public Builder setScript(final AbstractScript script) {
100             this.script = script;
101             return this;
102         }
103 
104         public Builder setProperties(final PatternMatch[] properties) {
105             this.properties = properties;
106             return this;
107         }
108 
109         public Builder setDefaultPattern(final String defaultPattern) {
110             this.defaultPattern = defaultPattern;
111             return this;
112         }
113 
114         public Builder setAlwaysWriteExceptions(final boolean alwaysWriteExceptions) {
115             this.alwaysWriteExceptions = alwaysWriteExceptions;
116             return this;
117         }
118 
119         public Builder setDisableAnsi(final boolean disableAnsi) {
120             this.disableAnsi = disableAnsi;
121             return this;
122         }
123 
124         public Builder setNoConsoleNoAnsi(final boolean noConsoleNoAnsi) {
125             this.noConsoleNoAnsi = noConsoleNoAnsi;
126             return this;
127         }
128 
129         public Builder setConfiguration(final Configuration config) {
130             this.configuration = config;
131             return this;
132         }
133     }
134 
135     private final Map<String, PatternFormatter[]> formatterMap = new HashMap<>();
136 
137     private final Map<String, String> patternMap = new HashMap<>();
138 
139     private final PatternFormatter[] defaultFormatters;
140 
141     private final String defaultPattern;
142 
143     private static Logger LOGGER = StatusLogger.getLogger();
144     private final AbstractScript script;
145     private final Configuration configuration;
146 
147 
148     /**
149      * @deprecated Use {@link #newBuilder()} instead. This will be private in a future version.
150      */
151     @Deprecated
152     public ScriptPatternSelector(final AbstractScript script, final PatternMatch[] properties, final String defaultPattern,
153                                  final boolean alwaysWriteExceptions, final boolean disableAnsi,
154                                  final boolean noConsoleNoAnsi, final Configuration config) {
155         this.script = script;
156         this.configuration = config;
157         if (!(script instanceof ScriptRef)) {
158             config.getScriptManager().addScript(script);
159         }
160         final PatternParser parser = PatternLayout.createPatternParser(config);
161         for (final PatternMatch property : properties) {
162             try {
163                 final List<PatternFormatter> list = parser.parse(property.getPattern(), alwaysWriteExceptions, disableAnsi, noConsoleNoAnsi);
164                 formatterMap.put(property.getKey(), list.toArray(new PatternFormatter[list.size()]));
165                 patternMap.put(property.getKey(), property.getPattern());
166             } catch (final RuntimeException ex) {
167                 throw new IllegalArgumentException("Cannot parse pattern '" + property.getPattern() + "'", ex);
168             }
169         }
170         try {
171             final List<PatternFormatter> list = parser.parse(defaultPattern, alwaysWriteExceptions, disableAnsi, noConsoleNoAnsi);
172             defaultFormatters = list.toArray(new PatternFormatter[list.size()]);
173             this.defaultPattern = defaultPattern;
174         } catch (final RuntimeException ex) {
175             throw new IllegalArgumentException("Cannot parse pattern '" + defaultPattern + "'", ex);
176         }
177     }
178 
179     @Override
180     public PatternFormatter[] getFormatters(final LogEvent event) {
181         final SimpleBindings bindings = new SimpleBindings();
182         bindings.putAll(configuration.getProperties());
183         bindings.put("substitutor", configuration.getStrSubstitutor());
184         bindings.put("logEvent", event);
185         final Object object = configuration.getScriptManager().execute(script.getName(), bindings);
186         if (object == null) {
187             return defaultFormatters;
188         }
189         final PatternFormatter[] patternFormatter = formatterMap.get(object.toString());
190 
191         return patternFormatter == null ? defaultFormatters : patternFormatter;
192     }
193 
194 
195     /**
196      * Creates a builder for a custom ScriptPatternSelector.
197      *
198      * @return a ScriptPatternSelector builder.
199      */
200     @PluginBuilderFactory
201     public static Builder newBuilder() {
202         return new Builder();
203     }
204 
205     /**
206      * Deprecated, use {@link #newBuilder()} instead.
207      *
208      * @param script
209      * @param properties
210      * @param defaultPattern
211      * @param alwaysWriteExceptions
212      * @param noConsoleNoAnsi
213      * @param configuration
214      * @return a new ScriptPatternSelector
215      * @deprecated Use {@link #newBuilder()} instead.
216      */
217     @Deprecated
218     public static ScriptPatternSelector createSelector(
219             final AbstractScript script,
220             final PatternMatch[] properties,
221             final String defaultPattern,
222             final boolean alwaysWriteExceptions,
223             final boolean noConsoleNoAnsi,
224             final Configuration configuration) {
225         final Builder builder = newBuilder();
226         builder.setScript(script);
227         builder.setProperties(properties);
228         builder.setDefaultPattern(defaultPattern);
229         builder.setAlwaysWriteExceptions(alwaysWriteExceptions);
230         builder.setNoConsoleNoAnsi(noConsoleNoAnsi);
231         builder.setConfiguration(configuration);
232         return builder.build();
233     }
234 
235     @Override
236     public String toString() {
237         final StringBuilder sb = new StringBuilder();
238         boolean first = true;
239         for (final Map.Entry<String, String> entry : patternMap.entrySet()) {
240             if (!first) {
241                 sb.append(", ");
242             }
243             sb.append("key=\"").append(entry.getKey()).append("\", pattern=\"").append(entry.getValue()).append("\"");
244             first = false;
245         }
246         if (!first) {
247             sb.append(", ");
248         }
249         sb.append("default=\"").append(defaultPattern).append("\"");
250         return sb.toString();
251     }
252 }