1 module des.util.logsys.output;
2 
3 import std.stdio : stdout, stderr;
4 import std..string : toStringz;
5 import std.datetime;
6 
7 import des.util.logsys.base;
8 
9 /// output for logger
10 synchronized abstract class LogOutput
11 {
12     ///
13     final void opCall( in LogMessage lm ) { write( lm, formatLogMessage( lm ) ); }
14 
15 protected:
16 
17     ///
18     void write( in LogMessage, string );
19 
20     /// by default call des.util.logsys.base.defaultFormatLogMessage
21     string formatLogMessage( in LogMessage lm ) const
22     out(ret){ assert( ret.length ); }
23     body { return defaultFormatLogMessage( lm ); }
24 }
25 
26 ///
27 synchronized class NullLogOutput : LogOutput
28 {
29 protected:
30     override
31     {
32         /// empty
33         void write( in LogMessage, string ){}
34 
35         /// not call formating message
36         string formatLogMessage( in LogMessage lm ) const { return "null"; }
37     }
38 }
39 
40 /// output to file
41 synchronized class FileLogOutput : LogOutput
42 {
43     import core.stdc.stdio;
44     import std.datetime;
45     FILE* file; ///
46 
47     ///
48     this( string filename )
49     {
50         file = fopen( filename.toStringz, "a" );
51         if( file is null )
52             throw new LogSysException( "unable to open file '" ~ filename ~ "' at write mode" );
53         fprintf( file, "%s\n", firstLine().toStringz );
54     }
55 
56     ~this() { if( file ) fclose( file ); }
57 
58 protected:
59 
60     /// fprintf
61     override void write( in LogMessage, string msg )
62     { fprintf( file, "%s\n", msg.toStringz ); }
63 
64     /++ call from ctor and past to file first line with datetime
65         Returns:
66         format( "%02d.%02d.%4d %02d:%02d:%02d", dt.day, dt.month, dt.year, dt.hour, dt.minute, dt.second );
67      +/
68     string firstLine() const
69     {
70         auto dt = Clock.currTime;
71         return format( "%02d.%02d.%4d %02d:%02d:%02d",
72                 dt.day, dt.month, dt.year, dt.hour, dt.minute, dt.second );
73     }
74 }
75 
76 ///
77 synchronized class ConsoleLogOutput : LogOutput
78 {
79     /// log messages with level > ERROR puts to stdout, and stderr otherwise
80     protected override void write( in LogMessage lm, string str )
81     {
82         if( lm.level > LogMessage.Level.ERROR )
83             stdout.writeln( str );
84         else
85             stderr.writeln( str );
86     }
87 }
88 
89 /// colorise console output with escape seqence
90 synchronized class ColorConsoleLogOutput : ConsoleLogOutput
91 {
92     enum 
93     {
94         COLOR_OFF = "\x1b[0m", /// reset color
95 
96         // Regular Colors
97         FG_BLACK  = "\x1b[0;30m", ///
98         FG_RED    = "\x1b[0;31m", ///
99         FG_GREEN  = "\x1b[0;32m", ///
100         FG_YELLOW = "\x1b[0;33m", ///
101         FG_BLUE   = "\x1b[0;34m", ///
102         FG_PURPLE = "\x1b[0;35m", ///
103         FG_CYAN   = "\x1b[0;36m", ///
104         FG_WHITE  = "\x1b[0;37m", ///
105 
106         // Bold
107         FG_B_BLACK  = "\x1b[1;30m", ///
108         FG_B_RED    = "\x1b[1;31m", ///
109         FG_B_GREEN  = "\x1b[1;32m", ///
110         FG_B_YELLOW = "\x1b[1;33m", ///
111         FG_B_BLUE   = "\x1b[1;34m", ///
112         FG_B_PURPLE = "\x1b[1;35m", ///
113         FG_B_CYAN   = "\x1b[1;36m", ///
114         FG_B_WHITE  = "\x1b[1;37m", ///
115 
116         // Underline
117         FG_U_BLACK  = "\x1b[4;30m", ///
118         FG_U_RED    = "\x1b[4;31m", ///
119         FG_U_GREEN  = "\x1b[4;32m", ///
120         FG_U_YELLOW = "\x1b[4;33m", ///
121         FG_U_BLUE   = "\x1b[4;34m", ///
122         FG_U_PURPLE = "\x1b[4;35m", ///
123         FG_U_CYAN   = "\x1b[4;36m", ///
124         FG_U_WHITE  = "\x1b[4;37m", ///
125 
126         // Background
127         BG_BLACK  = "\x1b[40m", ///
128         BG_RED    = "\x1b[41m", ///
129         BG_GREEN  = "\x1b[42m", ///
130         BG_YELLOW = "\x1b[43m", ///
131         BG_BLUE   = "\x1b[44m", ///
132         BG_PURPLE = "\x1b[45m", ///
133         BG_CYAN   = "\x1b[46m", ///
134         BG_WHITE  = "\x1b[47m", ///
135 
136         // High Intensity
137         FG_I_BLACK  = "\x1b[0;90m", ///
138         FG_I_RED    = "\x1b[0;91m", ///
139         FG_I_GREEN  = "\x1b[0;92m", ///
140         FG_I_YELLOW = "\x1b[0;93m", ///
141         FG_I_BLUE   = "\x1b[0;94m", ///
142         FG_I_PURPLE = "\x1b[0;95m", ///
143         FG_I_CYAN   = "\x1b[0;96m", ///
144         FG_I_WHITE  = "\x1b[0;97m", ///
145 
146         // Bold High Intensity
147         FG_BI_BLACK  = "\x1b[1;90m", ///
148         FG_BI_RED    = "\x1b[1;91m", ///
149         FG_BI_GREEN  = "\x1b[1;92m", ///
150         FG_BI_YELLOW = "\x1b[1;93m", ///
151         FG_BI_BLUE   = "\x1b[1;94m", ///
152         FG_BI_PURPLE = "\x1b[1;95m", ///
153         FG_BI_CYAN   = "\x1b[1;96m", ///
154         FG_BI_WHITE  = "\x1b[1;97m", ///
155 
156         // High Intensity backgrounds
157         BG_I_BLACK  = "\x1b[0;100m", ///
158         BG_I_RED    = "\x1b[0;101m", ///
159         BG_I_GREEN  = "\x1b[0;102m", ///
160         BG_I_YELLOW = "\x1b[0;103m", ///
161         BG_I_BLUE   = "\x1b[0;104m", ///
162         BG_I_PURPLE = "\x1b[0;105m", ///
163         BG_I_CYAN   = "\x1b[0;106m", ///
164         BG_I_WHITE  = "\x1b[0;107m", ///
165     };
166 
167 protected:
168 
169     /// formatting with colors
170     override string formatLogMessage( in LogMessage lm ) const
171     {
172         auto color = chooseColors( lm );
173         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",
174                        lm.ts / 1e9f, lm.level, lm.emitter, lm.message,
175                        COLOR_OFF, color[0], color[1], color[2], color[3] );
176     }
177 
178     /// returns 4 colors for timestamp, log level, emitter name, message text
179     string[4] chooseColors( in LogMessage lm ) const
180     {
181         string ts, type, emitter, msg;
182 
183         final switch( lm.level )
184         {
185             case LogMessage.Level.FATAL: type = FG_BLACK ~ BG_RED; break;
186             case LogMessage.Level.ERROR: type = FG_RED; break;
187             case LogMessage.Level.WARN: type = FG_PURPLE; break;
188             case LogMessage.Level.INFO: type = FG_CYAN; break;
189             case LogMessage.Level.DEBUG: type = FG_YELLOW; break;
190             case LogMessage.Level.TRACE: break;
191         }
192 
193         ts = type;
194         emitter = type;
195         msg = type;
196 
197         return [ts, type, emitter, msg];
198     }
199 }
200 
201 /// main logging output center
202 synchronized final class LogOutputHandler
203 {
204 package:
205     LogOutput[string] list; ///
206     bool[string] enabled; /// any of log output can be disabled or enabled
207 
208     ///
209     this( bool console_color=true )
210     {
211         version(linux)
212         {
213             if( console_color )
214                 list[console_name] = new shared ColorConsoleLogOutput;
215             else
216                 list[console_name] = new shared ConsoleLogOutput;
217         }
218         else
219         {
220             list[console_name] = new shared ConsoleLogOutput;
221         }
222         list[null_name] = new shared NullLogOutput;
223 
224         enabled[console_name] = true;
225         enabled[null_name] = false;
226     }
227 
228     /// call from Logger.writeLog by default
229     void opCall( string output_name, in LogMessage lm )
230     {
231         if( broadcast )
232         {
233             foreach( name, e; enabled )
234                 if( name in list && e )
235                     list[name](lm);
236         }
237         else
238         {
239             if( output_name in list )
240                 list[output_name](lm);
241         }
242     }
243 
244     bool _broadcast = true;
245 
246 public:
247 
248     enum console_name = "console"; ///
249     enum null_name = "null"; ///
250 
251     ///
252     bool broadcast() const @property { return _broadcast; }
253 
254     ///
255     bool broadcast( bool b ) @property { _broadcast = b; return b; }
256 
257     /// enable output
258     void enable( string name ) { enabled[name] = true; }
259 
260     /// disable output
261     void disable( string name ) { enabled[name] = false; }
262 
263     /// append output to list
264     void append( string name, shared LogOutput output )
265     in{ assert( output !is null ); }
266     body
267     {
268         list[name] = output;
269         enable( name );
270     }
271 
272     /// remove output from list
273     void remove( string name )
274     {
275         if( name == console_name || name == null_name )
276             throw new LogSysException( "can not unregister '" ~ name ~ "' log output" );
277         list.remove( name );
278     }
279 }