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 }