1 module des.log.output;
2 
3 import std.stdio : stdout, stderr;
4 import std..string : toStringz;
5 import std.datetime;
6 import std.exception;
7 
8 import des.log.base;
9 import des.log.rule;
10 
11 /// output for logger
12 synchronized abstract class LogOutput
13 {
14     Rule rule;
15     private bool _enable = true;
16 
17     @property
18     {
19         bool enable() const { return _enable; }
20         bool enable( bool v ) { _enable = v; return v; }
21     }
22 
23     this()
24     {
25         rule = new shared Rule;
26         rule.setLevel( LogLevel.TRACE );
27     }
28 
29     ///
30     void opCall( in LogMessage lm )
31     {
32         if( enable && rule.isAllowed(lm) )
33             writeMessage( lm, formatLogMessage( lm ) );
34     }
35 
36 protected:
37 
38     ///
39     void writeMessage( in LogMessage, string );
40 
41     /// by default call des.util.logsys.base.defaultFormatLogMessage
42     string formatLogMessage( in LogMessage lm ) const
43     out(ret){ assert( ret.length ); }
44     body { return defaultFormatLogMessage( lm ); }
45 }
46 
47 ///
48 synchronized class NullLogOutput : LogOutput
49 {
50 protected:
51     override
52     {
53         /// empty
54         void writeMessage( in LogMessage, string ) {}
55 
56         /// not call formating message
57         string formatLogMessage( in LogMessage lm ) const { return "null"; }
58     }
59 }
60 
61 /// output to file
62 synchronized class FileLogOutput : LogOutput
63 {
64     import core.stdc.stdio;
65     import std.exception;
66     FILE* file;
67 
68     ///
69     this( string fname )
70     {
71         file = enforce( fopen( fname.toStringz, "a\0".ptr ),
72                 "Cannot open file '" ~ fname ~ "' in append mode" );
73         fprintf( file, "%s\n", firstLine().toStringz );
74     }
75 
76     ~this() { fclose( file ); }
77 
78 protected:
79 
80     /// 
81     override void writeMessage( in LogMessage, string msg ) { fprintf( file, "%s\n", msg.toStringz ); }
82 
83     /++ call from ctor and past to file first line with datetime
84         Returns:
85         format( "%02d.%02d.%4d %02d:%02d:%02d", dt.day, dt.month, dt.year, dt.hour, dt.minute, dt.second );
86      +/
87     string firstLine() const
88     {
89         import core.runtime;
90         import std.datetime;
91         import std.array;
92 
93         auto dt = Clock.currTime;
94         return format( "%02d.%02d.%4d %02d:%02d:%02d %s",
95                 dt.day, dt.month, dt.year, dt.hour, dt.minute, dt.second,
96                 Runtime.args.join(" ") );
97     }
98 }
99 
100 ///
101 synchronized class ConsoleLogOutput : LogOutput
102 {
103     /// log messages with level > ERROR puts to stdout, and stderr otherwise
104     protected override void writeMessage( in LogMessage lm, string str )
105     {
106         if( lm.level > LogMessage.Level.ERROR )
107             stdout.writeln( str );
108         else
109             stderr.writeln( str );
110     }
111 }
112 
113 /// colorise console output with escape seqence
114 synchronized class ColorConsoleLogOutput : ConsoleLogOutput
115 {
116 protected:
117 
118     import des.log.consolecolor;
119 
120     /// formatting with colors
121     override string formatLogMessage( in LogMessage lm ) const
122     {
123         auto color = chooseColors( lm );
124         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",
125                        lm.ts / 1e9f, lm.level, lm.emitter, lm.message,
126                        cast(string)CEColor.OFF, color[0], color[1], color[2], color[3] );
127     }
128 
129     /// returns 4 colors for timestamp, log level, emitter name, message text
130     string[4] chooseColors( in LogMessage lm ) const
131     {
132         string clr;
133 
134         final switch( lm.level )
135         {
136             //case LogMessage.Level.FATAL: clr = CEColor.FG_BLACK ~ CEColor.BG_RED; break;
137             case LogMessage.Level.FATAL: clr = CEColor.FG_BI_RED; break;
138             case LogMessage.Level.ERROR: clr = CEColor.FG_RED; break;
139             case LogMessage.Level.WARN: clr = CEColor.FG_PURPLE; break;
140             case LogMessage.Level.INFO: clr = CEColor.FG_CYAN; break;
141             case LogMessage.Level.DEBUG: clr = CEColor.FG_YELLOW; break;
142             case LogMessage.Level.TRACE: break;
143         }
144 
145         return [clr,clr,clr,clr];
146     }
147 }
148 
149 /// main logging output center
150 synchronized class OutputHandler
151 {
152 package:
153     LogOutput[string] list; ///
154 
155     ///
156     this()
157     {
158         version(linux)
159             list[console] = new shared ColorConsoleLogOutput;
160         else
161             list[console] = new shared ConsoleLogOutput;
162     }
163 
164     /// call from Logger.writeLog by default
165     void writeMessage( string output_name, in LogMessage lm )
166     {
167         if( output_name.length )
168         {
169             if( output_name in list )
170                 list[output_name](lm);
171         }
172         else foreach( name, lo; list ) lo(lm);
173     }
174 
175 public:
176 
177     enum console = "console"; ///
178 
179     /// get output
180     shared(LogOutput) opIndex( string name )
181     {
182         enforce( name in list, LogException.fmt( "no output named '%s'", name ) );
183         return list[name];
184     }
185 
186     /// set output
187     shared(LogOutput) opIndexAssign( shared LogOutput output, string name )
188     in{ assert( output !is null ); } body
189     { return list[name] = output; }
190 
191     string[] names() @property { return list.keys; }
192 
193     ///
194     void remove( string name )
195     {
196         if( name == console )
197             throw new LogException( "can not unregister '" ~ name ~ "' log output" );
198         list.remove( name );
199     }
200 }