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}