/** \file \brief Declares diagnostic message macros. \who \kpr */ #ifndef MUTILS_LOG_H #define MUTILS_LOG_H #include #include #include #include /// \brief Uniform messaging macro with variable debug level. /// /// \param[in] lev Message Log::Level. /// \param[in] src Source name (program or method). /// \param[in] msg Debug message (\b must be a literal string constant). /// \param[in] ... Arguments to satisfy \a msg mutils::cxxprintf directives. /// \warning This \b must be defined as a macro to get proper line information. #define MU_MESSAGE(lev, src, msg, ...) do { \ if ((lev) <= mutils::Log::level) { \ bool db = (mutils::Log::level >= mutils::Log::DEBUG); \ mutils::cxxfprintf(mutils::Log::stream, "%s: %s:%0*d: " msg "\n", \ mutils::Log::name(lev), (db ? (src) : ""), \ (db ? 1 : 4), __LINE__, ## __VA_ARGS__); \ } \ } while (0) /// \brief Conditionally log a message at specific level. /// /// \param[in] lev Message log level. /// \param[in] cond Failure test condition. /// \param[in] src Source name (program or method). /// \param[in] msg Error message (\b must be a literal string constant). /// \param[in] ... Arguments to satisfy \a msg mutils::cxxprintf directives. /// \warning This \b must be defined as a macro to get proper line information. #define MU_MESSAGE_IF(lev, cond, src, msg, ...) do { \ if (cond) { MU_MESSAGE(lev, src, msg, ## __VA_ARGS__); } \ } while (0) /// \brief Conditionally log a message at a specific level (with return). /// /// \param[in] lev Message log level. /// \param[in] rtn Failure return value. /// \param[in] cond Failure test condition. /// \param[in] src Source name (program or method). /// \param[in] msg Error message (\b must be a literal string constant). /// \param[in] ... Arguments to satisfy \a msg mutils::cxxprintf directives. /// \post If \a cond is true the caller will return with a value of \a rtn. /// \warning This \b must be defined as a macro to get proper line information. #define MU_MESSAGE_RTN(lev, rtn, cond, src, msg, ...) do { \ if (cond) { \ MU_MESSAGE(lev, src, msg, ## __VA_ARGS__); \ return (rtn); \ } \ } while (0) /// \brief Log a message at level Log::DEBUG. /// /// \param[in] src Source name (program or method). /// \param[in] msg Debug message (\b must be a literal string constant). /// \param[in] ... Arguments to satisfy \a msg mutils::cxxprintf directives. /// \warning This \b must be defined as a macro to get proper line information. #define MU_DEBUG(src, msg, ...) \ MU_MESSAGE(mutils::Log::DEBUG, src, msg, ## __VA_ARGS__) /// \brief Conditionally log a message at level Log::DEBUG. /// /// \param[in] cond Failure test condition. /// \param[in] src Source name (program or method). /// \param[in] msg Error message (\b must be a literal string constant). /// \param[in] ... Arguments to satisfy \a msg mutils::cxxprintf directives. /// \warning This \b must be defined as a macro to get proper line information. #define MU_DEBUG_IF(cond, src, msg, ...) \ MU_MESSAGE_IF(mutils::Log::DEBUG, cond, src, msg, ## __VA_ARGS__) /// \brief Conditionally log a message at level Log::DEBUG (with return). /// /// \param[in] rtn Failure return value. /// \param[in] cond Failure test condition. /// \param[in] src Source name (program or method). /// \param[in] msg Error message (\b must be a literal string constant). /// \param[in] ... Arguments to satisfy \a msg mutils::cxxprintf directives. /// \post If \a cond is true the caller will return with a value of \a rtn. /// \warning This \b must be defined as a macro to get proper line information. #define MU_DEBUG_RTN(rtn, cond, src, msg, ...) \ MU_MESSAGE_RTN(mutils::Log::DEBUG, rtn, cond, src, msg, ## __VA_ARGS__) /// \brief Log a message at level Log::INFO. /// /// \param[in] src Source name (program or method). /// \param[in] msg Debug message (\b must be a literal string constant). /// \param[in] ... Arguments to satisfy \a msg mutils::cxxprintf directives. /// \warning This \b must be defined as a macro to get proper line information. #define MU_INFO(src, msg, ...) \ MU_MESSAGE(mutils::Log::INFO, src, msg, ## __VA_ARGS__) /// \brief Conditionally log a message at level Log::INFO. /// /// \param[in] cond Failure test condition. /// \param[in] src Source name (program or method). /// \param[in] msg Error message (\b must be a literal string constant). /// \param[in] ... Arguments to satisfy \a msg mutils::cxxprintf directives. /// \warning This \b must be defined as a macro to get proper line information. #define MU_INFO_IF(cond, src, msg, ...) \ MU_MESSAGE_IF(mutils::Log::INFO, cond, src, msg, ## __VA_ARGS__) /// \brief Conditionally log a message at level Log::INFO (with return). /// /// \param[in] rtn Failure return value. /// \param[in] cond Failure test condition. /// \param[in] src Source name (program or method). /// \param[in] msg Error message (\b must be a literal string constant). /// \param[in] ... Arguments to satisfy \a msg mutils::cxxprintf directives. /// \post If \a cond is true the caller will return with a value of \a rtn. /// \warning This \b must be defined as a macro to get proper line information. #define MU_INFO_RTN(rtn, cond, src, msg, ...) \ MU_MESSAGE_RTN(mutils::Log::INFO, rtn, cond, src, msg, ## __VA_ARGS__) /// \brief Log a message at level Log::WARN. /// /// \param[in] src Source name (program or method). /// \param[in] msg Debug message (\b must be a literal string constant). /// \param[in] ... Arguments to satisfy \a msg mutils::cxxprintf directives. /// \warning This \b must be defined as a macro to get proper line information. #define MU_WARN(src, msg, ...) \ MU_MESSAGE(mutils::Log::WARN, src, msg, ## __VA_ARGS__) /// \brief Conditionally log a message at level Log::WARN. /// /// \param[in] cond Failure test condition. /// \param[in] src Source name (program or method). /// \param[in] msg Error message (\b must be a literal string constant). /// \param[in] ... Arguments to satisfy \a msg mutils::cxxprintf directives. /// \warning This \b must be defined as a macro to get proper line information. #define MU_WARN_IF(cond, src, msg, ...) \ MU_MESSAGE_IF(mutils::Log::WARN, cond, src, msg, ## __VA_ARGS__) /// \brief Conditionally log a message at level Log::WARN (with return). /// /// \param[in] rtn Failure return value. /// \param[in] cond Failure test condition. /// \param[in] src Source name (program or method). /// \param[in] msg Error message (\b must be a literal string constant). /// \param[in] ... Arguments to satisfy \a msg mutils::cxxprintf directives. /// \post If \a cond is true the caller will return with a value of \a rtn. /// \warning This \b must be defined as a macro to get proper line information. #define MU_WARN_RTN(rtn, cond, src, msg, ...) \ MU_MESSAGE_RTN(mutils::Log::WARN, rtn, cond, src, msg, ## __VA_ARGS__) /// \brief Log a message at level Log::ERROR. /// /// \param[in] src Source name (program or method). /// \param[in] msg Debug message (\b must be a literal string constant). /// \param[in] ... Arguments to satisfy \a msg mutils::cxxprintf directives. /// \warning This \b must be defined as a macro to get proper line information. #define MU_ERROR(src, msg, ...) \ MU_MESSAGE(mutils::Log::ERROR, src, msg, ## __VA_ARGS__) /// \brief Conditionally log a message at level Log::ERROR. /// /// \param[in] cond Failure test condition. /// \param[in] src Source name (program or method). /// \param[in] msg Error message (\b must be a literal string constant). /// \param[in] ... Arguments to satisfy \a msg mutils::cxxprintf directives. /// \warning This \b must be defined as a macro to get proper line information. #define MU_ERROR_IF(cond, src, msg, ...) \ MU_MESSAGE_IF(mutils::Log::ERROR, cond, src, msg, ## __VA_ARGS__) /// \brief Conditionally log a message at level Log::ERROR (with return). /// /// \param[in] rtn Failure return value. /// \param[in] cond Failure test condition. /// \param[in] src Source name (program or method). /// \param[in] msg Error message (\b must be a literal string constant). /// \param[in] ... Arguments to satisfy \a msg mutils::cxxprintf directives. /// \post If \a cond is true the caller will return with a value of \a rtn. /// \warning This \b must be defined as a macro to get proper line information. #define MU_ERROR_RTN(rtn, cond, src, msg, ...) \ MU_MESSAGE_RTN(mutils::Log::ERROR, rtn, cond, src, msg, ## __VA_ARGS__) /// \brief Conditionally log a message at level Log::ERROR (with errno). /// /// \param[in] cond Failure test condition. /// \param[in] src Source name (program or method). /// \param[in] msg Error message (\b must be a literal string constant). /// \param[in] ... Arguments to satisfy \a msg mutils::cxxprintf directives. /// \warning This \b must be defined as a macro to get proper line information. /// \warning This macro is not thread-safe. #define MU_ERRNO_IF(cond, src, msg, ...) \ MU_MESSAGE_IF(mutils::Log::ERROR, cond, src, msg ": %s", \ ## __VA_ARGS__, strerror(errno)) /// \brief Conditionally log a message at level Log::ERRROR /// (with errno and return). /// /// \param[in] rtn Failure return value. /// \param[in] cond Failure test condition. /// \param[in] src Source name (program or method). /// \param[in] msg Error message (\b must be a literal string constant). /// \param[in] ... Arguments to satisfy \a msg mutils::cxxprintf directives. /// \post If \a cond is true the caller will return with a value of \a rtn. /// \warning This \b must be defined as a macro to get proper line information. /// \warning This macro is not thread-safe. #define MU_ERRNO_RTN(rtn, cond, src, msg, ...) \ MU_MESSAGE_RTN(mutils::Log::ERROR, rtn, cond, src, msg ": %s", \ ## __VA_ARGS__, strerror(errno)) /// \mu package. namespace mutils { /// Converts input to well-defined type for stdio. inline const char *Cform(const std::string &s) { return s.c_str(); } /// Converts input to well-defined type for stdio. templateinline T *Cform(T *p) { return p; } /// Converts input to well-defined type for stdio. inline double Cform(double i) { return i; } /// Converts input to well-defined type for stdio. inline unsigned long Cform(unsigned long long i) { return i; } /// Converts input to well-defined type for stdio. inline unsigned long Cform(unsigned long i) { return i; } /// Converts input to well-defined type for stdio. inline unsigned long Cform(unsigned i) { return i; } /// Converts input to well-defined type for stdio. inline long Cform(long long i) { return i; } /// Converts input to well-defined type for stdio. inline long Cform(long i) { return i; } /// Converts input to well-defined type for stdio. inline long Cform(int i) { return i; } /// Converts input to well-defined type for stdio. inline int Cform(unsigned char i) { return i; } /// Converts input to well-defined type for stdio. inline int Cform(char i) { return i; } /** \brief C printf() with type *conversion* (**not** safety). Wraps each argument in a Cform() call and passes them to printf(). This converts \b all signed integers to type `long` and \b all unsigned integers to type `unsigned long`, so that the format conversion applied to them \b must be `%%ld` or `%%lu`, respectively. Characters are converted to type `int` and hence should use either `%%c` or `%%d`. Both C-strings and C++ std::string can be given as arguments using the `%%s` conversion. All the usual format qualifiers may also be used. \param[in] args Input arguments (beginning with format string). \return Number of bytes successfully written, or a negative value on error. \warning This method defeats all compile-time format checking (alas). \see mutils::xprintf */ template inline int cxxprintf(Args&&... args) { return printf(Cform(args)...); } /** \brief C fprintf() with type *conversion* (**not** safety). \param[in] f Open output file stream. \param[in] args Input arguments (beginning with format string). \return Number of bytes successfully written, or a negative value on error. \warning This method defeats all compile-time format checking (alas). \see mutils::cxxprintf, mutils::xfprintf */ template inline int cxxfprintf(FILE *f, Args&&... args) { return fprintf(f, Cform(args)...); } /** \brief C snprintf() with type *conversion* (**not** safety). \param[in] buf Output buffer. \param[in] len Buffer length. \param[in] args Input arguments (beginning with format string). \return Minimum required buffer size minus one, or a negative value on error. \warning This method defeats all compile-time format checking (alas). \see mutils::cxxprintf */ template inline int cxxsnprintf(char *buf, size_t len, Args&&... args) { return snprintf(buf, len, Cform(args)...); } /** \brief Standardized fopen(). Equivalent to \ fopen(), except: - the file name argument is a std::string - file names of \c "-" and \c "!" are interpreted as \c stdin/stdout and \c stderr, respectively. \param[in] file File name. \param[in] mode File mode. */ inline FILE *cxxfopen(const std::string &file, const char *mode) { return (file == "-" ? (*mode == 'r' ? stdin : stdout) : file == "!" ? stderr : fopen(file.c_str(), mode)); } /** \brief Standardized fclose(). Equivalent to \ fclose(), except that the call is ignored if \a fp is \c stdin, \c stdout or \c stderr. \param[in] fp File stream pointer. */ inline int cxxfclose(FILE *fp) { return (fp == stdin || fp == stdout || fp == stderr ? 0 : fclose(fp)); } /** \headerfile Log.h \brief Logging facility. Supports the generation and use of log messages. Defined messaging levels: Level | Name | Indication | Expectation :---: | :--: | :--------- | :---------- 0 | ERROR | error condition | fatal, execution terminates 1 | WARN | warning condition | recoverable, execution at risk 2 | INFO | informational | execution not at risk 3 | DEBUG | verbose information | normal execution */ struct Log { // Message logging levels. static constexpr int ERROR = 0, ///< Error condition. WARN = 1, ///< Warning condition. INFO = 2, ///< Informational message. DEBUG = 3; ///< Verbose information. /// Standard logging level name string. /// \param[in] lev Message level (default: current logging level). /// \returns Logging level name string. static const char *name(int lev = mutils::Log::level) { if (lev <= ERROR) { return "ERROR"; } switch (lev) { case WARN: return "WARN"; case INFO: return "INFO"; } return "DEBUG"; } /// Sets logging level from name string. /// \param[in] str Standard logging level name string. /// \note If \a str is not recognized, the level is not changed. /// \see name() static void setLevel(const std::string &str) { for (int l=ERROR; l<=DEBUG; l++) { if (str == name(l)) { level = l; } } } /// Sets the logging stream. /// \param[in] fname %Log file name ("-" is stdout, "!" is stderr). /// \param[in] append Whether to append to existing files (else overwrite). /// \return Zero on success, else -1. /// \note If the new file cannot be opened, the existing stream is retained. static int setStream(const std::string &fname, bool append = true) { FILE *f = cxxfopen(fname, append ? "ab" : "wb"); if (f) { if (stream) { cxxfclose(stream); } file = fname; stream = f; } return (f ? 0 : -1); } /** \brief Logs a message. Logs a message to #stream if \a lev is less than or equal to #level. The \a args are passed verbatim to cxxfprintf(). \param[in] lev Minimum log level required to print message. \param[in] args Arguments for cxxfprintf() (not including the stream). \return Number of characters written. */ template static int message(int lev, Args&&... args) { return (lev <= level ? cxxfprintf(stream, args...) : 0); } /// \brief Logs a message at level #ERROR. /// \param[in] args Arguments for cxxfprintf() (not including the stream). template static int error(Args&&... args) { return message(ERROR, args...); } /// \brief Logs a message at level #WARN. /// \param[in] args Arguments for cxxfprintf() (not including the stream). template static int warn(Args&&... args) { return message(WARN, args...); } /// \brief Logs a message at level #INFO. /// \param[in] args Arguments for cxxfprintf() (not including the stream). template static int info(Args&&... args) { return message(INFO, args...); } /// \brief Logs a message at level #DEBUG. /// \param[in] args Arguments for cxxfprintf() (not including the stream). template static int debug(Args&&... args) { return message(DEBUG, args...); } /// Message logging level for output to #stream. static int level; /// Message log file stream. static FILE *stream; /// Message log file name. static std::string file; }; } // namespace mutils #endif // MUTILS_LOG_H