132
|
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 char[128] tmp = 0;
|
|
236 auto log = Log.getLogger("context");
|
|
237 log.setLevel(log.Level.Info);
|
|
238
|
|
239 //first test, use all defaults, validating it is working. None of the trace()
|
|
240 //calls should count in the test.
|
|
241 for (int i=0;i < 10; i++) {
|
|
242 log.info(log.format(tmp, "test1 {}", i));
|
|
243 log.trace(log.format(tmp, "test1 {}", i));
|
|
244 }
|
|
245 if (appender.events !is 10) {
|
|
246 log.error(log.format(tmp, "events:{}", appender.events));
|
|
247 throw new Exception("Incorrect Number of events in normal mode");
|
|
248 }
|
|
249
|
|
250 appender.events = 0;
|
|
251
|
|
252 //test the thread local implementation without any threads, as a baseline.
|
|
253 //should be same result as test1
|
|
254 auto context = new ThreadLocalDiagnosticContext;
|
|
255 Log.getHierarchy.context(context);
|
|
256 for (int i=0;i < 10; i++) {
|
|
257 log.info(log.format(tmp, "test2 {}", i));
|
|
258 log.trace(log.format(tmp, "test2 {}", i));
|
|
259 }
|
|
260 if (appender.events !is 10) {
|
|
261 log.error(log.format(tmp, "events:{}", appender.events));
|
|
262 throw new Exception("Incorrect Number of events in TLS single thread mode");
|
|
263 }
|
|
264
|
|
265 appender.events = 0;
|
|
266
|
|
267 //test the thread local implementation without any threads, as a baseline.
|
|
268 //This should count all logging requests, because the DiagnosticContext has
|
|
269 //'overridden' the logging level on ALL loggers up to TRACE.
|
|
270 context.setLevel(log.Level.Trace);
|
|
271 for (int i=0;i < 10; i++) {
|
|
272 log.info(log.format(tmp, "test3 {}", i));
|
|
273 log.trace(log.format(tmp, "test3 {}", i));
|
|
274 }
|
|
275 if (appender.events !is 20) {
|
|
276 log.error(log.format(tmp, "events:{}", appender.events));
|
|
277 throw new Exception("Incorrect Number of events in TLS single thread mode with level set");
|
|
278 }
|
|
279
|
|
280 appender.events = 0;
|
|
281
|
|
282 //test the thread local implementation without any threads, as a baseline.
|
|
283 context.setLevel(log.Level.None);
|
|
284 for (int i=0;i < 10; i++) {
|
|
285 log.info(log.format(tmp, "test4 {}", i));
|
|
286 log.trace(log.format(tmp, "test4 {}", i));
|
|
287 }
|
|
288 if (appender.events !is 10) {
|
|
289 log.error(log.format(tmp, "events:{}", appender.events));
|
|
290 throw new Exception("Incorrect Number of events in TLS single thread mode after level reset");
|
|
291 }
|
|
292
|
|
293 //Now test threading. set up a trace context in one thread, with a label, while
|
|
294 //keeping the second thread at the normal configuration.
|
|
295 appender.events = 0;
|
|
296 ThreadGroup tg = new ThreadGroup();
|
|
297 tg.create({
|
|
298 char[128] tmp = 0;
|
|
299 context.setLevel(log.Level.Trace);
|
|
300 context.push("specialthread");
|
|
301 context.push("2ndlevel");
|
|
302 for (int i=0;i < 10; i++) {
|
|
303 log.info(log.format(tmp, "test5 {}", i));
|
|
304 log.trace(log.format(tmp, "test5 {}", i));
|
|
305 }
|
|
306 });
|
|
307 tg.create({
|
|
308 char[128] tmp = 0;
|
|
309 context.setLevel(log.Level.None);
|
|
310 for (int i=0;i < 10; i++) {
|
|
311 log.info(log.format(tmp, "test6 {}", i));
|
|
312 log.trace(log.format(tmp, "test6 {}", i));
|
|
313 }
|
|
314 });
|
|
315 tg.joinAll();
|
|
316
|
|
317 if (appender.events !is 30) {
|
|
318 log.error(log.format(tmp, "events:{}", appender.events));
|
|
319 throw new Exception("Incorrect Number of events in TLS multi thread mode");
|
|
320 }
|
|
321 }
|
|
322
|