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