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ülcü
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 }