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