1 /******************************************************************************* 2 3 copyright: Copyright (c) 2007 Stonecobra. All rights reserved 4 5 license: BSD style: $(LICENSE) 6 7 version: Initial release: December 2007 8 9 author: stonecobra 10 11 *******************************************************************************/ 12 13 module context; 14 15 import tango.core.Thread, 16 tango.util.log.Log, 17 tango.util.log.Event, 18 tango.util.log.EventLayout, 19 tango.util.log.ConsoleAppender; 20 21 import tango.util.log.model.IHierarchy; 22 23 /******************************************************************************* 24 25 Allows the dynamic setting of log levels on a per-thread basis. 26 Imagine that a user request comes into your production threaded 27 server. You can't afford to turn logging up to trace for the sake 28 of debugging this one users problem, but you also can't afford to 29 find the problem and fix it. So now you just set the override log 30 level to TRACE for the thread the user is on, and you get full trace 31 output for only that user. 32 33 *******************************************************************************/ 34 35 class ThreadLocalDiagnosticContext : IHierarchy.Context 36 { 37 private ThreadLocal!(DCData) dcData; 38 private char[128] tmp; 39 40 /*********************************************************************** 41 42 ***********************************************************************/ 43 44 public this() 45 { 46 dcData = new ThreadLocal!(DCData); 47 } 48 49 /*********************************************************************** 50 51 set the 'diagnostic' Level for logging. This overrides 52 the Level in the current Logger. The default level starts 53 at NONE, so as not to modify the behavior of existing clients 54 of tango.util.log 55 56 ***********************************************************************/ 57 58 void setLevel (ILevel.Level level) 59 { 60 auto data = dcData.val; 61 data.level = level; 62 dcData.val = data; 63 } 64 65 /*********************************************************************** 66 67 All log appends will be checked against this to see if a 68 log level needs to be temporarily adjusted. 69 70 ***********************************************************************/ 71 72 bool isEnabled (ILevel.Level setting, ILevel.Level level = ILevel.Level.Trace) 73 { 74 return level >= setting || level >= dcData.val.level; 75 } 76 77 /*********************************************************************** 78 79 Return the label to use for the current log message. Usually 80 called by the Layout. This implementation returns "{}". 81 82 ***********************************************************************/ 83 84 char[] label () 85 { 86 return dcData.val.getLabel; 87 } 88 89 /*********************************************************************** 90 91 Push another string into the 'stack'. This strings will be 92 appened together when getLabel is called. 93 94 ***********************************************************************/ 95 96 void push (char[] label) 97 { 98 auto data = dcData.val; 99 data.push(label); 100 dcData.val = data; 101 } 102 103 /*********************************************************************** 104 105 pop the current label off the stack. 106 107 ***********************************************************************/ 108 109 void pop () 110 { 111 auto data = dcData.val; 112 data.pop; 113 dcData.val = data; 114 } 115 116 /*********************************************************************** 117 118 Clear the label stack. 119 120 ***********************************************************************/ 121 122 void clear() 123 { 124 auto data = dcData.val; 125 data.clear; 126 dcData.val = data; 127 } 128 } 129 130 131 /******************************************************************************* 132 133 The thread locally stored struct to hold the logging level and 134 the label stack. 135 136 *******************************************************************************/ 137 138 private struct DCData { 139 140 ILevel.Level level = ILevel.Level.None; 141 char[][8] stack; 142 bool shouldUpdate = true; 143 int stackIndex = 0; 144 uint labelLength; 145 char[256] labelContent; 146 147 148 char[] getLabel() { 149 if (shouldUpdate) { 150 labelLength = 0; 151 append(" {"); 152 for (int i = 0; i < stackIndex; i++) { 153 append(stack[i]); 154 if (i < stackIndex - 1) { 155 append(" "); 156 } 157 } 158 append("}"); 159 shouldUpdate = false; 160 } 161 return labelContent[0..labelLength]; 162 } 163 164 void append(char[] x) { 165 uint addition = x.length; 166 uint newLength = labelLength + x.length; 167 168 if (newLength < labelContent.length) 169 { 170 labelContent [labelLength..newLength] = x[0..addition]; 171 labelLength = newLength; 172 } 173 } 174 175 void push(char[] label) { 176 shouldUpdate = true; 177 stack[stackIndex] = label.dup; 178 stackIndex++; 179 } 180 181 void pop() { 182 shouldUpdate = true; 183 if (stackIndex > 0) { 184 stack[stackIndex] = null; 185 stackIndex--; 186 } 187 } 188 189 void clear() { 190 shouldUpdate = true; 191 for (int i = 0; i < stack.length; i++) { 192 stack[i] = null; 193 } 194 } 195 } 196 197 198 /******************************************************************************* 199 200 Simple console appender that counts the number of log lines it 201 has written. 202 203 *******************************************************************************/ 204 205 class TestingConsoleAppender : ConsoleAppender { 206 207 int events = 0; 208 209 this (EventLayout layout = null) 210 { 211 super(layout); 212 } 213 214 override void append (Event event) 215 { 216 events++; 217 super.append(event); 218 } 219 } 220 221 222 /******************************************************************************* 223 224 Testing harness for the DiagnosticContext functionality. 225 226 *******************************************************************************/ 227 228 void main(char[][] args) 229 { 230 //set up our appender that counts the log output. This is the configuration 231 //equivalent of importing tango.util.log.Configurator. 232 auto appender = new TestingConsoleAppender(new SimpleTimerLayout); 233 Log.getRootLogger.addAppender(appender); 234 235 auto log = Log.getLogger("context"); 236 log.setLevel(log.Level.Info); 237 238 //first test, use all defaults, validating it is working. None of the trace() 239 //calls should count in the test. 240 for (int i=0;i < 10; i++) { 241 log.info("test1 {}", i); 242 log.trace("test1 {}", i); 243 } 244 if (appender.events !is 10) { 245 log.error("events:{}", appender.events); 246 throw new Exception("Incorrect Number of events in normal mode"); 247 } 248 249 appender.events = 0; 250 251 //test the thread local implementation without any threads, as a baseline. 252 //should be same result as test1 253 auto context = new ThreadLocalDiagnosticContext; 254 Log.getHierarchy.context(context); 255 for (int i=0;i < 10; i++) { 256 log.info(tmp, "test2 {}", i); 257 log.trace("test2 {}", i); 258 } 259 if (appender.events !is 10) { 260 log.error("events:{}", appender.events); 261 throw new Exception("Incorrect Number of events in TLS single thread mode"); 262 } 263 264 appender.events = 0; 265 266 //test the thread local implementation without any threads, as a baseline. 267 //This should count all logging requests, because the DiagnosticContext has 268 //'overridden' the logging level on ALL loggers up to TRACE. 269 context.setLevel(log.Level.Trace); 270 for (int i=0;i < 10; i++) { 271 log.info("test3 {}", i); 272 log.trace("test3 {}", i); 273 } 274 if (appender.events !is 20) { 275 log.error("events:{}", appender.events); 276 throw new Exception("Incorrect Number of events in TLS single thread mode with level set"); 277 } 278 279 appender.events = 0; 280 281 //test the thread local implementation without any threads, as a baseline. 282 context.setLevel(log.Level.None); 283 for (int i=0;i < 10; i++) { 284 log.info("test4 {}", i); 285 log.trace("test4 {}", i); 286 } 287 if (appender.events !is 10) { 288 log.error("events:{}", appender.events); 289 throw new Exception("Incorrect Number of events in TLS single thread mode after level reset"); 290 } 291 292 //Now test threading. set up a trace context in one thread, with a label, while 293 //keeping the second thread at the normal configuration. 294 appender.events = 0; 295 ThreadGroup tg = new ThreadGroup(); 296 tg.create({ 297 context.setLevel(log.Level.Trace); 298 context.push("specialthread"); 299 context.push("2ndlevel"); 300 for (int i=0;i < 10; i++) { 301 log.info("test5 {}", i); 302 log.trace("test5 {}", i); 303 } 304 }); 305 tg.create({ 306 context.setLevel(log.Level.None); 307 for (int i=0;i < 10; i++) { 308 log.info("test6 {}", i); 309 log.trace("test6 {}", i); 310 } 311 }); 312 tg.joinAll(); 313 314 if (appender.events !is 30) { 315 log.error("events:{}", appender.events); 316 throw new Exception("Incorrect Number of events in TLS multi thread mode"); 317 } 318 } 319