1 module logger.extendedfilelogger; 2 3 import std.experimental.logger; 4 import std.stdio; 5 6 import std.concurrency : Tid; 7 import std.datetime.systime : SysTime; 8 import std.range : isOutputRange, isInputRange; 9 10 private enum PatternToken = '%'; 11 12 /// A simple pattern - 020-06-08T17:50:25.673 info : 13 enum string simplePattern = "%d %-8p: "; 14 15 /// Interface of all log patterns 16 interface ILogPattern 17 { 18 /** 19 * Writes on a outputRange of chars, the logging pattern 20 * Params: 21 * outputFile = output File where where tow rite the logging request. 22 * file = file name where the logging request was issued. 23 * line = line number from where the logging request was issued. 24 * funcName = the function or method name where the logging request was issued. 25 * prettyFuncName = the function or method prettyfied name where the logging request was issued. 26 * moduleName = the module name where the logging request was issued. 27 * logLevel = the actual log level of the accepter logging request. 28 * threadId = the thread id where the logging request was issued. 29 * timestamp = the timestamp of the logging request. 30 * timestamp = the timestamp of when the logger was created. 31 */ 32 void applyPattern (File outputFile, string file, int line, string funcName, 33 string prettyFuncName, string moduleName, LogLevel logLevel, Tid threadId, SysTime timestamp, 34 SysTime startTimeStamp ) @trusted; 35 36 } 37 38 /// Simple loging pattern that outputs the same pattern that classic FileLogger 39 class SimpleLogPattern : ILogPattern 40 { 41 /** 42 * Writes on a outputRange of chars, the logging pattern 43 * Params: 44 * outputFile = output File where where tow rite the logging request. 45 * file = file name where the logging request was issued. 46 * line = line number from where the logging request was issued. 47 * funcName = the function or method name where the logging request was issued. 48 * prettyFuncName = the function or method prettyfied name where the logging request was issued. 49 * moduleName = the module name where the logging request was issued. 50 * logLevel = the actual log level of the accepter logging request. 51 * threadId = the thread id where the logging request was issued. 52 * timestamp = the timestamp of the logging request. 53 * timestamp = the timestamp of when the logger was created. 54 */ 55 void applyPattern (File outputFile, string file, int line, string funcName, 56 string prettyFuncName, string moduleName, LogLevel logLevel, Tid threadId, SysTime timestamp, 57 SysTime startTimeStamp ) @trusted 58 { 59 auto lt = outputFile.lockingTextWriter(); 60 import std.experimental.logger.core : systimeToISOString; 61 systimeToISOString(lt, timestamp); 62 63 import std.conv : to; 64 import std.format : formattedWrite; 65 formattedWrite(lt, " [%s] %s:%u:%s ", logLevel.to!string, file, line, funcName); 66 } 67 } 68 69 /** 70 * Configurable logging pattern that parses a string pattern 71 * 72 * The pattern it's based of log4j pattern layout format: 73 * | Conversion Character | Effect | 74 * | -------------------- | -----: | 75 * | m | Module name | 76 * | d | Used to output the date of the logging event. Actually only outputs on ISO format | 77 * | F | Used to output the file name where the logging request was issued. | 78 * | L | Used to output the line number from where the logging request was issued. | 79 * | M | Used to output the function or method name where the logging request was issued. | 80 * | p | Used to output the priority of the logging event. | 81 * | r | Used to output number of milliseconds elapsed from the construction of this logger until the logging event. | 82 * | t | Used to output the thread that generated the logging event. | 83 */ 84 class ConfigurableLogPattern : ILogPattern 85 { 86 private string logPattern; 87 88 /** 89 * Builds a configurable log pattern 90 * Params: 91 * logPattern = String pattern to apply. By default uses `simplePattern` 92 */ 93 this(string logPattern = simplePattern) @safe 94 { 95 this.logPattern = logPattern; 96 } 97 98 /** 99 * Writes on a outputRange of chars, the logging pattern 100 * Params: 101 * outputFile = output File where where tow rite the logging request. 102 * file = file name where the logging request was issued. 103 * line = line number from where the logging request was issued. 104 * funcName = the function or method name where the logging request was issued. 105 * prettyFuncName = the function or method prettyfied name where the logging request was issued. 106 * moduleName = the module name where the logging request was issued. 107 * logLevel = the actual log level of the accepter logging request. 108 * threadId = the thread id where the logging request was issued. 109 * timestamp = the timestamp of the logging request. 110 * timestamp = the timestamp of when the logger was created. 111 */ 112 void applyPattern (File outputFile, string file, int line, string funcName, 113 string prettyFuncName, string moduleName, LogLevel logLevel, Tid threadId, SysTime timestamp, 114 SysTime startTimeStamp ) @trusted 115 { 116 auto lt = outputFile.lockingTextWriter(); 117 118 import std.uni : byCodePoint; 119 this.parsePattern(lt, this.logPattern.byCodePoint, file, line, funcName, prettyFuncName, moduleName, 120 logLevel, threadId, timestamp, startTimeStamp); 121 } 122 123 private void parsePattern(OutputRange, InputRange)(OutputRange outputRange, InputRange patternRange, string file, 124 int line, string funcName, string prettyFuncName, string moduleName, LogLevel logLevel, Tid threadId, 125 SysTime timestamp, SysTime startTimeStamp ) @trusted 126 if (isOutputRange!(OutputRange, char) && isInputRange!(InputRange)) 127 { 128 // Outputs anything before finding the '%' format special character 129 while(!patternRange.empty && patternRange.front != PatternToken) { 130 outputRange.put(patternRange.front); 131 patternRange.popFront; 132 } 133 if (patternRange.empty) { 134 return; 135 } 136 patternRange.popFront; // And consumes the '%' 137 138 auto modifier = this.formatModifier!(InputRange)(patternRange); 139 140 // Value stores the output string 141 import std.conv : to; 142 switch (patternRange.front) { 143 case '%': // The sequence %% outputs a single percent sign. 144 outputRange.put('%'); 145 break; 146 147 case 'm': // Module name 148 outputRange.writeWithModifier(moduleName, modifier[0], modifier[1]); 149 break; 150 151 case 'd': // Date on ISO format 152 outputRange.systimeToISOString(timestamp); 153 break; 154 155 case 'p': // priority of log level 156 outputRange.put(this.logLevelPrefix(logLevel)); 157 outputRange.writeWithModifier(logLevel.to!string, modifier[0], modifier[1]); 158 outputRange.put(this.logLevelPostfix(logLevel)); 159 break; 160 161 case 'F': // Filename 162 outputRange.writeWithModifier(file, modifier[0], modifier[1]); 163 break; 164 165 case 'L': // Lines 166 outputRange.writeWithModifier(line.to!string, modifier[0], modifier[1]); 167 break; 168 169 case 'M': // Function/Method where the logging was issued 170 outputRange.writeWithModifier(funcName, modifier[0], modifier[1]); 171 break; 172 173 case 't': // name of the thread that generated the logging event. 174 outputRange.writeWithModifier(threadId.to!string, modifier[0], modifier[1]); 175 break; 176 177 case 'r': // number of milliseconds elapsed from the construction of this logger until the logging event. 178 import std.datetime : Clock; 179 const delta = (Clock.currTime() - startTimeStamp).total!"msecs"; 180 outputRange.writeWithModifier(delta.to!string, modifier[0], modifier[1]); 181 break; 182 183 default: // unsuported formats are simply ignored 184 185 } 186 patternRange.popFront; 187 188 // Iterate for the next pattern token 189 if (!patternRange.empty) { 190 this.parsePattern(outputRange, patternRange, file, line, funcName, prettyFuncName, moduleName, 191 logLevel, threadId, timestamp, startTimeStamp); 192 } 193 } 194 195 private auto formatModifier(InputRange)(ref InputRange patternRange) @safe pure 196 if (isInputRange!(InputRange)) 197 { 198 import std.ascii : isDigit; 199 import std.typecons : Tuple, tuple; 200 Tuple!(int, size_t) modifier = tuple(0, size_t.max); 201 202 // Get the padding modifier 203 if (patternRange.front == '+' || patternRange.front == '-' || patternRange.front.isDigit) { 204 import std.conv : parse, ConvException; 205 try { 206 modifier[0] = patternRange.parse!int; 207 } catch (ConvException ex) { 208 // We ignore it silently 209 } 210 } 211 // Get the truncate modifier 212 if (patternRange.front == '.') { 213 patternRange.popFront; // Consume the '.' separator 214 if (patternRange.front.isDigit) { 215 import std.conv : parse, ConvException; 216 try { 217 modifier[1] = patternRange.parse!size_t; 218 } catch (ConvException ex) { 219 // Silently we ignore it 220 } 221 } 222 } 223 return modifier; 224 } 225 226 /// string preceding loglevel. Usefull to colorize it, with ANSI 227 protected string logLevelPrefix(const LogLevel logLevel) { 228 return ""; 229 } 230 231 /// string following loglevel. Usefull to colorize it, with ANSI 232 protected string logLevelPostfix(const LogLevel logLevel) { 233 return ""; 234 } 235 } 236 237 import std.range : isOutputRange; 238 239 // writes to the output the value with the padding and truncation 240 private void writeWithModifier(OutputRange)(OutputRange outputRange, string value, int padding, size_t truncate) @safe 241 if (isOutputRange!(OutputRange, char)) 242 { 243 if (padding > 0) { 244 // left padd 245 padding -= value.length; 246 while(padding > 0) { 247 outputRange.put(' '); 248 padding--; 249 } 250 } 251 import std.format : formattedWrite; 252 if (truncate != size_t.max && truncate < value.length) { 253 outputRange.formattedWrite("%s", value[0..truncate]); 254 } else { 255 outputRange.formattedWrite("%s", value); 256 } 257 if (padding < 0) { 258 // right padd 259 padding += value.length; 260 while(padding < 0) { 261 outputRange.put(' '); 262 padding++; 263 } 264 } 265 } 266 267 /** 268 * Extends FileLogger to support configurable pattern 269 */ 270 class ExtendedFileLogger : FileLogger 271 { 272 import std.concurrency : Tid; 273 import std.datetime.systime : SysTime; 274 import std.format : formattedWrite; 275 276 /** A constructor for the `ExtendedFileLogger` Logger. 277 Params: 278 fn = The filename of the output file of the `ExtendedFileLogger`. If that 279 file can not be opened for writting an exception will be thrown. 280 lv = The `LogLevel` for the `ExtendedFileLogger`. By default the 281 logPattern = An implementation of the `ILogPattern`. By default uses SimpleLogPattern. 282 Example: 283 ------------- 284 auto l1 = new ExtendedFileLogger("logFile"); 285 auto l2 = new ExtendedFileLogger("logFile", LogLevel.fatal); 286 auto l3 = new ExtendedFileLogger("logFile", LogLevel.fatal, new ConfigurableLogPattern("%m %-5p %d - ")); 287 ------------- 288 */ 289 this(const string fn, const LogLevel lv = LogLevel.all, ILogPattern logPattern = new SimpleLogPattern()) @safe 290 { 291 this(fn, lv, logPattern, CreateFolder.yes); 292 } 293 294 /** A constructor for the `ExtendedFileLogger` Logger that takes a reference to a `File`. 295 The `File` passed must be open for all the log call to the `ExtendedFileLogger`. If the `File` 296 gets closed, using the `ExtendedFileLogger` for logging will result in undefined behaviour. 297 Params: 298 fn = The file used for logging. 299 lv = The `LogLevel` for the `ExtendedFileLogger`. By default the `LogLevel` for 300 `ExtendedFileLogger` is `LogLevel.all`. 301 createFileNameFolder = if yes and fn contains a folder name, this folder will be created. 302 logPattern = An implementation of the `ILogPattern`. 303 Example: 304 ------------- 305 auto file = File("logFile.log", "w"); 306 auto l1 = new ExtendedFileLogger(file); 307 auto l2 = new ExtendedFileLogger(file, LogLevel.fatal); 308 ------------- 309 */ 310 this(const string fn, const LogLevel lv, ILogPattern logPattern, CreateFolder createFileNameFolder) @safe 311 { 312 import std.datetime : Clock; 313 super(fn, lv, createFileNameFolder); 314 this.logPattern = logPattern; 315 this.startTimeStamp = Clock.currTime(); 316 } 317 318 /** A constructor for the `ExtendedFileLogger` Logger that takes a reference to a `File`. 319 The `File` passed must be open for all the log call to the `ExtendedFileLogger`. If 320 the `File` gets closed, using the `ExtendedFileLogger` for logging will result in 321 undefined behaviour. 322 Params: 323 file = The file used for logging. 324 lv = The `LogLevel` for the `ExtendedFileLogger`. By default the `LogLevel` for 325 `ExtendedFileLogger` is `LogLevel.all`. 326 logPattern = An implementation of the `ILogPattern`. By default uses SimpleLogPattern. 327 Example: 328 ------------- 329 auto file = File("logFile.log", "w"); 330 auto l1 = new ExtendedFileLogger(file); 331 auto l2 = new ExtendedFileLogger(file, LogLevel.fatal); 332 ------------- 333 */ 334 this(File file, const LogLevel lv = LogLevel.all, ILogPattern logPattern = new SimpleLogPattern()) @safe 335 { 336 import std.datetime : Clock; 337 super(file, lv); 338 this.logPattern = logPattern; 339 this.startTimeStamp = Clock.currTime(); 340 } 341 342 /// This method overrides the base class method in order to call the log pattern 343 override protected void beginLogMsg(string file, int line, string funcName, 344 string prettyFuncName, string moduleName, LogLevel logLevel, 345 Tid threadId, SysTime timestamp, Logger logger) 346 @safe 347 { 348 import std.string : lastIndexOf; 349 ptrdiff_t fnIdx = file.lastIndexOf('/') + 1; 350 ptrdiff_t funIdx = funcName.lastIndexOf('.') + 1; 351 352 this.logPattern.applyPattern(this.file, file[fnIdx..$], line, funcName[funIdx..$], prettyFuncName, moduleName, 353 logLevel, threadId, timestamp, this.startTimeStamp); 354 } 355 356 protected ILogPattern logPattern; 357 protected SysTime startTimeStamp; 358 } 359