1 // Written in the D programming language.
2 
3 /**
4 This module provides logging utilities.
5 
6 Use Log class static methods.
7 
8 Synopsis:
9 
10 ----
11 import dlangui.core.logger;
12 
13 // setup:
14 
15 // use stderror for logging
16 setStderrLogger();
17 // set log level
18 setLogLevel(LogLevel.Debug);
19 
20 // usage:
21 
22 // log debug message
23 Log.d("mouse clicked at ", x, ",", y);
24 // or with format string:
25 Log.fd("mouse clicked at %d,%d", x, y);
26 // log error message
27 Log.e("exception while reading file", e);
28 ----
29 
30 
31 For Android, set log tag instead of setXXXLogger:
32 
33 ----
34 Log.setLogTag("myApp");
35 ----
36 
37 Copyright: Vadim Lopatin, 2014
38 License:   Boost License 1.0
39 Authors:   Vadim Lopatin, coolreader.org@gmail.com
40 */
41 module dlangui.core.logger;
42 
43 import std.stdio;
44 import std.datetime : SysTime, Clock;
45 import core.sync.mutex;
46 
47 version (Android) {
48     import android.log;
49 }
50 
51 /// Log levels
52 enum LogLevel : int {
53     /// Fatal error, cannot resume
54     Fatal,
55     /// Error
56     Error,
57     /// Warning
58     Warn,
59     /// Informational message
60     Info,
61     /// Debug message
62     Debug,
63     /// Tracing message
64     Trace
65 }
66 
67 /// Returns timestamp in milliseconds since 1970 UTC similar to Java System.currentTimeMillis()
68 @property long currentTimeMillis() {
69     static import std.datetime;
70     return std.datetime.Clock.currStdTime / 10000;
71 }
72 
73 /** 
74     
75     Logging utilities
76 
77 Setup example:
78 ----
79 // use stderror for logging
80 setStderrLogger();
81 // set log level
82 setLogLevel(LogLeve.Debug);
83 ----
84 
85 Logging example:
86 ----
87 // log debug message
88 Log.d("mouse clicked at ", x, ",", y);
89 // log error message
90 Log.e("exception while reading file", e);
91 ----
92 
93 */
94 
95 class Log {
96     static __gshared private LogLevel logLevel = LogLevel.Info;
97     static __gshared private std.stdio.File * logFile = null;
98     static __gshared private Mutex _mutex = null;
99 
100     static public @property Mutex mutex() {
101         if (_mutex is null)
102             _mutex = new Mutex();
103         return _mutex;
104     }
105         
106     /// Redirects output to stdout
107     static public void setStdoutLogger() {
108         synchronized(mutex) {
109             logFile = &stdout;
110         }
111     }
112 
113     /// Redirects output to stderr
114     static public void setStderrLogger() {
115         synchronized(mutex) {
116             logFile = &stderr;
117         }
118     }
119 
120     /// Redirects output to file
121     static public void setFileLogger(File * file) {
122         synchronized(mutex) {
123             if (logFile !is null && logFile != &stdout && logFile != &stderr) {
124                 logFile.close();
125                 destroy(logFile);
126                 logFile = null;
127             }
128             logFile = file;
129             if (logFile !is null)
130                 logFile.writeln("DlangUI log file");
131         }
132     }
133 
134     /// Sets log level (one of LogLevel)
135     static public void setLogLevel(LogLevel level) {
136         synchronized(mutex) {
137             logLevel = level;
138             i("Log level changed to ", level);
139         }
140     }
141 
142     /// returns true if messages for level are enabled
143     static public bool isLogLevelEnabled(LogLevel level) {
144         return logLevel >= level;
145     }
146 
147     /// returns true if debug log level is enabled
148     @property static public bool debugEnabled() {
149         return logLevel >= LogLevel.Debug;
150     }
151 
152     /// returns true if trace log level is enabled
153     @property static public bool traceEnabled() {
154         return logLevel >= LogLevel.Trace;
155     }
156 
157     /// returns true if warn log level is enabled
158     @property static public bool warnEnabled() {
159         return logLevel >= LogLevel.Warn;
160     }
161 
162     /// Log level to name helper function
163     static public string logLevelName(LogLevel level) {
164         switch (level) with(LogLevel)
165         {
166             case Fatal: return "F";
167             case Error: return "E";
168             case Warn: return "W";
169             case Info: return "I";
170             case Debug: return "D";
171             case Trace: return "V";
172             default: return "?";
173         }
174     }
175     version (Android) {
176         static android_LogPriority toAndroidLogPriority(LogLevel level) {
177             switch (level) with (LogLevel) {
178                 /// Fatal error, cannot resume
179                 case Fatal:
180                     return android_LogPriority.ANDROID_LOG_FATAL;
181                 /// Error
182                 case Error:
183                     return android_LogPriority.ANDROID_LOG_ERROR;
184                 /// Warning
185                 case Warn:
186                     return android_LogPriority.ANDROID_LOG_WARN;
187                 /// Informational message
188                 case Info:
189                     return android_LogPriority.ANDROID_LOG_INFO;
190                 /// Debug message
191                 case Debug:
192                     return android_LogPriority.ANDROID_LOG_DEBUG;
193                 /// Tracing message
194                 case Trace:
195                 default:
196                     return android_LogPriority.ANDROID_LOG_VERBOSE;
197             }
198         }
199     }
200     /// Log message with arbitrary log level
201     static public void log(S...)(LogLevel level, S args) {
202         if (logLevel >= level) {
203             version (Android) {
204                 import std.format;
205                 import std.string : toStringz;
206                 import std.format;
207                 import std.conv : to;
208                 char[] msg;
209                 foreach(arg; args) {
210                     msg ~= to!string(arg);
211                 }
212                 msg ~= cast(char)0;
213                 __android_log_write(toAndroidLogPriority(level), ANDROID_LOG_TAG, msg.ptr);
214             } else {
215                 if (logFile !is null && logFile.isOpen) {
216                     SysTime ts = Clock.currTime();
217                     logFile.writef("%04d-%02d-%02d %02d:%02d:%02d.%03d %s  ", ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second, ts.fracSecs.split!("msecs").msecs, logLevelName(level));
218                     logFile.writeln(args);
219                     logFile.flush();
220                 }
221             }
222         }
223     }
224     /// Log message with arbitrary log level with format string
225     static public void logf(S...)(LogLevel level, string fmt, S args) {
226         if (logLevel >= level) {
227             version (Android) {
228                 import std.string : toStringz;
229                 import std.format;
230                 string msg = fmt.format(args);
231                 __android_log_write(toAndroidLogPriority(level), ANDROID_LOG_TAG, msg.toStringz);
232             } else {
233                 if (logFile !is null && logFile.isOpen) {
234                     SysTime ts = Clock.currTime();
235                     logFile.writef("%04d-%02d-%02d %02d:%02d:%02d.%03d %s  ", ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second, ts.fracSecs.split!("msecs").msecs, logLevelName(level));
236                     logFile.writefln(fmt, args);
237                     logFile.flush();
238                 }
239             }
240         }
241     }
242     /// Log verbose / trace message
243     static public void v(S...)(S args) {
244         synchronized(mutex) {
245             if (logLevel >= LogLevel.Trace)
246                 log(LogLevel.Trace, args);
247         }
248     }
249     /// Log verbose / trace message with format string
250     static public void fv(S...)(S args) {
251         synchronized(mutex) {
252             if (logLevel >= LogLevel.Trace)
253                 logf(LogLevel.Trace, args);
254         }
255     }
256     /// Log debug message
257     static public void d(S...)(S args) {
258         synchronized(mutex) {
259             if (logLevel >= LogLevel.Debug)
260                 log(LogLevel.Debug, args);
261         }
262     }
263     /// Log debug message with format string
264     static public void fd(S...)(S args) {
265         synchronized(mutex) {
266             if (logLevel >= LogLevel.Debug)
267                 logf(LogLevel.Debug, args);
268         }
269     }
270     /// Log info message
271     static public void i(S...)(S args) {
272         synchronized(mutex) {
273             if (logLevel >= LogLevel.Info)
274                 log(LogLevel.Info, args);
275         }
276     }
277     /// Log info message
278     static public void fi(S...)(S args) {
279         synchronized(mutex) {
280             if (logLevel >= LogLevel.Info)
281                 logf(LogLevel.Info, args);
282         }
283     }
284     /// Log warn message
285     static public void w(S...)(S args) {
286         synchronized(mutex) {
287             if (logLevel >= LogLevel.Warn)
288                 log(LogLevel.Warn, args);
289         }
290     }
291     /// Log warn message
292     static public void fw(S...)(S args) {
293         synchronized(mutex) {
294             if (logLevel >= LogLevel.Warn)
295                 logf(LogLevel.Warn, args);
296         }
297     }
298     /// Log error message
299     static public void e(S...)(S args) {
300         synchronized(mutex) {
301             if (logLevel >= LogLevel.Error)
302                 log(LogLevel.Error, args);
303         }
304     }
305     /// Log error message
306     static public void fe(S...)(S args) {
307         synchronized(mutex) {
308             if (logLevel >= LogLevel.Error)
309                 logf(LogLevel.Error, args);
310         }
311     }
312     /// Log fatal error message
313     static public void f(S...)(S args) {
314         synchronized(mutex) {
315             if (logLevel >= LogLevel.Fatal)
316                 log(LogLevel.Fatal, args);
317         }
318     }
319     /// Log fatal error message
320     static public void ff(S...)(S args) {
321         synchronized(mutex) {
322             if (logLevel >= LogLevel.Fatal)
323                 logf(LogLevel.Fatal, args);
324         }
325     }
326     
327     version (Android) {
328         static public void setLogTag(const char * tag) {
329             ANDROID_LOG_TAG = tag;
330         }
331     }
332 }
333 
334 debug {
335     private static __gshared bool _appShuttingDown = false;
336 
337     @property bool appShuttingDown() { return _appShuttingDown; }
338 
339     /// for debug purposes - sets shutdown flag to log widgets not destroyed in time.
340     void setAppShuttingDownFlag() {
341         _appShuttingDown = true;
342     }
343 }
344 
345 void onResourceDestroyWhileShutdown(string resourceName, string objname = null) {
346     Log.e("Resource leak: destroying resource while shutdown! ", resourceName, " ", objname);
347 }
348 
349 /// set to true when exiting main - to detect destructor calls for resources by GC
350 __gshared bool APP_IS_SHUTTING_DOWN = false;
351