001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2022, 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 org.slf4j.helpers.ThreadLocalMapOfStacks; 017import org.slf4j.spi.MDCAdapter; 018 019import java.util.Collections; 020import java.util.Deque; 021import java.util.HashMap; 022import java.util.Map; 023import java.util.Set; 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 * @author Michael Franz 040 */ 041public class LogbackMDCAdapter implements MDCAdapter { 042 043 044 // BEWARE: Keys or values placed in a ThreadLocal should not be of a type/class 045 // not included in the JDK. See also https://jira.qos.ch/browse/LOGBACK-450 046 047 final ThreadLocal<Map<String, String>> readWriteThreadLocalMap = new ThreadLocal<Map<String, String>>(); 048 final ThreadLocal<Map<String, String>> readOnlyThreadLocalMap = new ThreadLocal<Map<String, String>>(); 049 private final ThreadLocalMapOfStacks threadLocalMapOfDeques = new ThreadLocalMapOfStacks(); 050 051 /** 052 * Put a context value (the <code>val</code> parameter) as identified with the 053 * <code>key</code> parameter into the current thread's context map. Note that 054 * contrary to log4j, the <code>val</code> parameter can be null. 055 * <p/> 056 * <p/> 057 * If the current thread does not have a context map it is created as a side 058 * effect of this call. 059 * <p/> 060 * <p/> 061 * Each time a value is added, a new instance of the map is created. This is 062 * to be certain that the serialization process will operate on the updated 063 * map and not send a reference to the old map, thus not allowing the remote 064 * logback component to see the latest changes. 065 * 066 * @throws IllegalArgumentException in case the "key" parameter is null 067 */ 068 public void put(String key, String val) throws IllegalArgumentException { 069 if (key == null) { 070 throw new IllegalArgumentException("key cannot be null"); 071 } 072 Map<String, String> current = readWriteThreadLocalMap.get(); 073 if (current == null) { 074 current = new HashMap<String, String>(); 075 readWriteThreadLocalMap.set(current); 076 } 077 078 current.put(key, val); 079 nullifyReadOnlyThreadLocalMap(); 080 } 081 082 /** 083 * Get the context identified by the <code>key</code> parameter. 084 * <p/> 085 * <p/> 086 * This method has no side effects. 087 */ 088 @Override 089 public String get(String key) { 090 Map<String, String> hashMap = readWriteThreadLocalMap.get(); 091 092 if ((hashMap != null) && (key != null)) { 093 return hashMap.get(key); 094 } else { 095 return null; 096 } 097 } 098 099 /** 100 * <p>Remove the context identified by the <code>key</code> parameter. 101 * <p/> 102 */ 103 @Override 104 public void remove(String key) { 105 if (key == null) { 106 return; 107 } 108 109 Map<String, String> current = readWriteThreadLocalMap.get(); 110 if (current != null) { 111 current.remove(key); 112 nullifyReadOnlyThreadLocalMap(); 113 } 114 } 115 116 private void nullifyReadOnlyThreadLocalMap() { 117 readOnlyThreadLocalMap.set(null); 118 } 119 120 /** 121 * Clear all entries in the MDC. 122 */ 123 @Override 124 public void clear() { 125 readWriteThreadLocalMap.set(null); 126 nullifyReadOnlyThreadLocalMap(); 127 } 128 129 /** 130 * <p>Get the current thread's MDC as a map. This method is intended to be used 131 * internally.</p> 132 * 133 * The returned map is unmodifiable (since version 1.3.2/1.4.2). 134 */ 135 @SuppressWarnings("unchecked") 136 public Map<String, String> getPropertyMap() { 137 Map<String, String> readOnlyMap = readOnlyThreadLocalMap.get(); 138 if (readOnlyMap == null) { 139 Map<String, String> current = readWriteThreadLocalMap.get(); 140 if (current != null) { 141 final Map<String, String> tempMap = new HashMap<String, String>(current); 142 readOnlyMap = Collections.unmodifiableMap(tempMap); 143 readOnlyThreadLocalMap.set(readOnlyMap); 144 } 145 } 146 return readOnlyMap; 147 } 148 149 /** 150 * Return a copy of the current thread's context map. Returned value may be 151 * null. 152 */ 153 public Map getCopyOfContextMap() { 154 Map<String, String> readOnlyMap = getPropertyMap(); 155 if (readOnlyMap == null) { 156 return null; 157 } else { 158 return new HashMap<String, String>(readOnlyMap); 159 } 160 } 161 162 /** 163 * Returns the keys in the MDC as a {@link Set}. The returned value can be 164 * null. 165 */ 166 public Set<String> getKeys() { 167 Map<String, String> readOnlyMap = getPropertyMap(); 168 169 if (readOnlyMap != null) { 170 return readOnlyMap.keySet(); 171 } else { 172 return null; 173 } 174 } 175 176 @SuppressWarnings("unchecked") 177 public void setContextMap(Map contextMap) { 178 if (contextMap != null) { 179 readWriteThreadLocalMap.set(new HashMap<String, String>(contextMap)); 180 } else { 181 readWriteThreadLocalMap.set(null); 182 } 183 nullifyReadOnlyThreadLocalMap(); 184 } 185 186 187 @Override 188 public void pushByKey(String key, String value) { 189 threadLocalMapOfDeques.pushByKey(key, value); 190 } 191 192 @Override 193 public String popByKey(String key) { 194 return threadLocalMapOfDeques.popByKey(key); 195 } 196 197 @Override 198 public Deque<String> getCopyOfDequeByKey(String key) { 199 return threadLocalMapOfDeques.getCopyOfDequeByKey(key); 200 } 201 202 @Override 203 public void clearDequeByKey(String key) { 204 threadLocalMapOfDeques.clearDequeByKey(key); 205 } 206 207}