1 /**
2 * Logback: the reliable, generic, fast and flexible logging framework.
3 * Copyright (C) 1999-2015, 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 java.util.Collections;
17 import java.util.Deque;
18 import java.util.HashMap;
19 import java.util.Map;
20 import java.util.Set;
21
22 import org.slf4j.helpers.ThreadLocalMapOfStacks;
23 import org.slf4j.spi.MDCAdapter;
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 */
40 public class LogbackMDCAdapterSimple implements MDCAdapter {
41
42
43 // We wish to avoid unnecessarily copying of the map. To ensure
44 // efficient/timely copying, we have a variable keeping track of the previous
45 // operation. A copy is necessary on 'put' or 'remove' but only if the last
46 // operation was a 'get'. Get operations never necessitate a copy nor
47 // successive 'put/remove' operations, only a get followed by a 'put/remove'
48 // requires copying the map.
49 // See http://jira.qos.ch/browse/LOGBACK-620 for the original discussion.
50
51 // We no longer use CopyOnInheritThreadLocal in order to solve LBCLASSIC-183
52 // Initially the contents of the thread local in parent and child threads
53 // reference the same map. However, as soon as a thread invokes the put()
54 // method, the maps diverge as they should.
55 // final ThreadLocal<Map<String, String>> modifiableMap = new ThreadLocal<Map<String, String>>();
56
57 final ThreadLocal<Map<String, String>> threadLocalUnmodifiableMap = new ThreadLocal<Map<String, String>>();
58
59 // private static final int WRITE_OPERATION = 1;
60 // private static final int MAP_COPY_OPERATION = 2;
61
62 // keeps track of the previous operation performed
63 // final ThreadLocal<Integer> previousOperation = new ThreadLocal<Integer>();
64
65 private final ThreadLocalMapOfStacks threadLocalMapOfDeques = new ThreadLocalMapOfStacks();
66
67 // private Integer getAndSetPreviousOperation(int op) {
68 // Integer penultimateOp = previousOperation.get();
69 // previousOperation.set(op);
70 // return penultimateOp;
71 // }
72
73 // private boolean wasPreviousOpReadOrNull(Integer lastOp) {
74 // return lastOp == null || lastOp.intValue() == MAP_COPY_OPERATION;
75 // }
76
77 // private Map<String, String> duplicateAndInsertNewMap(Map<String, String> oldMap) {
78 // Map<String, String> newMap = duplicateOldMap(oldMap);
79 // modifiableMap.set(newMap);
80 // return newMap;
81 // }
82
83 private Map<String, String> duplicateMap(Map<String, String> oldMap) {
84 if(oldMap != null)
85 return new HashMap<>(oldMap);
86 else
87 return new HashMap<>();
88 }
89
90 /**
91 * Put a context value (the <code>val</code> parameter) as identified with the
92 * <code>key</code> parameter into the current thread's context map. Note that
93 * contrary to log4j, the <code>val</code> parameter can be null.
94 * <p/>
95 * <p/>
96 * If the current thread does not have a context map it is created as a side
97 * effect of this call.
98 *
99 * @throws IllegalArgumentException in case the "key" parameter is null
100 */
101 public void put(String key, String val) throws IllegalArgumentException {
102 if (key == null) {
103 throw new IllegalArgumentException("key cannot be null");
104 }
105
106 Map<String, String> oldMap = threadLocalUnmodifiableMap.get();
107 Map<String, String> newMap = duplicateMap(oldMap);
108 newMap.put(key, val);
109 makeUnmodifiableAndThreadLocalSet(newMap);
110 }
111
112 private void makeUnmodifiableAndThreadLocalSet(Map<String, String> aMap) {
113 Map<String, String> unmodifiable = Collections.unmodifiableMap(aMap);
114 threadLocalUnmodifiableMap.set(unmodifiable);
115 }
116
117 /**
118 * Remove the context identified by the <code>key</code> parameter.
119 * <p/>
120 */
121 public void remove(String key) {
122 if (key == null) {
123 return;
124 }
125 Map<String, String> oldMap = threadLocalUnmodifiableMap.get();
126 if (oldMap == null)
127 return;
128
129 Map<String, String> newMap = duplicateMap(oldMap);
130 newMap.remove(key);
131 makeUnmodifiableAndThreadLocalSet(newMap);
132 }
133
134 /**
135 * Clear all entries in the MDC.
136 */
137 public void clear() {
138 threadLocalUnmodifiableMap.remove();
139 }
140
141 /**
142 * Get the context identified by the <code>key</code> parameter.
143 * <p/>
144 */
145 public String get(String key) {
146 final Map<String, String> map = threadLocalUnmodifiableMap.get();
147 if ((map != null) && (key != null)) {
148 return map.get(key);
149 } else {
150 return null;
151 }
152 }
153
154 /**
155 * Get the current thread's MDC as a map. This method is intended to be used
156 * internally.
157 */
158 public Map<String, String> getPropertyMap() {
159 return threadLocalUnmodifiableMap.get();
160 }
161
162 /**
163 * Returns the keys in the MDC as a {@link Set}. The returned value can be null.
164 */
165 public Set<String> getKeys() {
166 Map<String, String> map = getPropertyMap();
167
168 if (map != null) {
169 return map.keySet();
170 } else {
171 return null;
172 }
173 }
174
175 /**
176 * Return a copy of the current thread's context map. Returned value may be
177 * null.
178 */
179 public Map<String, String> getCopyOfContextMap() {
180 Map<String, String> hashMap = threadLocalUnmodifiableMap.get();
181 return duplicateMap(hashMap);
182 }
183
184 /**
185 * Set the MDC map to the map passed as parameter.
186 *
187 * @param contextMap the new map
188 */
189 public void setContextMap(Map<String, String> contextMap) {
190 duplicateMap(contextMap);
191 }
192
193 @Override
194 public void pushByKey(String key, String value) {
195 threadLocalMapOfDeques.pushByKey(key, value);
196 }
197
198 @Override
199 public String popByKey(String key) {
200 return threadLocalMapOfDeques.popByKey(key);
201 }
202
203 @Override
204 public Deque<String> getCopyOfDequeByKey(String key) {
205 return threadLocalMapOfDeques.getCopyOfDequeByKey(key);
206 }
207 @Override
208 public void clearDequeByKey(String key) {
209 threadLocalMapOfDeques.clearDequeByKey(key);
210 }
211 }