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