1 module des.log.output; 2 3 import std.stdio : stdout, stderr; 4 import std..string : toStringz; 5 import std.datetime; 6 import std.exception; 7 8 import des.log.base; 9 import des.log.rule; 10 11 /// output for logger 12 synchronized abstract class LogOutput 13 { 14 Rule rule; 15 private bool _enable = true; 16 17 @property 18 { 19 bool enable() const { return _enable; } 20 bool enable( bool v ) { _enable = v; return v; } 21 } 22 23 this() 24 { 25 rule = new shared Rule; 26 rule.setLevel( LogLevel.TRACE ); 27 } 28 29 /// 30 void opCall( in LogMessage lm ) 31 { 32 if( enable && rule.isAllowed(lm) ) 33 writeMessage( lm, formatLogMessage( lm ) ); 34 } 35 36 protected: 37 38 /// 39 void writeMessage( in LogMessage, string ); 40 41 /// by default call des.util.logsys.base.defaultFormatLogMessage 42 string formatLogMessage( in LogMessage lm ) const 43 out(ret){ assert( ret.length ); } 44 body { return defaultFormatLogMessage( lm ); } 45 } 46 47 /// 48 synchronized class NullLogOutput : LogOutput 49 { 50 protected: 51 override 52 { 53 /// empty 54 void writeMessage( in LogMessage, string ) {} 55 56 /// not call formating message 57 string formatLogMessage( in LogMessage lm ) const { return "null"; } 58 } 59 } 60 61 /// output to file 62 synchronized class FileLogOutput : LogOutput 63 { 64 import core.stdc.stdio; 65 import std.exception; 66 FILE* file; 67 68 /// 69 this( string fname ) 70 { 71 file = enforce( fopen( fname.toStringz, "a\0".ptr ), 72 "Cannot open file '" ~ fname ~ "' in append mode" ); 73 fprintf( file, "%s\n", firstLine().toStringz ); 74 } 75 76 ~this() { fclose( file ); } 77 78 protected: 79 80 /// 81 override void writeMessage( in LogMessage, string msg ) { fprintf( file, "%s\n", msg.toStringz ); } 82 83 /++ call from ctor and past to file first line with datetime 84 Returns: 85 format( "%02d.%02d.%4d %02d:%02d:%02d", dt.day, dt.month, dt.year, dt.hour, dt.minute, dt.second ); 86 +/ 87 string firstLine() const 88 { 89 import core.runtime; 90 import std.datetime; 91 import std.array; 92 93 auto dt = Clock.currTime; 94 return format( "%02d.%02d.%4d %02d:%02d:%02d %s", 95 dt.day, dt.month, dt.year, dt.hour, dt.minute, dt.second, 96 Runtime.args.join(" ") ); 97 } 98 } 99 100 /// 101 synchronized class ConsoleLogOutput : LogOutput 102 { 103 /// log messages with level > ERROR puts to stdout, and stderr otherwise 104 protected override void writeMessage( in LogMessage lm, string str ) 105 { 106 if( lm.level > LogMessage.Level.ERROR ) 107 stdout.writeln( str ); 108 else 109 stderr.writeln( str ); 110 } 111 } 112 113 /// colorise console output with escape seqence 114 synchronized class ColorConsoleLogOutput : ConsoleLogOutput 115 { 116 protected: 117 118 import des.log.consolecolor; 119 120 /// formatting with colors 121 override string formatLogMessage( in LogMessage lm ) const 122 { 123 auto color = chooseColors( lm ); 124 return format( "[%6$s%1$016.9f%5$s][%7$s%2$5s%5$s][%8$s%3$s%5$s]: %9$s%4$s%5$s", 125 lm.ts / 1e9f, lm.level, lm.emitter, lm.message, 126 cast(string)CEColor.OFF, color[0], color[1], color[2], color[3] ); 127 } 128 129 /// returns 4 colors for timestamp, log level, emitter name, message text 130 string[4] chooseColors( in LogMessage lm ) const 131 { 132 string clr; 133 134 final switch( lm.level ) 135 { 136 //case LogMessage.Level.FATAL: clr = CEColor.FG_BLACK ~ CEColor.BG_RED; break; 137 case LogMessage.Level.FATAL: clr = CEColor.FG_BI_RED; break; 138 case LogMessage.Level.ERROR: clr = CEColor.FG_RED; break; 139 case LogMessage.Level.WARN: clr = CEColor.FG_PURPLE; break; 140 case LogMessage.Level.INFO: clr = CEColor.FG_CYAN; break; 141 case LogMessage.Level.DEBUG: clr = CEColor.FG_YELLOW; break; 142 case LogMessage.Level.TRACE: break; 143 } 144 145 return [clr,clr,clr,clr]; 146 } 147 } 148 149 /// main logging output center 150 synchronized class OutputHandler 151 { 152 package: 153 LogOutput[string] list; /// 154 155 /// 156 this() 157 { 158 version(linux) 159 list[console] = new shared ColorConsoleLogOutput; 160 else 161 list[console] = new shared ConsoleLogOutput; 162 } 163 164 /// call from Logger.writeLog by default 165 void writeMessage( string output_name, in LogMessage lm ) 166 { 167 if( output_name.length ) 168 { 169 if( output_name in list ) 170 list[output_name](lm); 171 } 172 else foreach( name, lo; list ) lo(lm); 173 } 174 175 public: 176 177 enum console = "console"; /// 178 179 /// get output 180 shared(LogOutput) opIndex( string name ) 181 { 182 enforce( name in list, LogException.fmt( "no output named '%s'", name ) ); 183 return list[name]; 184 } 185 186 /// set output 187 shared(LogOutput) opIndexAssign( shared LogOutput output, string name ) 188 in{ assert( output !is null ); } body 189 { return list[name] = output; } 190 191 string[] names() @property { return list.keys; } 192 193 /// 194 void remove( string name ) 195 { 196 if( name == console ) 197 throw new LogException( "can not unregister '" ~ name ~ "' log output" ); 198 list.remove( name ); 199 } 200 }