View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2022, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.classic.util;
15  
16  import org.slf4j.helpers.ThreadLocalMapOfStacks;
17  import org.slf4j.spi.MDCAdapter;
18  
19  import java.util.Collections;
20  import java.util.Deque;
21  import java.util.HashMap;
22  import java.util.Map;
23  import java.util.Set;
24  
25  /**
26   * A <em>Mapped Diagnostic Context</em>, or MDC in short, is an instrument for
27   * distinguishing interleaved log output from different sources. Log output is
28   * typically interleaved when a server handles multiple clients
29   * near-simultaneously.
30   * <p/>
31   * <b><em>The MDC is managed on a per thread basis</em></b>. Note that a child
32   * thread <b>does not</b> inherit the mapped diagnostic context of its parent.
33   * <p/>
34   * <p/>
35   * For more information about MDC, please refer to the online manual at
36   * http://logback.qos.ch/manual/mdc.html
37   *
38   * @author Ceki G&uuml;lc&uuml;
39   * @author Michael Franz
40   */
41  public class LogbackMDCAdapter implements MDCAdapter  {
42  
43  
44      // BEWARE: Keys or values placed in a ThreadLocal should not be of a type/class
45      // not included in the JDK. See also https://jira.qos.ch/browse/LOGBACK-450
46  
47      final ThreadLocal<Map<String, String>> readWriteThreadLocalMap = new ThreadLocal<Map<String, String>>();
48      final ThreadLocal<Map<String, String>> readOnlyThreadLocalMap = new ThreadLocal<Map<String, String>>();
49      private final ThreadLocalMapOfStacks threadLocalMapOfDeques = new ThreadLocalMapOfStacks();
50  
51      /**
52       * Put a context value (the <code>val</code> parameter) as identified with the
53       * <code>key</code> parameter into the current thread's context map. Note that
54       * contrary to log4j, the <code>val</code> parameter can be null.
55       * <p/>
56       * <p/>
57       * If the current thread does not have a context map it is created as a side
58       * effect of this call.
59       * <p/>
60       * <p/>
61       * Each time a value is added, a new instance of the map is created. This is
62       * to be certain that the serialization process will operate on the updated
63       * map and not send a reference to the old map, thus not allowing the remote
64       * logback component to see the latest changes.
65       *
66       * @throws IllegalArgumentException in case the "key" parameter is null
67       */
68      public void put(String key, String val) throws IllegalArgumentException {
69          if (key == null) {
70              throw new IllegalArgumentException("key cannot be null");
71          }
72          Map<String, String> current = readWriteThreadLocalMap.get();
73          if (current == null) {
74              current = new HashMap<String, String>();
75              readWriteThreadLocalMap.set(current);
76          }
77  
78          current.put(key, val);
79          nullifyReadOnlyThreadLocalMap();
80      }
81  
82      /**
83       * Get the context identified by the <code>key</code> parameter.
84       * <p/>
85       * <p/>
86       * This method has no side effects.
87       */
88      @Override
89      public String get(String key) {
90          Map<String, String> hashMap = readWriteThreadLocalMap.get();
91  
92          if ((hashMap != null) && (key != null)) {
93              return hashMap.get(key);
94          } else {
95              return null;
96          }
97      }
98  
99      /**
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 }