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