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 }