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