View Javadoc
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&uuml;lc&uuml;
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 }