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}