001/** 002 * Copyright (c) 2004-2023 QOS.ch 003 * All rights reserved. 004 * 005 * Permission is hereby granted, free of charge, to any person obtaining 006 * a copy of this software and associated documentation files (the 007 * "Software"), to deal in the Software without restriction, including 008 * without limitation the rights to use, copy, modify, merge, publish, 009 * distribute, sublicense, and/or sell copies of the Software, and to 010 * permit persons to whom the Software is furnished to do so, subject to 011 * the following conditions: 012 * 013 * The above copyright notice and this permission notice shall be 014 * included in all copies or substantial portions of the Software. 015 * 016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 017 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 018 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 019 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 020 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 021 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 022 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 023 * 024 */ 025package org.slf4j; 026 027import org.slf4j.spi.MDCAdapter; 028 029import java.util.HashSet; 030import java.util.Set; 031import java.util.concurrent.Callable; 032 033/** 034 * <p>This class assists in the creation and removal (aka closing) of {@link org.slf4j.MDC MDC} entries.</p> 035 * 036 * <p>Typical Usage example:</p> 037 * 038 * <pre> 039 * MDCAmbit mdca = new MDCAmbit(); 040 * try { 041 * mdca.put("k0", "v0"); 042 * doSomething(); 043 * } catch (RuntimeException e) { 044 * // here MDC.get("k0") would return "v0" 045 * } finally { 046 * // MDC remove "k0" 047 * mdca.clear(); 048 * } 049 * </pre> 050 * 051 * <p>It is also possible to chain {@link #put}, {@link #addKeys(String...)} and {@link #addKey(String)} 052 * invocations.</p> 053 * 054 * <p>For example:</p> 055 * <pre> 056 * MDCAmbit mdca = new MDCAmbit(); 057 * try { 058 * // assume "k0" was added to MDC at an earlier stage 059 * mdca.addKey("k0").put("k1", "v1").put("k2, "v2"); 060 * doSomething(); 061 * } finally { 062 * // MDC remove "k0", "k1", "k2", clear the set of tracked keys 063 * mdch.clear(); 064 * } 065 * </pre> 066 * 067 * <p>The {@link #run(Runnable)} and {@link #call(Callable)} methods invoke the run/callable methods of 068 * objects passed as parameter in a <code>try/finally</code> block, and afterwards invoking {@link #clear()} 069 * method from within <code>finally</code>. 070 * 071 * </p> 072 * 073 * <pre> 074 * DCAmbit mdca = new MDCAmbit(); 075 * Runnable runnable = ...; 076 * mdca.put("k0", "v0").run(runnable); 077 * </pre> 078 * 079 * @since 2.1.0 080 */ 081public class MDCAmbit { 082 083 /** 084 * Set of keys under management of this instance 085 */ 086 Set<String> keySet = new HashSet(); 087 088 MDCAdapter mdcAdapter; 089 090 public MDCAmbit() { 091 mdcAdapter = MDC.getMDCAdapter(); 092 } 093 094 MDCAmbit(MDCAdapter mdcAdapter) { 095 this.mdcAdapter = mdcAdapter; 096 } 097 098 /** 099 * Put the key/value couple in the MDC and keep track of the key for later 100 * removal by a call to {@link #clear()} }. 101 * 102 * @param key 103 * @param value 104 * @return this instance 105 */ 106 public MDCAmbit put(String key, String value) { 107 mdcAdapter.put(key, value); 108 keySet.add(key); 109 return this; 110 } 111 112 /** 113 * Keep track of a key for later removal by a call to {@link #clear()}. 114 * . 115 * @param key 116 * @return this instance 117 */ 118 public MDCAmbit addKey(String key) { 119 keySet.add(key); 120 return this; 121 } 122 123 /** 124 * Keep track of several keys for later removal by a call to {@link #clear()} . 125 * @param keys 126 * @return this instance 127 */ 128 public MDCAmbit addKeys(String... keys) { 129 if(keys == null) 130 return this; 131 132 for(String k: keys) { 133 keySet.add(k); 134 } 135 return this; 136 } 137 138 /** 139 * Run the runnable object passed as parameter within a try/finally block. 140 * 141 * <p>Afterwards, the {@link #clear()} method will be called within the `finally` block. 142 * </p> 143 144 * @param runnable 145 */ 146 public void run(Runnable runnable) { 147 try { 148 runnable.run(); 149 } finally { 150 clear(); 151 } 152 } 153 154 /** 155 * Invoke the {@link Callable#call()} method of the callable object passed as parameter within a try/finally block. 156 * 157 * <p>Afterwards, the {@link #clear()} method will be invoked within the `finally` block. 158 * </p> 159 * @param callable 160 */ 161 public <T> T call(Callable<? extends T> callable) throws Exception { 162 try { 163 return callable.call(); 164 } finally { 165 clear(); 166 } 167 } 168 169 /** 170 * Clear tracked keys by calling {@link MDC#remove} on each key. 171 * 172 * <p>In addition, the set of tracked keys is cleared.</p> 173 * 174 * <p>This method is usually called from within finally statement of a 175 * try/catch/finally block</p> 176 * 177 */ 178 public void clear() { 179 for(String key: keySet) { 180 mdcAdapter.remove(key); 181 } 182 keySet.clear(); 183 } 184}