1 module logdefer.logger;
2 
3 
4 import std.array : Appender;
5 import std.conv : to;
6 
7 public import logdefer.common : DefaultTimeProvider, DelegateWriter, EventContext, LogEntry, LogLevel, Verbosity;
8 import logdefer.serializer.json : JSONSerializer;
9 import logdefer.time.duration : Nanos;
10 import logdefer.time.utils : toDuration;
11 import logdefer.timer : Timer;
12 
13 import unixtime : ClockType, UnixTimeHiRes;
14 
15 alias DefaultLogger = Logger!();
16 
17 /**
18   The primary interface into the logging system.  Logger is templatized
19   based on:
20 
21   Serializer - formats the output and passes it to writer
22   TimeProvider - what source of time to use
23 
24   By default the Serializer is JSON and the TimeProvider uses the standard Clock
25   in std.datetime.  In this case simply pass a delegate callback that takes the
26   serialized string data and does whatever you want with it.  The constructor
27   will take care of initializing the JSON serializer with your provided callback.
28 
29   If you want to use a different serializer simple pass it in instead to the
30   properly templatized struct:
31 
32   auto mySerializer = MySerializer();
33   auto logger = Logger!(MySerializer)(mySerializer);
34 
35   The only constraint is that your serializer implements opCall which takes
36   a ref to the EventContext object.   Thus it could be a function, delegate,
37   struct or class.
38 
39   If you want to provide your own source of time (for testing, or some external
40   time provider) you just call the constructor with your TimeProvider which
41   again must implement opCall and returns a UnixTime:
42 
43   auto myTimeProvider = () { return UnixTime(0); };
44   auto logger = Logger!(MySerializer, typeof(myTimeProvider))(xxx, myTimeProvider);
45   
46   As you log against the log defer instance it will accumulate the logs into its
47   internal buffer.  Once the object goes out of scope the logs are passed to the
48   serializer which handles formatting and writing out the logs.
49   */
50 @safe
51 struct Logger(
52     Serializer = JSONSerializer!(DelegateWriter),
53     TimeProvider = typeof(DefaultTimeProvider)
54 )
55 {
56     public:
57         
58         @disable this(this);
59 
60         /*
61            Convenience constructor.  Uses default serializer and time provider
62            with provided delegate to write log
63         */
64         @safe
65         alias Callback = void delegate(string msg);
66 
67         this()(Callback callback)
68         {
69             serializer_ = Serializer(callback);
70             eventContext_.realStartTime = DefaultTimeProvider();
71             eventContext_.monotonicStartTime = UnixTimeHiRes.now!(ClockType.MONOTONIC)();
72         }
73 
74         /*
75             Uses default time provider and provided serializer
76         */
77         this()(Serializer serializer)
78         {
79             serializer_  = serializer;
80             eventContext_.realStartTime = DefaultTimeProvider();
81             eventContext_.monotonicStartTime = UnixTimeHiRes.now!(ClockType.MONOTONIC)();
82         }
83 
84         /*
85            Uses provided serializer and time provider
86         */
87         this()(Serializer serializer, TimeProvider timeProvider)
88         {
89             serializer_ = serializer;
90             eventContext_.realStartTime = timeProvider();
91             eventContext_.monotonicStartTime = UnixTimeHiRes.now!(ClockType.MONOTONIC)();
92         }
93 
94         // Commit log when going out of scope
95         ~this()
96         {
97             commit();
98         }
99 
100         // Add given parameters to the log with given verbosity
101         void addLog(Param...)(Verbosity verbosity, const Param params)
102         {
103             // Log threshold not met, ignore log
104             if (verbosity > logLevel_)
105             {
106                 return;
107             }
108 
109             // Concatenate all the log params into a string
110             auto msg = Appender!string();
111             foreach(param; params)
112             {
113                 msg.put(to!string(param));
114             }
115             // Store the message along with verbosity and duration
116             eventContext_.logs.put(LogEntry(toDuration!Nanos(eventContext_.monotonicStartTime, UnixTimeHiRes.now!(ClockType.MONOTONIC)()), verbosity, msg.data));
117         }
118 
119         // Add/update associated metadata with the event context
120         void opIndexAssign(Key, Value)(const auto ref Value value, const auto ref Key key)
121         {
122             eventContext_.metadata[to!string(key)] = to!string(value);
123         }
124 
125         string opIndex(Key)(const auto ref Key key)
126         {
127             return eventContext_.metadata[to!string(key)];
128         }
129 
130         auto timer(string timerName)
131         {
132             eventContext_.timers.put(new Timer(timerName, eventContext_.monotonicStartTime));
133             return eventContext_.timers.data[$-1].startTimer();
134         }
135 
136         // Convenience wrappers...
137         void error(Param...)(auto ref const Param params)
138         {
139             addLog(LogLevel.Error, params);
140         }
141 
142         void warn(Param...)(auto ref const Param params)
143         {
144             addLog(LogLevel.Warn, params);
145         }
146 
147         void info(Param...)(auto ref const Param params)
148         {
149             addLog(LogLevel.Info, params);
150         }
151 
152         void trace(Param...)(auto ref const Param params)
153         {
154             addLog(LogLevel.Trace, params);
155         }
156 
157         // Manipulate and view log levels...
158         void setLogLevel(LogLevel logLevel)
159         {
160             logLevel_ = logLevel;
161         }
162 
163         void setLogLevel(int logLevel)
164         {
165             logLevel_ = logLevel;
166         }
167 
168         int logLevel()
169         {
170             return logLevel_;
171         }
172 
173         bool isError()
174         {
175             return logLevel_ <= LogLevel.Error;
176         }
177 
178         void setError()
179         {
180             logLevel_ = LogLevel.Error;
181         }
182 
183         bool isWarn()
184         {
185             return logLevel_ <= LogLevel.Warn;
186         }
187 
188         void setWarn()
189         {
190             logLevel_ = LogLevel.Warn;
191         }
192 
193         bool isInfo()
194         {
195             return logLevel_ <= LogLevel.Info;
196         }
197 
198         void setInfo()
199         {
200             logLevel_ = LogLevel.Info;
201         }
202 
203         bool isTrace()
204         {
205             return logLevel_ <= LogLevel.Trace;
206         }
207 
208         void setTrace()
209         {
210             logLevel_ = LogLevel.Trace;
211         }
212 
213 
214     private:
215 
216         Serializer serializer_ = void;
217         TimeProvider timeProvider_;
218         EventContext eventContext_;
219         int logLevel_ = int.max;
220 
221         // Commit the logs in the buffer
222         void commit()
223         {
224             eventContext_.endOffset = toDuration!Nanos(eventContext_.monotonicStartTime, UnixTimeHiRes.now!(ClockType.MONOTONIC)());
225             serializer_(eventContext_);
226         }
227 }
228 
229 version (unittest)
230 {
231     import std.array : front;
232     import std.stdio : writeln;
233     import std.typecons : Tuple;
234 
235     auto time = ()
236     {
237         return UnixTimeHiRes(1234);
238     };
239 
240     const(EventContext)[] events;
241     auto serializer = (const ref EventContext eventContext)
242     {
243         events ~= eventContext;
244     };
245 
246     alias Spec = Tuple!(int, string);
247 
248     auto testSpecs = [
249         Spec(45, "custom 1"),
250         Spec(40, "trace"),
251         Spec(30, "info"),
252         Spec(20, "warn"),
253         Spec(10, "error"),
254         Spec(5, "custom 2"),
255     ];
256 
257     void verifyLogs(const ref EventContext evt, Spec[] specs)
258     {
259         auto logs = evt.logs.data;
260         assert(logs.length == specs.length);
261         uint i;
262         foreach(spec; specs)
263         {
264             assert(logs[i].verbosity == spec[0]);
265             assert(logs[i].msg == spec[1]);
266             i++;
267         }
268     }
269 
270     alias TestLogger = Logger!(typeof(serializer), typeof(time));
271 
272     void testLog(ref TestLogger logger)
273     {
274         logger.addLog(45, "custom 1");
275         logger.trace("trace");
276         logger.info("info");
277         logger.warn("warn");
278         logger.error("error");
279         logger.addLog(5, "custom 2");
280 
281     }
282 }
283 
284 unittest
285 {
286     writeln("[UnitTest Logger] - default log level, start time");
287 
288     events.destroy;
289 
290     {
291         auto logger = TestLogger(serializer, time);
292         testLog(logger);
293         {
294             auto timer = logger.timer("timer");
295         }
296         logger["RequestID"] = 7;
297         assert(events.length == 0);
298     }
299 
300     assert(events.length == 1);
301 
302     auto evt = events.front;
303     assert(evt.realStartTime == time());
304     assert(evt.endOffset.value > 0);
305 
306     assert(evt.metadata.length == 1);
307     assert(evt.metadata["RequestID"] == "7");
308 
309     assert(evt.timers.data.length == 1);
310     assert(evt.timers.data.front.name == "timer");
311     assert(evt.timers.data.front.start > 0);
312     assert(evt.timers.data.front.end > 0);
313 
314     verifyLogs(evt, testSpecs);
315 }
316 
317 unittest
318 {
319     writeln("[UnitTest Logger] - trace log level");
320 
321     events.destroy;
322 
323     {
324         auto logger = TestLogger(serializer, time);
325         logger.setTrace();
326 
327         testLog(logger);
328     }
329 
330     verifyLogs(events.front, testSpecs[1..$]);
331 }
332 
333 unittest
334 {
335     writeln("[UnitTest Logger] - info log level");
336 
337     events.destroy;
338 
339     {
340         auto logger = TestLogger(serializer, time);
341         logger.setInfo();
342 
343         testLog(logger);
344     }
345 
346     verifyLogs(events.front, testSpecs[2..$]);
347 }
348 
349 // Log level set to warn 
350 unittest
351 {
352     writeln("[UnitTest Logger] - warn log level");
353 
354     events.destroy;
355 
356     {
357         auto logger = TestLogger(serializer, time);
358         logger.setWarn();
359 
360         testLog(logger);
361     }
362 
363     verifyLogs(events.front, testSpecs[3..$]);
364 }
365 
366 
367 // Log level set to error 
368 unittest
369 {
370     writeln("[UnitTest Logger] - error log level");
371 
372     events.destroy;
373 
374     {
375         auto logger = TestLogger(serializer, time);
376         logger.setError();
377 
378         testLog(logger);
379     }
380 
381     verifyLogs(events.front, testSpecs[4..$]);
382 }
383 
384 // Log level set to custom
385 unittest
386 {
387     writeln("[UnitTest Logger] - custom log level");
388 
389     events.destroy;
390 
391     {
392         auto logger = TestLogger(serializer, time);
393         logger.setLogLevel(7);
394 
395         testLog(logger);
396     }
397 
398     verifyLogs(events.front, testSpecs[5..$]);
399 }
400 
401 // Log level set to 0
402 unittest
403 {
404     writeln("[UnitTest Logger] - 0 log level");
405 
406     events.destroy;
407 
408     {
409         auto logger = TestLogger(serializer, time);
410         logger.setLogLevel(0);
411 
412         testLog(logger);
413     }
414 
415     verifyLogs(events.front, testSpecs[6..$]);
416 }