001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.logging.log4j2;
018
019import java.util.Collections;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import org.apache.logging.log4j.Level;
025import org.apache.logging.log4j.core.LogEvent;
026import org.apache.logging.log4j.core.config.Configuration;
027import org.apache.logging.log4j.core.config.plugins.Plugin;
028import org.apache.logging.log4j.core.layout.PatternLayout;
029import org.apache.logging.log4j.core.pattern.ConverterKeys;
030import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
031import org.apache.logging.log4j.core.pattern.PatternConverter;
032import org.apache.logging.log4j.core.pattern.PatternFormatter;
033import org.apache.logging.log4j.core.pattern.PatternParser;
034
035import org.springframework.boot.ansi.AnsiColor;
036import org.springframework.boot.ansi.AnsiElement;
037import org.springframework.boot.ansi.AnsiOutput;
038import org.springframework.boot.ansi.AnsiStyle;
039
040/**
041 * Log4j2 {@link LogEventPatternConverter} colors output using the {@link AnsiOutput}
042 * class. A single option 'styling' can be provided to the converter, or if not specified
043 * color styling will be picked based on the logging level.
044 *
045 * @author Vladimir Tsanev
046 * @since 1.3.0
047 */
048@Plugin(name = "color", category = PatternConverter.CATEGORY)
049@ConverterKeys({ "clr", "color" })
050public final class ColorConverter extends LogEventPatternConverter {
051
052        private static final Map<String, AnsiElement> ELEMENTS;
053
054        static {
055                Map<String, AnsiElement> ansiElements = new HashMap<>();
056                ansiElements.put("faint", AnsiStyle.FAINT);
057                ansiElements.put("red", AnsiColor.RED);
058                ansiElements.put("green", AnsiColor.GREEN);
059                ansiElements.put("yellow", AnsiColor.YELLOW);
060                ansiElements.put("blue", AnsiColor.BLUE);
061                ansiElements.put("magenta", AnsiColor.MAGENTA);
062                ansiElements.put("cyan", AnsiColor.CYAN);
063                ELEMENTS = Collections.unmodifiableMap(ansiElements);
064        }
065
066        private static final Map<Integer, AnsiElement> LEVELS;
067
068        static {
069                Map<Integer, AnsiElement> ansiLevels = new HashMap<>();
070                ansiLevels.put(Level.FATAL.intLevel(), AnsiColor.RED);
071                ansiLevels.put(Level.ERROR.intLevel(), AnsiColor.RED);
072                ansiLevels.put(Level.WARN.intLevel(), AnsiColor.YELLOW);
073                LEVELS = Collections.unmodifiableMap(ansiLevels);
074        }
075
076        private final List<PatternFormatter> formatters;
077
078        private final AnsiElement styling;
079
080        private ColorConverter(List<PatternFormatter> formatters, AnsiElement styling) {
081                super("style", "style");
082                this.formatters = formatters;
083                this.styling = styling;
084        }
085
086        /**
087         * Creates a new instance of the class. Required by Log4J2.
088         * @param config the configuration
089         * @param options the options
090         * @return a new instance, or {@code null} if the options are invalid
091         */
092        public static ColorConverter newInstance(Configuration config, String[] options) {
093                if (options.length < 1) {
094                        LOGGER.error("Incorrect number of options on style. "
095                                        + "Expected at least 1, received {}", options.length);
096                        return null;
097                }
098                if (options[0] == null) {
099                        LOGGER.error("No pattern supplied on style");
100                        return null;
101                }
102                PatternParser parser = PatternLayout.createPatternParser(config);
103                List<PatternFormatter> formatters = parser.parse(options[0]);
104                AnsiElement element = (options.length != 1) ? ELEMENTS.get(options[1]) : null;
105                return new ColorConverter(formatters, element);
106        }
107
108        @Override
109        public boolean handlesThrowable() {
110                for (PatternFormatter formatter : this.formatters) {
111                        if (formatter.handlesThrowable()) {
112                                return true;
113                        }
114                }
115                return super.handlesThrowable();
116        }
117
118        @Override
119        public void format(LogEvent event, StringBuilder toAppendTo) {
120                StringBuilder buf = new StringBuilder();
121                for (PatternFormatter formatter : this.formatters) {
122                        formatter.format(event, buf);
123                }
124                if (buf.length() > 0) {
125                        AnsiElement element = this.styling;
126                        if (element == null) {
127                                // Assume highlighting
128                                element = LEVELS.get(event.getLevel().intLevel());
129                                element = (element != null) ? element : AnsiColor.GREEN;
130                        }
131                        appendAnsiString(toAppendTo, buf.toString(), element);
132                }
133        }
134
135        protected void appendAnsiString(StringBuilder toAppendTo, String in,
136                        AnsiElement element) {
137                toAppendTo.append(AnsiOutput.toString(element, in));
138        }
139
140}