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 }