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}