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}