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