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 = (key, value, sb) -> { 082 sb.append(key).append('='); 083 StringBuilders.appendValue(sb, value); 084 sb.append(", "); 085 }; 086 087 /** 088 * {@inheritDoc} 089 */ 090 @Override 091 public void format(final LogEvent event, final StringBuilder toAppendTo) { 092 final ReadOnlyStringMap contextData = event.getContextData(); 093 // if there is no additional options, we output every single 094 // Key/Value pair for the MDC in a similar format to Hashtable.toString() 095 if (full) { 096 if (contextData == null || contextData.isEmpty()) { 097 toAppendTo.append("{}"); 098 return; 099 } 100 appendFully(contextData, toAppendTo); 101 } else if (keys != null) { 102 if (contextData == null || contextData.isEmpty()) { 103 toAppendTo.append("{}"); 104 return; 105 } 106 appendSelectedKeys(keys, contextData, toAppendTo); 107 } else if (contextData != null){ 108 // otherwise they just want a single key output 109 final Object value = contextData.getValue(key); 110 if (value != null) { 111 StringBuilders.appendValue(toAppendTo, value); 112 } 113 } 114 } 115 116 private static void appendFully(final ReadOnlyStringMap contextData, final StringBuilder toAppendTo) { 117 toAppendTo.append("{"); 118 final int start = toAppendTo.length(); 119 contextData.forEach(WRITE_KEY_VALUES_INTO, toAppendTo); 120 final int end = toAppendTo.length(); 121 if (end > start) { 122 toAppendTo.setCharAt(end - 2, '}'); 123 toAppendTo.deleteCharAt(end - 1); 124 } else { 125 toAppendTo.append('}'); 126 } 127 } 128 129 private static void appendSelectedKeys(final String[] keys, final ReadOnlyStringMap contextData, final StringBuilder sb) { 130 // Print all the keys in the array that have a value. 131 final int start = sb.length(); 132 sb.append('{'); 133 for (int i = 0; i < keys.length; i++) { 134 final String theKey = keys[i]; 135 final Object value = contextData.getValue(theKey); 136 if (value != null) { // !contextData.containskey(theKey) 137 if (sb.length() - start > 1) { 138 sb.append(", "); 139 } 140 sb.append(theKey).append('='); 141 StringBuilders.appendValue(sb, value); 142 } 143 } 144 sb.append('}'); 145 } 146}