1 /* 2 * Hunt - A refined core library for D programming language. 3 * 4 * Copyright (C) 2018-2019 HuntLabs 5 * 6 * Website: https://www.huntlabs.net/ 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 module hunt.logging.Logger; 12 13 // import hunt.util.ThreadHelper; 14 15 import hunt.util.ThreadHelper; 16 17 import core.thread; 18 19 import std.algorithm.iteration; 20 import std.array; 21 import std.concurrency; 22 import std.exception; 23 import std.file; 24 import std.parallelism; 25 import std.stdio; 26 import std.datetime; 27 import std.format; 28 import std.range; 29 import std.conv; 30 import std.regex; 31 import std.path; 32 import std.typecons; 33 import std.traits; 34 import std.string; 35 36 alias LogLayoutHandler = string delegate(string time_prior, string tid, string level, string myFunc, 37 string msg, string file, size_t line); 38 39 private: 40 41 __gshared LogLayoutHandler _layoutHandler; 42 43 LogLayoutHandler layoutHandler() { 44 if(_layoutHandler is null) { 45 _layoutHandler = (string time_prior, string tid, string level, string myFunc, 46 string msg, string file, size_t line) { 47 48 return time_prior ~ " (" ~ tid ~ ") [" ~ level ~ "] " ~ myFunc ~ 49 " - " ~ msg ~ " - " ~ file ~ ":" ~ to!string(line); 50 }; 51 } 52 53 return _layoutHandler; 54 } 55 56 class SizeBaseRollover 57 { 58 59 import std.path; 60 import std.string; 61 import std.typecons; 62 63 string path; 64 string dir; 65 string baseName; 66 string ext; 67 string activeFilePath; 68 69 /** 70 * Max size of one file 71 */ 72 uint maxSize; 73 74 /** 75 * Max number of working files 76 */ 77 uint maxHistory; 78 79 this(string fileName, string size, uint maxNum) 80 { 81 path = fileName; 82 auto fileInfo = parseConfigFilePath(fileName); 83 dir = fileInfo[0]; 84 baseName = fileInfo[1]; 85 ext = fileInfo[2]; 86 87 activeFilePath = path; 88 maxSize = extractSize(size); 89 90 maxHistory = maxNum; 91 } 92 93 auto parseConfigFilePath(string rawConfigFile) 94 { 95 string configFile = buildNormalizedPath(rawConfigFile); 96 97 immutable dir = configFile.dirName; 98 string fullBaseName = std.path.baseName(configFile); 99 auto ldotPos = fullBaseName.lastIndexOf("."); 100 immutable ext = (ldotPos > 0) ? fullBaseName[ldotPos + 1 .. $] : "log"; 101 immutable baseName = (ldotPos > 0) ? fullBaseName[0 .. ldotPos] : fullBaseName; 102 103 return tuple(dir, baseName, ext); 104 } 105 106 uint extractSize(string size) 107 { 108 import std.uni : toLower; 109 import std.uni : toUpper; 110 import std.conv; 111 112 uint nsize = 0; 113 auto n = matchAll(size, regex(`\d*`)); 114 if (!n.empty && (n.hit.length != 0)) 115 { 116 nsize = to!int(n.hit); 117 auto m = matchAll(size, regex(`\D{1}`)); 118 if (!m.empty && (m.hit.length != 0)) 119 { 120 switch (m.hit.toUpper) 121 { 122 case "K": 123 nsize *= KB; 124 break; 125 case "M": 126 nsize *= MB; 127 break; 128 case "G": 129 nsize *= GB; 130 break; 131 case "T": 132 nsize *= TB; 133 break; 134 case "P": 135 nsize *= PB; 136 break; 137 default: 138 throw new Exception("In Logger configuration uncorrect number: " ~ size); 139 } 140 } 141 } 142 return nsize; 143 } 144 145 enum KB = 1024; 146 enum MB = KB * 1024; 147 enum GB = MB * 1024; 148 enum TB = GB * 1024; 149 enum PB = TB * 1024; 150 151 /** 152 * Scan work directory 153 * save needed files to pool 154 */ 155 string[] scanDir() 156 { 157 import std.algorithm.sorting : sort; 158 import std.algorithm; 159 160 bool tc(string s) 161 { 162 static import std.path; 163 164 auto base = std.path.baseName(s); 165 auto m = matchAll(base, regex(baseName ~ `\d*\.` ~ ext)); 166 if (m.empty || (m.hit != base)) 167 { 168 return false; 169 } 170 return true; 171 } 172 173 return std.file.dirEntries(dir, SpanMode.shallow) 174 .filter!(a => a.isFile).map!(a => a.name).filter!(a => tc(a)) 175 .array.sort!("a < b").array; 176 } 177 178 /** 179 * Do files rolling by size 180 */ 181 182 bool roll(string msg) 183 { 184 auto filePool = scanDir(); 185 if (filePool.length == 0) 186 { 187 return false; 188 } 189 if ((getSize(filePool[0]) + msg.length) >= maxSize) 190 { 191 //if ((filePool.front.getSize == 0) throw 192 if (filePool.length >= maxHistory) 193 { 194 std.file.remove(filePool[$ - 1]); 195 filePool = filePool[0 .. $ - 1]; 196 } 197 //carry(filePool); 198 return true; 199 } 200 return false; 201 } 202 203 /** 204 * Rename log files 205 */ 206 207 void carry() 208 { 209 import std.conv; 210 import std.path; 211 212 auto filePool = scanDir(); 213 foreach_reverse (ref file; filePool) 214 { 215 auto newFile = dir ~ dirSeparator ~ baseName ~ to!string(extractNum(file) + 1) 216 ~ "." ~ ext; 217 std.file.rename(file, newFile); 218 file = newFile; 219 } 220 } 221 222 /** 223 * Extract number from file name 224 */ 225 uint extractNum(string file) 226 { 227 import std.conv; 228 229 uint num = 0; 230 try 231 { 232 static import std.path; 233 import std.string; 234 235 auto fch = std.path.baseName(file).chompPrefix(baseName); 236 auto m = matchAll(fch, regex(`\d*`)); 237 238 if (!m.empty && m.hit.length > 0) 239 { 240 num = to!uint(m.hit); 241 } 242 } 243 catch (Exception e) 244 { 245 throw new Exception("Uncorrect log file name: " ~ file ~ " -> " ~ e.msg); 246 } 247 return num; 248 } 249 250 } 251 252 __gshared Logger g_logger = null; 253 254 version (Windows) 255 { 256 import core.sys.windows.wincon; 257 import core.sys.windows.winbase; 258 import core.sys.windows.windef; 259 260 private __gshared HANDLE g_hout; 261 shared static this() { 262 g_hout = GetStdHandle(STD_OUTPUT_HANDLE); 263 } 264 } 265 266 267 string code(string func, LogLevel level, bool f = false)() 268 { 269 return "void " ~ func 270 ~ `(string file = __FILE__ , size_t line = __LINE__ , string func = __FUNCTION__ , A ...)(lazy A args) 271 { 272 LogLayoutHandler handler = layoutHandler(); 273 274 if(g_logger is null) { 275 Logger.writeFormatColor(` 276 ~ level.stringof ~ ` , Logger.toFormat(func , Logger.logFormat` ~ (f 277 ? "f" : "") ~ `(args) , file , line , ` ~ level.stringof ~ `, handler)); 278 } else { 279 g_logger.write(` 280 ~ level.stringof ~ ` , Logger.toFormat(func , Logger.logFormat` ~ (f 281 ? "f" : "") ~ `(args) , file , line ,` ~ level.stringof ~ `, handler)); 282 } 283 }`; 284 } 285 286 287 288 public: 289 290 /** 291 * 292 */ 293 class Logger 294 { 295 private LogLayoutHandler _layoutHandler; 296 private bool _isRunning = true; 297 298 __gshared Logger[string] g_logger; 299 static Logger createLogger(string name , LogConf conf) 300 { 301 g_logger[name] = new Logger(conf); 302 return g_logger[name]; 303 } 304 305 static Logger getLogger(string name) 306 { 307 return g_logger[name]; 308 } 309 310 this(LogConf conf, LogLayoutHandler handler = null) 311 { 312 _layoutHandler = handler; 313 _conf = conf; 314 string fileName = conf.fileName; 315 316 if (!fileName.empty) 317 { 318 if(exists(fileName) && isDir(fileName)) 319 throw new Exception("A direction has existed with the same name."); 320 321 createPath(conf.fileName); 322 _file = File(conf.fileName, "a"); 323 _rollover = new SizeBaseRollover(conf.fileName, _conf.maxSize, _conf.maxNum); 324 } 325 326 immutable void* data = cast(immutable void*) this; 327 if(!_conf.fileName.empty) 328 _tid = spawn(&Logger.worker, data); 329 } 330 331 void logLayoutHandler(LogLayoutHandler handler) { 332 _layoutHandler = handler; 333 } 334 335 LogConf conf() { 336 return _conf; 337 } 338 339 void stop() { 340 _isRunning = false; 341 } 342 343 bool isRunning() { 344 return _isRunning; 345 } 346 347 void log(string file = __FILE__ , size_t line = __LINE__ , string func = __FUNCTION__ , A ...)(LogLevel level , lazy A args) 348 { 349 write(level , toFormat(func , logFormat(args) , file , line , level, _layoutHandler)); 350 } 351 352 void logf(string file = __FILE__ , size_t line = __LINE__ , string func = __FUNCTION__ , A ...)(LogLevel level , lazy A args) 353 { 354 write(level , toFormat(func , logFormatf(args) , file , line , level, _layoutHandler)); 355 } 356 357 void trace(string file = __FILE__ , size_t line = __LINE__ , string func = __FUNCTION__ , A ...)(lazy A args) 358 { 359 enum LogLevel level = LogLevel.LOG_DEBUG; 360 write(level, toFormat(func , logFormat(args) , file , line , level, _layoutHandler)); 361 } 362 363 void tracef(string file = __FILE__ , size_t line = __LINE__ , string func = __FUNCTION__ , A ...)(lazy A args) 364 { 365 enum LogLevel level = LogLevel.LOG_DEBUG; 366 write(level , toFormat(func , logFormatf(args) , file , line , level, _layoutHandler)); 367 } 368 369 void info(string file = __FILE__ , size_t line = __LINE__ , string func = __FUNCTION__ , A ...)(lazy A args) 370 { 371 enum LogLevel level = LogLevel.LOG_INFO; 372 write(level, toFormat(func , logFormat(args) , file , line , level, _layoutHandler)); 373 } 374 375 void infof(string file = __FILE__ , size_t line = __LINE__ , string func = __FUNCTION__ , A ...)(lazy A args) 376 { 377 enum LogLevel level = LogLevel.LOG_INFO; 378 write(level , toFormat(func , logFormatf(args) , file , line , level, _layoutHandler)); 379 } 380 381 void warning(string file = __FILE__ , size_t line = __LINE__ , string func = __FUNCTION__ , A ...)(lazy A args) 382 { 383 enum LogLevel level = LogLevel.LOG_WARNING; 384 write(level, toFormat(func , logFormat(args) , file , line , level, _layoutHandler)); 385 } 386 387 void warningf(string file = __FILE__ , size_t line = __LINE__ , string func = __FUNCTION__ , A ...)(lazy A args) 388 { 389 enum LogLevel level = LogLevel.LOG_WARNING; 390 write(level , toFormat(func , logFormatf(args) , file , line , level, _layoutHandler)); 391 } 392 393 void error(string file = __FILE__ , size_t line = __LINE__ , string func = __FUNCTION__ , A ...)(lazy A args) 394 { 395 enum LogLevel level = LogLevel.LOG_ERROR; 396 write(level, toFormat(func , logFormat(args) , file , line , level, _layoutHandler)); 397 } 398 399 void errorf(string file = __FILE__ , size_t line = __LINE__ , string func = __FUNCTION__ , A ...)(lazy A args) 400 { 401 enum LogLevel level = LogLevel.LOG_ERROR; 402 write(level , toFormat(func , logFormatf(args) , file , line , level, _layoutHandler)); 403 } 404 405 void critical(string file = __FILE__ , size_t line = __LINE__ , string func = __FUNCTION__ , A ...)(lazy A args) 406 { 407 enum LogLevel level = LogLevel.LOG_FATAL; 408 write(level, toFormat(func , logFormat(args) , file , line , level, _layoutHandler)); 409 } 410 411 void criticalf(string file = __FILE__ , size_t line = __LINE__ , string func = __FUNCTION__ , A ...)(lazy A args) 412 { 413 enum LogLevel level = LogLevel.LOG_FATAL; 414 write(level , toFormat(func , logFormatf(args) , file , line , level, _layoutHandler)); 415 } 416 417 void write(LogLevel level, string msg) 418 { 419 if (level >= _conf.level) 420 { 421 //#1 console 422 //check if enableConsole or appender == AppenderConsole 423 424 if (_conf.fileName == "" || !_conf.disableConsole) 425 { 426 writeFormatColor(level, msg); 427 } 428 429 //#2 file 430 if (_conf.fileName != "") 431 { 432 send(_tid, msg); 433 } 434 } 435 } 436 437 438 439 protected: 440 441 static void worker(immutable void* ptr) 442 { 443 import std.stdio; 444 Logger logger = cast(Logger) ptr; 445 while (logger !is null && logger.isRunning()) 446 { 447 receive((string msg) { 448 logger.saveMsg(msg); 449 }, (OwnerTerminated e) { 450 version(HUNT_DEBUG_MORE) { 451 logger.saveMsg("Logger OwnerTerminated"); 452 } 453 }, (Variant any) { 454 logger.saveMsg("Unknown data type"); 455 }); 456 } 457 } 458 459 void saveMsg(string msg) 460 { 461 try 462 { 463 464 if (!_file.name.exists) 465 { 466 _file = File(_rollover.activeFilePath, "w"); 467 } 468 else if (_rollover.roll(msg)) 469 { 470 _file.detach(); 471 _rollover.carry(); 472 _file = File(_rollover.activeFilePath, "w"); 473 } 474 else if (!_file.isOpen()) 475 { 476 _file.open("a"); 477 } 478 _file.writeln(msg); 479 _file.flush(); 480 481 } 482 catch (Throwable e) 483 { 484 writeln(e.toString()); 485 } 486 487 } 488 489 static void createPath(string fileFullName) 490 { 491 import std.path : dirName; 492 import std.file : mkdirRecurse; 493 import std.file : exists; 494 495 string dir = dirName(fileFullName); 496 if (!exists(dir)) 497 mkdirRecurse(dir); 498 } 499 500 static string toString(LogLevel level) 501 { 502 string l; 503 final switch (level) with (LogLevel) 504 { 505 case LOG_DEBUG: 506 l = "debug"; 507 break; 508 case LOG_INFO: 509 l = "info"; 510 break; 511 case LOG_WARNING: 512 l = "warning"; 513 break; 514 case LOG_ERROR: 515 l = "error"; 516 break; 517 case LOG_FATAL: 518 l = "fatal"; 519 break; 520 case LOG_Off: 521 l = "off"; 522 break; 523 } 524 return l; 525 } 526 527 static string logFormatf(A...)(A args) 528 { 529 auto strings = appender!string(); 530 formattedWrite(strings, args); 531 return strings.data; 532 } 533 534 static string logFormat(A...)(A args) 535 { 536 auto w = appender!string(); 537 foreach (arg; args) 538 { 539 alias A = typeof(arg); 540 static if (isAggregateType!A || is(A == enum)) 541 { 542 import std.format : formattedWrite; 543 544 formattedWrite(w, "%s", arg); 545 } 546 else static if (isSomeString!A) 547 { 548 put(w, arg); 549 } 550 else static if (isIntegral!A) 551 { 552 import std.conv : toTextRange; 553 554 toTextRange(arg, w); 555 } 556 else static if (isBoolean!A) 557 { 558 put(w, arg ? "true" : "false"); 559 } 560 else static if (isSomeChar!A) 561 { 562 put(w, arg); 563 } 564 else 565 { 566 import std.format : formattedWrite; 567 568 // Most general case 569 formattedWrite(w, "%s", arg); 570 } 571 } 572 return w.data; 573 } 574 575 static string toFormat(string func, string msg, string file, size_t line, 576 LogLevel level, LogLayoutHandler handler= null) 577 { 578 import hunt.util.DateTime; 579 string time_prior = date("Y-m-d H:i:s"); 580 581 string tid = to!string(getTid()); 582 583 string[] funcs = func.split("."); 584 string myFunc; 585 if (funcs.length > 0) 586 myFunc = funcs[$ - 1]; 587 else 588 myFunc = func; 589 590 // LogLayoutHandler handler = layoutHandler(); 591 if(handler !is null) { 592 return handler(time_prior, tid, toString(level), myFunc, msg, file, line); 593 } else { 594 return time_prior ~ " (" ~ tid ~ ") [" ~ toString( 595 level) ~ "] " ~ myFunc ~ " - " ~ msg ~ " - " ~ file ~ ":" ~ to!string(line); 596 } 597 } 598 599 protected: 600 601 LogConf _conf; 602 Tid _tid; 603 File _file; 604 SizeBaseRollover _rollover; 605 version (Posix) 606 { 607 enum PRINT_COLOR_NONE = "\033[m"; 608 enum PRINT_COLOR_RED = "\033[0;32;31m"; 609 enum PRINT_COLOR_GREEN = "\033[0;32;32m"; 610 enum PRINT_COLOR_YELLOW = "\033[1;33m"; 611 } 612 613 static void writeFormatColor(LogLevel level, string msg) 614 { 615 version (Posix) 616 { 617 string prior_color; 618 switch (level) with (LogLevel) 619 { 620 case LOG_ERROR: 621 case LOG_FATAL: 622 prior_color = PRINT_COLOR_RED; 623 break; 624 case LOG_WARNING: 625 prior_color = PRINT_COLOR_YELLOW; 626 break; 627 case LOG_INFO: 628 prior_color = PRINT_COLOR_GREEN; 629 break; 630 default: 631 prior_color = string.init; 632 } 633 634 writeln(prior_color ~ msg ~ PRINT_COLOR_NONE); 635 } 636 else version (Windows) 637 { 638 import std.windows.charset; 639 import core.stdc.stdio; 640 641 enum defaultColor = FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE; 642 643 ushort color; 644 switch (level) with (LogLevel) 645 { 646 case LOG_ERROR: 647 case LOG_FATAL: 648 color = FOREGROUND_RED; 649 break; 650 case LOG_WARNING: 651 color = FOREGROUND_GREEN | FOREGROUND_RED; 652 break; 653 case LOG_INFO: 654 color = FOREGROUND_GREEN; 655 break; 656 default: 657 color = defaultColor; 658 } 659 660 SetConsoleTextAttribute(g_hout, color); 661 printf("%s\n", toMBSz(msg)); 662 if(color != defaultColor) 663 SetConsoleTextAttribute(g_hout, defaultColor); 664 } 665 } 666 } 667 668 enum LogLevel 669 { 670 LOG_DEBUG = 0, 671 LOG_INFO = 1, 672 LOG_WARNING = 2, 673 LOG_ERROR = 3, 674 LOG_FATAL = 4, 675 LOG_Off = 5 676 } 677 678 struct LogConf 679 { 680 LogLevel level; // 0 debug 1 info 2 warning 3 error 4 fatal 681 bool disableConsole; 682 string fileName = ""; 683 string maxSize = "2MB"; 684 uint maxNum = 5; 685 } 686 687 void logLoadConf(LogConf conf) 688 { 689 g_logger = new Logger(conf); 690 } 691 692 void setLogLayout(LogLayoutHandler handler) { 693 _layoutHandler = handler; 694 } 695 696 mixin(code!("logDebug", LogLevel.LOG_DEBUG)); 697 mixin(code!("logDebugf", LogLevel.LOG_DEBUG, true)); 698 mixin(code!("logInfo", LogLevel.LOG_INFO)); 699 mixin(code!("logInfof", LogLevel.LOG_INFO, true)); 700 mixin(code!("logWarning", LogLevel.LOG_WARNING)); 701 mixin(code!("logWarningf", LogLevel.LOG_WARNING, true)); 702 mixin(code!("logError", LogLevel.LOG_ERROR)); 703 mixin(code!("logErrorf", LogLevel.LOG_ERROR, true)); 704 mixin(code!("logFatal", LogLevel.LOG_FATAL)); 705 mixin(code!("logFatalf", LogLevel.LOG_FATAL, true)); 706 707 alias trace = logDebug; 708 alias tracef = logDebugf; 709 alias info = logInfo; 710 alias infof = logInfof; 711 alias warning = logWarning; 712 alias warningf = logWarningf; 713 alias error = logError; 714 alias errorf = logErrorf; 715 alias critical = logFatal; 716 alias criticalf = logFatalf; 717 718 719