1 /// Service for handling both text logs and variable logging 2 module tagion.services.logger; 3 4 @safe: 5 6 import std.array; 7 import std.conv : to; 8 import std.datetime.systime : Clock; 9 import std.format; 10 import std.stdio; 11 import std.string; 12 import tagion.actor; 13 import tagion.hibon.Document : Document; 14 import tagion.hibon.HiBONRecord; 15 import tagion.logger.LogRecords; 16 import tagion.logger.Logger; 17 import tagion.utils.Term; 18 19 private { 20 enum TIMESTAMP_WIDTH = 10; 21 enum LOG_LEVEL_MAX_WIDTH = 5; 22 enum LOG_FORMAT = "%-" ~ TIMESTAMP_WIDTH.to!string ~ "s | %s%-" ~ LOG_LEVEL_MAX_WIDTH.to!string ~ "s%s | %s: %s"; 23 } 24 25 enum LogType { 26 Console, // Enables colored output 27 File, 28 } 29 30 struct LoggerServiceOptions { 31 LogType log_type = LogType.Console; 32 string file; // Default is stdout 33 } 34 35 /** 36 * LoggerTask 37 * Struct represents LoggerService which handles logs and provides passing them to LogSubscriptionService 38 */ 39 struct LoggerService { 40 41 immutable(LoggerServiceOptions) options; 42 43 const(string) formatLog(LogLevel level, string task_name, string text) { 44 const _format(string color = string.init) { 45 const _RESET = (color is string.init) ? "" : RESET; 46 final switch (options.log_type) { 47 case LogType.Console: 48 return format(LOG_FORMAT, Clock.currTime().toTimeSpec.tv_sec, color, level, _RESET, task_name, text); 49 case LogType.File: 50 return format(LOG_FORMAT, Clock.currTime().toTimeSpec.tv_sec, level, task_name, text); 51 } 52 } 53 54 switch (level) with (LogLevel) { 55 case TRACE: 56 return _format(WHITE); 57 case WARN: 58 return _format(YELLOW); 59 case ERROR: 60 return _format(RED); 61 case FATAL: 62 return _format(BOLD ~ RED); 63 default: 64 return _format(); 65 } 66 } 67 68 void task() { 69 File file; 70 71 /** Task method that receives logs from Logger and sends them to console, file and LogSubscriptionService 72 * @param info - log info about passed log 73 * @param doc - log itself, that can be either TextLog or some HiBONRecord variable 74 */ 75 void receiveLogs(immutable(LogInfo) info, immutable(Document) doc) { 76 enum _msg = GetLabel!(TextLog.message).name; 77 if (info.isTextLog && doc.hasMember(_msg)) { 78 const output = formatLog(info.level, info.task_name, doc[_msg].get!string); 79 if (!file.error) { 80 file.writeln(output); 81 } 82 } 83 } 84 85 if (options.file !is string.init) { 86 file = File(options.file, "w"); 87 } 88 else { 89 file = (() @trusted => stdout())(); 90 } 91 92 run(&receiveLogs); 93 } 94 }