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