1 /++ ### Simple example 2 + 3 + --- 4 + import des.log; 5 + 6 + void func() 7 + { 8 + logger.fatal( "fatal" ); 9 + logger.error( "error" ); 10 + logger.warn( "warn" ); 11 + logger.info( "info" ); 12 + logger.Debug( "debug" ); 13 + logger.trace( "trace" ); 14 + } 15 + 16 + void main() 17 + { 18 + logger.fatal( "fatal message" ); 19 + logger.error( "error message" ); 20 + logger.warn( "warn message" ); 21 + logger.info( "info message" ); 22 + logger.Debug( "debug message" ); 23 + logger.trace( "trace message" ); 24 + 25 + func(); 26 + } 27 + --- 28 + 29 + can have output like this: 30 + 31 + --- 32 + ./app 33 + [000000.000122258][FATAL][app.main]: fatal message 34 + [000000.000214030][ERROR][app.main]: error message 35 + [000000.000261067][FATAL][app.func]: fatal 36 + [000000.000285349][ERROR][app.func]: error 37 + --- 38 + 39 40 + Flag `--log` used for setting max level of logging output. 41 + Default level is `error`. If log function called with greater level it's skipped. 42 + Level has attitudes `off < fatal < error < warn < info < debug < trace`. 43 + 44 + 45 + --- 46 + $ ./app --log app.func:debug 47 + [log use min]: false 48 + [log rules]: 49 + ERROR 50 + app : ERROR 51 + func : DEBUG 52 + [000000.000162889][FATAL][app.main]: fatal message 53 + [000000.000207483][ERROR][app.main]: error message 54 + [000000.000242506][FATAL][app.func]: fatal 55 + [000000.000261887][ERROR][app.func]: error 56 + [000000.000285754][ WARN][app.func]: warn 57 + [000000.000304789][ INFO][app.func]: info 58 + [000000.000323652][DEBUG][app.func]: debug 59 + --- 60 + 61 + 62 + --- 63 + $ ./app --log info --log app.func:trace 64 + [log use min]: false 65 + [log rules]: 66 + INFO 67 + app : INFO 68 + func : TRACE 69 + [000000.000245525][FATAL][app.main]: fatal message 70 + [000000.000308796][ERROR][app.main]: error message 71 + [000000.000338714][ WARN][app.main]: warn message 72 + [000000.000365555][ INFO][app.main]: info message 73 + [000000.000406501][FATAL][app.func]: fatal 74 + [000000.000434482][ERROR][app.func]: error 75 + [000000.000461296][ WARN][app.func]: warn 76 + [000000.000487242][ INFO][app.func]: info 77 + [000000.000512884][DEBUG][app.func]: debug 78 + [000000.000538288][TRACE][app.func]: trace 79 + --- 80 + 81 + 82 + Flag `--log` can be used with module name `./program --log draw.point:debug`. 83 + It will set `debug` level for module `draw.point` and default to other. 84 + 85 + Flag `--log-use-min` is boolean flag. It forces logging system to skip output from 86 + all child modules if their level greater than parent. Default is `false`. 87 + 88 + `./program --log trace --log draw:info --log draw.point:trace --log-use-min=true` 89 + skips all output from `logger.trace` and `logger.Debug` from whole draw.point, 90 + and doesn't skip from other modules. 91 + 92 + `./program --log trace --log draw:info --log draw.point:trace` allow `log_trace` 93 + and `log_debug` only from `draw.point` from module `draw`. For other modules in 94 + `draw` sets level `info` 95 + 96 + You can compile program with `version=des_log_onlyerror` for skip all 97 + `trace`, `debug`, `info` and `warn` outputs in logger. It can improve program 98 + release speed. 99 + 100 + ### Class logging 101 + 102 + Module provides some functional for useful logging classes. 103 + 104 + Example: 105 + 106 + --- 107 + module x; 108 + import des.log; 109 + class A 110 + { 111 + mixin ClassLogger; 112 + void func() { logger.trace( "hello" ); } 113 + } 114 + --- 115 + --- 116 + module y; 117 + import x; 118 + class B : A { } 119 + --- 120 + --- 121 + auto b = new B; 122 + b.func(); 123 + --- 124 + 125 + outputs: 126 + --- 127 + [000000.148628473][TRACE][x.A.func]: hello 128 + --- 129 + 130 + If create instance logger 131 + --- 132 + class B : A { this(){ logger = new InstanceLogger(this); } } 133 + --- 134 + 135 + outputs: 136 + --- 137 + [000000.148628473][TRACE][y.B.func]: hello 138 + --- 139 + 140 + If create instance logger with instance name 141 + --- 142 + class B : A { this(){ logger = new InstanceLogger(this,"my object"); } } 143 + --- 144 + 145 + outputs: 146 + --- 147 + [000000.148628473][TRACE][y.B.[my object].func]: hello 148 + --- 149 + 150 + If create instance full logger 151 + --- 152 + class B : A { this(){ logger = new InstanceFullLogger(this); } } 153 + --- 154 + 155 + outputs: 156 + --- 157 + [000000.148628473][TRACE][y.B.[x.A.func]]: hello 158 + --- 159 + 160 + If create instance full logger with name 161 + 162 + --- 163 + class B : A { this(){ logger = new InstanceFullLogger(this,"name"); } } 164 + --- 165 + 166 + outputs: 167 + --- 168 + [000000.148628473][TRACE][y.B.[name].[x.A.func]]: hello 169 + --- 170 + 171 + Flag `--log` can get full emitter string `y.B.[name].[x.A.func]`. 172 + --- 173 + ./program --log "y.B.[one]:trace" --log "y.B.[two]:debug" 174 + --- 175 +/ 176 module des.log; 177 178 public 179 { 180 import des.log.base; 181 import des.log.logcls; 182 import des.log.rule; 183 import des.log.output; 184 } 185 186 import std.stdio; 187 import std.file; 188 import std..string; 189 import std.typecons; 190 import std.getopt; 191 192 /// for simple adding logging to class 193 mixin template ClassLogger() 194 { 195 static if( !is( typeof( __logger ) ) ) 196 { 197 private Logger __logger; 198 protected nothrow final @property 199 { 200 const(Logger) logger() const 201 { 202 mixin( "static import " ~ __MODULE__ ~ ";" ); 203 if( __logger is null ) 204 mixin( "return " ~ __MODULE__ ~ ".logger;" ); 205 else return __logger; 206 } 207 void logger( Logger lg ) { __logger = lg; } 208 } 209 } 210 } 211 212 Logger logger; /// 213 214 static this() { logger = new Logger; } 215 216 shared static this() 217 { 218 if( g_rule !is null ) return; 219 g_rule = new shared Rule; 220 g_output = new shared OutputHandler(); 221 } 222 223 void logReadSettingsFromFile( string fname ) 224 { 225 auto set = "" ~ readText( fname ).splitLines.join(" ").split(" "); 226 getopt( set, log_getopt_base.expand ); 227 } 228 229 private enum log_getopt_base = tuple( 230 "log", `set logging level <[emitter:]level>, default emitter value=""`, &setLogRule, 231 "log-use-min", "using minimal logging level in rule hierarchy <bool>", &setLogUseMin, 232 "log-only-reg", "logging only for setted emitters <bool>", &setLogOnlyReg, 233 234 "log-file", `set log file <[output:]path/to/logfile>, default output value="logfile"`, &setLogFile, 235 236 "log-output", `set rules for log output <output:[emitter:]level>, default emitter value=""`, &setLogOutputRule, 237 "log-output-use-min", "using minimal logging level in output rule <output:bool>", &setLogOutputUseMin, 238 "log-output-only-reg", "logging only for setted emitters into output <output:bool>", &setLogOutputOnlyReg, 239 ); 240 241 enum log_getopt_file_settings = tuple( 242 "log-settings", "read settings file <path/to/logsettings>, format as logging options for getopt, without this option", &logReadSettingsFromFileOpt 243 ); 244 245 enum log_getopt = tuple( log_getopt_base.expand, log_getopt_file_settings.expand ); 246 247 /// 248 class LogGetOptException : GetOptException 249 { 250 @safe pure nothrow 251 this( string msg, string file=__FILE__, size_t line=__LINE__ ) 252 { super( msg, file, line ); } 253 254 static fmt(string file=__FILE__,size_t line=__LINE__,Args...)( Exception e, Args args ) 255 { return new LogGetOptException( format( args ) ~ ": " ~ e.msg, file, line ); } 256 } 257 258 private: 259 void setLogRule( string opt, string value ) 260 { 261 auto sp = value.split(":"); 262 try 263 { 264 if( sp.length == 1 ) g_rule.setLevel( toLogLevel( sp[0] ) ); 265 else if( sp.length == 2 ) g_rule.setLevel( toLogLevel( sp[1] ), sp[0] ); 266 else throw new Exception( "bad split" ); 267 } 268 catch( Exception e ) 269 throw LogGetOptException.fmt( e, "error option %s=%s", opt, value ); 270 } 271 272 void setLogUseMin( string opt, string value ) 273 { 274 try g_rule.useMinimal = to!bool( value ); 275 catch( Exception e ) 276 throw LogGetOptException.fmt( e, "error option %s=%s", opt, value ); 277 } 278 279 void setLogOnlyReg( string opt, string value ) 280 { 281 try g_rule.onlyRegister = to!bool( value ); 282 catch( Exception e ) 283 throw LogGetOptException.fmt( e, "error option %s=%s", opt, value ); 284 } 285 286 void setLogFile( string opt, string value ) 287 { 288 auto sp = value.split(":"); 289 try 290 { 291 if( sp.length == 1 ) g_output["logfile"] = new shared FileLogOutput(sp[0]); 292 else if( sp.length == 2 ) g_output[sp[0]] = new shared FileLogOutput(sp[1]); 293 else throw new Exception( "bad split" ); 294 } 295 catch( Exception e ) 296 throw LogGetOptException.fmt( e, "error option %s=%s", opt, value ); 297 } 298 299 void setLogOutputRule( string opt, string value ) 300 { 301 auto sp = value.split(":"); 302 try 303 { 304 if( sp.length == 2 ) g_output[sp[0]].rule.setLevel( toLogLevel(sp[1]) ); 305 else if( sp.length == 3 ) g_output[sp[0]].rule.setLevel( toLogLevel(sp[2]), sp[1] ); 306 else throw new Exception( "bad split" ); 307 } 308 catch( Exception e ) 309 throw LogGetOptException.fmt( e, "error option %s=%s", opt, value ); 310 } 311 312 void setLogOutputUseMin( string opt, string value ) 313 { 314 auto sp = value.split(":"); 315 try 316 { 317 if( sp.length == 2 ) g_output[sp[0]].rule.useMinimal = to!bool( sp[1] ); 318 else throw new Exception( "bad split" ); 319 } 320 catch( Exception e ) 321 throw LogGetOptException.fmt( e, "error option %s=%s", opt, value ); 322 } 323 324 void setLogOutputOnlyReg( string opt, string value ) 325 { 326 auto sp = value.split(":"); 327 try 328 { 329 if( sp.length == 2 ) g_output[sp[0]].rule.onlyRegister = to!bool( sp[1] ); 330 else throw new Exception( "bad split" ); 331 } 332 catch( Exception e ) 333 throw LogGetOptException.fmt( e, "error option %s=%s", opt, value ); 334 } 335 336 void logReadSettingsFromFileOpt( string opt, string value ) 337 { 338 try logReadSettingsFromFile( value ); 339 catch( Exception e ) 340 throw LogGetOptException.fmt( e, "error option %s=%s", opt, value ); 341 }