View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.pattern;
18  
19  import org.apache.logging.log4j.util.PerformanceSensitive;
20  import org.apache.logging.log4j.util.ReadOnlyStringMap;
21  import org.apache.logging.log4j.core.LogEvent;
22  import org.apache.logging.log4j.core.config.plugins.Plugin;
23  import org.apache.logging.log4j.core.util.Constants;
24  import org.apache.logging.log4j.util.TriConsumer;
25  import org.apache.logging.log4j.util.StringBuilders;
26  
27  /**
28   * Able to handle the contents of the LogEvent's MDC and either
29   * output the entire contents of the properties in a similar format to the
30   * java.util.Hashtable.toString(), or to output the value of a specific key
31   * within the property bundle
32   * when this pattern converter has the option set.
33   */
34  @Plugin(name = "MdcPatternConverter", category = PatternConverter.CATEGORY)
35  @ConverterKeys({ "X", "mdc", "MDC" })
36  @PerformanceSensitive("allocation")
37  public final class MdcPatternConverter extends LogEventPatternConverter {
38  
39      private static final ThreadLocal<StringBuilder> threadLocal = new ThreadLocal<>();
40      private static final int DEFAULT_STRING_BUILDER_SIZE = 64;
41  
42      /**
43       * Name of property to output.
44       */
45      private final String key;
46      private final String[] keys;
47      private final boolean full;
48  
49      /**
50       * Private constructor.
51       *
52       * @param options options, may be null.
53       */
54      private MdcPatternConverter(final String[] options) {
55          super(options != null && options.length > 0 ? "MDC{" + options[0] + '}' : "MDC", "mdc");
56          if (options != null && options.length > 0) {
57              full = false;
58              if (options[0].indexOf(',') > 0) {
59                  keys = options[0].split(",");
60                  for (int i = 0; i < keys.length; i++) {
61                      keys[i] = keys[i].trim();
62                  }
63                  key = null;
64              } else {
65                  keys = null;
66                  key = options[0];
67              }
68          } else {
69              full = true;
70              key = null;
71              keys = null;
72          }
73      }
74  
75      /**
76       * Obtains an instance of PropertiesPatternConverter.
77       *
78       * @param options options, may be null or first element contains name of property to format.
79       * @return instance of PropertiesPatternConverter.
80       */
81      public static MdcPatternConverter newInstance(final String[] options) {
82          return new MdcPatternConverter(options);
83      }
84  
85      private static final TriConsumer<String, Object, StringBuilder> WRITE_KEY_VALUES_INTO = new TriConsumer<String, Object, StringBuilder>() {
86          @Override
87          public void accept(final String key, final Object value, final StringBuilder sb) {
88              if (sb.length() > 1) {
89                  sb.append(", ");
90              }
91              sb.append(key).append('=');
92              StringBuilders.appendValue(sb, value);
93          }
94      };
95  
96      /**
97       * {@inheritDoc}
98       */
99      @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 }