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(¤tTime, nullptr);
424 timersub(¤tTime, &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