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 }