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 org.apache.logging.log4j.util.PerformanceSensitive; 020import org.apache.logging.log4j.util.ReadOnlyStringMap; 021import org.apache.logging.log4j.core.LogEvent; 022import org.apache.logging.log4j.core.config.plugins.Plugin; 023import org.apache.logging.log4j.core.util.Constants; 024import org.apache.logging.log4j.util.TriConsumer; 025import org.apache.logging.log4j.util.StringBuilders; 026 027/** 028 * Able to handle the contents of the LogEvent's MDC and either 029 * output the entire contents of the properties in a similar format to the 030 * java.util.Hashtable.toString(), or to output the value of a specific key 031 * within the property bundle 032 * when this pattern converter has the option set. 033 */ 034@Plugin(name = "MdcPatternConverter", category = PatternConverter.CATEGORY) 035@ConverterKeys({ "X", "mdc", "MDC" }) 036@PerformanceSensitive("allocation") 037public final class MdcPatternConverter extends LogEventPatternConverter { 038 039 private static final ThreadLocal<StringBuilder> threadLocal = new ThreadLocal<>(); 040 private static final int DEFAULT_STRING_BUILDER_SIZE = 64; 041 042 /** 043 * Name of property to output. 044 */ 045 private final String key; 046 private final String[] keys; 047 private final boolean full; 048 049 /** 050 * Private constructor. 051 * 052 * @param options options, may be null. 053 */ 054 private MdcPatternConverter(final String[] options) { 055 super(options != null && options.length > 0 ? "MDC{" + options[0] + '}' : "MDC", "mdc"); 056 if (options != null && options.length > 0) { 057 full = false; 058 if (options[0].indexOf(',') > 0) { 059 keys = options[0].split(","); 060 for (int i = 0; i < keys.length; i++) { 061 keys[i] = keys[i].trim(); 062 } 063 key = null; 064 } else { 065 keys = null; 066 key = options[0]; 067 } 068 } else { 069 full = true; 070 key = null; 071 keys = null; 072 } 073 } 074 075 /** 076 * Obtains an instance of PropertiesPatternConverter. 077 * 078 * @param options options, may be null or first element contains name of property to format. 079 * @return instance of PropertiesPatternConverter. 080 */ 081 public static MdcPatternConverter newInstance(final String[] options) { 082 return new MdcPatternConverter(options); 083 } 084 085 private static final TriConsumer<String, Object, StringBuilder> WRITE_KEY_VALUES_INTO = new TriConsumer<String, Object, StringBuilder>() { 086 @Override 087 public void accept(final String key, final Object value, final StringBuilder sb) { 088 if (sb.length() > 1) { 089 sb.append(", "); 090 } 091 sb.append(key).append('='); 092 StringBuilders.appendValue(sb, value); 093 } 094 }; 095 096 /** 097 * {@inheritDoc} 098 */ 099 @Override 100 public void format(final LogEvent event, final StringBuilder toAppendTo) { 101 final ReadOnlyStringMap contextData = event.getContextData(); 102 // if there is no additional options, we output every single 103 // Key/Value pair for the MDC in a similar format to Hashtable.toString() 104 if (full) { 105 if (contextData == null || contextData.size() == 0) { 106 toAppendTo.append("{}"); 107 return; 108 } 109 appendFully(contextData, toAppendTo); 110 } else { 111 if (keys != null) { 112 if (contextData == null || contextData.size() == 0) { 113 toAppendTo.append("{}"); 114 return; 115 } 116 appendSelectedKeys(keys, contextData, toAppendTo); 117 } else if (contextData != null){ 118 // otherwise they just want a single key output 119 final Object value = contextData.getValue(key); 120 if (value != null) { 121 StringBuilders.appendValue(toAppendTo, value); 122 } 123 } 124 } 125 } 126 127 private static void appendFully(final ReadOnlyStringMap contextData, final StringBuilder toAppendTo) { 128 final StringBuilder sb = getStringBuilder(); 129 sb.append("{"); 130 contextData.forEach(WRITE_KEY_VALUES_INTO, sb); 131 sb.append('}'); 132 toAppendTo.append(sb); 133 trimToMaxSize(sb); 134 } 135 136 private static void appendSelectedKeys(final String[] keys, final ReadOnlyStringMap contextData, final StringBuilder toAppendTo) { 137 // Print all the keys in the array that have a value. 138 final StringBuilder sb = getStringBuilder(); 139 sb.append("{"); 140 for (int i = 0; i < keys.length; i++) { 141 final String theKey = keys[i]; 142 final Object value = contextData.getValue(theKey); 143 if (value != null) { // !contextData.containskey(theKey) 144 if (sb.length() > 1) { 145 sb.append(", "); 146 } 147 sb.append(theKey).append('='); 148 StringBuilders.appendValue(sb, value); 149 } 150 } 151 sb.append('}'); 152 toAppendTo.append(sb); 153 trimToMaxSize(sb); 154 } 155 156 private static StringBuilder getStringBuilder() { 157 StringBuilder result = threadLocal.get(); 158 if (result == null) { 159 result = new StringBuilder(DEFAULT_STRING_BUILDER_SIZE); 160 threadLocal.set(result); 161 } 162 result.setLength(0); 163 return result; 164 } 165 166 private static void trimToMaxSize(final StringBuilder stringBuilder) { 167 StringBuilders.trimToMaxSize(stringBuilder, Constants.MAX_REUSABLE_MESSAGE_SIZE); 168 } 169}