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