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.HashMap;
18 import java.util.Map;
19 import java.util.Set;
20
21 import org.slf4j.spi.MDCAdapter;
22
23 /**
24 * A <em>Mapped Diagnostic Context</em>, or MDC in short, is an instrument for
25 * distinguishing interleaved log output from different sources. Log output is
26 * typically interleaved when a server handles multiple clients
27 * near-simultaneously.
28 * <p/>
29 * <b><em>The MDC is managed on a per thread basis</em></b>. A child thread
30 * automatically inherits a <em>copy</em> of the mapped diagnostic context of
31 * its parent.
32 * <p/>
33 * <p/>
34 * For more information about MDC, please refer to the online manual at
35 * http://logback.qos.ch/manual/mdc.html
36 *
37 * @author Ceki Gülcü
38 */
39 public class LogbackMDCAdapter implements MDCAdapter {
40
41 // The internal map is copied so as
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 last
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>> copyOnThreadLocal = new ThreadLocal<Map<String, String>>();
56
57 private static final int WRITE_OPERATION = 1;
58 private static final int MAP_COPY_OPERATION = 2;
59
60 // keeps track of the last operation performed
61 final ThreadLocal<Integer> lastOperation = new ThreadLocal<Integer>();
62
63 private Integer getAndSetLastOperation(int op) {
64 Integer lastOp = lastOperation.get();
65 lastOperation.set(op);
66 return lastOp;
67 }
68
69 private boolean wasLastOpReadOrNull(Integer lastOp) {
70 return lastOp == null || lastOp.intValue() == MAP_COPY_OPERATION;
71 }
72
73 private Map<String, String> duplicateAndInsertNewMap(Map<String, String> oldMap) {
74 Map<String, String> newMap = Collections.synchronizedMap(new HashMap<String, String>());
75 if (oldMap != null) {
76 // we don't want the parent thread modifying oldMap while we are
77 // iterating over it
78 synchronized (oldMap) {
79 newMap.putAll(oldMap);
80 }
81 }
82
83 copyOnThreadLocal.set(newMap);
84 return newMap;
85 }
86
87 /**
88 * Put a context value (the <code>val</code> parameter) as identified with the
89 * <code>key</code> parameter into the current thread's context map. Note that
90 * contrary to log4j, the <code>val</code> parameter can be null.
91 * <p/>
92 * <p/>
93 * If the current thread does not have a context map it is created as a side
94 * effect of this call.
95 *
96 * @throws IllegalArgumentException in case the "key" parameter is null
97 */
98 public void put(String key, String val) throws IllegalArgumentException {
99 if (key == null) {
100 throw new IllegalArgumentException("key cannot be null");
101 }
102
103 Map<String, String> oldMap = copyOnThreadLocal.get();
104 Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);
105
106 if (wasLastOpReadOrNull(lastOp) || oldMap == null) {
107 Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);
108 newMap.put(key, val);
109 } else {
110 oldMap.put(key, val);
111 }
112 }
113
114 /**
115 * Remove the the context identified by the <code>key</code> parameter.
116 * <p/>
117 */
118 public void remove(String key) {
119 if (key == null) {
120 return;
121 }
122 Map<String, String> oldMap = copyOnThreadLocal.get();
123 if (oldMap == null)
124 return;
125
126 Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);
127
128 if (wasLastOpReadOrNull(lastOp)) {
129 Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);
130 newMap.remove(key);
131 } else {
132 oldMap.remove(key);
133 }
134 }
135
136 /**
137 * Clear all entries in the MDC.
138 */
139 public void clear() {
140 lastOperation.set(WRITE_OPERATION);
141 copyOnThreadLocal.remove();
142 }
143
144 /**
145 * Get the context identified by the <code>key</code> parameter.
146 * <p/>
147 */
148 public String get(String key) {
149 final Map<String, String> map = copyOnThreadLocal.get();
150 if ((map != null) && (key != null)) {
151 return map.get(key);
152 } else {
153 return null;
154 }
155 }
156
157 /**
158 * Get the current thread's MDC as a map. This method is intended to be used
159 * internally.
160 */
161 public Map<String, String> getPropertyMap() {
162 lastOperation.set(MAP_COPY_OPERATION);
163 return copyOnThreadLocal.get();
164 }
165
166 /**
167 * Returns the keys in the MDC as a {@link Set}. The returned value can be
168 * null.
169 */
170 public Set<String> getKeys() {
171 Map<String, String> map = getPropertyMap();
172
173 if (map != null) {
174 return map.keySet();
175 } else {
176 return null;
177 }
178 }
179
180 /**
181 * Return a copy of the current thread's context map. Returned value may be
182 * null.
183 */
184 public Map<String, String> getCopyOfContextMap() {
185 Map<String, String> hashMap = copyOnThreadLocal.get();
186 if (hashMap == null) {
187 return null;
188 } else {
189 return new HashMap<String, String>(hashMap);
190 }
191 }
192
193 public void setContextMap(Map<String, String> contextMap) {
194 lastOperation.set(WRITE_OPERATION);
195
196 Map<String, String> newMap = Collections.synchronizedMap(new HashMap<String, String>());
197 newMap.putAll(contextMap);
198
199 // the newMap replaces the old one for serialisation's sake
200 copyOnThreadLocal.set(newMap);
201 }
202 }
203