1 /// \file LogRecords.d
2 
3 module tagion.logger.LogRecords;
4 
5 import tagion.hibon.HiBONRecord;
6 import tagion.logger.Logger : LogLevel, Topic;
7 
8 /** @brief Definitions of auxiliary structs and types for working with logs
9  */
10 
11 /**
12  * \enum LogFiltersAction
13  * Defines type of action for list of log filters
14  */
15 enum LogFiltersAction {
16     ADD,
17     REMOVE
18 }
19 
20 /**
21  * \struct LogFilter
22  * Struct represents filter for receiving logs
23  */
24 @safe struct LogFilter {
25     /** Task name to listen */
26     @label("task") string task_name;
27     /** Log level. Applied for text logs */
28     @label("level") LogLevel level;
29     /** Name of symbol to listen. Optional field */
30     @label("symbol") string symbol_name;
31 
32     mixin HiBONRecord!(q{
33         /** Ctor for text logs
34           *     @param task_name - task name
35           *     @param level - log level
36           */
37         this(string task_name, LogLevel level) nothrow {
38             this.task_name = task_name;
39             this.level = level;
40             this.symbol_name = "";
41         }
42 
43         /** Ctor for symbol logs
44           *     @param task_name - task name
45           *     @param symbol_name - symbol name
46           */
47         this(string task_name, string symbol_name) nothrow {
48             this.task_name = task_name;
49             this.level = LogLevel.ALL;
50             this.symbol_name = symbol_name;
51         }
52 
53         /** Ctor from \link LogInfo
54           *     @param info - log info for creating filter
55           */
56         this(in LogInfo info) nothrow
57         {
58             if (info.isTextLog)
59             {
60                 this(info.task_name, info.level);
61             }
62             else
63             {
64                 this(info.task_name, info.symbol_name);
65             }
66         }
67     });
68 
69     /** Method that check if given filter matches current filter
70       *     @param filter - given filter to check for matching
71       *     @return result of check
72       */
73     @nogc bool match(in LogFilter filter) pure const nothrow {
74         return this.task_name == filter.task_name && this.level & filter.level && this.symbol_name == filter
75             .symbol_name;
76     }
77 
78     /** Method that check if given log info matches current filter
79       *     @param info - given log info to check for matching
80       *     @return result of check
81       */
82     @nogc bool match(in LogInfo info) pure const nothrow {
83         if (this.isTextLog != info.isTextLog) {
84             return false;
85         }
86 
87         bool result;
88         if (info.isTextLog) {
89             result = this.task_name == info.task_name && this.level & info.level;
90         }
91         else {
92             result = this.task_name == info.task_name && this.symbol_name == info.symbol_name;
93         }
94         return result;
95     }
96 
97     /** Method that check if current filter for text log
98       *     @return result of check
99       */
100     @nogc bool isTextLog() pure const nothrow {
101         import std.range;
102 
103         return symbol_name.empty;
104     }
105 }
106 
107 /**
108  * \struct LogFilterArray
109  * Struct stores array of \link LogFilter
110  */
111 @safe struct LogFilterArray {
112     /** Array of filters */
113     immutable(LogFilter[]) array;
114 
115     /** Main ctor
116       *     @param filters - array of filters
117       */
118     this(immutable(LogFilter[]) filters) nothrow {
119         this.array = filters;
120     }
121 }
122 
123 /**
124  * \struct TextLog
125  * Struct for wrapping text log into \link HiBONRecord
126  */
127 @safe struct TextLog {
128     /** Text log message */
129     @label("msg") string message;
130 
131     mixin HiBONRecord!(q{
132         /** Main ctor
133          *     @param msg - text message
134          */
135         this(string msg) nothrow {
136             this.message = msg;
137         }
138     });
139 }
140 
141 /**
142  * \struct LogInfo
143  * Struct stores info about passing log
144  */
145 @safe struct LogInfo {
146     private {
147         /** Value that stores type of log */
148         const bool _is_text_log;
149     }
150 
151     /** Task name */
152     const string task_name;
153     /** Log level */
154     const LogLevel level;
155 
156     /** Ctor for text logs
157      *     @param task_name - task name
158      *     @param level - log level
159      */
160     this(string task_name, LogLevel level) nothrow pure {
161         this.task_name = task_name;
162         this.level = level;
163 
164         _is_text_log = true;
165     }
166 
167     const string topic_name;
168     /** Symbol name */
169     const string symbol_name;
170 
171     /** Ctor for symbol logs
172      *     @param task_name - task name
173      *     @param symbol_name - symbol name
174      */
175     this(Topic topic, string task_name, string symbol_name) nothrow pure {
176         this.task_name = task_name;
177         this.topic_name = topic.name;
178         this.symbol_name = symbol_name;
179 
180         _is_text_log = false;
181     }
182 
183     /** Method that return whether current filter is text log
184       *     @return result
185       */
186     @nogc bool isTextLog() pure const nothrow {
187         return _is_text_log;
188     }
189 }
190 
191 unittest {
192     enum task1 = "sometaskname";
193     enum task2 = "anothertaskname";
194     enum symbol1 = "some_symbol";
195     enum symbol2 = "another_symbol";
196 
197     /// LogFilter_match_symmetrical
198     {
199         auto f1 = LogFilter(task1, LogLevel.STDERR);
200         auto f2 = LogFilter(task1, LogLevel.ERROR);
201 
202         assert(f1.match(f2));
203         assert(f2.match(f1));
204     }
205 
206     /// LogFilter_match_text_logs
207     {
208         assert(LogFilter(task1, LogLevel.ERROR).match(LogFilter(task1, LogLevel.STDERR)));
209         assert(LogFilter(task1, LogLevel.ALL).match(LogFilter(task1, LogLevel.INFO)));
210         assert(LogFilter(task2, LogLevel.ERROR).match(LogFilter(task2, LogLevel.ERROR)));
211 
212         assert(!LogFilter(task1, LogLevel.STDERR).match(LogFilter(task1, LogLevel.INFO)));
213         assert(!LogFilter(task1, LogLevel.ERROR).match(LogFilter(task2, LogLevel.ERROR)));
214         assert(!LogFilter(task1, LogLevel.INFO).match(LogFilter("", LogLevel.INFO)));
215 
216         assert(!LogFilter(task1, LogLevel.NONE).match(LogFilter(task1, LogLevel.NONE)));
217     }
218 
219     /// LogFilter_match_text_log_info
220     {
221         assert(LogFilter(task1, LogLevel.STDERR).match(LogInfo(task1, LogLevel.ERROR)));
222         assert(LogFilter(task1, LogLevel.ALL).match(LogInfo(task1, LogLevel.INFO)));
223 
224         assert(!LogFilter(task1, LogLevel.STDERR).match(LogInfo(task1, LogLevel.INFO)));
225         assert(!LogFilter(task1, LogLevel.INFO).match(LogInfo(task2, LogLevel.INFO)));
226 
227         assert(!LogFilter(task1, LogLevel.NONE).match(LogInfo(task1, LogLevel.NONE)));
228     }
229 
230     /// LogFilter_match_symbol_log
231     {
232         assert(LogFilter(task1, symbol1).match(LogFilter(task1, symbol1)));
233         assert(LogFilter(task2, symbol2).match(LogFilter(task2, symbol2)));
234 
235         assert(!LogFilter(task1, symbol1).match(LogFilter(task1, LogLevel.ALL)));
236         assert(!LogFilter(task1, symbol1).match(LogFilter(task1, symbol2)));
237         assert(!LogFilter(task1, symbol1).match(LogFilter(task1, "")));
238     }
239 
240     /// LogFilter_match_symbol_log_info
241     {
242         assert(LogFilter(task1, symbol1).match(LogInfo(Topic(""), task1, symbol1)));
243 
244         assert(!LogFilter(task1, symbol1).match(LogInfo(Topic(""), task1, symbol2)));
245         assert(!LogFilter(task1, symbol1).match(LogInfo(task1, LogLevel.ALL)));
246     }
247 
248     /// LogFilter_isTextLog
249     {
250         assert(LogFilter(task1, LogLevel.ERROR).isTextLog);
251         assert(LogFilter(task1, LogLevel.NONE).isTextLog);
252         assert(LogFilter(task1, "").isTextLog);
253 
254         assert(!LogFilter(task1, symbol1).isTextLog);
255     }
256 
257     /// LogInfo_isTextLog
258     {
259         assert(LogInfo(task1, LogLevel.ERROR).isTextLog);
260         assert(LogInfo(task1, LogLevel.NONE).isTextLog);
261 
262         assert(!LogInfo(Topic(), task1, symbol1).isTextLog);
263         assert(!LogInfo(Topic(), task1, "").isTextLog);
264     }
265 }