132
|
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
|