1 /+ 2 The MIT License (MIT) 3 4 Copyright (c) <2013> <Oleg Butko (deviator), Anton Akzhigitov (Akzwar)> 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy 7 of this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights 9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 copies of the Software, and to permit persons to whom the Software is 11 furnished to do so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in 14 all copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 THE SOFTWARE. 23 +/ 24 25 module des.util.logger; 26 27 import std.stdio; 28 import std.string; 29 import std.conv; 30 import std.datetime; 31 import std.traits; 32 33 import std.algorithm; 34 35 enum LogLevel 36 { 37 OFF = 0, 38 ERROR = 1, 39 WARN = 2, 40 INFO = 3, 41 DEBUG = 4, 42 TRACE = 5 43 }; 44 45 struct LogMessage 46 { 47 string emitter; 48 ulong ts; 49 LogLevel level; 50 string message; 51 52 @disable this(); 53 54 pure nothrow @safe this( string emitter, ulong ts, 55 LogLevel level, string message ) 56 { 57 this.emitter = emitter; 58 this.ts = ts; 59 this.level = level; 60 this.message = message; 61 } 62 } 63 64 string formatLogMessage( in LogMessage lm ) 65 { 66 auto fmt = "[%016.9f][%5s][%s]: %s"; 67 return format( fmt, lm.ts / 1e9f, lm.level, lm.emitter, lm.message ); 68 } 69 70 mixin template AnywayLogger() 71 { 72 private Logger __logger; 73 protected nothrow final @property 74 { 75 const(Logger) logger() const 76 { 77 static import des.util.logger; 78 if( __logger is null ) 79 return des.util.logger.simple_logger; 80 else return __logger; 81 } 82 void logger( Logger lg ) { __logger = lg; } 83 } 84 } 85 86 abstract class Logger 87 { 88 mixin( getLogFunctions( "", "__log", "procFuncName", true ) ); 89 protected: 90 nothrow string procFuncName( string name ) const 91 in{ assert( name.length ); } 92 out(ret){ assert( ret.length ); } 93 body { return name; } 94 } 95 96 private class SimpleLogger : Logger {} 97 98 SimpleLogger simple_logger; 99 100 static this() 101 { 102 simple_logger = new SimpleLogger; 103 } 104 105 class InstanceLogger : Logger 106 { 107 protected: 108 string class_name; 109 string inst_name; 110 111 public: 112 this( Object obj, string inst="" ) 113 { 114 class_name = typeid(obj).name; 115 inst_name = inst; 116 } 117 118 nothrow @property 119 { 120 void instance( string i ) { inst_name = i; } 121 string instance() const { return inst_name; } 122 } 123 124 protected: 125 126 override nothrow string procFuncName( string name ) const 127 { 128 try return fullEmitterName ~ "." ~ name.split(".")[$-1]; 129 catch(Exception e) return fullEmitterName; 130 } 131 132 nothrow @property string fullEmitterName() const 133 { return class_name ~ (inst_name.length?".["~inst_name~"]":""); } 134 } 135 136 class InstanceFullLogger : InstanceLogger 137 { 138 public this( Object obj, string inst="" ) { super(obj,inst); } 139 protected override nothrow string procFuncName( string name ) const 140 { return fullEmitterName ~ ".[" ~ name ~ "]"; } 141 } 142 143 mixin( getLogFunctions( "log_", "__log" ) ); 144 145 private string getLogFunctions( string prefix, string baselog, string emitterProc="", bool isConst=false ) 146 { 147 string fnc = ` 148 nothrow void %s%s(string fnc=__FUNCTION__,Args...)( Args args )%s 149 { %s( LogMessage( %s, __ts, LogLevel.%s, toMessage(args) ) ); } 150 `; 151 152 string emitter = emitterProc ? emitterProc ~ "(fnc)" : "fnc"; 153 154 string ret; 155 foreach( lvl; [EnumMembers!LogLevel] ) 156 { 157 if( lvl == LogLevel.OFF ) continue; 158 auto slvl = to!string(lvl); 159 auto fname = slvl.toLower; 160 if( fname == "debug" && prefix.length == 0 ) fname = "Debug"; 161 ret ~= format( fnc, prefix, fname, isConst?" const":"", 162 baselog, emitter, slvl ); 163 } 164 return ret; 165 } 166 167 private nothrow @property ulong __ts() 168 { 169 try return Clock.currAppTick().length; 170 catch(Exception e) return 0; 171 } 172 173 private nothrow void __log( in LogMessage lm ) 174 { 175 try 176 { 177 if( log_rule.allow(lm.emitter) >= lm.level ) 178 { 179 if( lm.level < LogLevel.INFO ) 180 stderr.writeln( formatLogMessage( lm ) ); 181 else 182 stdout.writeln( formatLogMessage( lm ) ); 183 } 184 } 185 catch(Exception e) 186 { 187 try stderr.writefln( "[INTERNAL LOG EXCEPTION]: %s", e ); 188 catch(Exception){} 189 } 190 } 191 192 nothrow string toMessage(Args...)( Args args ) 193 { 194 try 195 { 196 static if( is( Args[0] == string ) ) 197 return format( args ); 198 else 199 { 200 string res; 201 foreach( arg; args ) 202 res ~= to!string(arg); 203 return res; 204 } 205 } 206 catch(Exception e) 207 return "[MESSAGE CTOR EXCEPTION]: " ~ e.msg; 208 } 209 210 private synchronized class Rule 211 { 212 protected: 213 shared Rule parent; 214 215 LogLevel level = LogLevel.ERROR; 216 shared Rule[string] inner; 217 218 bool use_minimal = true; 219 220 public: 221 this( shared Rule parent = null ) { this.parent = parent; } 222 223 @property bool useMinimal() const 224 { 225 if( parent !is null ) 226 return parent.useMinimal(); 227 else return use_minimal; 228 } 229 230 void setLevel( LogLevel lvl, string emitter="" ) 231 { 232 auto addr = splitAddress( emitter ); 233 if( addr[0].length == 0 ) { level = lvl; return; } 234 auto iname = addr[0]; 235 if( iname !in inner ) inner[iname] = new shared Rule(this); 236 inner[iname].setLevel( lvl, addr[1] ); 237 } 238 239 LogLevel allow( string emitter="" ) 240 { 241 auto addr = splitAddress( emitter ); 242 if( addr[0].length == 0 ) return level; 243 auto iname = addr[0]; 244 if( iname !in inner ) return level; 245 if( useMinimal ) 246 return min( level, inner[iname].allow( addr[1] ) ); 247 else 248 return inner[iname].allow( addr[1] ); 249 } 250 251 string print( string offset="" ) const 252 { 253 string ret = format( "%s", level ); 254 foreach( key, val; inner ) 255 ret ~= format( "\n%s%s : %s", offset, key, val.print( offset ~ mlt(" ",key.length) ) ); 256 return ret; 257 } 258 259 protected: 260 261 static string[2] splitAddress( string emitter ) 262 { 263 auto addr = emitter.split("."); 264 if( addr.length == 0 ) return ["",""]; 265 if( addr.length == 1 ) return [addr[0],""]; 266 267 return [ addr[0], addr[1..$].join(".") ]; 268 } 269 } 270 271 private T[] mlt(T)( T[] val, size_t cnt ) 272 { 273 T[] buf; 274 foreach( i; 0 .. cnt ) 275 buf ~= val; 276 return buf; 277 } 278 279 unittest { assert( " ", mlt( " ", 4 ) ); } 280 281 unittest 282 { 283 auto r = new shared Rule; 284 285 r.setLevel( LogLevel.INFO ); 286 r.setLevel( LogLevel.TRACE, "des.gl" ); 287 r.setLevel( LogLevel.WARN, "des" ); 288 289 assert( r.allow() == LogLevel.INFO ); 290 assert( r.allow("des") == LogLevel.WARN ); 291 assert( r.allow("des.gl") == LogLevel.WARN ); 292 293 r.use_minimal = false; 294 295 assert( r.allow() == LogLevel.INFO ); 296 assert( r.allow("des") == LogLevel.WARN ); 297 assert( r.allow("des.gl") == LogLevel.TRACE ); 298 } 299 300 private static shared Rule log_rule; 301 302 shared static this() 303 { 304 if( log_rule !is null ) return; 305 306 import core.runtime, std.getopt; 307 import std.stdio; 308 import std.file; 309 310 log_rule = new shared Rule; 311 312 auto args = thisExePath ~ Runtime.args; 313 string[] logging; 314 bool useMinimal = false; 315 316 try 317 { 318 getopt( args, 319 "log", &logging, 320 "log-use-min", &useMinimal, 321 ); 322 } 323 catch( Exception e ) stderr.writefln( "bad log arguments: %s", e.msg ); 324 325 log_rule.use_minimal = useMinimal; 326 327 foreach( ln; logging ) 328 { 329 auto sp = ln.split(":"); 330 if( sp.length == 1 ) 331 { 332 try log_rule.setLevel( toLogLevel( sp[0] ) ); 333 catch( Exception e ) 334 stderr.writefln( "log argument '%s' can't conv to LogLevel: %s", ln, e.msg ); 335 } 336 else if( sp.length == 2 ) 337 { 338 try 339 { 340 auto level = toLogLevel( sp[1] ); 341 log_rule.setLevel( level, sp[0] ); 342 } 343 catch( Exception e ) 344 stderr.writefln( "log argument '%s' can't conv '%s' to LogLevel: %s", ln, sp[1], e.msg ); 345 } 346 else stderr.writefln( "bad log argument: %s" ); 347 } 348 349 if( logging.length ) 350 { 351 writeln( "[log use min]: ", useMinimal ); 352 writeln( "[log rules]:\n", log_rule.print() ); 353 } 354 } 355 356 LogLevel toLogLevel( string s ) { return to!LogLevel( s.toUpper ); }