1 module glued.application.logging;
2 
3 import std.functional;
4 
5 import glued.logging;
6 import glued.pathtree;
7 
8 import optional;
9 
10 alias Predicate(T) = bool delegate(T);
11 alias Callable(T) = void delegate(T);
12 
13 class FilteringSink: LogSink {
14     private LogSink wrapped;
15     private Predicate!LogEvent predicate;
16     private Optional!(Callable!LogEvent) discardedConsumer;
17     
18     this(LogSink wrapped, Predicate!LogEvent predicate){
19         this.wrapped = wrapped;
20         this.predicate = predicate;
21         this.discardedConsumer = no!(Callable!LogEvent);
22     }
23     
24     this(LogSink wrapped, Predicate!LogEvent predicate, Callable!LogEvent discardedConsumer){
25         this.wrapped = wrapped;
26         this.predicate = predicate;
27         this.discardedConsumer = discardedConsumer.some;
28     }
29     
30     void consume(LogEvent e){
31         if (predicate(e)) {
32             wrapped.consume(e);
33         } else {
34             if (!discardedConsumer.empty){
35                 (discardedConsumer.front())(e);
36             }
37         }
38     }
39 }
40 
41 alias PathExtractor = string delegate(LogEvent);
42 
43 struct ModuleExtractors {
44     @property
45     static PathExtractor fromLogger() { return toDelegate((LogEvent e) => e.loggerLocation.moduleName); }
46     @property
47     static PathExtractor fromEvent() { return toDelegate((LogEvent e) => e.eventLocation.moduleName); }
48 }
49 
50 Predicate!LogEvent levelConfig(PathTreeView!Level config, PathExtractor extractor=ModuleExtractors.fromEvent, Level defaultLevel=Level.ANY){
51     return (LogEvent e) => (e.level >= config.get(extractor(e)).or(defaultLevel.some).front());
52 }
53 
54 //todo another source set
55 //todo this is an awfully trivial test suite, extend it
56 version(unittest){
57     import std.datetime: SysTime;
58     import std.concurrency: Tid;
59 
60     enum ActionTaken { UNKNOWN, CONSUMED, DISCARDED }
61     
62     auto event(string loggerModule, string eventModule, Level lvl){
63         return LogEvent(
64             lvl, 
65             CodeLocation("", 0, loggerModule, "", "", ""),
66             CodeLocation("", 0, eventModule, "", "", ""),
67             "",
68             no!SysTime,
69             no!Tid
70         );
71     }
72     
73     ActionTaken whenFilteredWithPredicate(Predicate!LogEvent pred, LogEvent e){
74         ActionTaken action = ActionTaken.UNKNOWN;
75         class Consume: LogSink { 
76             void consume(LogEvent e){
77                 action = ActionTaken.CONSUMED;
78             }
79         }
80         void discard(LogEvent e){ action = ActionTaken.DISCARDED; }
81         new FilteringSink(new Consume, pred, &discard).consume(e);
82         return action;
83     }
84 }
85 
86 unittest {
87     assert(
88         whenFilteredWithPredicate(
89             levelConfig(
90                 new ConcretePathTree!Level, 
91                 ModuleExtractors.fromEvent,
92                 Level.ANY
93             ), 
94             event("a", "b", Level.INFO)
95         ) 
96         ==
97         ActionTaken.CONSUMED
98     );
99     assert(
100         whenFilteredWithPredicate(
101             levelConfig(
102                 new ConcretePathTree!Level, 
103                 ModuleExtractors.fromEvent,
104                 Level.INFO
105             ), 
106             event("a", "b", Level.DEBUG)
107         ) 
108         ==
109         ActionTaken.DISCARDED
110     );
111 }