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