Line data Source code
1 : /** 2 : * @file SimpleLogger.h 3 : * @author Damien Balima (www.dams-labs.net) 4 : * @brief a simple logger, using fluent interfaces. 5 : * @date 2023-11-02 6 : * 7 : * @copyright Damien Balima (c) CC-BY-NC-SA-4.0 2023 8 : * 9 : */ 10 : #pragma once 11 : #include <chrono> 12 : #include <ctime> 13 : #include <functional> 14 : #include <iomanip> 15 : #include <iostream> 16 : #include <mutex> 17 : #include <sstream> 18 : 19 : namespace sipai { 20 : enum class LogLevel { INFO, WARN, ERROR, DEBUG }; 21 : 22 : /** 23 : * @brief SimpleLogger class 24 : * 25 : */ 26 : class SimpleLogger { 27 : public: 28 : using LogCallback = std::function<void( 29 : const std::string &, const std::string &, const std::string &)>; 30 : /** 31 : * @brief Get the Instance object 32 : * @remark This use a thread safe Meyers’ Singleton 33 : * @return const SimpleLogger& 34 : */ 35 188 : const static SimpleLogger &getInstance() { 36 188 : static SimpleLogger instance; 37 188 : return instance; 38 : } 39 : SimpleLogger(SimpleLogger const &) = delete; 40 : void operator=(SimpleLogger const &) = delete; 41 : 42 : /** 43 : * @brief Set the Log Callback 44 : * 45 : * @param callback 46 : */ 47 : void setLogCallback(LogCallback callback) { logCallback = callback; } 48 : 49 : /** 50 : * @brief Logs messages with a timestamp and log level. 51 : * 52 : * @tparam Args The types of the arguments that are passed to the method. 53 : * @param level The log level of the message (INFO, WARN, ERROR, DEBUG). 54 : * @param endl A boolean value that determines whether a newline character is 55 : * appended at the end of the log message. The default value is true. 56 : * @param args The arguments that make up the log message. These are forwarded 57 : * to the output stream. 58 : * @return const SimpleLogger& A reference to the SimpleLogger instance. 59 : */ 60 : template <typename... Args> 61 186 : const SimpleLogger &log(LogLevel level, bool endl, Args &&...args) const { 62 186 : std::scoped_lock<std::mutex> lock(threadMutex_); 63 186 : currentLevel = level; 64 : 65 186 : std::ostringstream streamArgs; 66 186 : (streamArgs << ... << args); 67 : 68 186 : std::ostream *stream = (level == LogLevel::INFO) ? osout : oserr; 69 186 : *stream << "[" << get_timestamp() << "] [" << toString(level) << "] "; 70 186 : stream->precision(current_precision); 71 186 : *stream << std::fixed; 72 186 : *stream << streamArgs.str(); 73 186 : if (endl) { 74 186 : *stream << std::endl; 75 : } 76 : 77 : // Call the log callback if set 78 186 : if (logCallback) { 79 0 : logCallback(get_timestamp(), toString(level), streamArgs.str()); 80 : } 81 186 : return *this; 82 186 : } 83 : 84 : /** 85 : * @brief Appends messages to the output stream. 86 : * 87 : * @tparam Args The types of the arguments that are passed to the method. 88 : * @param args The arguments that make up the message. These are forwarded to 89 : * the output stream. 90 : * @return const SimpleLogger& A reference to the SimpleLogger instance. 91 : */ 92 : template <typename... Args> const SimpleLogger &append(Args &&...args) const { 93 : std::scoped_lock<std::mutex> lock(threadMutex_); 94 : std::ostream *stream = (currentLevel == LogLevel::INFO) ? osout : oserr; 95 : stream->precision(current_precision); 96 : (*stream << ... << args); 97 : return *this; 98 : } 99 : 100 : /** 101 : * @brief Outputs messages to the output stream with a newline 102 : * character at the end, without timestamp and additional infos. 103 : * 104 : * @tparam Args The types of the arguments that are passed to the method. 105 : * @param args The arguments that make up the message. These are forwarded to 106 : * the output stream. 107 : * @return const SimpleLogger& A reference to the SimpleLogger instance. 108 : */ 109 : template <typename... Args> const SimpleLogger &out(Args &&...args) const { 110 : std::scoped_lock<std::mutex> lock(threadMutex_); 111 : osout->precision(current_precision); 112 : (*osout << ... << args); 113 : *osout << std::endl; 114 : return *this; 115 : } 116 : 117 : /** 118 : * @brief Outputs messages to the error stream with a newline 119 : * character at the end, without timestamp and additional infos. 120 : * 121 : * @tparam Args The types of the arguments that are passed to the method. 122 : * @param args The arguments that make up the message. These are forwarded to 123 : * the error stream. 124 : * @return const SimpleLogger& A reference to the SimpleLogger instance. 125 : */ 126 : template <typename... Args> const SimpleLogger &err(Args &&...args) const { 127 : std::scoped_lock<std::mutex> lock(threadMutex_); 128 : oserr->precision(current_precision); 129 : (*oserr << ... << args); 130 : *oserr << std::endl; 131 : return *this; 132 : } 133 : 134 : /** 135 : * @brief Logs messages with the INFO log level. 136 : * 137 : * @tparam Args The types of the arguments that are passed to the method. 138 : * @param args The arguments that make up the log message. These are forwarded 139 : * to the output stream. 140 : * @return const SimpleLogger& A reference to the SimpleLogger instance. 141 : */ 142 105 : template <typename... Args> const SimpleLogger &info(Args &&...args) const { 143 105 : return log(LogLevel::INFO, true, args...); 144 : } 145 : 146 : /** 147 : * @brief Logs messages with the WARN log level. 148 : * 149 : * @tparam Args The types of the arguments that are passed to the method. 150 : * @param args The arguments that make up the log message. These are forwarded 151 : * to the error stream. 152 : * @return const SimpleLogger& A reference to the SimpleLogger instance. 153 : */ 154 1 : template <typename... Args> const SimpleLogger &warn(Args &&...args) const { 155 1 : return log(LogLevel::WARN, true, args...); 156 : } 157 : 158 : /** 159 : * @brief Logs messages with the ERROR log level. 160 : * 161 : * @tparam Args The types of the arguments that are passed to the method. 162 : * @param args The arguments that make up the log message. These are forwarded 163 : * to the error stream. 164 : * @return const SimpleLogger& A reference to the SimpleLogger instance. 165 : */ 166 1 : template <typename... Args> const SimpleLogger &error(Args &&...args) const { 167 1 : return log(LogLevel::ERROR, true, args...); 168 : } 169 : 170 : /** 171 : * @brief Logs messages with the DEBUG log level. 172 : * 173 : * @tparam Args The types of the arguments that are passed to the method. 174 : * @param args The arguments that make up the log message. These are forwarded 175 : * to the error stream. 176 : * @return const SimpleLogger& A reference to the SimpleLogger instance. 177 : */ 178 74 : template <typename... Args> const SimpleLogger &debug(Args &&...args) const { 179 74 : return log(LogLevel::DEBUG, true, args...); 180 : } 181 : 182 : /** 183 : * @brief Outputs a newline character and flushes the stream. 184 : * @return const SimpleLogger& A reference to the SimpleLogger instance. 185 : */ 186 : const SimpleLogger &endl() const { 187 : std::scoped_lock<std::mutex> lock(threadMutex_); 188 : std::ostream *stream = (currentLevel == LogLevel::INFO) ? osout : oserr; 189 : *stream << std::endl; 190 : stream->flush(); 191 : return *this; 192 : } 193 : 194 : /** 195 : * @brief Set the Stream Out 196 : * 197 : * @param oss 198 : * @return const SimpleLogger& 199 : */ 200 2 : const SimpleLogger &setStreamOut(std::ostream &oss) const { 201 2 : std::scoped_lock<std::mutex> lock(threadMutex_); 202 2 : osout = &oss; 203 2 : return *this; 204 2 : } 205 : 206 : /** 207 : * @brief Set the Stream Err 208 : * 209 : * @param oss 210 : * @return const SimpleLogger& 211 : */ 212 2 : const SimpleLogger &setStreamErr(std::ostream &oss) const { 213 2 : std::scoped_lock<std::mutex> lock(threadMutex_); 214 2 : oserr = &oss; 215 2 : return *this; 216 2 : } 217 : 218 : /** 219 : * @brief Set the floating precision 220 : * 221 : * @param precision 222 : * @return const SimpleLogger& 223 : */ 224 6 : const SimpleLogger &setPrecision(std::streamsize precision) const { 225 6 : std::scoped_lock<std::mutex> lock(threadMutex_); 226 6 : current_precision = precision; 227 6 : return *this; 228 6 : } 229 : 230 : /** 231 : * @brief Get the current floating precision 232 : * 233 : * @return precision 234 : */ 235 2 : const std::streamsize &getPrecision() const { return current_precision; } 236 : 237 : /** 238 : * @brief Reset the floating precision 239 : * 240 : * @return const SimpleLogger& 241 : */ 242 : const SimpleLogger &resetPrecision() const { 243 : std::scoped_lock<std::mutex> lock(threadMutex_); 244 : current_precision = default_precision; 245 : return *this; 246 : } 247 : 248 : /** 249 : * @brief static shortcut for log info. 250 : * @tparam Args 251 : * @param args 252 : * @return const SimpleLogger& 253 : */ 254 : template <typename... Args> 255 105 : static const SimpleLogger &LOG_INFO(Args &&...args) { 256 105 : return getInstance().info(args...); 257 : } 258 : 259 : /** 260 : * @brief static shortcut for log warning. 261 : * @tparam Args 262 : * @param args 263 : * @return const SimpleLogger& 264 : */ 265 : template <typename... Args> 266 1 : static const SimpleLogger &LOG_WARN(Args &&...args) { 267 1 : return getInstance().warn(args...); 268 : } 269 : 270 : /** 271 : * @brief static shortcut for log error. 272 : * @tparam Args 273 : * @param args 274 : * @return const SimpleLogger& 275 : */ 276 : template <typename... Args> 277 1 : static const SimpleLogger &LOG_ERROR(Args &&...args) { 278 1 : return getInstance().error(args...); 279 : } 280 : 281 : /** 282 : * @brief static shortcut for log debug. 283 : * @tparam Args 284 : * @param args 285 : * @return const SimpleLogger& 286 : */ 287 : template <typename... Args> 288 74 : static const SimpleLogger &LOG_DEBUG(Args &&...args) { 289 74 : return getInstance().debug(args...); 290 : } 291 : 292 : private: 293 1 : SimpleLogger() { 294 1 : osout = &std::cout; 295 1 : oserr = &std::cerr; 296 1 : }; 297 : std::streamsize default_precision = std::cout.precision(); 298 : mutable std::streamsize current_precision = std::cout.precision(); 299 : mutable std::mutex threadMutex_; 300 : mutable std::ostream *osout; 301 : mutable std::ostream *oserr; 302 : mutable LogLevel currentLevel = LogLevel::INFO; 303 : LogCallback logCallback = {}; 304 : 305 186 : std::string get_timestamp() const { 306 186 : auto now = std::chrono::system_clock::now(); 307 186 : auto now_c = std::chrono::system_clock::to_time_t(now); 308 186 : std::tm now_tm{}; 309 186 : std::stringstream sst; 310 : #if defined(WIN32) || defined(_WIN32) || \ 311 : defined(__WIN32) && !defined(__CYGWIN__) 312 : localtime_s(&now_tm, &now_c); 313 : #else 314 186 : localtime_r(&now_c, &now_tm); 315 : #endif 316 : 317 186 : sst << std::put_time(&now_tm, "%F %T"); 318 : 319 372 : return sst.str(); 320 186 : } 321 : 322 186 : std::string toString(LogLevel level) const { 323 186 : switch (level) { 324 106 : case LogLevel::INFO: 325 106 : return "INFO"; 326 2 : case LogLevel::WARN: 327 2 : return "WARN"; 328 2 : case LogLevel::ERROR: 329 2 : return "ERROR"; 330 75 : case LogLevel::DEBUG: 331 75 : return "DEBUG"; 332 1 : default: 333 1 : return "UNKNOWN"; 334 : } 335 : } 336 : }; 337 : } // namespace sipai