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 #pragma once
22 
23 #include "indiapi.h"
24 #include "defaultdevice.h"
25 
26 #include <stdarg.h>
27 #include <fstream>
28 #include <ostream>
29 #include <string>
30 #include <sstream>
31 #ifdef Q_OS_WIN
32     #include <windows.h>
33 #else
34     #include <sys/time.h>
35 #endif
36 
37 /**
38  * @brief Macro to configure the logger.
39  * Example of configuration of the Logger:
40  * 	DEBUG_CONF("outputfile", Logger::file_on|Logger::screen_on, DBG_DEBUG, DBG_ERROR);
41  */
42 #define DEBUG_CONF(outputFile, configuration, fileVerbosityLevel, screenVerbosityLevel)                       \
43     {                                                                                                         \
44         Logger::getInstance().configure(outputFile, configuration, fileVerbosityLevel, screenVerbosityLevel); \
45     }
46 
47 /**
48  * @brief Macro to print log messages.
49  * Example of usage of the Logger:
50  *	    DEBUG(DBG_DEBUG, "hello " << "world");
51  */
52 /*
53 #define DEBUG(priority, msg) { \
54 	std::ostringstream __debug_stream__; \
55 	__debug_stream__ << msg; \
56 	Logger::getInstance().print(priority, __FILE__, __LINE__, \
57 			__debug_stream__.str()); \
58 	}
59 */
60 #define DEBUG(priority, msg) INDI::Logger::getInstance().print(getDeviceName(), priority, __FILE__, __LINE__, msg)
61 #define DEBUGF(priority, msg, ...) \
62     INDI::Logger::getInstance().print(getDeviceName(), priority, __FILE__, __LINE__, msg, __VA_ARGS__)
63 
64 #define DEBUGDEVICE(device, priority, msg) INDI::Logger::getInstance().print(device, priority, __FILE__, __LINE__, msg)
65 #define DEBUGFDEVICE(device, priority, msg, ...) \
66     INDI::Logger::getInstance().print(device, priority, __FILE__, __LINE__, msg, __VA_ARGS__)
67 
68 /**
69  * @brief Shorter logging macros. In order to use these macros, the function
70  * (or method) "getDeviceName()" must be defined in the calling scope.
71  *
72  * Usage examples:
73  *	    LOG_DEBUG("hello " << "world");
74  *	    LOGF_WARN("hello %s", "world");
75  */
76 #define LOG_ERROR(txt)  DEBUG(INDI::Logger::DBG_ERROR, (txt))
77 #define LOG_WARN(txt)   DEBUG(INDI::Logger::DBG_WARNING, (txt))
78 #define LOG_INFO(txt)   DEBUG(INDI::Logger::DBG_SESSION, (txt))
79 #define LOG_DEBUG(txt)  DEBUG(INDI::Logger::DBG_DEBUG, (txt))
80 #define LOG_EXTRA1(txt)  DEBUG(INDI::Logger::DBG_EXTRA_1, (txt))
81 #define LOG_EXTRA2(txt)  DEBUG(INDI::Logger::DBG_EXTRA_2, (txt))
82 #define LOG_EXTRA3(txt)  DEBUG(INDI::Logger::DBG_EXTRA_3, (txt))
83 
84 #define LOGF_ERROR(fmt, ...) DEBUGF(INDI::Logger::DBG_ERROR, (fmt), __VA_ARGS__)
85 #define LOGF_WARN(fmt, ...)  DEBUGF(INDI::Logger::DBG_WARNING, (fmt), __VA_ARGS__)
86 #define LOGF_INFO(fmt, ...)  DEBUGF(INDI::Logger::DBG_SESSION, (fmt), __VA_ARGS__)
87 #define LOGF_DEBUG(fmt, ...) DEBUGF(INDI::Logger::DBG_DEBUG, (fmt), __VA_ARGS__)
88 #define LOGF_EXTRA1(fmt, ...) DEBUGF(INDI::Logger::DBG_EXTRA_1, (fmt), __VA_ARGS__)
89 #define LOGF_EXTRA2(fmt, ...) DEBUGF(INDI::Logger::DBG_EXTRA_2, (fmt), __VA_ARGS__)
90 #define LOGF_EXTRA3(fmt, ...) DEBUGF(INDI::Logger::DBG_EXTRA_3, (fmt), __VA_ARGS__)
91 
92 
93 namespace INDI
94 {
95 /**
96  * @class INDI::Logger
97  * @brief The Logger class is a simple logger to log messages to file and INDI clients. This is the implementation of a simple
98  *  logger in C++. It is implemented as a Singleton, so it can be easily called through two DEBUG macros.
99  * It is Pthread-safe. It allows to log on both file and screen, and to specify a verbosity threshold for both of them.
100  *
101  * - By default, the class defines 4 levels of debugging/logging levels:
102  *      -# Errors: Use macro DEBUG(INDI::Logger::DBG_ERROR, "My Error Message)
103  *
104  *      -# Warnings: Use macro DEBUG(INDI::Logger::DBG_WARNING, "My Warning Message)
105  *
106  *      -# Session: Use macro DEBUG(INDI::Logger::DBG_SESSION, "My Message) Session messages are the regular status messages from the driver.
107  *
108  *      -# Driver Debug: Use macro DEBUG(INDI::Logger::DBG_DEBUG, "My Driver Debug Message)
109  *
110  * @note Use DEBUGF macro if you have a variable list message. e.g. DEBUGF(INDI::Logger::DBG_SESSION, "Hello %s!", "There")
111  *
112  * The default \e active debug levels are Error, Warning, and Session. Driver Debug can be enabled by the client.
113  *
114  * To add a new debug level, call addDebugLevel(). You can add an additional 4 custom debug/logging levels.
115  *
116  * Check INDI Tutorial two for an example simple implementation.
117  */
118 class Logger
119 {
120     /** Type used for the configuration */
121     enum loggerConf_
122     {
123         L_nofile_   = 1 << 0,
124         L_file_     = 1 << 1,
125         L_noscreen_ = 1 << 2,
126         L_screen_   = 1 << 3
127     };
128 
129 #ifdef LOGGER_MULTITHREAD
130     /// Lock for mutual exclusion between different threads
131     static pthread_mutex_t lock_;
132 #endif
133 
134     bool configured_ { false };
135 
136     /** Pointer to the unique Logger (i.e., Singleton) */
137     static Logger *m_;
138 
139     /**
140      * @brief Initial part of the name of the file used for Logging.
141      * Date and time are automatically appended.
142      */
143     static std::string logFile_;
144 
145     /**
146      * @brief Directory where log file is stored. it is created under ~/.indi/logs/[DATE]/[DRIVER_EXEC]
147      */
148     static std::string logDir_;
149 
150     /**
151      * @brief Current configuration of the logger.
152      * Variable to know if logging on file and on screen are enabled. Note that if the log on
153      * file is enabled, it means that the logger has been already configured, therefore the
154      * stream is already open.
155      */
156     static loggerConf_ configuration_;
157 
158     /// Stream used when logging on a file
159     std::ofstream out_;
160     /// Initial time (used to print relative times)
161     struct timeval initialTime_;
162     /// Verbosity threshold for files
163     static unsigned int fileVerbosityLevel_;
164     /// Verbosity threshold for screen
165     static unsigned int screenVerbosityLevel_;
166     static unsigned int rememberscreenlevel_;
167 
168     /**
169      * @brief Constructor.
170      * It is a private constructor, called only by getInstance() and only the
171      * first time. It is called inside a lock, so lock inside this method
172      * is not required.
173      * It only initializes the initial time. All configuration is done inside the
174      * configure() method.
175      */
176     Logger();
177 
178     /**
179      * @brief Destructor.
180      * It only closes the file, if open, and cleans memory.
181      */
182     ~Logger();
183 
184     /** Method to lock in case of multithreading */
185     inline static void lock();
186 
187     /** Method to unlock in case of multithreading */
188     inline static void unlock();
189 
190     static INDI::DefaultDevice *parentDevice;
191 
192   public:
193     enum VerbosityLevel
194     {
195         DBG_IGNORE  = 0x0,
196         DBG_ERROR   = 0x1,
197         DBG_WARNING = 0x2,
198         DBG_SESSION = 0x4,
199         DBG_DEBUG   = 0x8,
200         DBG_EXTRA_1 = 0x10,
201         DBG_EXTRA_2 = 0X20,
202         DBG_EXTRA_3 = 0x40,
203         DBG_EXTRA_4 = 0x80
204     };
205 
206     struct switchinit
207     {
208         char name[MAXINDINAME];
209         char label[MAXINDILABEL];
210         ISState state;
211         unsigned int levelmask;
212     };
213 
214     static const unsigned int defaultlevel = DBG_ERROR | DBG_WARNING | DBG_SESSION;
215     static const unsigned int nlevels      = 8;
216     static struct switchinit LoggingLevelSInit[nlevels];
217     static ISwitch LoggingLevelS[nlevels];
218     static ISwitchVectorProperty LoggingLevelSP;
219     static ISwitch ConfigurationS[2];
220     static ISwitchVectorProperty ConfigurationSP;
221     typedef loggerConf_ loggerConf;
222     static const loggerConf file_on    = L_nofile_;
223     static const loggerConf file_off   = L_file_;
224     static const loggerConf screen_on  = L_noscreen_;
225     static const loggerConf screen_off = L_screen_;
226     static unsigned int customLevel;
227     static unsigned int nDevices;
228 
getLogFile()229     static std::string getLogFile() { return logFile_; }
getConfiguration()230     static loggerConf_ getConfiguration() { return configuration_; }
231 
232     /**
233      * @brief Method to get a reference to the object (i.e., Singleton)
234      * It is a static method.
235      * @return Reference to the object.
236      */
237     static Logger &getInstance();
238 
239     static bool saveConfigItems(FILE *fp);
240 
241     /**
242      * @brief Adds a new debugging level to the driver.
243      *
244      * @param debugLevelName The descriptive debug level defined to the client. e.g. Scope Status
245      * @param LoggingLevelName the short logging level recorded in the logfile. e.g. SCOPE
246      * @return bitmask of the new debugging level to be used for any subsequent calls to DEBUG and DEBUGF to
247      * record events to this debug level.
248      */
249     int addDebugLevel(const char *debugLevelName, const char *LoggingLevelName);
250 
251     void print(const char *devicename, const unsigned int verbosityLevel, const std::string &sourceFile,
252                const int codeLine,
253                //const std::string& 	message,
254                const char *message, ...);
255 
256     /**
257      * @brief Method to configure the logger. Called by the DEBUG_CONF() macro. To make implementation
258      * easier, the old stream is always closed.
259      * Then, in case, it is open again in append mode.
260      * @param outputFile of the file used for logging
261      * @param configuration (i.e., log on file and on screen on or off)
262      * @param fileVerbosityLevel threshold for file
263      * @param screenVerbosityLevel threshold for screen
264      */
265     void configure(const std::string &outputFile, const loggerConf configuration, const int fileVerbosityLevel,
266                    const int screenVerbosityLevel);
267 
268     static struct switchinit DebugLevelSInit[nlevels];
269     static ISwitch DebugLevelS[nlevels];
270     static ISwitchVectorProperty DebugLevelSP;
271     static bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n);
272     static bool initProperties(INDI::DefaultDevice *device);
273     static bool updateProperties(bool enable);
274     static char Tags[nlevels][MAXINDINAME];
275 
276     /**
277      * @brief Method used to print message called by the DEBUG() macro.
278      * @param i which debugging to query its rank. The lower the rank, the more priority it is.
279      * @return rank of debugging level requested.
280      */
281     static unsigned int rank(unsigned int l);
282 };
283 
284 inline Logger::loggerConf operator|(Logger::loggerConf __a, Logger::loggerConf __b)
285 {
286     return Logger::loggerConf(static_cast<int>(__a) | static_cast<int>(__b));
287 }
288 
289 inline Logger::loggerConf operator&(Logger::loggerConf __a, Logger::loggerConf __b)
290 {
291     return Logger::loggerConf(static_cast<int>(__a) & static_cast<int>(__b));
292 }
293 }
294