1 module des.util.logsys.output; 2 3 import std.stdio : stdout, stderr; 4 import std..string : toStringz; 5 import std.datetime; 6 7 import des.util.logsys.base; 8 9 /// output for logger 10 synchronized abstract class LogOutput 11 { 12 /// 13 final void opCall( in LogMessage lm ) { write( lm, formatLogMessage( lm ) ); } 14 15 protected: 16 17 /// 18 void write( in LogMessage, string ); 19 20 /// by default call des.util.logsys.base.defaultFormatLogMessage 21 string formatLogMessage( in LogMessage lm ) const 22 out(ret){ assert( ret.length ); } 23 body { return defaultFormatLogMessage( lm ); } 24 } 25 26 /// 27 synchronized class NullLogOutput : LogOutput 28 { 29 protected: 30 override 31 { 32 /// empty 33 void write( in LogMessage, string ){} 34 35 /// not call formating message 36 string formatLogMessage( in LogMessage lm ) const { return "null"; } 37 } 38 } 39 40 /// output to file 41 synchronized class FileLogOutput : LogOutput 42 { 43 import core.stdc.stdio; 44 import std.datetime; 45 FILE* file; /// 46 47 /// 48 this( string filename ) 49 { 50 file = fopen( filename.toStringz, "a" ); 51 if( file is null ) 52 throw new LogSysException( "unable to open file '" ~ filename ~ "' at write mode" ); 53 fprintf( file, "%s\n", firstLine().toStringz ); 54 } 55 56 ~this() { if( file ) fclose( file ); } 57 58 protected: 59 60 /// fprintf 61 override void write( in LogMessage, string msg ) 62 { fprintf( file, "%s\n", msg.toStringz ); } 63 64 /++ call from ctor and past to file first line with datetime 65 Returns: 66 format( "%02d.%02d.%4d %02d:%02d:%02d", dt.day, dt.month, dt.year, dt.hour, dt.minute, dt.second ); 67 +/ 68 string firstLine() const 69 { 70 auto dt = Clock.currTime; 71 return format( "%02d.%02d.%4d %02d:%02d:%02d", 72 dt.day, dt.month, dt.year, dt.hour, dt.minute, dt.second ); 73 } 74 } 75 76 /// 77 synchronized class ConsoleLogOutput : LogOutput 78 { 79 /// log messages with level > ERROR puts to stdout, and stderr otherwise 80 protected override void write( in LogMessage lm, string str ) 81 { 82 if( lm.level > LogMessage.Level.ERROR ) 83 stdout.writeln( str ); 84 else 85 stderr.writeln( str ); 86 } 87 } 88 89 /// colorise console output with escape seqence 90 synchronized class ColorConsoleLogOutput : ConsoleLogOutput 91 { 92 enum 93 { 94 COLOR_OFF = "\x1b[0m", /// reset color 95 96 // Regular Colors 97 FG_BLACK = "\x1b[0;30m", /// 98 FG_RED = "\x1b[0;31m", /// 99 FG_GREEN = "\x1b[0;32m", /// 100 FG_YELLOW = "\x1b[0;33m", /// 101 FG_BLUE = "\x1b[0;34m", /// 102 FG_PURPLE = "\x1b[0;35m", /// 103 FG_CYAN = "\x1b[0;36m", /// 104 FG_WHITE = "\x1b[0;37m", /// 105 106 // Bold 107 FG_B_BLACK = "\x1b[1;30m", /// 108 FG_B_RED = "\x1b[1;31m", /// 109 FG_B_GREEN = "\x1b[1;32m", /// 110 FG_B_YELLOW = "\x1b[1;33m", /// 111 FG_B_BLUE = "\x1b[1;34m", /// 112 FG_B_PURPLE = "\x1b[1;35m", /// 113 FG_B_CYAN = "\x1b[1;36m", /// 114 FG_B_WHITE = "\x1b[1;37m", /// 115 116 // Underline 117 FG_U_BLACK = "\x1b[4;30m", /// 118 FG_U_RED = "\x1b[4;31m", /// 119 FG_U_GREEN = "\x1b[4;32m", /// 120 FG_U_YELLOW = "\x1b[4;33m", /// 121 FG_U_BLUE = "\x1b[4;34m", /// 122 FG_U_PURPLE = "\x1b[4;35m", /// 123 FG_U_CYAN = "\x1b[4;36m", /// 124 FG_U_WHITE = "\x1b[4;37m", /// 125 126 // Background 127 BG_BLACK = "\x1b[40m", /// 128 BG_RED = "\x1b[41m", /// 129 BG_GREEN = "\x1b[42m", /// 130 BG_YELLOW = "\x1b[43m", /// 131 BG_BLUE = "\x1b[44m", /// 132 BG_PURPLE = "\x1b[45m", /// 133 BG_CYAN = "\x1b[46m", /// 134 BG_WHITE = "\x1b[47m", /// 135 136 // High Intensity 137 FG_I_BLACK = "\x1b[0;90m", /// 138 FG_I_RED = "\x1b[0;91m", /// 139 FG_I_GREEN = "\x1b[0;92m", /// 140 FG_I_YELLOW = "\x1b[0;93m", /// 141 FG_I_BLUE = "\x1b[0;94m", /// 142 FG_I_PURPLE = "\x1b[0;95m", /// 143 FG_I_CYAN = "\x1b[0;96m", /// 144 FG_I_WHITE = "\x1b[0;97m", /// 145 146 // Bold High Intensity 147 FG_BI_BLACK = "\x1b[1;90m", /// 148 FG_BI_RED = "\x1b[1;91m", /// 149 FG_BI_GREEN = "\x1b[1;92m", /// 150 FG_BI_YELLOW = "\x1b[1;93m", /// 151 FG_BI_BLUE = "\x1b[1;94m", /// 152 FG_BI_PURPLE = "\x1b[1;95m", /// 153 FG_BI_CYAN = "\x1b[1;96m", /// 154 FG_BI_WHITE = "\x1b[1;97m", /// 155 156 // High Intensity backgrounds 157 BG_I_BLACK = "\x1b[0;100m", /// 158 BG_I_RED = "\x1b[0;101m", /// 159 BG_I_GREEN = "\x1b[0;102m", /// 160 BG_I_YELLOW = "\x1b[0;103m", /// 161 BG_I_BLUE = "\x1b[0;104m", /// 162 BG_I_PURPLE = "\x1b[0;105m", /// 163 BG_I_CYAN = "\x1b[0;106m", /// 164 BG_I_WHITE = "\x1b[0;107m", /// 165 }; 166 167 protected: 168 169 /// formatting with colors 170 override string formatLogMessage( in LogMessage lm ) const 171 { 172 auto color = chooseColors( lm ); 173 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", 174 lm.ts / 1e9f, lm.level, lm.emitter, lm.message, 175 COLOR_OFF, color[0], color[1], color[2], color[3] ); 176 } 177 178 /// returns 4 colors for timestamp, log level, emitter name, message text 179 string[4] chooseColors( in LogMessage lm ) const 180 { 181 string ts, type, emitter, msg; 182 183 final switch( lm.level ) 184 { 185 case LogMessage.Level.FATAL: type = FG_BLACK ~ BG_RED; break; 186 case LogMessage.Level.ERROR: type = FG_RED; break; 187 case LogMessage.Level.WARN: type = FG_PURPLE; break; 188 case LogMessage.Level.INFO: type = FG_CYAN; break; 189 case LogMessage.Level.DEBUG: type = FG_YELLOW; break; 190 case LogMessage.Level.TRACE: break; 191 } 192 193 ts = type; 194 emitter = type; 195 msg = type; 196 197 return [ts, type, emitter, msg]; 198 } 199 } 200 201 /// main logging output center 202 synchronized final class LogOutputHandler 203 { 204 package: 205 LogOutput[string] list; /// 206 bool[string] enabled; /// any of log output can be disabled or enabled 207 208 /// 209 this( bool console_color=true ) 210 { 211 version(linux) 212 { 213 if( console_color ) 214 list[console_name] = new shared ColorConsoleLogOutput; 215 else 216 list[console_name] = new shared ConsoleLogOutput; 217 } 218 else 219 { 220 list[console_name] = new shared ConsoleLogOutput; 221 } 222 list[null_name] = new shared NullLogOutput; 223 224 enabled[console_name] = true; 225 enabled[null_name] = false; 226 } 227 228 /// call from Logger.writeLog by default 229 void opCall( string output_name, in LogMessage lm ) 230 { 231 if( broadcast ) 232 { 233 foreach( name, e; enabled ) 234 if( name in list && e ) 235 list[name](lm); 236 } 237 else 238 { 239 if( output_name in list ) 240 list[output_name](lm); 241 } 242 } 243 244 bool _broadcast = true; 245 246 public: 247 248 enum console_name = "console"; /// 249 enum null_name = "null"; /// 250 251 /// 252 bool broadcast() const @property { return _broadcast; } 253 254 /// 255 bool broadcast( bool b ) @property { _broadcast = b; return b; } 256 257 /// enable output 258 void enable( string name ) { enabled[name] = true; } 259 260 /// disable output 261 void disable( string name ) { enabled[name] = false; } 262 263 /// append output to list 264 void append( string name, shared LogOutput output ) 265 in{ assert( output !is null ); } 266 body 267 { 268 list[name] = output; 269 enable( name ); 270 } 271 272 /// remove output from list 273 void remove( string name ) 274 { 275 if( name == console_name || name == null_name ) 276 throw new LogSysException( "can not unregister '" ~ name ~ "' log output" ); 277 list.remove( name ); 278 } 279 }