View Javadoc

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