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 static org.fusesource.jansi.AnsiRenderer.Code.BG_RED; 020import static org.fusesource.jansi.AnsiRenderer.Code.BOLD; 021import static org.fusesource.jansi.AnsiRenderer.Code.RED; 022import static org.fusesource.jansi.AnsiRenderer.Code.WHITE; 023import static org.fusesource.jansi.AnsiRenderer.Code.YELLOW; 024 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.Locale; 028import java.util.Map; 029 030import org.apache.logging.log4j.status.StatusLogger; 031import org.fusesource.jansi.Ansi; 032import org.fusesource.jansi.AnsiRenderer; 033import org.fusesource.jansi.AnsiRenderer.Code; 034 035/** 036 * Renders an input as ANSI escaped output. 037 * 038 * Uses the JAnsi rendering syntax as the default to render a message into an ANSI escaped string. 039 * 040 * The default syntax for embedded ANSI codes is: 041 * 042 * <pre> 043 * @|<em>code</em>(,<em>code</em>)* <em>text</em>|@ 044 * </pre> 045 * 046 * For example, to render the message {@code "Hello"} in green, use: 047 * 048 * <pre> 049 * @|green Hello|@ 050 * </pre> 051 * 052 * To render the message {@code "Hello"} in bold and red, use: 053 * 054 * <pre> 055 * @|bold,red Warning!|@ 056 * </pre> 057 * 058 * You can also define custom style names in the configuration with the syntax: 059 * 060 * <pre> 061 * %message{ansi}{StyleName=value(,value)*( StyleName=value(,value)*)*}%n 062 * </pre> 063 * 064 * For example: 065 * 066 * <pre> 067 * %message{ansi}{WarningStyle=red,bold KeyStyle=white ValueStyle=blue}%n 068 * </pre> 069 * 070 * The call site can look like this: 071 * 072 * <pre> 073 * logger.info("@|KeyStyle {}|@ = @|ValueStyle {}|@", entry.getKey(), entry.getValue()); 074 * </pre> 075 * 076 * Note: This class originally copied and then heavily modified code from JAnsi's AnsiRenderer (which is licensed as 077 * Apache 2.0.) 078 * 079 * @see AnsiRenderer 080 */ 081public final class JAnsiTextRenderer implements TextRenderer { 082 083 public static final Map<String, Code[]> DefaultExceptionStyleMap; 084 static final Map<String, Code[]> DefaultMessageStyleMap; 085 private static final Map<String, Map<String, Code[]>> PrefedinedStyleMaps; 086 087 private static void put(final Map<String, Code[]> map, final String name, final Code... codes) { 088 map.put(name, codes); 089 } 090 091 static { 092 final Map<String, Map<String, Code[]>> tempPreDefs = new HashMap<>(); 093 // Default style: Spock 094 { 095 // TODO Should the keys be in an enum? 096 final Map<String, Code[]> map = new HashMap<>(); 097 put(map, "Prefix", WHITE); 098 put(map, "Name", BG_RED, WHITE); 099 put(map, "NameMessageSeparator", BG_RED, WHITE); 100 put(map, "Message", BG_RED, WHITE, BOLD); 101 put(map, "At", WHITE); 102 put(map, "CauseLabel", WHITE); 103 put(map, "Text", WHITE); 104 put(map, "More", WHITE); 105 put(map, "Suppressed", WHITE); 106 // StackTraceElement 107 put(map, "StackTraceElement.ClassName", YELLOW); 108 put(map, "StackTraceElement.ClassMethodSeparator", YELLOW); 109 put(map, "StackTraceElement.MethodName", YELLOW); 110 put(map, "StackTraceElement.NativeMethod", YELLOW); 111 put(map, "StackTraceElement.FileName", RED); 112 put(map, "StackTraceElement.LineNumber", RED); 113 put(map, "StackTraceElement.Container", RED); 114 put(map, "StackTraceElement.ContainerSeparator", WHITE); 115 put(map, "StackTraceElement.UnknownSource", RED); 116 // ExtraClassInfo 117 put(map, "ExtraClassInfo.Inexact", YELLOW); 118 put(map, "ExtraClassInfo.Container", YELLOW); 119 put(map, "ExtraClassInfo.ContainerSeparator", YELLOW); 120 put(map, "ExtraClassInfo.Location", YELLOW); 121 put(map, "ExtraClassInfo.Version", YELLOW); 122 // Save 123 DefaultExceptionStyleMap = Collections.unmodifiableMap(map); 124 tempPreDefs.put("Spock", DefaultExceptionStyleMap); 125 } 126 // Style: Kirk 127 { 128 // TODO Should the keys be in an enum? 129 final Map<String, Code[]> map = new HashMap<>(); 130 put(map, "Prefix", WHITE); 131 put(map, "Name", BG_RED, YELLOW, BOLD); 132 put(map, "NameMessageSeparator", BG_RED, YELLOW); 133 put(map, "Message", BG_RED, WHITE, BOLD); 134 put(map, "At", WHITE); 135 put(map, "CauseLabel", WHITE); 136 put(map, "Text", WHITE); 137 put(map, "More", WHITE); 138 put(map, "Suppressed", WHITE); 139 // StackTraceElement 140 put(map, "StackTraceElement.ClassName", BG_RED, WHITE); 141 put(map, "StackTraceElement.ClassMethodSeparator", BG_RED, YELLOW); 142 put(map, "StackTraceElement.MethodName", BG_RED, YELLOW); 143 put(map, "StackTraceElement.NativeMethod", BG_RED, YELLOW); 144 put(map, "StackTraceElement.FileName", RED); 145 put(map, "StackTraceElement.LineNumber", RED); 146 put(map, "StackTraceElement.Container", RED); 147 put(map, "StackTraceElement.ContainerSeparator", WHITE); 148 put(map, "StackTraceElement.UnknownSource", RED); 149 // ExtraClassInfo 150 put(map, "ExtraClassInfo.Inexact", YELLOW); 151 put(map, "ExtraClassInfo.Container", WHITE); 152 put(map, "ExtraClassInfo.ContainerSeparator", WHITE); 153 put(map, "ExtraClassInfo.Location", YELLOW); 154 put(map, "ExtraClassInfo.Version", YELLOW); 155 // Save 156 tempPreDefs.put("Kirk", Collections.unmodifiableMap(map)); 157 } 158 { 159 final Map<String, Code[]> temp = new HashMap<>(); 160 // TODO 161 DefaultMessageStyleMap = Collections.unmodifiableMap(temp); 162 } 163 PrefedinedStyleMaps = Collections.unmodifiableMap(tempPreDefs); 164 } 165 166 private final String beginToken; 167 private final int beginTokenLen; 168 private final String endToken; 169 private final int endTokenLen; 170 private final Map<String, Code[]> styleMap; 171 172 public JAnsiTextRenderer(final String[] formats, final Map<String, Code[]> defaultStyleMap) { 173 String tempBeginToken = AnsiRenderer.BEGIN_TOKEN; 174 String tempEndToken = AnsiRenderer.END_TOKEN; 175 Map<String, Code[]> map; 176 if (formats.length > 1) { 177 final String allStylesStr = formats[1]; 178 // Style def split 179 final String[] allStyleAssignmentsArr = allStylesStr.split(" "); 180 map = new HashMap<>(allStyleAssignmentsArr.length + defaultStyleMap.size()); 181 map.putAll(defaultStyleMap); 182 for (final String styleAssignmentStr : allStyleAssignmentsArr) { 183 final String[] styleAssignmentArr = styleAssignmentStr.split("="); 184 if (styleAssignmentArr.length != 2) { 185 StatusLogger.getLogger().warn("{} parsing style \"{}\", expected format: StyleName=Code(,Code)*", 186 getClass().getSimpleName(), styleAssignmentStr); 187 } else { 188 final String styleName = styleAssignmentArr[0]; 189 final String codeListStr = styleAssignmentArr[1]; 190 final String[] codeNames = codeListStr.split(","); 191 if (codeNames.length == 0) { 192 StatusLogger.getLogger().warn( 193 "{} parsing style \"{}\", expected format: StyleName=Code(,Code)*", 194 getClass().getSimpleName(), styleAssignmentStr); 195 } else { 196 switch (styleName) { 197 case "BeginToken": 198 tempBeginToken = codeNames[0]; 199 break; 200 case "EndToken": 201 tempEndToken = codeNames[0]; 202 break; 203 case "StyleMapName": 204 final String predefinedMapName = codeNames[0]; 205 final Map<String, Code[]> predefinedMap = PrefedinedStyleMaps.get(predefinedMapName); 206 if (predefinedMap != null) { 207 map.putAll(predefinedMap); 208 } else { 209 StatusLogger.getLogger().warn("Unknown predefined map name {}, pick one of {}", 210 predefinedMapName, null); 211 } 212 break; 213 default: 214 final Code[] codes = new Code[codeNames.length]; 215 for (int i = 0; i < codes.length; i++) { 216 codes[i] = toCode(codeNames[i]); 217 } 218 map.put(styleName, codes); 219 } 220 } 221 } 222 } 223 } else { 224 map = defaultStyleMap; 225 } 226 styleMap = map; 227 beginToken = tempBeginToken; 228 endToken = tempEndToken; 229 beginTokenLen = tempBeginToken.length(); 230 endTokenLen = tempEndToken.length(); 231 } 232 233 public Map<String, Code[]> getStyleMap() { 234 return styleMap; 235 } 236 237 private void render(final Ansi ansi, final Code code) { 238 if (code.isColor()) { 239 if (code.isBackground()) { 240 ansi.bg(code.getColor()); 241 } else { 242 ansi.fg(code.getColor()); 243 } 244 } else if (code.isAttribute()) { 245 ansi.a(code.getAttribute()); 246 } 247 } 248 249 private void render(final Ansi ansi, final Code... codes) { 250 for (final Code code : codes) { 251 render(ansi, code); 252 } 253 } 254 255 /** 256 * Renders the given text with the given names which can be ANSI code names or Log4j style names. 257 * 258 * @param text 259 * The text to render 260 * @param names 261 * ANSI code names or Log4j style names. 262 * @return A rendered string containing ANSI codes. 263 */ 264 private String render(final String text, final String... names) { 265 final Ansi ansi = Ansi.ansi(); 266 for (final String name : names) { 267 final Code[] codes = styleMap.get(name); 268 if (codes != null) { 269 render(ansi, codes); 270 } else { 271 render(ansi, toCode(name)); 272 } 273 } 274 return ansi.a(text).reset().toString(); 275 } 276 277 // EXACT COPY OF StringBuilder version of the method but typed as String for input 278 @Override 279 public void render(final String input, final StringBuilder output, final String styleName) 280 throws IllegalArgumentException { 281 output.append(render(input, styleName)); 282 } 283 284 @Override 285 public void render(final StringBuilder input, final StringBuilder output) throws IllegalArgumentException { 286 int i = 0; 287 int j, k; 288 289 while (true) { 290 j = input.indexOf(beginToken, i); 291 if (j == -1) { 292 if (i == 0) { 293 output.append(input); 294 return; 295 } 296 output.append(input.substring(i, input.length())); 297 return; 298 } 299 output.append(input.substring(i, j)); 300 k = input.indexOf(endToken, j); 301 302 if (k == -1) { 303 output.append(input); 304 return; 305 } 306 j += beginTokenLen; 307 final String spec = input.substring(j, k); 308 309 final String[] items = spec.split(AnsiRenderer.CODE_TEXT_SEPARATOR, 2); 310 if (items.length == 1) { 311 output.append(input); 312 return; 313 } 314 final String replacement = render(items[1], items[0].split(",")); 315 316 output.append(replacement); 317 318 i = k + endTokenLen; 319 } 320 } 321 322 private Code toCode(final String name) { 323 return Code.valueOf(name.toUpperCase(Locale.ENGLISH)); 324 } 325 326 @Override 327 public String toString() { 328 return "JAnsiMessageRenderer [beginToken=" + beginToken + ", beginTokenLen=" + beginTokenLen + ", endToken=" 329 + endToken + ", endTokenLen=" + endTokenLen + ", styleMap=" + styleMap + "]"; 330 } 331 332}