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 org.apache.logging.log4j.Level;
020import org.apache.logging.log4j.Logger;
021import org.apache.logging.log4j.core.LogEvent;
022import org.apache.logging.log4j.core.config.Configuration;
023import org.apache.logging.log4j.core.config.Node;
024import org.apache.logging.log4j.core.config.plugins.Plugin;
025import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
026import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
027import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
028import org.apache.logging.log4j.core.config.plugins.PluginElement;
029import org.apache.logging.log4j.core.impl.LocationAware;
030import org.apache.logging.log4j.core.pattern.PatternFormatter;
031import org.apache.logging.log4j.core.pattern.PatternParser;
032import org.apache.logging.log4j.status.StatusLogger;
033
034import java.util.HashMap;
035import java.util.List;
036import java.util.Map;
037
038/**
039 * Selects the pattern to use based on the Level in the LogEvent.
040 */
041@Plugin(name = "LevelPatternSelector", category = Node.CATEGORY, elementType = PatternSelector.ELEMENT_TYPE, printObject = true)
042public class LevelPatternSelector implements PatternSelector, LocationAware {
043
044    /**
045     * Custom MarkerPatternSelector builder. Use the {@link LevelPatternSelector#newBuilder() builder factory method} to create this.
046     */
047    public static class Builder implements org.apache.logging.log4j.core.util.Builder<LevelPatternSelector> {
048
049        @PluginElement("PatternMatch")
050        private PatternMatch[] properties;
051
052        @PluginBuilderAttribute("defaultPattern")
053        private String defaultPattern;
054
055        @PluginBuilderAttribute(value = "alwaysWriteExceptions")
056        private boolean alwaysWriteExceptions = true;
057
058        @PluginBuilderAttribute(value = "disableAnsi")
059        private boolean disableAnsi;
060
061        @PluginBuilderAttribute(value = "noConsoleNoAnsi")
062        private boolean noConsoleNoAnsi;
063
064        @PluginConfiguration
065        private Configuration configuration;
066
067        @Override
068        public LevelPatternSelector build() {
069            if (defaultPattern == null) {
070                defaultPattern = PatternLayout.DEFAULT_CONVERSION_PATTERN;
071            }
072            if (properties == null || properties.length == 0) {
073                LOGGER.warn("No marker patterns were provided with PatternMatch");
074                return null;
075            }
076            return new LevelPatternSelector(properties, defaultPattern, alwaysWriteExceptions, disableAnsi,
077                    noConsoleNoAnsi, configuration);
078        }
079
080        public Builder setProperties(final PatternMatch[] properties) {
081            this.properties = properties;
082            return this;
083        }
084
085        public Builder setDefaultPattern(final String defaultPattern) {
086            this.defaultPattern = defaultPattern;
087            return this;
088        }
089
090        public Builder setAlwaysWriteExceptions(final boolean alwaysWriteExceptions) {
091            this.alwaysWriteExceptions = alwaysWriteExceptions;
092            return this;
093        }
094
095        public Builder setDisableAnsi(final boolean disableAnsi) {
096            this.disableAnsi = disableAnsi;
097            return this;
098        }
099
100        public Builder setNoConsoleNoAnsi(final boolean noConsoleNoAnsi) {
101            this.noConsoleNoAnsi = noConsoleNoAnsi;
102            return this;
103        }
104
105        public Builder setConfiguration(final Configuration configuration) {
106            this.configuration = configuration;
107            return this;
108        }
109
110    }
111
112    private final Map<String, PatternFormatter[]> formatterMap = new HashMap<>();
113
114    private final Map<String, String> patternMap = new HashMap<>();
115
116    private final PatternFormatter[] defaultFormatters;
117
118    private final String defaultPattern;
119
120    private static Logger LOGGER = StatusLogger.getLogger();
121
122    private final boolean requiresLocation;
123
124    /**
125     * @deprecated Use {@link #newBuilder()} instead. This will be private in a future version.
126     */
127    @Deprecated
128    public LevelPatternSelector(final PatternMatch[] properties, final String defaultPattern,
129                                 final boolean alwaysWriteExceptions, final boolean noConsoleNoAnsi,
130                                 final Configuration config) {
131        this(properties, defaultPattern, alwaysWriteExceptions, false, noConsoleNoAnsi, config);
132    }
133
134    private LevelPatternSelector(final PatternMatch[] properties, final String defaultPattern,
135                                 final boolean alwaysWriteExceptions, final boolean disableAnsi,
136                                 final boolean noConsoleNoAnsi, final Configuration config) {
137        boolean needsLocation = false;
138        final PatternParser parser = PatternLayout.createPatternParser(config);
139        for (final PatternMatch property : properties) {
140            try {
141                final List<PatternFormatter> list = parser.parse(property.getPattern(), alwaysWriteExceptions,
142                        disableAnsi, noConsoleNoAnsi);
143                PatternFormatter[] formatters = list.toArray(new PatternFormatter[0]);
144                formatterMap.put(property.getKey(), formatters);
145                for (int i = 0; !needsLocation && i < formatters.length; ++i) {
146                    needsLocation = formatters[i].requiresLocation();
147                }
148
149                patternMap.put(property.getKey(), property.getPattern());
150            } catch (final RuntimeException ex) {
151                throw new IllegalArgumentException("Cannot parse pattern '" + property.getPattern() + "'", ex);
152            }
153        }
154        try {
155            final List<PatternFormatter> list = parser.parse(defaultPattern, alwaysWriteExceptions, disableAnsi,
156                    noConsoleNoAnsi);
157            defaultFormatters = list.toArray(new PatternFormatter[0]);
158            this.defaultPattern = defaultPattern;
159            for (int i = 0; !needsLocation && i < defaultFormatters.length; ++i) {
160                needsLocation = defaultFormatters[i].requiresLocation();
161            }
162        } catch (final RuntimeException ex) {
163            throw new IllegalArgumentException("Cannot parse pattern '" + defaultPattern + "'", ex);
164        }
165        requiresLocation = needsLocation;
166    }
167
168    @Override
169    public boolean requiresLocation() {
170        return requiresLocation;
171    }
172
173    @Override
174    public PatternFormatter[] getFormatters(final LogEvent event) {
175        final Level level = event.getLevel();
176        if (level == null) {
177            return defaultFormatters;
178        }
179        for (final String key : formatterMap.keySet()) {
180            if (level.name().equalsIgnoreCase(key)) {
181                return formatterMap.get(key);
182            }
183        }
184        return defaultFormatters;
185    }
186
187    /**
188     * Creates a builder for a custom ScriptPatternSelector.
189     *
190     * @return a ScriptPatternSelector builder.
191     */
192    @PluginBuilderFactory
193    public static Builder newBuilder() {
194        return new Builder();
195    }
196
197    /**
198     * Deprecated, use {@link #newBuilder()} instead.
199     * @param properties
200     * @param defaultPattern
201     * @param alwaysWriteExceptions
202     * @param noConsoleNoAnsi
203     * @param configuration
204     * @return a new MarkerPatternSelector.
205     * @deprecated Use {@link #newBuilder()} instead.
206     */
207    @Deprecated
208    public static LevelPatternSelector createSelector(
209            final PatternMatch[] properties,
210            final String defaultPattern,
211            final boolean alwaysWriteExceptions,
212            final boolean noConsoleNoAnsi,
213            final Configuration configuration) {
214        final Builder builder = newBuilder();
215        builder.setProperties(properties);
216        builder.setDefaultPattern(defaultPattern);
217        builder.setAlwaysWriteExceptions(alwaysWriteExceptions);
218        builder.setNoConsoleNoAnsi(noConsoleNoAnsi);
219        builder.setConfiguration(configuration);
220        return builder.build();
221    }
222
223    @Override
224    public String toString() {
225        final StringBuilder sb = new StringBuilder();
226        boolean first = true;
227        for (final Map.Entry<String, String> entry : patternMap.entrySet()) {
228            if (!first) {
229                sb.append(", ");
230            }
231            sb.append("key=\"").append(entry.getKey()).append("\", pattern=\"").append(entry.getValue()).append("\"");
232            first = false;
233        }
234        if (!first) {
235            sb.append(", ");
236        }
237        sb.append("default=\"").append(defaultPattern).append("\"");
238        return sb.toString();
239    }
240}