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 }