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