View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  
18  package org.apache.logging.log4j;
19  
20  import org.apache.logging.log4j.message.ParameterizedMessage;
21  import org.apache.logging.log4j.spi.DefaultThreadContextMap;
22  import org.apache.logging.log4j.spi.LoggerContextFactory;
23  import org.apache.logging.log4j.spi.Provider;
24  import org.apache.logging.log4j.spi.ThreadContextMap;
25  import org.apache.logging.log4j.status.StatusLogger;
26  import org.apache.logging.log4j.util.PropertiesUtil;
27  import org.apache.logging.log4j.util.ProviderUtil;
28  
29  import java.io.Serializable;
30  import java.util.ArrayList;
31  import java.util.Collection;
32  import java.util.HashMap;
33  import java.util.Iterator;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.NoSuchElementException;
37  
38  /**
39   * The ThreadContext allows applications to store information either in a Map.
40   * <p>
41   * <b><em>The MDC is managed on a per thread basis</em></b>. A child thread automatically inherits a <em>copy</em> of
42   * the mapped diagnostic context of its parent.
43   * </p>
44   */
45  public final class ThreadContext  {
46  
47      /**
48       * Empty, immutable Map.
49       */
50      public static final Map<String, String> EMPTY_MAP = new ImmutableMap();
51  
52      /**
53       * Empty, immutable ContextStack.
54       */
55      public static final ContextStack EMPTY_STACK = new ImmutableStack();
56  
57      private static final String DISABLE_MAP = "disableThreadContextMap";
58  
59      private static final String DISABLE_STACK = "disableThreadContextStack";
60  
61      private static final String DISABLE_ALL = "disableThreadContext";
62  
63      private static final String THREAD_CONTEXT_KEY = "log4j2.threadContextMap";
64  
65      private static boolean all;
66  
67      private static boolean useMap;
68  
69      private static boolean useStack;
70  
71      private static ThreadContextMap contextMap;
72  
73      private static final Logger LOGGER = StatusLogger.getLogger();
74  
75      static {
76          final PropertiesUtil managerProps = PropertiesUtil.getProperties();
77          all = managerProps.getBooleanProperty(DISABLE_ALL);
78          useMap = !(managerProps.getBooleanProperty(DISABLE_MAP) || all);
79          useStack = !(managerProps.getBooleanProperty(DISABLE_STACK) || all);
80          String threadContextMapName = managerProps.getStringProperty(THREAD_CONTEXT_KEY);
81          final ClassLoader cl = ProviderUtil.findClassLoader();
82          if (threadContextMapName != null) {
83              try {
84                  final Class<?> clazz = cl.loadClass(threadContextMapName);
85                  if (ThreadContextMap.class.isAssignableFrom(clazz)) {
86                      contextMap = (ThreadContextMap) clazz.newInstance();
87                  }
88              } catch (final ClassNotFoundException cnfe) {
89                  LOGGER.error("Unable to locate configured LoggerContextFactory {}", threadContextMapName);
90              } catch (final Exception ex) {
91                  LOGGER.error("Unable to create configured LoggerContextFactory {}", threadContextMapName, ex);
92              }
93          }
94          if (contextMap == null && ProviderUtil.hasProviders()) {
95              final LoggerContextFactory factory = LogManager.getFactory();
96              final Iterator<Provider> providers = ProviderUtil.getProviders();
97              while (providers.hasNext()) {
98                  final Provider provider = providers.next();
99                  threadContextMapName = provider.getThreadContextMap();
100                 final String factoryClassName = provider.getClassName();
101                 if (threadContextMapName != null && factory.getClass().getName().equals(factoryClassName)) {
102                     try {
103                         final Class<?> clazz = cl.loadClass(threadContextMapName);
104                         if (ThreadContextMap.class.isAssignableFrom(clazz)) {
105                             contextMap = (ThreadContextMap) clazz.newInstance();
106                             break;
107                         }
108                     } catch (final ClassNotFoundException cnfe) {
109                         LOGGER.error("Unable to locate configured LoggerContextFactory {}", threadContextMapName);
110                         contextMap = new DefaultThreadContextMap(useMap);
111                     } catch (final Exception ex) {
112                         LOGGER.error("Unable to create configured LoggerContextFactory {}", threadContextMapName, ex);
113                         contextMap = new DefaultThreadContextMap(useMap);
114                     }
115                 }
116             }
117             if (contextMap == null) {
118                 contextMap = new DefaultThreadContextMap(useMap);
119             }
120 
121         } else {
122             contextMap = new DefaultThreadContextMap(useMap);
123         }
124     }
125 
126     private static ThreadLocal<ContextStack> localStack = new ThreadLocal<ContextStack>();
127 
128     private ThreadContext() {
129 
130     }
131 
132     /**
133      * Put a context value (the <code>o</code> parameter) as identified
134      * with the <code>key</code> parameter into the current thread's
135      * context map.
136      * <p/>
137      * <p>If the current thread does not have a context map it is
138      * created as a side effect.
139      * @param key The key name.
140      * @param value The key value.
141      */
142     public static void put(final String key, final String value) {
143         contextMap.put(key, value);
144     }
145 
146     /**
147      * Get the context identified by the <code>key</code> parameter.
148      * <p/>
149      * <p>This method has no side effects.
150      * @param key The key to locate.
151      * @return The value associated with the key or null.
152      */
153     public static String get(final String key) {
154         return contextMap.get(key);
155     }
156 
157     /**
158      * Remove the the context identified by the <code>key</code>
159      * parameter.
160      * @param key The key to remove.
161      */
162     public static void remove(final String key) {
163         contextMap.remove(key);
164     }
165 
166     /**
167      * Clear the context.
168      */
169     public static void clear() {
170         contextMap.clear();
171     }
172 
173     /**
174      * Determine if the key is in the context.
175      * @param key The key to locate.
176      * @return True if the key is in the context, false otherwise.
177      */
178     public static boolean containsKey(final String key) {
179         return contextMap.containsKey(key);
180     }
181 
182     /**
183      * Get a copy of current thread's context Map.
184      * @return a copy of the context.
185      */
186     public static Map<String, String> getContext() {
187         return contextMap.getContext();
188     }
189 
190     /**
191      * Get an immutable copy of the current thread's context Map.
192      * @return An immutable copy of the ThreadContext Map.
193      */
194     public static Map<String, String> getImmutableContext() {
195         final Map<String, String> map = contextMap.get();
196         return map == null ? new ImmutableMap() : new ImmutableMap(map);
197     }
198 
199     /**
200      * Returns true if the Map is empty.
201      * @return true if the Map is empty, false otherwise.
202      */
203     public static boolean isEmpty() {
204         return contextMap.isEmpty();
205     }
206 
207     /**
208      * Clear the stack for this thread.
209      */
210     public static void clearStack() {
211         localStack.remove();
212     }
213 
214     /**
215      * Returns a copy of this thread's stack.
216      * @return A copy of this thread's stack.
217      */
218     public static ContextStack cloneStack() {
219         final ContextStack stack = localStack.get();
220         return stack == null ? new ThreadContextStack() : new ThreadContextStack(stack.asList());
221     }
222 
223     /**
224      * Get an immutable copy of this current thread's context stack.
225      * @return an immutable copy of the ThreadContext stack.
226      */
227     public static ContextStack getImmutableStack() {
228         final ContextStack stack = localStack.get();
229         return stack == null ? EMPTY_STACK : new ImmutableStack(stack.asList());
230     }
231 
232     /**
233      * Set this thread's stack.
234      * @param stack The stack to use.
235      */
236     public static void setStack(final Collection<String> stack) {
237         if (stack.size() == 0 || !useStack) {
238             return;
239         }
240         localStack.set(new ThreadContextStack(stack));
241     }
242 
243     /**
244      * Get the current nesting depth of this thread's stack.
245      * @return the number of items in the stack.
246      *
247      * @see #trim
248      */
249     public static int getDepth() {
250         final ContextStack stack = localStack.get();
251         return stack == null ? 0 : stack.getDepth();
252     }
253 
254     /**
255      * Returns the value of the last item placed on the stack.
256      * <p/>
257      * <p>The returned value is the value that was pushed last. If no
258      * context is available, then the empty string "" is returned.
259      *
260      * @return String The innermost diagnostic context.
261      */
262     public static String pop() {
263         final ContextStack s = localStack.get();
264         if (s == null || s.getDepth() == 0) {
265             return "";
266         }
267         return s.pop();
268     }
269 
270     /**
271      * Looks at the last diagnostic context at the top of this NDC
272      * without removing it.
273      * <p/>
274      * <p>The returned value is the value that was pushed last. If no
275      * context is available, then the empty string "" is returned.
276      *
277      * @return String The innermost diagnostic context.
278      */
279     public static String peek() {
280         final ContextStack s = localStack.get();
281         if (s == null || s.getDepth() == 0) {
282             return "";
283         }
284         return s.peek();
285     }
286 
287     /**
288      * Push new diagnostic context information for the current thread.
289      * <p/>
290      * <p>The contents of the <code>message</code> parameter is
291      * determined solely by the client.
292      *
293      * @param message The new diagnostic context information.
294      */
295     public static void push(final String message) {
296         if (!useStack) {
297             return;
298         }
299         ContextStack stack = localStack.get();
300         if (stack == null) {
301             stack = new ThreadContextStack();
302             localStack.set(stack);
303         }
304         stack.push(message);
305     }
306     /**
307      * Push new diagnostic context information for the current thread.
308      * <p/>
309      * <p>The contents of the <code>message</code> and args parameters are
310      * determined solely by the client. The message will be treated as a format String
311      * and tokens will be replaced with the String value of the arguments in accordance
312      * with ParameterizedMessage.
313      *
314      * @param message The new diagnostic context information.
315      * @param args Parameters for the message.
316      */
317     public static void push(final String message, final Object... args) {
318         if (!useStack) {
319             return;
320         }
321         ContextStack stack = localStack.get();
322         if (stack == null) {
323             stack = new ThreadContextStack();
324             localStack.set(stack);
325         }
326         stack.push(ParameterizedMessage.format(message, args));
327     }
328 
329     /**
330      * Remove the diagnostic context for this thread.
331      * <p/>
332      * <p>Each thread that created a diagnostic context by calling
333      * {@link #push} should call this method before exiting. Otherwise,
334      * the memory used by the <b>thread</b> cannot be reclaimed by the
335      * VM.
336      * <p/>
337      * <p>As this is such an important problem in heavy duty systems and
338      * because it is difficult to always guarantee that the remove
339      * method is called before exiting a thread, this method has been
340      * augmented to lazily remove references to dead threads. In
341      * practice, this means that you can be a little sloppy and
342      * occasionally forget to call {@link #remove} before exiting a
343      * thread. However, you must call <code>remove</code> sometime. If
344      * you never call it, then your application is sure to run out of
345      * memory.
346      */
347     public static void removeStack() {
348         localStack.remove();
349     }
350 
351     /**
352      * Trims elements from this diagnostic context. If the current
353      * depth is smaller or equal to <code>maxDepth</code>, then no
354      * action is taken. If the current depth is larger than newDepth
355      * then all elements at maxDepth or higher are discarded.
356      * <p/>
357      * <p>This method is a convenient alternative to multiple {@link
358      * #pop} calls. Moreover, it is often the case that at the end of
359      * complex call sequences, the depth of the ThreadContext is
360      * unpredictable. The <code>trim</code> method circumvents
361      * this problem.
362      * <p/>
363      * <p>For example, the combination
364      * <pre>
365      * void foo() {
366      * &nbsp;  int depth = ThreadContext.getDepth();
367      * <p/>
368      * &nbsp;  ... complex sequence of calls
369      * <p/>
370      * &nbsp;  ThreadContext.trim(depth);
371      * }
372      * </pre>
373      * <p/>
374      * ensures that between the entry and exit of foo the depth of the
375      * diagnostic stack is conserved.
376      *
377      * @see #getDepth
378      * @param depth The number of elements to keep.
379      */
380     public static void trim(final int depth) {
381         final ContextStack stack = localStack.get();
382         if (stack != null) {
383             stack.trim(depth);
384         }
385     }
386 
387     /**
388      * The ThreadContext Stack interface.
389      */
390     public interface ContextStack extends Serializable {
391 
392         /**
393          * Clears all elements from the stack.
394          */
395         void clear();
396 
397         /**
398          * Returns the element at the top of the stack.
399          * @return The element at the top of the stack.
400          * @throws java.util.NoSuchElementException if the stack is empty.
401          */
402         String pop();
403 
404         /**
405          * Returns the element at the top of the stack without removing it or null if the stack is empty.
406          * @return the element at the top of the stack or null if the stack is empty.
407          */
408         String peek();
409 
410         /**
411          * Add an element to the stack.
412          * @param message The element to add.
413          */
414         void push(String message);
415 
416         /**
417          * Returns the number of elements in the stack.
418          * @return the number of elements in the stack.
419          */
420         int getDepth();
421 
422         /**
423          * Returns all the elements in the stack in a List.
424          * @return all the elements in the stack in a List.
425          */
426         List<String> asList();
427 
428         /**
429          * Trims elements from the end of the stack.
430          * @param depth The maximum number of items in the stack to keep.
431          */
432         void trim(int depth);
433 
434         /**
435          * Returns a copy of the ContextStack.
436          * @return a copy of the ContextStack.
437          */
438         ContextStack copy();
439     }
440 
441     /**
442      * The ContextStack implementation.
443      */
444     private static class ThreadContextStack extends ArrayList<String> implements ContextStack {
445 
446         private static final long serialVersionUID = 5050501L;
447 
448         public ThreadContextStack() {
449             super();
450         }
451 
452         public ThreadContextStack(final Collection<String> collection) {
453             super(collection);
454         }
455 
456         public String pop() {
457             final int index = size() - 1;
458             if (index >= 0) {
459                 final String result = get(index);
460                 remove(index);
461                 return result;
462             }
463             throw new NoSuchElementException("The ThreadContext stack is empty");
464         }
465 
466         public String peek() {
467             final int index = size() - 1;
468             if (index >= 0) {
469                 return get(index);
470             }
471             return null;
472         }
473 
474         public void push(final String message) {
475             add(message);
476         }
477 
478         public int getDepth() {
479             return size();
480         }
481 
482         public List<String> asList() {
483             return this;
484         }
485 
486         public void trim(final int depth) {
487             if (depth < 0) {
488                 throw new IllegalArgumentException("Maximum stack depth cannot be negative");
489             }
490             while (size() > depth) {
491                 remove(size() - 1);
492             }
493 
494         }
495 
496         public ContextStack copy() {
497             return new ThreadContextStack(this);
498         }
499     }
500 
501     /**
502      * An immutable ContextStack.
503      */
504     public static class ImmutableStack extends ThreadContextStack {
505 
506         private static final long serialVersionUID = 5050502L;
507 
508         public ImmutableStack() {
509         }
510 
511         public ImmutableStack(final Collection<String> collection) {
512             super(collection);
513         }
514 
515         public ImmutableStack(final ThreadContextStack stack) {
516             super(stack);
517         }
518 
519         @Override
520         public void push(final String message) {
521             throw new UnsupportedOperationException("Stack cannot be modified");
522         }
523 
524         @Override
525         public void trim(final int depth) {
526             throw new UnsupportedOperationException("Stack cannot be modified");
527         }
528     }
529 
530     /**
531      * An immutable Context Map.
532      */
533     public static class ImmutableMap extends HashMap<String, String> {
534         private static final long serialVersionUID = 5050503L;
535 
536         public ImmutableMap() {
537             super();
538         }
539 
540         public ImmutableMap(final Map<String, String> map) {
541             super(map);
542         }
543 
544         @Override
545         public String put(final String s, final String s1) {
546             throw new UnsupportedOperationException("Map cannot be modified");
547         }
548 
549         @Override
550         public void putAll(final Map<? extends String, ? extends String> map) {
551             throw new UnsupportedOperationException("Map cannot be modified");
552         }
553 
554         @Override
555         public String remove(final Object o) {
556             throw new UnsupportedOperationException("Map cannot be modified");
557         }
558 
559         @Override
560         public void clear() {
561             throw new UnsupportedOperationException("Map cannot be modified");
562         }
563     }
564 }