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 private auto std_io_err_helper(alias v)()
96 {
97     static if (__VERSION__ < 2075)
98         return &v;
99     else
100         return &v();
101 }
102 
103 class Log {
104     static __gshared private LogLevel logLevel = LogLevel.Info;
105     static __gshared private std.stdio.File * logFile = null;
106     static __gshared private Mutex _mutex = null;
107 
108     static public @property Mutex mutex() {
109         if (_mutex is null)
110             _mutex = new Mutex();
111         return _mutex;
112     }
113 
114     /// Redirects output to stdout
115     static public void setStdoutLogger() {
116         synchronized(mutex) {
117             logFile = std_io_err_helper!stdout;
118         }
119     }
120 
121     /// Redirects output to stderr
122     static public void setStderrLogger() {
123         synchronized(mutex) {
124             logFile = std_io_err_helper!stderr;
125         }
126     }
127 
128     /// Redirects output to file
129     static public void setFileLogger(File * file) {
130         synchronized(mutex) {
131             if (logFile !is null && *logFile != stdout && *logFile != stderr) {
132                 logFile.close();
133                 destroy(logFile);
134                 logFile = null;
135             }
136             logFile = file;
137             if (logFile !is null)
138                 logFile.writeln("DlangUI log file");
139         }
140     }
141 
142     /// Sets log level (one of LogLevel)
143     static public void setLogLevel(LogLevel level) {
144         synchronized(mutex) {
145             logLevel = level;
146             i("Log level changed to ", level);
147         }
148     }
149 
150     /// returns true if messages for level are enabled
151     static public bool isLogLevelEnabled(LogLevel level) {
152         return logLevel >= level;
153     }
154 
155     /// returns true if debug log level is enabled
156     @property static public bool debugEnabled() {
157         return logLevel >= LogLevel.Debug;
158     }
159 
160     /// returns true if trace log level is enabled
161     @property static public bool traceEnabled() {
162         return logLevel >= LogLevel.Trace;
163     }
164 
165     /// returns true if warn log level is enabled
166     @property static public bool warnEnabled() {
167         return logLevel >= LogLevel.Warn;
168     }
169 
170     /// Log level to name helper function
171     static public string logLevelName(LogLevel level) {
172         switch (level) with(LogLevel)
173         {
174             case Fatal: return "F";
175             case Error: return "E";
176             case Warn: return "W";
177             case Info: return "I";
178             case Debug: return "D";
179             case Trace: return "V";
180             default: return "?";
181         }
182     }
183     version (Android) {
184         static android_LogPriority toAndroidLogPriority(LogLevel level) {
185             switch (level) with (LogLevel) {
186                 /// Fatal error, cannot resume
187                 case Fatal:
188                     return android_LogPriority.ANDROID_LOG_FATAL;
189                 /// Error
190                 case Error:
191                     return android_LogPriority.ANDROID_LOG_ERROR;
192                 /// Warning
193                 case Warn:
194                     return android_LogPriority.ANDROID_LOG_WARN;
195                 /// Informational message
196                 case Info:
197                     return android_LogPriority.ANDROID_LOG_INFO;
198                 /// Debug message
199                 case Debug:
200                     return android_LogPriority.ANDROID_LOG_DEBUG;
201                 /// Tracing message
202                 case Trace:
203                 default:
204                     return android_LogPriority.ANDROID_LOG_VERBOSE;
205             }
206         }
207     }
208     /// Log message with arbitrary log level
209     static public void log(S...)(LogLevel level, S args) {
210         if (logLevel >= level) {
211             version (Android) {
212                 import std.format;
213                 import std.string : toStringz;
214                 import std.format;
215                 import std.conv : to;
216                 char[] msg;
217                 foreach(arg; args) {
218                     msg ~= to!string(arg);
219                 }
220                 msg ~= cast(char)0;
221                 __android_log_write(toAndroidLogPriority(level), ANDROID_LOG_TAG, msg.ptr);
222             } else {
223                 if (logFile !is null && logFile.isOpen) {
224                     SysTime ts = Clock.currTime();
225                     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));
226                     logFile.writeln(args);
227                     logFile.flush();
228                 }
229             }
230         }
231     }
232     /// Log message with arbitrary log level with format string
233     static public void logf(S...)(LogLevel level, string fmt, S args) {
234         if (logLevel >= level) {
235             version (Android) {
236                 import std.string : toStringz;
237                 import std.format;
238                 string msg = fmt.format(args);
239                 __android_log_write(toAndroidLogPriority(level), ANDROID_LOG_TAG, msg.toStringz);
240             } else {
241                 if (logFile !is null && logFile.isOpen) {
242                     SysTime ts = Clock.currTime();
243                     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));
244                     logFile.writefln(fmt, args);
245                     logFile.flush();
246                 }
247             }
248         }
249     }
250     /// Log verbose / trace message
251     static public void v(S...)(S args) {
252         synchronized(mutex) {
253             if (logLevel >= LogLevel.Trace)
254                 log(LogLevel.Trace, args);
255         }
256     }
257     /// Log verbose / trace message with format string
258     static public void fv(S...)(S args) {
259         synchronized(mutex) {
260             if (logLevel >= LogLevel.Trace)
261                 logf(LogLevel.Trace, args);
262         }
263     }
264     /// Log debug message
265     static public void d(S...)(S args) {
266         synchronized(mutex) {
267             if (logLevel >= LogLevel.Debug)
268                 log(LogLevel.Debug, args);
269         }
270     }
271     /// Log debug message with format string
272     static public void fd(S...)(S args) {
273         synchronized(mutex) {
274             if (logLevel >= LogLevel.Debug)
275                 logf(LogLevel.Debug, args);
276         }
277     }
278     /// Log info message
279     static public void i(S...)(S args) {
280         synchronized(mutex) {
281             if (logLevel >= LogLevel.Info)
282                 log(LogLevel.Info, args);
283         }
284     }
285     /// Log info message
286     static public void fi(S...)(S args) {
287         synchronized(mutex) {
288             if (logLevel >= LogLevel.Info)
289                 logf(LogLevel.Info, args);
290         }
291     }
292     /// Log warn message
293     static public void w(S...)(S args) {
294         synchronized(mutex) {
295             if (logLevel >= LogLevel.Warn)
296                 log(LogLevel.Warn, args);
297         }
298     }
299     /// Log warn message
300     static public void fw(S...)(S args) {
301         synchronized(mutex) {
302             if (logLevel >= LogLevel.Warn)
303                 logf(LogLevel.Warn, args);
304         }
305     }
306     /// Log error message
307     static public void e(S...)(S args) {
308         synchronized(mutex) {
309             if (logLevel >= LogLevel.Error)
310                 log(LogLevel.Error, args);
311         }
312     }
313     /// Log error message
314     static public void fe(S...)(S args) {
315         synchronized(mutex) {
316             if (logLevel >= LogLevel.Error)
317                 logf(LogLevel.Error, args);
318         }
319     }
320     /// Log fatal error message
321     static public void f(S...)(S args) {
322         synchronized(mutex) {
323             if (logLevel >= LogLevel.Fatal)
324                 log(LogLevel.Fatal, args);
325         }
326     }
327     /// Log fatal error message
328     static public void ff(S...)(S args) {
329         synchronized(mutex) {
330             if (logLevel >= LogLevel.Fatal)
331                 logf(LogLevel.Fatal, args);
332         }
333     }
334 
335     version (Android) {
336         static public void setLogTag(const char * tag) {
337             ANDROID_LOG_TAG = tag;
338         }
339     }
340 }
341 
342 debug {
343     private static __gshared bool _appShuttingDown = false;
344 
345     @property bool appShuttingDown() { return _appShuttingDown; }
346 
347     /// for debug purposes - sets shutdown flag to log widgets not destroyed in time.
348     void setAppShuttingDownFlag() {
349         _appShuttingDown = true;
350     }
351 }
352 
353 void onResourceDestroyWhileShutdown(string resourceName, string objname = null) {
354     Log.e("Resource leak: destroying resource while shutdown! ", resourceName, " ", objname);
355 }
356 
357 /// set to true when exiting main - to detect destructor calls for resources by GC
358 __gshared bool APP_IS_SHUTTING_DOWN = false;