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