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.Locale; 020 021import org.apache.logging.log4j.core.LogEvent; 022import org.apache.logging.log4j.core.config.Configuration; 023import org.apache.logging.log4j.core.config.plugins.Plugin; 024import org.apache.logging.log4j.core.util.ArrayUtils; 025import org.apache.logging.log4j.core.util.Constants; 026import org.apache.logging.log4j.core.util.Loader; 027import org.apache.logging.log4j.message.Message; 028import org.apache.logging.log4j.message.MultiformatMessage; 029import org.apache.logging.log4j.status.StatusLogger; 030import org.apache.logging.log4j.util.MultiFormatStringBuilderFormattable; 031import org.apache.logging.log4j.util.PerformanceSensitive; 032import org.apache.logging.log4j.util.StringBuilderFormattable; 033 034/** 035 * Returns the event's rendered message in a StringBuilder. 036 */ 037@Plugin(name = "MessagePatternConverter", category = PatternConverter.CATEGORY) 038@ConverterKeys({ "m", "msg", "message" }) 039@PerformanceSensitive("allocation") 040public final class MessagePatternConverter extends LogEventPatternConverter { 041 042 private static final String NOLOOKUPS = "nolookups"; 043 044 private final String[] formats; 045 private final Configuration config; 046 private final TextRenderer textRenderer; 047 private final boolean noLookups; 048 049 /** 050 * Private constructor. 051 * 052 * @param options 053 * options, may be null. 054 */ 055 private MessagePatternConverter(final Configuration config, final String[] options) { 056 super("Message", "message"); 057 this.formats = options; 058 this.config = config; 059 final int noLookupsIdx = loadNoLookups(options); 060 this.noLookups = Constants.FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS || noLookupsIdx >= 0; 061 this.textRenderer = loadMessageRenderer(noLookupsIdx >= 0 ? ArrayUtils.remove(options, noLookupsIdx) : options); 062 } 063 064 private int loadNoLookups(final String[] options) { 065 if (options != null) { 066 for (int i = 0; i < options.length; i++) { 067 final String option = options[i]; 068 if (NOLOOKUPS.equalsIgnoreCase(option)) { 069 return i; 070 } 071 } 072 } 073 return -1; 074 } 075 076 private TextRenderer loadMessageRenderer(final String[] options) { 077 if (options != null) { 078 for (final String option : options) { 079 switch (option.toUpperCase(Locale.ROOT)) { 080 case "ANSI": 081 if (Loader.isJansiAvailable()) { 082 return new JAnsiTextRenderer(options, JAnsiTextRenderer.DefaultMessageStyleMap); 083 } 084 StatusLogger.getLogger() 085 .warn("You requested ANSI message rendering but JANSI is not on the classpath."); 086 return null; 087 case "HTML": 088 return new HtmlTextRenderer(options); 089 } 090 } 091 } 092 return null; 093 } 094 095 /** 096 * Obtains an instance of pattern converter. 097 * 098 * @param config 099 * The Configuration. 100 * @param options 101 * options, may be null. 102 * @return instance of pattern converter. 103 */ 104 public static MessagePatternConverter newInstance(final Configuration config, final String[] options) { 105 return new MessagePatternConverter(config, options); 106 } 107 108 /** 109 * {@inheritDoc} 110 */ 111 @Override 112 public void format(final LogEvent event, final StringBuilder toAppendTo) { 113 final Message msg = event.getMessage(); 114 if (msg instanceof StringBuilderFormattable) { 115 116 final boolean doRender = textRenderer != null; 117 final StringBuilder workingBuilder = doRender ? new StringBuilder(80) : toAppendTo; 118 119 final int offset = workingBuilder.length(); 120 if (msg instanceof MultiFormatStringBuilderFormattable) { 121 ((MultiFormatStringBuilderFormattable) msg).formatTo(formats, workingBuilder); 122 } else { 123 ((StringBuilderFormattable) msg).formatTo(workingBuilder); 124 } 125 126 // TODO can we optimize this? 127 if (config != null && !noLookups) { 128 for (int i = offset; i < workingBuilder.length() - 1; i++) { 129 if (workingBuilder.charAt(i) == '$' && workingBuilder.charAt(i + 1) == '{') { 130 final String value = workingBuilder.substring(offset, workingBuilder.length()); 131 workingBuilder.setLength(offset); 132 workingBuilder.append(config.getStrSubstitutor().replace(event, value)); 133 } 134 } 135 } 136 if (doRender) { 137 textRenderer.render(workingBuilder, toAppendTo); 138 } 139 return; 140 } 141 if (msg != null) { 142 String result; 143 if (msg instanceof MultiformatMessage) { 144 result = ((MultiformatMessage) msg).getFormattedMessage(formats); 145 } else { 146 result = msg.getFormattedMessage(); 147 } 148 if (result != null) { 149 toAppendTo.append(config != null && result.contains("${") 150 ? config.getStrSubstitutor().replace(event, result) : result); 151 } else { 152 toAppendTo.append("null"); 153 } 154 } 155 } 156}