001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v1.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014package ch.qos.logback.classic.util; 015 016import java.util.Collections; 017import java.util.Deque; 018import java.util.HashMap; 019import java.util.Map; 020import java.util.Set; 021 022import org.slf4j.helpers.ThreadLocalMapOfStacks; 023import org.slf4j.spi.MDCAdapter; 024 025/** 026 * A <em>Mapped Diagnostic Context</em>, or MDC in short, is an instrument for 027 * distinguishing interleaved log output from different sources. Log output is 028 * typically interleaved when a server handles multiple clients 029 * near-simultaneously. 030 * <p/> 031 * <b><em>The MDC is managed on a per thread basis</em></b>. Note that a child 032 * thread <b>does not</b> inherit the mapped diagnostic context of its parent. 033 * <p/> 034 * <p/> 035 * For more information about MDC, please refer to the online manual at 036 * http://logback.qos.ch/manual/mdc.html 037 * 038 * @author Ceki Gülcü 039 */ 040public class LogbackMDCAdapter implements MDCAdapter { 041 042 // The internal map is copied so as 043 044 // We wish to avoid unnecessarily copying of the map. To ensure 045 // efficient/timely copying, we have a variable keeping track of the last 046 // operation. A copy is necessary on 'put' or 'remove' but only if the last 047 // operation was a 'get'. Get operations never necessitate a copy nor 048 // successive 'put/remove' operations, only a get followed by a 'put/remove' 049 // requires copying the map. 050 // See http://jira.qos.ch/browse/LOGBACK-620 for the original discussion. 051 052 // We no longer use CopyOnInheritThreadLocal in order to solve LBCLASSIC-183 053 // Initially the contents of the thread local in parent and child threads 054 // reference the same map. However, as soon as a thread invokes the put() 055 // method, the maps diverge as they should. 056 final ThreadLocal<Map<String, String>> copyOnThreadLocal = new ThreadLocal<Map<String, String>>(); 057 058 private static final int WRITE_OPERATION = 1; 059 private static final int MAP_COPY_OPERATION = 2; 060 061 // keeps track of the last operation performed 062 final ThreadLocal<Integer> lastOperation = new ThreadLocal<Integer>(); 063 064 private final ThreadLocalMapOfStacks threadLocalMapOfDeques = new ThreadLocalMapOfStacks(); 065 066 private Integer getAndSetLastOperation(int op) { 067 Integer lastOp = lastOperation.get(); 068 lastOperation.set(op); 069 return lastOp; 070 } 071 072 private boolean wasLastOpReadOrNull(Integer lastOp) { 073 return lastOp == null || lastOp.intValue() == MAP_COPY_OPERATION; 074 } 075 076 private Map<String, String> duplicateAndInsertNewMap(Map<String, String> oldMap) { 077 Map<String, String> newMap = Collections.synchronizedMap(new HashMap<String, String>()); 078 if (oldMap != null) { 079 // we don't want the parent thread modifying oldMap while we are 080 // iterating over it 081 synchronized (oldMap) { 082 newMap.putAll(oldMap); 083 } 084 } 085 086 copyOnThreadLocal.set(newMap); 087 return newMap; 088 } 089 090 /** 091 * Put a context value (the <code>val</code> parameter) as identified with the 092 * <code>key</code> parameter into the current thread's context map. Note that 093 * contrary to log4j, the <code>val</code> parameter can be null. 094 * <p/> 095 * <p/> 096 * If the current thread does not have a context map it is created as a side 097 * effect of this call. 098 * 099 * @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 = copyOnThreadLocal.get(); 107 Integer lastOp = getAndSetLastOperation(WRITE_OPERATION); 108 109 if (wasLastOpReadOrNull(lastOp) || oldMap == null) { 110 Map<String, String> newMap = duplicateAndInsertNewMap(oldMap); 111 newMap.put(key, val); 112 } else { 113 oldMap.put(key, val); 114 } 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 = copyOnThreadLocal.get(); 126 if (oldMap == null) 127 return; 128 129 Integer lastOp = getAndSetLastOperation(WRITE_OPERATION); 130 131 if (wasLastOpReadOrNull(lastOp)) { 132 Map<String, String> newMap = duplicateAndInsertNewMap(oldMap); 133 newMap.remove(key); 134 } else { 135 oldMap.remove(key); 136 } 137 } 138 139 /** 140 * Clear all entries in the MDC. 141 */ 142 public void clear() { 143 lastOperation.set(WRITE_OPERATION); 144 copyOnThreadLocal.remove(); 145 } 146 147 /** 148 * Get the context identified by the <code>key</code> parameter. 149 * <p/> 150 */ 151 public String get(String key) { 152 final Map<String, String> map = copyOnThreadLocal.get(); 153 if ((map != null) && (key != null)) { 154 return map.get(key); 155 } else { 156 return null; 157 } 158 } 159 160 /** 161 * Get the current thread's MDC as a map. This method is intended to be used 162 * internally. 163 */ 164 public Map<String, String> getPropertyMap() { 165 lastOperation.set(MAP_COPY_OPERATION); 166 return copyOnThreadLocal.get(); 167 } 168 169 /** 170 * Returns the keys in the MDC as a {@link Set}. The returned value can be null. 171 */ 172 public Set<String> getKeys() { 173 Map<String, String> map = getPropertyMap(); 174 175 if (map != null) { 176 return map.keySet(); 177 } else { 178 return null; 179 } 180 } 181 182 /** 183 * Return a copy of the current thread's context map. Returned value may be 184 * null. 185 */ 186 public Map<String, String> getCopyOfContextMap() { 187 Map<String, String> hashMap = copyOnThreadLocal.get(); 188 if (hashMap == null) { 189 return null; 190 } else { 191 return new HashMap<String, String>(hashMap); 192 } 193 } 194 195 public void setContextMap(Map<String, String> contextMap) { 196 lastOperation.set(WRITE_OPERATION); 197 198 Map<String, String> newMap = Collections.synchronizedMap(new HashMap<String, String>()); 199 newMap.putAll(contextMap); 200 201 // the newMap replaces the old one for serialisation's sake 202 copyOnThreadLocal.set(newMap); 203 } 204 205 @Override 206 public void pushByKey(String key, String value) { 207 threadLocalMapOfDeques.pushByKey(key, value); 208 } 209 210 @Override 211 public String popByKey(String key) { 212 return threadLocalMapOfDeques.popByKey(key); 213 } 214 215 @Override 216 public Deque<String> getCopyOfDequeByKey(String key) { 217 return threadLocalMapOfDeques.getCopyOfDequeByKey(key); 218 } 219 @Override 220 public void clearDequeByKey(String key) { 221 threadLocalMapOfDeques.clearDequeByKey(key); 222 } 223}