001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2022, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.classic.util;
015
016import org.slf4j.helpers.ThreadLocalMapOfStacks;
017import org.slf4j.spi.MDCAdapter;
018
019import java.util.Collections;
020import java.util.Deque;
021import java.util.HashMap;
022import java.util.Map;
023import java.util.Set;
024
025/**
026 * A <em>Mapped Diagnostic Context</em>, or MDC in short, is an instrument for
027 * distinguishing interleaved log output from different sources. Log output is
028 * typically interleaved when a server handles multiple clients
029 * near-simultaneously.
030 * <p/>
031 * <b><em>The MDC is managed on a per thread basis</em></b>. Note that a child
032 * thread <b>does not</b> inherit the mapped diagnostic context of its parent.
033 * <p/>
034 * <p/>
035 * For more information about MDC, please refer to the online manual at
036 * http://logback.qos.ch/manual/mdc.html
037 *
038 * @author Ceki G&uuml;lc&uuml;
039 * @author Michael Franz
040 */
041public class LogbackMDCAdapter implements MDCAdapter  {
042
043
044    // BEWARE: Keys or values placed in a ThreadLocal should not be of a type/class
045    // not included in the JDK. See also https://jira.qos.ch/browse/LOGBACK-450
046
047    final ThreadLocal<Map<String, String>> readWriteThreadLocalMap = new ThreadLocal<Map<String, String>>();
048    final ThreadLocal<Map<String, String>> readOnlyThreadLocalMap = new ThreadLocal<Map<String, String>>();
049    private final ThreadLocalMapOfStacks threadLocalMapOfDeques = new ThreadLocalMapOfStacks();
050
051    /**
052     * Put a context value (the <code>val</code> parameter) as identified with the
053     * <code>key</code> parameter into the current thread's context map. Note that
054     * contrary to log4j, the <code>val</code> parameter can be null.
055     * <p/>
056     * <p/>
057     * If the current thread does not have a context map it is created as a side
058     * effect of this call.
059     * <p/>
060     * <p/>
061     * Each time a value is added, a new instance of the map is created. This is
062     * to be certain that the serialization process will operate on the updated
063     * map and not send a reference to the old map, thus not allowing the remote
064     * logback component to see the latest changes.
065     *
066     * @throws IllegalArgumentException in case the "key" parameter is null
067     */
068    public void put(String key, String val) throws IllegalArgumentException {
069        if (key == null) {
070            throw new IllegalArgumentException("key cannot be null");
071        }
072        Map<String, String> current = readWriteThreadLocalMap.get();
073        if (current == null) {
074            current = new HashMap<String, String>();
075            readWriteThreadLocalMap.set(current);
076        }
077
078        current.put(key, val);
079        nullifyReadOnlyThreadLocalMap();
080    }
081
082    /**
083     * Get the context identified by the <code>key</code> parameter.
084     * <p/>
085     * <p/>
086     * This method has no side effects.
087     */
088    @Override
089    public String get(String key) {
090        Map<String, String> hashMap = readWriteThreadLocalMap.get();
091
092        if ((hashMap != null) && (key != null)) {
093            return hashMap.get(key);
094        } else {
095            return null;
096        }
097    }
098
099    /**
100     * <p>Remove the context identified by the <code>key</code> parameter.
101     * <p/>
102     */
103    @Override
104    public void remove(String key) {
105        if (key == null) {
106            return;
107        }
108
109        Map<String, String> current = readWriteThreadLocalMap.get();
110        if (current != null) {
111            current.remove(key);
112            nullifyReadOnlyThreadLocalMap();
113        }
114    }
115
116    private void nullifyReadOnlyThreadLocalMap() {
117        readOnlyThreadLocalMap.set(null);
118    }
119
120    /**
121     * Clear all entries in the MDC.
122     */
123    @Override
124    public void clear() {
125        readWriteThreadLocalMap.set(null);
126        nullifyReadOnlyThreadLocalMap();
127    }
128
129    /**
130     * <p>Get the current thread's MDC as a map. This method is intended to be used
131     * internally.</p>
132     *
133     * The returned map is unmodifiable (since version 1.3.2/1.4.2).
134     */
135    @SuppressWarnings("unchecked")
136    public Map<String, String> getPropertyMap() {
137        Map<String, String> readOnlyMap = readOnlyThreadLocalMap.get();
138        if (readOnlyMap == null) {
139            Map<String, String> current = readWriteThreadLocalMap.get();
140            if (current != null) {
141                final Map<String, String> tempMap = new HashMap<String, String>(current);
142                readOnlyMap = Collections.unmodifiableMap(tempMap);
143                readOnlyThreadLocalMap.set(readOnlyMap);
144            }
145        }
146        return readOnlyMap;
147    }
148
149    /**
150     * Return a copy of the current thread's context map. Returned value may be
151     * null.
152     */
153    public Map getCopyOfContextMap() {
154        Map<String, String> readOnlyMap = getPropertyMap();
155        if (readOnlyMap == null) {
156            return null;
157        } else {
158            return new HashMap<String, String>(readOnlyMap);
159        }
160    }
161
162    /**
163     * Returns the keys in the MDC as a {@link Set}. The returned value can be
164     * null.
165     */
166    public Set<String> getKeys() {
167        Map<String, String> readOnlyMap = getPropertyMap();
168
169        if (readOnlyMap != null) {
170            return readOnlyMap.keySet();
171        } else {
172            return null;
173        }
174    }
175
176    @SuppressWarnings("unchecked")
177    public void setContextMap(Map contextMap) {
178        if (contextMap != null) {
179            readWriteThreadLocalMap.set(new HashMap<String, String>(contextMap));
180        } else {
181            readWriteThreadLocalMap.set(null);
182        }
183        nullifyReadOnlyThreadLocalMap();
184    }
185
186
187    @Override
188    public void pushByKey(String key, String value) {
189        threadLocalMapOfDeques.pushByKey(key, value);
190    }
191
192    @Override
193    public String popByKey(String key) {
194        return threadLocalMapOfDeques.popByKey(key);
195    }
196
197    @Override
198    public Deque<String> getCopyOfDequeByKey(String key) {
199        return threadLocalMapOfDeques.getCopyOfDequeByKey(key);
200    }
201
202    @Override
203    public void clearDequeByKey(String key) {
204        threadLocalMapOfDeques.clearDequeByKey(key);
205    }
206
207}