001/* 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2026, 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 v2.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 readOnlyMap = Collections.unmodifiableMap(new HashMap<>(current)); 142 readOnlyThreadLocalMap.set(readOnlyMap); 143 } 144 } 145 return readOnlyMap; 146 } 147 148 /** 149 * Return a copy of the current thread's context map. Returned value may be 150 * null. 151 */ 152 public Map getCopyOfContextMap() { 153 Map<String, String> readOnlyMap = getPropertyMap(); 154 if (readOnlyMap == null) { 155 return null; 156 } else { 157 return new HashMap<String, String>(readOnlyMap); 158 } 159 } 160 161 /** 162 * Returns the keys in the MDC as a {@link Set}. The returned value can be 163 * null. 164 */ 165 public Set<String> getKeys() { 166 Map<String, String> readOnlyMap = getPropertyMap(); 167 168 if (readOnlyMap != null) { 169 return readOnlyMap.keySet(); 170 } else { 171 return null; 172 } 173 } 174 175 @SuppressWarnings("unchecked") 176 public void setContextMap(Map contextMap) { 177 if (contextMap != null) { 178 readWriteThreadLocalMap.set(new HashMap<String, String>(contextMap)); 179 } else { 180 readWriteThreadLocalMap.set(null); 181 } 182 nullifyReadOnlyThreadLocalMap(); 183 } 184 185 186 @Override 187 public void pushByKey(String key, String value) { 188 threadLocalMapOfDeques.pushByKey(key, value); 189 } 190 191 @Override 192 public String popByKey(String key) { 193 return threadLocalMapOfDeques.popByKey(key); 194 } 195 196 @Override 197 public Deque<String> getCopyOfDequeByKey(String key) { 198 return threadLocalMapOfDeques.getCopyOfDequeByKey(key); 199 } 200 201 @Override 202 public void clearDequeByKey(String key) { 203 threadLocalMapOfDeques.clearDequeByKey(key); 204 } 205 206}