1 /**
2 * Logback: the reliable, generic, fast and flexible logging framework.
3 * Copyright (C) 1999-2011, 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.HashMap;
17 import java.util.Map;
18 import java.util.Collections;
19 import java.util.Set;
20
21
22 import org.slf4j.spi.MDCAdapter;
23
24 /**
25 * A <em>Mapped Diagnostic Context</em>, or MDC in short, is an instrument for
26 * distinguishing interleaved log output from different sources. Log output is
27 * typically interleaved when a server handles multiple clients
28 * near-simultaneously.
29 * <p/>
30 * <b><em>The MDC is managed on a per thread basis</em></b>. A child thread
31 * automatically inherits a <em>copy</em> of the mapped diagnostic context of
32 * 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 final class LogbackMDCAdapter implements MDCAdapter {
41
42 // We wish to avoid unnecessarily copying of the map. To ensure
43 // efficient/timely copying, we have a variable keeping track of the last
44 // operation. A copy is necessary on 'put' or 'remove' but only if the last
45 // operation was a 'get'. Get operations never necessitate a copy nor
46 // successive 'put/remove' operations, only a get followed by a 'put/remove'
47 // requires copying the map.
48 // See http://jira.qos.ch/browse/LBCLASSIC-254 for the original discussion.
49
50 // We no longer use CopyOnInheritThreadLocal in order to solve LBCLASSIC-183
51 // Initially the contents of the thread local in parent and child threads
52 // reference the same map. However, as soon as a thread invokes the put()
53 // method, the maps diverge as they should.
54 final InheritableThreadLocal<Map<String, String>> copyOnInheritThreadLocal = new InheritableThreadLocal<Map<String, String>>();
55
56 private static final int WRITE_OPERATION = 1;
57 private static final int READ_OPERATION = 2;
58
59 // keeps track of the last operation performed
60 final ThreadLocal<Integer> lastOperation = new ThreadLocal<Integer>();
61
62 private Integer getAndSetLastOperation(int op) {
63 Integer lastOp = lastOperation.get();
64 lastOperation.set(op);
65 return lastOp;
66 }
67
68 private boolean wasLastOpReadOrNull(Integer lastOp) {
69 return lastOp == null || lastOp.intValue() == READ_OPERATION;
70 }
71
72 private Map<String, String> duplicateAndInsertNewMap(Map<String, String> oldMap) {
73 Map<String, String> newMap = Collections.synchronizedMap(new HashMap<String, String>());
74 if (oldMap != null) {
75 // we don't want the parent thread modifying oldMap while we are
76 // iterating over it
77 synchronized (oldMap) {
78 newMap.putAll(oldMap);
79 }
80 }
81
82 copyOnInheritThreadLocal.set(newMap);
83 return newMap;
84 }
85
86 /**
87 * Put a context value (the <code>val</code> parameter) as identified with the
88 * <code>key</code> parameter into the current thread's context map. Note that
89 * contrary to log4j, the <code>val</code> parameter can be null.
90 * <p/>
91 * <p/>
92 * If the current thread does not have a context map it is created as a side
93 * effect of this call.
94 *
95 * @throws IllegalArgumentException in case the "key" parameter is null
96 */
97 public void put(String key, String val) throws IllegalArgumentException {
98 if (key == null) {
99 throw new IllegalArgumentException("key cannot be null");
100 }
101
102 Map<String, String> oldMap = copyOnInheritThreadLocal.get();
103 Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);
104
105 if (wasLastOpReadOrNull(lastOp) || oldMap == null) {
106 Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);
107 newMap.put(key, val);
108 } else {
109 oldMap.put(key, val);
110 }
111 }
112
113 /**
114 * Remove the the context identified by the <code>key</code> parameter.
115 * <p/>
116 */
117 public void remove(String key) {
118 if (key == null) {
119 return;
120 }
121 Map<String, String> oldMap = copyOnInheritThreadLocal.get();
122 if (oldMap == null) return;
123
124 Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);
125
126 if (wasLastOpReadOrNull(lastOp)) {
127 Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);
128 newMap.remove(key);
129 } else {
130 oldMap.remove(key);
131 }
132 }
133
134
135 /**
136 * Clear all entries in the MDC.
137 */
138 public void clear() {
139 lastOperation.set(WRITE_OPERATION);
140 copyOnInheritThreadLocal.remove();
141 }
142
143 /**
144 * Get the context identified by the <code>key</code> parameter.
145 * <p/>
146 */
147 public String get(String key) {
148 Map<String, String> map = getPropertyMap();
149 if ((map != null) && (key != null)) {
150 return map.get(key);
151 } else {
152 return null;
153 }
154 }
155
156 /**
157 * Get the current thread's MDC as a map. This method is intended to be used
158 * internally.
159 */
160 public Map<String, String> getPropertyMap() {
161 lastOperation.set(READ_OPERATION);
162 return copyOnInheritThreadLocal.get();
163 }
164
165 /**
166 * Returns the keys in the MDC as a {@link Set}. The returned value can be
167 * null.
168 */
169 public Set<String> getKeys() {
170 Map<String, String> map = getPropertyMap();
171
172 if (map != null) {
173 return map.keySet();
174 } else {
175 return null;
176 }
177 }
178
179 /**
180 * Return a copy of the current thread's context map. Returned value may be
181 * null.
182 */
183 public Map getCopyOfContextMap() {
184 lastOperation.set(READ_OPERATION);
185 Map<String, String> hashMap = copyOnInheritThreadLocal.get();
186 if (hashMap == null) {
187 return null;
188 } else {
189 return new HashMap<String, String>(hashMap);
190 }
191 }
192
193 @SuppressWarnings("unchecked")
194 public void setContextMap(Map contextMap) {
195 lastOperation.set(WRITE_OPERATION);
196
197 Map<String, String> newMap = Collections.synchronizedMap(new HashMap<String, String>());
198 newMap.putAll(contextMap);
199
200 // the newMap replaces the old one for serialisation's sake
201 copyOnInheritThreadLocal.set(newMap);
202 }
203 }