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 ); }