1 /**
2  * onyx-log: the generic, fast, multithreading logging library.
3  *
4  * Logger core implementation.
5  *
6  * Copyright: © 2015-2017 Oleg Nykytenko
7  * License: MIT license. License terms written in "LICENSE.txt" file
8  * Authors: Oleg Nykytenko, oleg.nykytenko@gmail.com
9  *
10  * Version: 0.xx
11  * Date: 20.03.2015
12  */
13 module onyx.core.logger;
14 
15 
16 import onyx.log;
17 import onyx.bundle;
18 
19 
20 @safe:
21 public:
22 
23 /**
24  * Create loggers
25  *
26  * Throws: ConfException, LogCreateException, Exception
27  */
28 @trusted
29 void create(immutable Bundle bundle)
30 {
31     synchronized (lock)
32     {
33         foreach(loggerName; bundle.glKeys())
34         {
35             if (loggerName in ids)
36             {
37                 throw new LogCreateException("Creating logger error. Logger with name: " ~ loggerName ~ " already created");
38             }
39             auto log = new Logger(bundle.subBundle(loggerName));
40             ids[loggerName] = log;
41         }
42     }
43 }
44 
45 
46 /**
47  * Delete loggers
48  *
49  * Throws: Exception
50  */
51 @trusted
52 void delete_(immutable GlKey[] loggerNames)
53 {
54     synchronized (lock)
55     {
56         foreach(loggerName; loggerNames)
57         {
58             if (loggerName in ids)
59             {
60             	ids.remove(loggerName);
61             }
62         }
63     }
64 }
65 
66 
67 /**
68  * Get created logger
69  *
70  * Throws: LogException
71  */
72 @trusted
73 Log get(immutable string loggerName)
74 {
75     if (loggerName in ids)
76     {
77         return ids[loggerName];
78     }
79     else
80     {
81         throw new LogException("Getting logger error. Logger with name: " ~ loggerName ~ " not created");
82     }
83 }
84 
85 
86 /**
87  * Set file for save loggers exception information
88  *
89  * Throws: Exception
90  */
91 @trusted
92 void setErrorFile(immutable string file)
93 {
94     synchronized (lock)
95     {
96         static import onyx.core.controller;
97         onyx.core.controller.createPath(file);
98         errorFile = File(file, "a");
99     }
100 }
101 
102 
103 /*
104  * Make class member with getter
105  */
106 template addVal(T, string name, string specificator)
107 {
108     const char[] member = "private " ~ T.stringof ~ " _" ~ name ~"; ";
109     const char[] getter = "@property nothrow pure " ~ specificator ~ " " ~ T.stringof ~ " " ~ name ~ "() { return _" ~ name ~ "; }";
110     const char[] addVal = member ~ getter;
111 }
112 
113 /**
114  * Make class member with getter and setter
115  *
116  */
117 template addVar(T, string name, string getterSpecificator, string setterSpecificator)
118 {
119     const char[] setter = "@property nothrow pure " ~ setterSpecificator ~ " void " ~ name ~ "(" ~ T.stringof ~ " var" ~ ") { _" ~ name ~ " = var; }";
120     const char[] addVar = addVal!(T, name, getterSpecificator) ~ setter;
121 }
122 
123 /*
124  **************************************************************************************
125  */
126 @system:
127 private:
128 
129 import core.sync.mutex;
130 
131 import std.stdio;
132 
133 import onyx.core.appender;
134 /*
135  * Mutex use for block work with loggers pool
136  */
137 __gshared Mutex lock;
138 
139 
140 /*
141  * Save loggers by names in pool
142  */
143 __gshared Logger[immutable string] ids;
144 
145 
146 /*
147  * Save loggers errors in file
148  */
149 __gshared File errorFile;
150 
151 
152 
153 shared static this()
154 {
155     lock = new Mutex();
156 }
157 
158 
159 /*
160  * Logger implementation
161  */
162 class Logger: Log
163 {
164     /*
165      * Configuration data
166      */
167     mixin(addVal!(immutable Bundle, "config", "public"));
168 
169 
170     /*
171      * Name
172      */
173     mixin(addVal!(immutable string, "name", "public"));
174 
175 
176     /*
177      * Level getter in string type
178      */
179     public immutable (string) level()
180     {
181         return mlevel.levelToString();
182     }
183 
184 
185     /*
186      * Level
187      */
188     Level mlevel;
189 
190 
191     /*
192      * Appender
193      */
194     Appender appender;
195 
196 
197     /*
198      * Encoder
199      */
200      Encoder encoder;
201 
202 
203     /*
204      * Create logger impl
205      *
206      * Throws: LogCreateException, ConfException
207      */
208     this(immutable Bundle bundle)
209     {
210         _config = bundle;
211         _name = bundle.glKeys[0];
212         mlevel = bundle.value(name, "level").toLevel;
213 
214         appender = createAppender(bundle);
215         encoder = new Encoder(bundle);
216     }
217 
218 
219     /*
220      * Extract logger type from bundle
221      *
222      * Throws: BundleException, LogCreateException
223      */
224     @trusted /* Object.factory is system */
225     Appender createAppender(immutable Bundle bundle)
226     {
227         try
228         {
229             string appenderType = bundle.value(name, "appender");
230             AppenderFactory f = cast(AppenderFactory)Object.factory("onyx.core.appender." ~ appenderType ~ "Factory");
231 
232             if (f is null)
233             {
234                 throw new  LogCreateException("Error create log appender: " ~ appenderType  ~ "  is Illegal appender type from config bundle.");
235             }
236 
237             Appender a = f.factory(bundle);
238             return a;
239         }
240         catch (BundleException e)
241         {
242             throw new BundleException("Error in Config bundle. [" ~ name ~ "]:" ~ e.msg);
243         }
244         catch (Exception e)
245         {
246             throw new LogCreateException("Error in creating appender for logger: " ~ name ~ ": " ~ e.msg);
247         }
248     }
249 
250 
251     /*
252      * Write message with level "debug" to logger
253      */
254     void debug_(lazy const string msg) nothrow
255     {
256         putMsg(msg, Level.debug_);
257     }
258 
259 
260     /*
261      * Write message with level "info" to logger
262      */
263     void info(lazy const string msg) nothrow
264     {
265         putMsg(msg, Level.info);
266     }
267 
268 
269     /*
270      * Write message with level "warning" to logger
271      */
272     void warning(lazy const string msg) nothrow
273     {
274         putMsg(msg, Level.warning);
275     }
276 
277 
278     /*
279      * Write message with level "error" to logger
280      */
281     void error(lazy const string msg) nothrow
282     {
283         putMsg(msg, Level.error);
284     }
285 
286 
287     /*
288      * Write message with level "critical" to logger
289      */
290     void critical(lazy const string msg) nothrow
291     {
292         putMsg(msg, Level.critical);
293     }
294 
295 
296     /*
297      * Write message with level "fatal" to logger
298      */
299     void fatal(lazy const string msg) nothrow
300     {
301         putMsg(msg, Level.fatal);
302     }
303 
304 
305     /*
306      * Encode message and put to appender
307      */
308     @trusted
309     void putMsg(lazy string msg, Level level) nothrow
310     {
311         string fmsg;
312         if (level >= mlevel)
313         {
314             try
315             {
316                 fmsg = encoder.encode(msg, level);
317             }
318             catch (Exception e)
319             {
320                 try
321                 {
322                     fmsg = encoder.encode("Error in encode log message: " ~ e.msg, Level.error);
323                 }
324                 catch (Exception ee)
325                 {
326                     fixException(ee);
327                 }
328             }
329             try
330             {
331                 appender.append(fmsg);
332             }
333             catch (Exception e)
334             {
335                 fixException(e);
336             }
337         }
338     }
339 
340 
341     /**
342      * Logger exeption handler
343      */
344     @trusted
345     void fixException (Exception e) nothrow
346     {
347         try
348         {
349             synchronized(lock)
350             {
351                 errorFile.writeln("Error to work with log-> " ~ name ~ " Exception-> "  ~ e.msg);
352             }
353         }
354         catch(Exception e){}
355     }
356 }
357 
358 
359 class Encoder
360 {
361     import std.datetime;
362 
363     /*
364      * Name
365      */
366      mixin(addVal!(immutable string, "name", "private"));
367 
368 
369     /*
370      * Build encoder
371      */
372     this(immutable Bundle bundle) pure nothrow
373     {
374         _name = bundle.glKeys[0];
375     }
376 
377 
378     /**
379      * Do make message finish string
380      *
381      * Throws: Exception
382      */
383     immutable (string) encode (immutable string message, immutable Level level)
384     {
385         import std.string;
386         return format("%-27s [%s] %s- %s", Clock.currTime.toISOExtString(), levelToString(level), name, message);
387     }
388 }
389 
390 
391 
392 /*
393  * Level type
394  */
395 enum Level:int
396 {
397     debug_ = 1,
398     info = 2,
399     warning = 3,
400     error = 4,
401     critical = 5,
402     fatal = 6
403 }
404 
405 
406 /*
407  * Convert level from string type to Level
408  */
409 Level toLevel(string str)
410 {
411     Level l;
412     switch (str)
413     {
414         case "debug":
415             l = Level.debug_;
416             break;
417         case "info":
418             l = Level.info;
419             break;
420         case "warning":
421             l = Level.warning;
422             break;
423         case "error":
424             l = Level.error;
425             break;
426         case "critical":
427             l = Level.critical;
428             break;
429         case "fatal":
430             l = Level.fatal;
431             break;
432         default:
433             throw new LogCreateException("Error log level value: " ~ str);
434     }
435     return l;
436 }
437 
438 
439 /*
440  * Convert level from Level type to string
441  */
442 @safe
443 string levelToString(Level level)
444 {
445     string l;
446     final switch (level)
447     {
448         case Level.debug_:
449             l = "debug";
450             break;
451         case Level.info:
452             l = "info";
453             break;
454         case Level.warning:
455             l = "warning";
456             break;
457         case Level.error:
458             l = "error";
459             break;
460         case Level.critical:
461             l = "critical";
462             break;
463         case Level.fatal:
464             l = "fatal";
465             break;
466     }
467     return l;
468 }