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