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 }