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.HashMap;
018import java.util.Map;
019import java.util.Set;
020
021import org.slf4j.spi.MDCAdapter;
022
023/**
024 * A <em>Mapped Diagnostic Context</em>, or MDC in short, is an instrument for
025 * distinguishing interleaved log output from different sources. Log output is
026 * typically interleaved when a server handles multiple clients
027 * near-simultaneously.
028 * <p/>
029 * <b><em>The MDC is managed on a per thread basis</em></b>. A child thread
030 * automatically inherits a <em>copy</em> of the mapped diagnostic context of
031 * its parent.
032 * <p/>
033 * <p/>
034 * For more information about MDC, please refer to the online manual at
035 * http://logback.qos.ch/manual/mdc.html
036 *
037 * @author Ceki G&uuml;lc&uuml;
038 */
039public class LogbackMDCAdapter implements MDCAdapter {
040
041    // The internal map is copied so as
042
043    // We wish to avoid unnecessarily copying of the map. To ensure
044    // efficient/timely copying, we have a variable keeping track of the last
045    // operation. A copy is necessary on 'put' or 'remove' but only if the last
046    // operation was a 'get'. Get operations never necessitate a copy nor
047    // successive 'put/remove' operations, only a get followed by a 'put/remove'
048    // requires copying the map.
049    // See http://jira.qos.ch/browse/LOGBACK-620 for the original discussion.
050
051    // We no longer use CopyOnInheritThreadLocal in order to solve LBCLASSIC-183
052    // Initially the contents of the thread local in parent and child threads
053    // reference the same map. However, as soon as a thread invokes the put()
054    // method, the maps diverge as they should.
055    final ThreadLocal<Map<String, String>> copyOnThreadLocal = new ThreadLocal<Map<String, String>>();
056
057    private static final int WRITE_OPERATION = 1;
058    private static final int MAP_COPY_OPERATION = 2;
059
060    // keeps track of the last operation performed
061    final ThreadLocal<Integer> lastOperation = new ThreadLocal<Integer>();
062
063    private Integer getAndSetLastOperation(int op) {
064        Integer lastOp = lastOperation.get();
065        lastOperation.set(op);
066        return lastOp;
067    }
068
069    private boolean wasLastOpReadOrNull(Integer lastOp) {
070        return lastOp == null || lastOp.intValue() == MAP_COPY_OPERATION;
071    }
072
073    private Map<String, String> duplicateAndInsertNewMap(Map<String, String> oldMap) {
074        Map<String, String> newMap = Collections.synchronizedMap(new HashMap<String, String>());
075        if (oldMap != null) {
076            // we don't want the parent thread modifying oldMap while we are
077            // iterating over it
078            synchronized (oldMap) {
079                newMap.putAll(oldMap);
080            }
081        }
082
083        copyOnThreadLocal.set(newMap);
084        return newMap;
085    }
086
087    /**
088     * Put a context value (the <code>val</code> parameter) as identified with the
089     * <code>key</code> parameter into the current thread's context map. Note that
090     * contrary to log4j, the <code>val</code> parameter can be null.
091     * <p/>
092     * <p/>
093     * If the current thread does not have a context map it is created as a side
094     * effect of this call.
095     *
096     * @throws IllegalArgumentException in case the "key" parameter is null
097     */
098    public void put(String key, String val) throws IllegalArgumentException {
099        if (key == null) {
100            throw new IllegalArgumentException("key cannot be null");
101        }
102
103        Map<String, String> oldMap = copyOnThreadLocal.get();
104        Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);
105
106        if (wasLastOpReadOrNull(lastOp) || oldMap == null) {
107            Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);
108            newMap.put(key, val);
109        } else {
110            oldMap.put(key, val);
111        }
112    }
113
114    /**
115     * Remove the the context identified by the <code>key</code> parameter.
116     * <p/>
117     */
118    public void remove(String key) {
119        if (key == null) {
120            return;
121        }
122        Map<String, String> oldMap = copyOnThreadLocal.get();
123        if (oldMap == null)
124            return;
125
126        Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);
127
128        if (wasLastOpReadOrNull(lastOp)) {
129            Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);
130            newMap.remove(key);
131        } else {
132            oldMap.remove(key);
133        }
134    }
135
136    /**
137     * Clear all entries in the MDC.
138     */
139    public void clear() {
140        lastOperation.set(WRITE_OPERATION);
141        copyOnThreadLocal.remove();
142    }
143
144    /**
145     * Get the context identified by the <code>key</code> parameter.
146     * <p/>
147     */
148    public String get(String key) {
149        final Map<String, String> map = copyOnThreadLocal.get();
150        if ((map != null) && (key != null)) {
151            return map.get(key);
152        } else {
153            return null;
154        }
155    }
156
157    /**
158     * Get the current thread's MDC as a map. This method is intended to be used
159     * internally.
160     */
161    public Map<String, String> getPropertyMap() {
162        lastOperation.set(MAP_COPY_OPERATION);
163        return copyOnThreadLocal.get();
164    }
165
166    /**
167     * Returns the keys in the MDC as a {@link Set}. The returned value can be
168     * null.
169     */
170    public Set<String> getKeys() {
171        Map<String, String> map = getPropertyMap();
172
173        if (map != null) {
174            return map.keySet();
175        } else {
176            return null;
177        }
178    }
179
180    /**
181     * Return a copy of the current thread's context map. Returned value may be
182     * null.
183     */
184    public Map<String, String> getCopyOfContextMap() {
185        Map<String, String> hashMap = copyOnThreadLocal.get();
186        if (hashMap == null) {
187            return null;
188        } else {
189            return new HashMap<String, String>(hashMap);
190        }
191    }
192
193    public void setContextMap(Map<String, String> contextMap) {
194        lastOperation.set(WRITE_OPERATION);
195
196        Map<String, String> newMap = Collections.synchronizedMap(new HashMap<String, String>());
197        newMap.putAll(contextMap);
198
199        // the newMap replaces the old one for serialisation's sake
200        copyOnThreadLocal.set(newMap);
201    }
202}