1 /*******************************************************************************
2   Copyright (C) 2012 Evidence Srl - www.evidence.eu.com
3 
4  Adapted to INDI Library by Jasem Mutlaq & Geehalel.
5 
6  This library is free software; you can redistribute it and/or
7  modify it under the terms of the GNU Library General Public
8  License version 2 as published by the Free Software Foundation.
9 
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  Library General Public License for more details.
14 
15  You should have received a copy of the GNU Library General Public License
16  along with this library; see the file COPYING.LIB.  If not, write to
17  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  Boston, MA 02110-1301, USA.
19 *******************************************************************************/
20 
21 #include "indilogger.h"
22 #include "indiutility.h"
23 
24 #include <dirent.h>
25 #include <cerrno>
26 #include <iostream>
27 #include <cstdlib>
28 #include <cstring>
29 #include <sys/stat.h>
30 
31 namespace INDI
32 {
33 char Logger::Tags[Logger::nlevels][MAXINDINAME] = { "ERROR",       "WARNING",     "INFO",        "DEBUG",
34                                                     "DBG_EXTRA_1", "DBG_EXTRA_2", "DBG_EXTRA_3", "DBG_EXTRA_4" };
35 
36 struct Logger::switchinit Logger::DebugLevelSInit[] = { { "DBG_ERROR", "Errors", ISS_ON, DBG_ERROR },
37                                                         { "DBG_WARNING", "Warnings", ISS_ON, DBG_WARNING },
38                                                         { "DBG_SESSION", "Messages", ISS_ON, DBG_SESSION },
39                                                         { "DBG_DEBUG", "Driver Debug", ISS_OFF, DBG_DEBUG },
40                                                         { "DBG_EXTRA_1", "Debug Extra 1", ISS_OFF, DBG_EXTRA_1 },
41                                                         { "DBG_EXTRA_2", "Debug Extra 2", ISS_OFF, DBG_EXTRA_2 },
42                                                         { "DBG_EXTRA_3", "Debug Extra 3", ISS_OFF, DBG_EXTRA_3 },
43                                                         { "DBG_EXTRA_4", "Debug Extra 4", ISS_OFF, DBG_EXTRA_4 } };
44 
45 struct Logger::switchinit Logger::LoggingLevelSInit[] = {
46     { "LOG_ERROR", "Errors", ISS_ON, DBG_ERROR },           { "LOG_WARNING", "Warnings", ISS_ON, DBG_WARNING },
47     { "LOG_SESSION", "Messages", ISS_ON, DBG_SESSION },     { "LOG_DEBUG", "Driver Debug", ISS_OFF, DBG_DEBUG },
48     { "LOG_EXTRA_1", "Log Extra 1", ISS_OFF, DBG_EXTRA_1 }, { "LOG_EXTRA_2", "Log Extra 2", ISS_OFF, DBG_EXTRA_2 },
49     { "LOG_EXTRA_3", "Log Extra 3", ISS_OFF, DBG_EXTRA_3 }, { "LOG_EXTRA_4", "Log Extra 4", ISS_OFF, DBG_EXTRA_4 }
50 };
51 
52 ISwitch Logger::DebugLevelS[Logger::nlevels];
53 ISwitchVectorProperty Logger::DebugLevelSP;
54 ISwitch Logger::LoggingLevelS[Logger::nlevels];
55 ISwitchVectorProperty Logger::LoggingLevelSP;
56 ISwitch Logger::ConfigurationS[2];
57 ISwitchVectorProperty Logger::ConfigurationSP;
58 
59 INDI::DefaultDevice *Logger::parentDevice  = nullptr;
60 unsigned int Logger::fileVerbosityLevel_   = Logger::defaultlevel;
61 unsigned int Logger::screenVerbosityLevel_ = Logger::defaultlevel;
62 unsigned int Logger::rememberscreenlevel_  = Logger::defaultlevel;
63 Logger::loggerConf Logger::configuration_  = Logger::screen_on | Logger::file_off;
64 std::string Logger::logDir_;
65 std::string Logger::logFile_;
66 unsigned int Logger::nDevices    = 0;
67 unsigned int Logger::customLevel = 4;
68 
addDebugLevel(const char * debugLevelName,const char * loggingLevelName)69 int Logger::addDebugLevel(const char *debugLevelName, const char *loggingLevelName)
70 {
71     // Cannot create any more levels
72     if (customLevel == nlevels)
73         return -1;
74 
75     strncpy(Tags[customLevel], loggingLevelName, MAXINDINAME);
76     strncpy(DebugLevelSInit[customLevel].label, debugLevelName, MAXINDINAME);
77     strncpy(LoggingLevelSInit[customLevel].label, debugLevelName, MAXINDINAME);
78 
79     return DebugLevelSInit[customLevel++].levelmask;
80 }
81 
initProperties(DefaultDevice * device)82 bool Logger::initProperties(DefaultDevice *device)
83 {
84     nDevices++;
85 
86     for (unsigned int i = 0; i < customLevel; i++)
87     {
88         IUFillSwitch(&DebugLevelS[i], DebugLevelSInit[i].name, DebugLevelSInit[i].label, DebugLevelSInit[i].state);
89         DebugLevelS[i].aux = (void *)&DebugLevelSInit[i].levelmask;
90         IUFillSwitch(&LoggingLevelS[i], LoggingLevelSInit[i].name, LoggingLevelSInit[i].label,
91                      LoggingLevelSInit[i].state);
92         LoggingLevelS[i].aux = (void *)&LoggingLevelSInit[i].levelmask;
93     }
94 
95     IUFillSwitchVector(&DebugLevelSP, DebugLevelS, customLevel, device->getDeviceName(), "DEBUG_LEVEL", "Debug Levels",
96                        OPTIONS_TAB, IP_RW, ISR_NOFMANY, 0, IPS_IDLE);
97     IUFillSwitchVector(&LoggingLevelSP, LoggingLevelS, customLevel, device->getDeviceName(), "LOGGING_LEVEL",
98                        "Logging Levels", OPTIONS_TAB, IP_RW, ISR_NOFMANY, 0, IPS_IDLE);
99 
100     IUFillSwitch(&ConfigurationS[0], "CLIENT_DEBUG", "To Client", ISS_ON);
101     IUFillSwitch(&ConfigurationS[1], "FILE_DEBUG", "To Log File", ISS_OFF);
102     IUFillSwitchVector(&ConfigurationSP, ConfigurationS, 2, device->getDeviceName(), "LOG_OUTPUT", "Log Output",
103                        OPTIONS_TAB, IP_RW, ISR_NOFMANY, 0, IPS_IDLE);
104 
105     parentDevice = device;
106 
107     return true;
108 }
109 
updateProperties(bool enable)110 bool Logger::updateProperties(bool enable)
111 {
112     if (enable)
113     {
114         parentDevice->defineProperty(&DebugLevelSP);
115         parentDevice->defineProperty(&LoggingLevelSP);
116 
117         screenVerbosityLevel_ = rememberscreenlevel_;
118 
119         parentDevice->defineProperty(&ConfigurationSP);
120     }
121     else
122     {
123         parentDevice->deleteProperty(DebugLevelSP.name);
124         parentDevice->deleteProperty(LoggingLevelSP.name);
125         parentDevice->deleteProperty(ConfigurationSP.name);
126         rememberscreenlevel_  = screenVerbosityLevel_;
127         screenVerbosityLevel_ = defaultlevel;
128     }
129 
130     return true;
131 }
132 
saveConfigItems(FILE * fp)133 bool Logger::saveConfigItems(FILE *fp)
134 {
135     IUSaveConfigSwitch(fp, &DebugLevelSP);
136     IUSaveConfigSwitch(fp, &LoggingLevelSP);
137     IUSaveConfigSwitch(fp, &ConfigurationSP);
138 
139     return true;
140 }
141 
ISNewSwitch(const char * dev,const char * name,ISState * states,char * names[],int n)142 bool Logger::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
143 {
144     int debug_level = 0, log_level = 0, bitmask = 0, verbose_level = 0;
145     if (strcmp(name, "DEBUG_LEVEL") == 0)
146     {
147         ISwitch *sw;
148         IUUpdateSwitch(&DebugLevelSP, states, names, n);
149         sw = IUFindOnSwitch(&DebugLevelSP);
150         if (sw == nullptr)
151         {
152             DebugLevelSP.s = IPS_IDLE;
153             IDSetSwitch(&DebugLevelSP, nullptr);
154             screenVerbosityLevel_ = 0;
155             return true;
156         }
157 
158         for (int i = 0; i < DebugLevelSP.nsp; i++)
159         {
160             sw      = &DebugLevelSP.sp[i];
161             bitmask = *((unsigned int *)sw->aux);
162             if (sw->s == ISS_ON)
163             {
164                 debug_level = i;
165                 verbose_level |= bitmask;
166             }
167             else
168                 verbose_level &= ~bitmask;
169         }
170 
171         screenVerbosityLevel_ = verbose_level;
172 
173         DEBUGFDEVICE(dev, Logger::DBG_DEBUG, "Toggle Debug Level -- %s", DebugLevelSInit[debug_level].label);
174         DebugLevelSP.s = IPS_OK;
175         IDSetSwitch(&DebugLevelSP, nullptr);
176         return true;
177     }
178 
179     if (strcmp(name, "LOGGING_LEVEL") == 0)
180     {
181         ISwitch *sw;
182         IUUpdateSwitch(&LoggingLevelSP, states, names, n);
183         sw = IUFindOnSwitch(&LoggingLevelSP);
184         if (sw == nullptr)
185         {
186             fileVerbosityLevel_ = 0;
187             LoggingLevelSP.s    = IPS_IDLE;
188             IDSetSwitch(&LoggingLevelSP, nullptr);
189             return true;
190         }
191 
192         for (int i = 0; i < LoggingLevelSP.nsp; i++)
193         {
194             sw      = &LoggingLevelSP.sp[i];
195             bitmask = *((unsigned int *)sw->aux);
196             if (sw->s == ISS_ON)
197             {
198                 log_level = i;
199                 fileVerbosityLevel_ |= bitmask;
200             }
201             else
202                 fileVerbosityLevel_ &= ~bitmask;
203         }
204 
205         DEBUGFDEVICE(dev, Logger::DBG_DEBUG, "Toggle Logging Level -- %s", LoggingLevelSInit[log_level].label);
206         LoggingLevelSP.s = IPS_OK;
207         IDSetSwitch(&LoggingLevelSP, nullptr);
208         return true;
209     }
210 
211     if (!strcmp(name, "LOG_OUTPUT"))
212     {
213         ISwitch *sw;
214         IUUpdateSwitch(&ConfigurationSP, states, names, n);
215         sw = IUFindOnSwitch(&ConfigurationSP);
216 
217         if (sw == nullptr)
218         {
219             configuration_    = screen_off | file_off;
220             ConfigurationSP.s = IPS_IDLE;
221             IDSetSwitch(&ConfigurationSP, nullptr);
222             return true;
223         }
224 
225         bool wasFileOff = configuration_ & file_off;
226 
227         configuration_ = (loggerConf)0;
228 
229         if (ConfigurationS[1].s == ISS_ON)
230             configuration_ = configuration_ | file_on;
231         else
232             configuration_ = configuration_ | file_off;
233 
234         if (ConfigurationS[0].s == ISS_ON)
235             configuration_ = configuration_ | screen_on;
236         else
237             configuration_ = configuration_ | screen_off;
238 
239         // If file was off, then on again
240         if (wasFileOff && (configuration_ & file_on))
241             Logger::getInstance().configure(logFile_, configuration_, fileVerbosityLevel_, screenVerbosityLevel_);
242 
243         ConfigurationSP.s = IPS_OK;
244         IDSetSwitch(&ConfigurationSP, nullptr);
245 
246         return true;
247     }
248 
249     return false;
250 }
251 
252 // Definition (and initialization) of static attributes
253 Logger *Logger::m_ = nullptr;
254 
255 #ifdef LOGGER_MULTITHREAD
256 pthread_mutex_t Logger::lock_ = PTHREAD_MUTEX_INITIALIZER;
lock()257 inline void Logger::lock()
258 {
259     pthread_mutex_lock(&lock_);
260 }
261 
unlock()262 inline void Logger::unlock()
263 {
264     pthread_mutex_unlock(&lock_);
265 }
266 #else
lock()267 void Logger::lock()
268 {
269 }
unlock()270 void Logger::unlock()
271 {
272 }
273 #endif
274 
Logger()275 Logger::Logger() : configured_(false)
276 {
277     gettimeofday(&initialTime_, nullptr);
278 }
279 
configure(const std::string & outputFile,const loggerConf configuration,const int fileVerbosityLevel,const int screenVerbosityLevel)280 void Logger::configure(const std::string &outputFile, const loggerConf configuration, const int fileVerbosityLevel,
281                        const int screenVerbosityLevel)
282 {
283     Logger::lock();
284 
285     fileVerbosityLevel_   = fileVerbosityLevel;
286     screenVerbosityLevel_ = screenVerbosityLevel;
287     rememberscreenlevel_  = screenVerbosityLevel_;
288     // Close the old stream, if needed
289     if (configuration_ & file_on)
290         out_.close();
291 
292     // Compute a new file name, if needed
293     if (outputFile != logFile_)
294     {
295         char ts_date[32], ts_time[32];
296         struct tm *tp;
297         time_t t;
298 
299         time(&t);
300         tp = gmtime(&t);
301         strftime(ts_date, sizeof(ts_date), "%Y-%m-%d", tp);
302         strftime(ts_time, sizeof(ts_time), "%H:%M:%S", tp);
303 
304         char dir[MAXRBUF];
305         snprintf(dir, MAXRBUF, "%s/.indi/logs/%s/%s", getenv("HOME"), ts_date, outputFile.c_str());
306         logDir_ = dir;
307 
308         char logFileBuf[MAXRBUF];
309         snprintf(logFileBuf, MAXRBUF, "%s/%s_%s.log", dir, outputFile.c_str(), ts_time);
310         logFile_ = logFileBuf;
311     }
312 
313     // Open a new stream, if needed
314     if (configuration & file_on)
315     {
316         INDI::mkpath(logDir_.c_str(), 0775);
317         out_.open(logFile_.c_str(), std::ios::app);
318     }
319 
320     configuration_ = configuration;
321     configured_    = true;
322 
323     Logger::unlock();
324 }
325 
~Logger()326 Logger::~Logger()
327 {
328     Logger::lock();
329     if (configuration_ & file_on)
330         out_.close();
331 
332     m_ = nullptr;
333     Logger::unlock();
334 }
335 
getInstance()336 Logger &Logger::getInstance()
337 {
338     Logger::lock();
339     if (m_ == nullptr)
340         m_ = new Logger;
341     Logger::unlock();
342     return *m_;
343 }
344 
rank(unsigned int l)345 unsigned int Logger::rank(unsigned int l)
346 {
347     switch (l)
348     {
349         case DBG_ERROR:
350             return 0;
351         case DBG_WARNING:
352             return 1;
353         case DBG_SESSION:
354             return 2;
355         case DBG_EXTRA_1:
356             return 4;
357         case DBG_EXTRA_2:
358             return 5;
359         case DBG_EXTRA_3:
360             return 6;
361         case DBG_EXTRA_4:
362             return 7;
363         case DBG_DEBUG:
364         default:
365             return 3;
366     }
367 }
368 
print(const char * devicename,const unsigned int verbosityLevel,const std::string & file,const int line,const char * message,...)369 void Logger::print(const char *devicename, const unsigned int verbosityLevel, const std::string &file, const int line,
370                    //const std::string& message,
371                    const char *message, ...)
372 {
373     // 0 is ignored
374     if (verbosityLevel == 0)
375         return;
376 
377     INDI_UNUSED(file);
378     INDI_UNUSED(line);
379     bool filelog   = (verbosityLevel & fileVerbosityLevel_) != 0;
380     bool screenlog = (verbosityLevel & screenVerbosityLevel_) != 0;
381 
382     va_list ap;
383     char msg[257];
384     char usec[7];
385 
386     msg[256] = '\0';
387     va_start(ap, message);
388     vsnprintf(msg, 257, message, ap);
389     va_end(ap);
390 
391     if (!configured_)
392     {
393         //std::cerr << "Warning! Logger not configured!" << std::endl;
394         std::cerr << msg << std::endl;
395         return;
396     }
397     struct timeval currentTime, resTime;
398     usec[6] = '\0';
399     gettimeofday(&currentTime, nullptr);
400     timersub(&currentTime, &initialTime_, &resTime);
401 #if defined(__APPLE__)
402     snprintf(usec, 7, "%06d", resTime.tv_usec);
403 #else
404     snprintf(usec, 7, "%06ld", resTime.tv_usec);
405 #endif
406     Logger::lock();
407 
408     if ((configuration_ & file_on) && filelog)
409     {
410         if (nDevices == 1)
411             out_ << Tags[rank(verbosityLevel)] << "\t" << (resTime.tv_sec) << "." << (usec) << " sec"
412                  << "\t: " << msg << std::endl;
413         else
414             out_ << Tags[rank(verbosityLevel)] << "\t" << (resTime.tv_sec) << "." << (usec) << " sec"
415                  << "\t: [" << devicename << "] " << msg << std::endl;
416     }
417 
418     if ((configuration_ & screen_on) && screenlog)
419         IDMessage(devicename, "[%s] %s", Tags[rank(verbosityLevel)], msg);
420 
421     Logger::unlock();
422 }
423 }
424