001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017
018 package org.apache.logging.log4j;
019
020 import org.apache.logging.log4j.message.ParameterizedMessage;
021 import org.apache.logging.log4j.spi.DefaultThreadContextMap;
022 import org.apache.logging.log4j.spi.LoggerContextFactory;
023 import org.apache.logging.log4j.spi.Provider;
024 import org.apache.logging.log4j.spi.ThreadContextMap;
025 import org.apache.logging.log4j.status.StatusLogger;
026 import org.apache.logging.log4j.util.PropertiesUtil;
027 import org.apache.logging.log4j.util.ProviderUtil;
028
029 import java.io.Serializable;
030 import java.util.ArrayList;
031 import java.util.Collection;
032 import java.util.HashMap;
033 import java.util.Iterator;
034 import java.util.List;
035 import java.util.Map;
036 import java.util.NoSuchElementException;
037
038 /**
039 * The ThreadContext allows applications to store information either in a Map.
040 * <p>
041 * <b><em>The MDC is managed on a per thread basis</em></b>. A child thread automatically inherits a <em>copy</em> of
042 * the mapped diagnostic context of its parent.
043 * </p>
044 */
045 public final class ThreadContext {
046
047 /**
048 * Empty, immutable Map.
049 */
050 public static final Map<String, String> EMPTY_MAP = new ImmutableMap();
051
052 /**
053 * Empty, immutable ContextStack.
054 */
055 public static final ContextStack EMPTY_STACK = new ImmutableStack();
056
057 private static final String DISABLE_MAP = "disableThreadContextMap";
058
059 private static final String DISABLE_STACK = "disableThreadContextStack";
060
061 private static final String DISABLE_ALL = "disableThreadContext";
062
063 private static final String THREAD_CONTEXT_KEY = "log4j2.threadContextMap";
064
065 private static boolean all;
066
067 private static boolean useMap;
068
069 private static boolean useStack;
070
071 private static ThreadContextMap contextMap;
072
073 private static final Logger LOGGER = StatusLogger.getLogger();
074
075 static {
076 final PropertiesUtil managerProps = PropertiesUtil.getProperties();
077 all = managerProps.getBooleanProperty(DISABLE_ALL);
078 useMap = !(managerProps.getBooleanProperty(DISABLE_MAP) || all);
079 useStack = !(managerProps.getBooleanProperty(DISABLE_STACK) || all);
080 String threadContextMapName = managerProps.getStringProperty(THREAD_CONTEXT_KEY);
081 final ClassLoader cl = ProviderUtil.findClassLoader();
082 if (threadContextMapName != null) {
083 try {
084 final Class<?> clazz = cl.loadClass(threadContextMapName);
085 if (ThreadContextMap.class.isAssignableFrom(clazz)) {
086 contextMap = (ThreadContextMap) clazz.newInstance();
087 }
088 } catch (final ClassNotFoundException cnfe) {
089 LOGGER.error("Unable to locate configured LoggerContextFactory {}", threadContextMapName);
090 } catch (final Exception ex) {
091 LOGGER.error("Unable to create configured LoggerContextFactory {}", threadContextMapName, ex);
092 }
093 }
094 if (contextMap == null && ProviderUtil.hasProviders()) {
095 final LoggerContextFactory factory = LogManager.getFactory();
096 final Iterator<Provider> providers = ProviderUtil.getProviders();
097 while (providers.hasNext()) {
098 final Provider provider = providers.next();
099 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 * int depth = ThreadContext.getDepth();
367 * <p/>
368 * ... complex sequence of calls
369 * <p/>
370 * 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 }