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.pattern;
018
019import java.util.ArrayList;
020import java.util.List;
021import java.util.Locale;
022
023import org.apache.logging.log4j.Logger;
024import org.apache.logging.log4j.core.LogEvent;
025import org.apache.logging.log4j.core.config.Configuration;
026import org.apache.logging.log4j.core.config.plugins.Plugin;
027import org.apache.logging.log4j.core.util.Loader;
028import org.apache.logging.log4j.message.Message;
029import org.apache.logging.log4j.message.MultiformatMessage;
030import org.apache.logging.log4j.status.StatusLogger;
031import org.apache.logging.log4j.util.MultiFormatStringBuilderFormattable;
032import org.apache.logging.log4j.util.PerformanceSensitive;
033import org.apache.logging.log4j.util.StringBuilderFormattable;
034
035/**
036 * Returns the event's rendered message in a StringBuilder.
037 */
038@Plugin(name = "MessagePatternConverter", category = PatternConverter.CATEGORY)
039@ConverterKeys({ "m", "msg", "message" })
040@PerformanceSensitive("allocation")
041public class MessagePatternConverter extends LogEventPatternConverter {
042    
043    private static final String LOOKUPS = "lookups";
044    private static final String NOLOOKUPS = "nolookups";
045
046    private MessagePatternConverter() {
047        super("Message", "message");
048    }
049
050    private static TextRenderer loadMessageRenderer(final String[] options) {
051        if (options != null) {
052            for (final String option : options) {
053                switch (option.toUpperCase(Locale.ROOT)) {
054                case "ANSI":
055                    if (Loader.isJansiAvailable()) {
056                        return new JAnsiTextRenderer(options, JAnsiTextRenderer.DefaultMessageStyleMap);
057                    }
058                    StatusLogger.getLogger()
059                            .warn("You requested ANSI message rendering but JANSI is not on the classpath.");
060                    return null;
061                case "HTML":
062                    return new HtmlTextRenderer(options);
063                }
064            }
065        }
066        return null;
067    }
068
069    /**
070     * Obtains an instance of pattern converter.
071     *
072     * @param config
073     *            The Configuration.
074     * @param options
075     *            options, may be null.
076     * @return instance of pattern converter.
077     */
078    public static MessagePatternConverter newInstance(final Configuration config, final String[] options) {
079        String[] formats = withoutLookupOptions(options);
080        TextRenderer textRenderer = loadMessageRenderer(formats);
081        MessagePatternConverter result = formats == null || formats.length == 0
082                ? SimpleMessagePatternConverter.INSTANCE
083                : new FormattedMessagePatternConverter(formats);
084        if (textRenderer != null) {
085            result = new RenderingPatternConverter(result, textRenderer);
086        }
087        return result;
088    }
089
090    private static String[] withoutLookupOptions(final String[] options) {
091        if (options == null || options.length == 0) {
092            return options;
093        }
094        List<String> results = new ArrayList<>(options.length);
095        for (String option : options) {
096            if (LOOKUPS.equalsIgnoreCase(option) || NOLOOKUPS.equalsIgnoreCase(option)) {
097                LOGGER.info("The {} option will be ignored. Message Lookups are no longer supported.", option);
098            } else {
099                results.add(option);
100            }
101        }
102        return results.toArray(new String[0]);
103    }
104
105    @Override
106    public void format(final LogEvent event, final StringBuilder toAppendTo) {
107        throw new UnsupportedOperationException();
108    }
109
110    private static final class SimpleMessagePatternConverter extends MessagePatternConverter {
111        private static final MessagePatternConverter INSTANCE = new SimpleMessagePatternConverter();
112
113        /**
114         * {@inheritDoc}
115         */
116        @Override
117        public void format(final LogEvent event, final StringBuilder toAppendTo) {
118            Message msg = event.getMessage();
119            if (msg instanceof StringBuilderFormattable) {
120                ((StringBuilderFormattable) msg).formatTo(toAppendTo);
121            } else if (msg != null) {
122                toAppendTo.append(msg.getFormattedMessage());
123            }
124        }
125    }
126
127    private static final class FormattedMessagePatternConverter extends MessagePatternConverter {
128
129        private final String[] formats;
130
131        FormattedMessagePatternConverter(final String[] formats) {
132            this.formats = formats;
133        }
134
135        /**
136         * {@inheritDoc}
137         */
138        @Override
139        public void format(final LogEvent event, final StringBuilder toAppendTo) {
140            Message msg = event.getMessage();
141            if (msg instanceof StringBuilderFormattable) {
142                if (msg instanceof MultiFormatStringBuilderFormattable) {
143                    ((MultiFormatStringBuilderFormattable) msg).formatTo(formats, toAppendTo);
144                } else {
145                    ((StringBuilderFormattable) msg).formatTo(toAppendTo);
146                }
147            } else if (msg != null) {
148                toAppendTo.append(msg instanceof MultiformatMessage
149                        ? ((MultiformatMessage) msg).getFormattedMessage(formats)
150                        : msg.getFormattedMessage());
151            }
152        }
153    }
154
155    private static final class RenderingPatternConverter extends MessagePatternConverter {
156
157        private final MessagePatternConverter delegate;
158        private final TextRenderer textRenderer;
159
160        RenderingPatternConverter(final MessagePatternConverter delegate, final TextRenderer textRenderer) {
161            this.delegate = delegate;
162            this.textRenderer = textRenderer;
163        }
164
165        /**
166         * {@inheritDoc}
167         */
168        @Override
169        public void format(final LogEvent event, final StringBuilder toAppendTo) {
170            StringBuilder workingBuilder = new StringBuilder(80);
171            delegate.format(event, workingBuilder);
172            textRenderer.render(workingBuilder, toAppendTo);
173        }
174
175    }
176}