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}