Mercurial > projects > ldc
comparison tango/tango/util/log/Hierarchy.d @ 132:1700239cab2e trunk
[svn r136] MAJOR UNSTABLE UPDATE!!!
Initial commit after moving to Tango instead of Phobos.
Lots of bugfixes...
This build is not suitable for most things.
author | lindquist |
---|---|
date | Fri, 11 Jan 2008 17:57:40 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
131:5825d48b27d1 | 132:1700239cab2e |
---|---|
1 /******************************************************************************* | |
2 | |
3 copyright: Copyright (c) 2004 Kris Bell. All rights reserved | |
4 | |
5 license: BSD style: $(LICENSE) | |
6 | |
7 version: Oct 2004: Initial release | |
8 version: Feb 2007: Switched to lazy expr | |
9 | |
10 author: Kris | |
11 | |
12 *******************************************************************************/ | |
13 | |
14 module tango.util.log.Hierarchy; | |
15 | |
16 private import tango.time.Clock; | |
17 | |
18 private import tango.core.Exception; | |
19 | |
20 private import tango.util.log.Logger, | |
21 tango.util.log.Appender; | |
22 | |
23 private import tango.text.convert.Layout; | |
24 | |
25 private import tango.util.log.model.IHierarchy; | |
26 | |
27 /******************************************************************************* | |
28 | |
29 Pull in additional functions from the C library | |
30 | |
31 *******************************************************************************/ | |
32 | |
33 extern (C) | |
34 { | |
35 int memcmp (void *, void *, int); | |
36 } | |
37 | |
38 /******************************************************************************* | |
39 | |
40 Hack to sidestep linux linker errors (big thanks to Keinfarbton) | |
41 | |
42 *******************************************************************************/ | |
43 | |
44 private Layout!(char) format; | |
45 | |
46 static this() | |
47 { | |
48 .format = new Layout!(char); | |
49 } | |
50 | |
51 /******************************************************************************* | |
52 | |
53 Loggers are named entities, sometimes shared, sometimes specific to | |
54 a particular portion of code. The names are generally hierarchical in | |
55 nature, using dot notation (with '.') to separate each named section. | |
56 For example, a typical name might be something like "mail.send.writer" | |
57 --- | |
58 import tango.util.log.Log; | |
59 | |
60 auto log = Log.getLogger ("mail.send.writer"); | |
61 | |
62 log.info ("an informational message"); | |
63 log.error ("an exception message: " ~ exception.toString); | |
64 | |
65 etc ... | |
66 --- | |
67 | |
68 It is considered good form to pass a logger instance as a function or | |
69 class-ctor argument, or to assign a new logger instance during static | |
70 class construction. For example: if it were considered appropriate to | |
71 have one logger instance per class, each might be constructed like so: | |
72 --- | |
73 private Logger log; | |
74 | |
75 static this() | |
76 { | |
77 log = Log.getLogger (nameOfThisClassOrStructOrModule); | |
78 } | |
79 --- | |
80 | |
81 Messages passed to a Logger are assumed to be pre-formatted. You | |
82 may find that the format() methos is handy for collating various | |
83 components of the message: | |
84 --- | |
85 char tmp[128] = void; | |
86 ... | |
87 log.warn (log.format (tmp, "temperature is {} degrees!", 101)); | |
88 --- | |
89 | |
90 Note that a provided workspace is used to format the message, which | |
91 should generally be located on the stack so as to support multiple | |
92 threads of execution. In the example above we indicate assignment as | |
93 "tmp = void", although this is an optional attribute (see the language | |
94 manual for more information). | |
95 | |
96 To avoid overhead when constructing formatted messages, the logging | |
97 system employs lazy expressions such that the message is not constructed | |
98 unless the logger is actually active. You can also explicitly check to | |
99 see whether a logger is active or not: | |
100 --- | |
101 if (log.isEnabled (log.Level.Warn)) | |
102 log.warn (log.format (tmp, "temperature is {} degrees!", 101)); | |
103 --- | |
104 | |
105 You might optionally configure various layout & appender implementations | |
106 to support specific rendering needs. | |
107 | |
108 tango.log closely follows both the API and the behaviour as documented | |
109 at the official Log4J site, where you'll find a good tutorial. Those | |
110 pages are hosted over | |
111 <A HREF="http://logging.apache.org/log4j/docs/documentation.html">here</A>. | |
112 | |
113 *******************************************************************************/ | |
114 | |
115 private class LoggerInstance : Logger | |
116 { | |
117 private LoggerInstance next, | |
118 parent; | |
119 | |
120 private char[] name_; | |
121 private Level level_; | |
122 private Appender appender; | |
123 private Hierarchy hierarchy; | |
124 private bool additive, | |
125 breakpoint; | |
126 | |
127 | |
128 /*********************************************************************** | |
129 | |
130 Construct a LoggerInstance with the specified name for the | |
131 given hierarchy. By default, logger instances are additive | |
132 and are set to emit all events. | |
133 | |
134 ***********************************************************************/ | |
135 | |
136 protected this (Hierarchy hierarchy, char[] name) | |
137 { | |
138 this.hierarchy = hierarchy; | |
139 this.level_ = Level.Trace; | |
140 this.additive = true; | |
141 this.name_ = name; | |
142 } | |
143 | |
144 /*********************************************************************** | |
145 | |
146 No, you should not delete or 'scope' these entites | |
147 | |
148 ***********************************************************************/ | |
149 | |
150 private ~this() | |
151 { | |
152 } | |
153 | |
154 /*********************************************************************** | |
155 | |
156 Is this logger enabed for the specified Level? | |
157 | |
158 ***********************************************************************/ | |
159 | |
160 final bool isEnabled (Level level = Level.Fatal) | |
161 { | |
162 return hierarchy.context.isEnabled (level_, level); | |
163 } | |
164 | |
165 /*********************************************************************** | |
166 | |
167 Is this a breakpoint Logger? | |
168 | |
169 ***********************************************************************/ | |
170 | |
171 final bool isBreakpoint () | |
172 { | |
173 return breakpoint; | |
174 } | |
175 | |
176 /*********************************************************************** | |
177 | |
178 Is this logger additive? That is, should we walk ancestors | |
179 looking for more appenders? | |
180 | |
181 ***********************************************************************/ | |
182 | |
183 final bool isAdditive () | |
184 { | |
185 return additive; | |
186 } | |
187 | |
188 /*********************************************************************** | |
189 | |
190 Append a trace message | |
191 | |
192 ***********************************************************************/ | |
193 | |
194 final Logger trace (lazy char[] msg) | |
195 { | |
196 return append (Level.Trace, msg); | |
197 } | |
198 | |
199 /*********************************************************************** | |
200 | |
201 Append an info message | |
202 | |
203 ***********************************************************************/ | |
204 | |
205 final Logger info (lazy char[] msg) | |
206 { | |
207 return append (Level.Info, msg); | |
208 } | |
209 | |
210 /*********************************************************************** | |
211 | |
212 Append a warning message | |
213 | |
214 ***********************************************************************/ | |
215 | |
216 final Logger warn (lazy char[] msg) | |
217 { | |
218 return append (Level.Warn, msg); | |
219 } | |
220 | |
221 /*********************************************************************** | |
222 | |
223 Append an error message | |
224 | |
225 ***********************************************************************/ | |
226 | |
227 final Logger error (lazy char[] msg) | |
228 { | |
229 return append (Level.Error, msg); | |
230 } | |
231 | |
232 /*********************************************************************** | |
233 | |
234 Append a fatal message | |
235 | |
236 ***********************************************************************/ | |
237 | |
238 final Logger fatal (lazy char[] msg) | |
239 { | |
240 return append (Level.Fatal, msg); | |
241 } | |
242 | |
243 /*********************************************************************** | |
244 | |
245 Return the name of this Logger (sans the appended dot). | |
246 | |
247 ***********************************************************************/ | |
248 | |
249 final char[] name () | |
250 { | |
251 int i = name_.length; | |
252 if (i > 0) | |
253 --i; | |
254 return name_[0 .. i]; | |
255 } | |
256 | |
257 /*********************************************************************** | |
258 | |
259 Return the Level this logger is set to | |
260 | |
261 ***********************************************************************/ | |
262 | |
263 final Level level () | |
264 { | |
265 return level_; | |
266 } | |
267 | |
268 /*********************************************************************** | |
269 | |
270 Set the current level for this logger (and only this logger). | |
271 | |
272 ***********************************************************************/ | |
273 | |
274 final Logger setLevel (Level level = Level.Trace) | |
275 { | |
276 return setLevel (level, false); | |
277 } | |
278 | |
279 /*********************************************************************** | |
280 | |
281 Set the current level for this logger, and (optionally) all | |
282 of its descendents. | |
283 | |
284 ***********************************************************************/ | |
285 | |
286 final Logger setLevel (Level level, bool propagate) | |
287 { | |
288 this.level_ = level; | |
289 hierarchy.updateLoggers (this, propagate); | |
290 return this; | |
291 } | |
292 | |
293 /*********************************************************************** | |
294 | |
295 Set the breakpoint status of this logger. | |
296 | |
297 ***********************************************************************/ | |
298 | |
299 final Logger setBreakpoint (bool enabled) | |
300 { | |
301 breakpoint = enabled; | |
302 hierarchy.updateLoggers (this, false); | |
303 return this; | |
304 } | |
305 | |
306 /*********************************************************************** | |
307 | |
308 Set the additive status of this logger. See isAdditive(). | |
309 | |
310 ***********************************************************************/ | |
311 | |
312 final Logger setAdditive (bool enabled) | |
313 { | |
314 additive = enabled; | |
315 return this; | |
316 } | |
317 | |
318 /*********************************************************************** | |
319 | |
320 Add (another) appender to this logger. Appenders are each | |
321 invoked for log events as they are produced. At most, one | |
322 instance of each appender will be invoked. | |
323 | |
324 ***********************************************************************/ | |
325 | |
326 final Logger addAppender (Appender next) | |
327 { | |
328 if (appender) | |
329 next.setNext (appender); | |
330 appender = next; | |
331 return this; | |
332 } | |
333 | |
334 /*********************************************************************** | |
335 | |
336 Remove all appenders from this Logger | |
337 | |
338 ***********************************************************************/ | |
339 | |
340 final Logger clearAppenders () | |
341 { | |
342 appender = null; | |
343 return this; | |
344 } | |
345 | |
346 | |
347 /*********************************************************************** | |
348 | |
349 Get time since this application started | |
350 | |
351 ***********************************************************************/ | |
352 | |
353 final TimeSpan runtime () | |
354 { | |
355 return Clock.now - Event.startedAt; | |
356 } | |
357 | |
358 /*********************************************************************** | |
359 | |
360 Append a message to this logger via its appender list. | |
361 | |
362 ***********************************************************************/ | |
363 | |
364 final Logger append (Level level, lazy char[] exp) | |
365 { | |
366 if (hierarchy.context.isEnabled (level_, level)) | |
367 { | |
368 auto event = Event.allocate; | |
369 scope (exit) | |
370 Event.deallocate (event); | |
371 | |
372 // set the event attributes | |
373 event.set (hierarchy, level, exp, name.length ? name_[0..$-1] : "root"); | |
374 | |
375 // combine appenders from all ancestors | |
376 auto links = this; | |
377 Appender.Mask masks = 0; | |
378 do { | |
379 auto appender = links.appender; | |
380 | |
381 // this level have an appender? | |
382 while (appender) | |
383 { | |
384 auto mask = appender.getMask; | |
385 | |
386 // have we used this appender already? | |
387 if ((masks & mask) is 0) | |
388 { | |
389 // no - append message and update mask | |
390 event.scratch.length = 0; | |
391 appender.append (event); | |
392 masks |= mask; | |
393 } | |
394 // process all appenders for this node | |
395 appender = appender.getNext; | |
396 } | |
397 // process all ancestors | |
398 } while (links.additive && ((links = links.parent) !is null)); | |
399 } | |
400 return this; | |
401 } | |
402 | |
403 /*********************************************************************** | |
404 | |
405 Format text using the formatter configured in the associated | |
406 hierarchy (see Hierarchy.setFormat) | |
407 | |
408 ***********************************************************************/ | |
409 | |
410 final char[] format (char[] buffer, char[] formatStr, ...) | |
411 { | |
412 return .format.vprint (buffer, formatStr, _arguments, _argptr); | |
413 } | |
414 | |
415 /*********************************************************************** | |
416 | |
417 See if the provided Logger is a good match as a parent of | |
418 this one. Note that each Logger name has a '.' appended to | |
419 the end, such that name segments will not partially match. | |
420 | |
421 ***********************************************************************/ | |
422 | |
423 private final bool isCloserAncestor (LoggerInstance other) | |
424 { | |
425 auto length = other.name_.length; | |
426 | |
427 // possible parent if length is shorter | |
428 if (length < name_.length) | |
429 // does the prefix match? Note we append a "." to each | |
430 if (length is 0 || | |
431 memcmp (&other.name_[0], &name_[0], length) is 0) | |
432 // is this a better (longer) match than prior parent? | |
433 if ((parent is null) || (length >= parent.name_.length)) | |
434 return true; | |
435 return false; | |
436 } | |
437 } | |
438 | |
439 | |
440 /******************************************************************************* | |
441 | |
442 The Logger hierarchy implementation. We keep a reference to each | |
443 logger in a hash-table for convenient lookup purposes, plus keep | |
444 each logger linked to the others in an ordered chain. Ordering | |
445 places shortest names at the head and longest ones at the tail, | |
446 making the job of identifying ancestors easier in an orderly | |
447 fashion. For example, when propagating levels across descendents | |
448 it would be a mistake to propagate to a child before all of its | |
449 ancestors were taken care of. | |
450 | |
451 *******************************************************************************/ | |
452 | |
453 class Hierarchy : IHierarchy, IHierarchy.Context | |
454 { | |
455 private char[] name, | |
456 address; | |
457 private LoggerInstance root; | |
458 private LoggerInstance[char[]] loggers; | |
459 private Context context_; | |
460 | |
461 /*********************************************************************** | |
462 | |
463 Construct a hierarchy with the given name. | |
464 | |
465 ***********************************************************************/ | |
466 | |
467 this (char[] name) | |
468 { | |
469 this.name = name; | |
470 this.address = "network"; | |
471 | |
472 // insert a root node; the root has an empty name | |
473 root = new LoggerInstance (this, ""); | |
474 context = this; | |
475 } | |
476 | |
477 /********************************************************************** | |
478 | |
479 **********************************************************************/ | |
480 | |
481 final char[] label () | |
482 { | |
483 return ""; | |
484 } | |
485 | |
486 /********************************************************************** | |
487 | |
488 | |
489 **********************************************************************/ | |
490 | |
491 final bool isEnabled (ILevel.Level level, ILevel.Level test) | |
492 { | |
493 return test >= level; | |
494 } | |
495 | |
496 /********************************************************************** | |
497 | |
498 Return the name of this Hierarchy | |
499 | |
500 **********************************************************************/ | |
501 | |
502 final char[] getName () | |
503 { | |
504 return name; | |
505 } | |
506 | |
507 /********************************************************************** | |
508 | |
509 Return the address of this Hierarchy. This is typically | |
510 attached when sending events to remote monitors. | |
511 | |
512 **********************************************************************/ | |
513 | |
514 final char[] getAddress () | |
515 { | |
516 return address; | |
517 } | |
518 | |
519 /********************************************************************** | |
520 | |
521 Set the name of this Hierarchy | |
522 | |
523 **********************************************************************/ | |
524 | |
525 final void setName (char[] name) | |
526 { | |
527 this.name = name; | |
528 } | |
529 | |
530 /********************************************************************** | |
531 | |
532 Set the address of this Hierarchy. The address is attached | |
533 used when sending events to remote monitors. | |
534 | |
535 **********************************************************************/ | |
536 | |
537 final void setAddress (char[] address) | |
538 { | |
539 this.address = address; | |
540 } | |
541 | |
542 /********************************************************************** | |
543 | |
544 Set the diagnostic context. Not usually necessary, as a | |
545 default was created. Useful when you need to provide a | |
546 different implementation, such as a ThreadLocal variant. | |
547 | |
548 **********************************************************************/ | |
549 | |
550 final void context (Context context) | |
551 { | |
552 this.context_ = context; | |
553 } | |
554 | |
555 /********************************************************************** | |
556 | |
557 Return the diagnostic context. Useful for setting an | |
558 override logging level. | |
559 | |
560 **********************************************************************/ | |
561 | |
562 final Context context () | |
563 { | |
564 return context_; | |
565 } | |
566 | |
567 /*********************************************************************** | |
568 | |
569 Return the root node. | |
570 | |
571 ***********************************************************************/ | |
572 | |
573 final LoggerInstance getRootLogger () | |
574 { | |
575 return root; | |
576 } | |
577 | |
578 /*********************************************************************** | |
579 | |
580 Return the instance of a Logger with the provided label. If | |
581 the instance does not exist, it is created at this time. | |
582 | |
583 ***********************************************************************/ | |
584 | |
585 final synchronized LoggerInstance getLogger (char[] label) | |
586 { | |
587 auto name = label ~ "."; | |
588 auto l = name in loggers; | |
589 | |
590 if (l is null) | |
591 { | |
592 // create a new logger | |
593 auto li = new LoggerInstance (this, name); | |
594 l = &li; | |
595 | |
596 // insert into linked list | |
597 insertLogger (li); | |
598 | |
599 // look for and adjust children | |
600 updateLoggers (li, true); | |
601 | |
602 // insert into map | |
603 loggers [name] = li; | |
604 } | |
605 | |
606 return *l; | |
607 } | |
608 | |
609 /********************************************************************** | |
610 | |
611 Iterate over all Loggers in list | |
612 | |
613 **********************************************************************/ | |
614 | |
615 final int opApply (int delegate(inout Logger) dg) | |
616 { | |
617 int result = 0; | |
618 LoggerInstance curr = root; | |
619 | |
620 while (curr) | |
621 { | |
622 // BUG: this uncovers a cast() issue in the 'inout' delegation | |
623 Logger logger = curr; | |
624 if ((result = dg (logger)) != 0) | |
625 break; | |
626 curr = curr.next; | |
627 } | |
628 return result; | |
629 } | |
630 | |
631 /*********************************************************************** | |
632 | |
633 Loggers are maintained in a sorted linked-list. The order | |
634 is maintained such that the shortest name is at the root, | |
635 and the longest at the tail. | |
636 | |
637 This is done so that updateLoggers() will always have a | |
638 known environment to manipulate, making it much faster. | |
639 | |
640 ***********************************************************************/ | |
641 | |
642 private void insertLogger (LoggerInstance l) | |
643 { | |
644 LoggerInstance prev, | |
645 curr = root; | |
646 | |
647 while (curr) | |
648 { | |
649 // insert here if the new name is shorter | |
650 if (l.name.length < curr.name.length) | |
651 if (prev is null) | |
652 throw new IllegalElementException ("invalid hierarchy"); | |
653 else | |
654 { | |
655 l.next = prev.next; | |
656 prev.next = l; | |
657 return; | |
658 } | |
659 else | |
660 // find best match for parent of new entry | |
661 propagate (l, curr, true); | |
662 | |
663 // remember where insertion point should be | |
664 prev = curr; | |
665 curr = curr.next; | |
666 } | |
667 | |
668 // add to tail | |
669 prev.next = l; | |
670 } | |
671 | |
672 /*********************************************************************** | |
673 | |
674 Propagate hierarchical changes across known loggers. | |
675 This includes changes in the hierarchy itself, and to | |
676 the various settings of child loggers with respect to | |
677 their parent(s). | |
678 | |
679 ***********************************************************************/ | |
680 | |
681 private void updateLoggers (LoggerInstance changed, bool force) | |
682 { | |
683 LoggerInstance logger = root; | |
684 | |
685 // scan all loggers | |
686 while (logger) | |
687 { | |
688 propagate (logger, changed, force); | |
689 | |
690 // try next entry | |
691 logger = logger.next; | |
692 } | |
693 } | |
694 | |
695 /*********************************************************************** | |
696 | |
697 Propagate changes in the hierarchy downward to child Loggers. | |
698 Note that while 'parent' and 'breakpoint' are always forced | |
699 to update, the update of 'level' is selectable. | |
700 | |
701 ***********************************************************************/ | |
702 | |
703 private void propagate (LoggerInstance logger, LoggerInstance changed, bool force) | |
704 { | |
705 // is the changed instance a better match for our parent? | |
706 if (logger.isCloserAncestor (changed)) | |
707 { | |
708 // update parent (might actually be current parent) | |
709 logger.parent = changed; | |
710 | |
711 // if we don't have an explicit level set, inherit it | |
712 if ((logger.level is Logger.Level.None) || force) | |
713 logger.setLevel (changed.level); | |
714 | |
715 // always force breakpoints to follow parent settings | |
716 logger.breakpoint = changed.breakpoint; | |
717 } | |
718 } | |
719 } | |
720 | |
721 |