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.pattern.PatternFormatter;
035import org.apache.logging.log4j.core.pattern.PatternParser;
036import org.apache.logging.log4j.core.script.AbstractScript;
037import org.apache.logging.log4j.core.script.ScriptRef;
038import org.apache.logging.log4j.status.StatusLogger;
039
040/**
041 * Selects the pattern to use based on the Marker in the LogEvent.
042 */
043@Plugin(name = "ScriptPatternSelector", category = Node.CATEGORY, elementType = PatternSelector.ELEMENT_TYPE, printObject = true)
044public class ScriptPatternSelector implements PatternSelector {
045
046    /**
047     * Custom ScriptPatternSelector builder. Use the {@link #newBuilder() builder factory method} to create this.
048     */
049    public static class Builder implements org.apache.logging.log4j.core.util.Builder<ScriptPatternSelector> {
050
051        @PluginElement("Script")
052        private AbstractScript script;
053
054        @PluginElement("PatternMatch")
055        private PatternMatch[] properties;
056
057        @PluginBuilderAttribute("defaultPattern")
058        private String defaultPattern;
059
060        @PluginBuilderAttribute("alwaysWriteExceptions")
061        private boolean alwaysWriteExceptions = true;
062
063        @PluginBuilderAttribute("disableAnsi")
064        private boolean disableAnsi;
065
066        @PluginBuilderAttribute("noConsoleNoAnsi")
067        private boolean noConsoleNoAnsi;
068
069        @PluginConfiguration
070        private Configuration configuration;
071
072        private Builder() {
073            // nothing
074        }
075
076        @Override
077        public ScriptPatternSelector build() {
078            if (script == null) {
079                LOGGER.error("A Script, ScriptFile or ScriptRef element must be provided for this ScriptFilter");
080                return null;
081            }
082            if (script instanceof ScriptRef) {
083                if (configuration.getScriptManager().getScript(script.getName()) == null) {
084                    LOGGER.error("No script with name {} has been declared.", script.getName());
085                    return null;
086                }
087            }
088            if (defaultPattern == null) {
089                defaultPattern = PatternLayout.DEFAULT_CONVERSION_PATTERN;
090            }
091            if (properties == null || properties.length == 0) {
092                LOGGER.warn("No marker patterns were provided");
093                return null;
094            }
095            return new ScriptPatternSelector(script, properties, defaultPattern, alwaysWriteExceptions, disableAnsi,
096                    noConsoleNoAnsi, configuration);
097        }
098
099        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}