diff --git a/README.md b/README.md index 95d4805..88733e9 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ Log levels use a classical semantics for a human skilled in the art, in decreasi - *Debug*: data that would help debugging the program if there was a bug later on. - *XDebug*: debugging information that would be heavy to read. -Note: the log levels constants are lower case (for example: `clutchlog::level::xdebug`), but their string representation is not (e.g. "XDebug", this should be taken into account when using `threshold` or `level_of`). +Note: the log levels constants are lower case (for example: `clutchlog::level::xdebug`), but their string representation is not (e.g. "XDebug", this should be taken into account when using `clutchlog::threshold` or `clutchlog::level_of`). Location filtering @@ -174,12 +174,12 @@ Note that the case of the log levels strings matters (see below). Output Configuration -------------------- -The output stream can be configured using the `out` method: +The output stream can be configured using the `clutchlog::out` method: ```cpp log.out(std::clog); // Defaults to clog. ``` -The format of the messages can be defined with the `format` method, passing a string with standardized tags surrounded by `{}`: +The format of the messages can be defined with the `clutchlog::format` method, passing a string with standardized tags surrounded by `{}`: ```cpp log.format("{msg}"); ``` @@ -213,7 +213,7 @@ clutchlog will not put the location-related tags in the message formats Output style ------------ -The output can be colored differently depending on the log level. +Output lines can be colored differently depending on the log level. ```cpp // Print error messages in bold red: log.style(clutchlog::level::error, // First, the log level. @@ -229,6 +229,9 @@ fmt warn(fmt::fg::magenta, fmt::typo::bold); log.style(clutchlog::level::warning, warn); ``` +Note: this inserts a style marker at the very beginning of the line. +If you add other styles later on the line, they will take precedence. + Using the `clutchlog::fmt` class, you can style: - the foreground color, passing a `clutchlog::fmt::fg`, @@ -272,11 +275,14 @@ log.format(format.str()); Note: messages at the "critical", "error" and "warning" log levels are colored by default. You may want to set their style to `none` if you want to stay in control of inserted colors in the format template. -The horizontal filling line (the `{hfill}` tag) can be configured separately with `hfill_style`, +The horizontal filling line (the `{hfill}` tag) can be configured separately with `clutchlog::hfill_style`, for example: ```cpp log.hfill_style(clutchlog::fmt::fg::black); ``` +Note: this will actually reset any styling after the hfill, +disabling any style you would have set for the whole message using `clutchlog::format` +for the remaining of the message. Advanced Usage @@ -304,13 +310,13 @@ clutchlog will not put the location-related tags in the message formats ### Marks -The mark used with the `{depth_marks}` tag can be configured with the `depth_mark` method, +The mark used with the `{depth_marks}` tag can be configured with the `clutchlog::depth_mark` method, and its default with the `CLUTCHLOG_DEFAULT_DEPTH_MARK` macro: ```cpp log.depth_mark(CLUTCHLOG_DEFAULT_DEPTH_MARK); // Defaults to ">". ``` -The character used with the `{hfill}` tag can be configured wth the `hfill_mark` method, +The character used with the `{hfill}` tag can be configured wth the `clutchlog::hfill_mark` method, and its default with the `CLUTCHLOG_DEFAULT_HFILL_MARK` macro: ```cpp log.hfill_mark(CLUTCHLOG_DEFAULT_HFILL_MARK); // Defaults to '.'. @@ -318,13 +324,13 @@ log.hfill_mark(CLUTCHLOG_DEFAULT_HFILL_MARK); // Defaults to '.'. Clutchlog measures the width of the standard error channel. If it is redirected, it may be measured as very large. -Thus, the `hfill_max` accessors allow to set a maximum width (in number of characters). +Thus, the `clutchlog::hfill_max` accessors allow to set a maximum width (in number of characters). ```cpp log.hfill_max(CLUTCHLOG_DEFAULT_HFILL_MAX); // Defaults to 300. ``` -Note: clutchlog will select the minimum between `hfill_max` +Note: clutchlog will select the minimum between `clutchlog::hfill_max` and the measured number of columns in the terminal, -so that you may use `hfill_max` as a way to constraint the output width +so that you may use `clutchlog::hfill_max` as a way to constraint the output width in any cases. @@ -333,7 +339,7 @@ in any cases. By default, clutchlog removes 5 levels of the calls stack, so that your `main` entrypoint corresponds to a depth of zero. You can change this behaviour by defining the `CLUTCHLOG_STRIP_CALLS` macro, -or calling `strip_calls`. +or calling `clutchlog::strip_calls`. ```cpp log.strip_calls(CLUTCHLOG_STRIP_CALLS); // Defaults to 5. ``` @@ -378,7 +384,7 @@ for example: std::string mark = log.depth_mark(); ``` -To control more precisely the logging, one can use the low-level `log` method: +To control more precisely the logging, one can use the low-level `clutchlog::log` method: ```cpp log.log(clutchlog::level::xdebug, "hello world", "main.cpp", "main", 122); ``` @@ -392,7 +398,7 @@ log.dump(clutchlog::level::xdebug, cont.begin(), cont.end(), CLUTCHLOC, "dumped_ log.dump(clutchlog::level::xdebug, cont.begin(), cont.end(), "main.cpp", "main", 122, "dumped.dat", "\n\n"); ``` -You can access the identifier of log levels with `level_of`: +You can access the identifier of log levels with `clutchlog::level_of`: ```cpp log.threshold( log.level_of("XDebug") ); // You have to know the exact string. ``` @@ -474,7 +480,7 @@ And here are all the functions you may call to log something: CLUTCHDUMP(note, my_list, "my_list_{n}.dat"); // Function call. - CLUTCHFUNC(warning, my_check, x, y); // Calls `my_check(x,y);` + CLUTCHFUNC(warning, my_check, x, y); // Calls: my_check(x,y); // Declutchable asserts. #define ASSERT(...) { CLUTCHFUNC(critical, assert, __VA_ARGS__) } diff --git a/clutchlog/clutchlog.h b/clutchlog/clutchlog.h index 4e8ff41..3777faa 100644 --- a/clutchlog/clutchlog.h +++ b/clutchlog/clutchlog.h @@ -1,5 +1,6 @@ #pragma once #ifndef CLUTCHLOG_H +//! Header guard. #define CLUTCHLOG_H /** @file */ @@ -22,7 +23,7 @@ #include #include -//! POSIX headers necessary for stack depth management are available. +//! True if POSIX headers necessary for stack depth management are available. #if __has_include() && __has_include() && __has_include() #include // execinfo #include // getenv @@ -32,6 +33,7 @@ #define CLUTCHLOG_HAVE_UNIX_SYSINFO 0 #endif +//! True if the system can handle the `hfill` feature. #if __has_include() && __has_include() && __has_include() #include #include @@ -57,7 +59,7 @@ **********************************************************************/ #ifdef WITH_CLUTCHLOG -/** @addtogroup DefaultConfigMacros Default configuration macros +/** @defgroup DefaultConfig Default configuration management * @{ **/ #ifndef CLUTCHLOG_DEFAULT_DEPTH_BUILT_NODEBUG @@ -65,10 +67,10 @@ #define CLUTCHLOG_DEFAULT_DEPTH_BUILT_NODEBUG clutchlog::level::progress #endif // CLUTCHLOG_DEFAULT_DEPTH_BUILT -/** @} */ +/** @} DefaultConfig */ -/** @addtogroup UseMacros High-level API macros +/** @defgroup UseMacros High-level API macros * @{ */ //! Handy shortcuts to location. @@ -150,7 +152,7 @@ } while(0) #endif // NDEBUG -/** @} */ +/** @} UseMacros */ #else // not WITH_CLUTCHLOG // Disabled macros can still be called in Release builds. @@ -165,18 +167,20 @@ **********************************************************************/ #ifdef WITH_CLUTCHLOG +/** @defgroup Main Main class + * @{ + */ /** The single class which holds everything. * * This is a Singleton class. - * - * @addtogroup Main Main class - * @{ */ class clutchlog { protected: - /** @addtogroup UseMacros High-level API macros + /** @name Default configuration members + * @{ */ + /** @ingroup DefaultConfig * @{ */ #ifndef NDEBUG #ifndef CLUTCHLOG_DEFAULT_FORMAT @@ -265,10 +269,11 @@ class clutchlog #endif #endif //! Default maximum number of character used as a filling for right-align the right part of messages with "{hfill}". - static inline unsigned short default_hfill_max = CLUTCHLOG_HFILL_MAX; + static inline size_t default_hfill_max = CLUTCHLOG_HFILL_MAX; // NOTE: there is no CLUTCHLOG_HFILL_STYLE for defaulting, // but you can still set `hfill_style(...)` on the logger singleton. + /* @} DefaultConfig */ /* @} */ @@ -297,9 +302,6 @@ class clutchlog /** @addtogroup Formating Formating tools * @{ */ - /** @name Formating API - * @{ */ - /** Color and style formatter for ANSI terminal escape sequences. * * @note All styles may not be supported by a given terminal/operating system. @@ -410,10 +412,18 @@ class clutchlog reset.print_on(os); return os.str(); } + + /** Return the formatting code as a string. + */ + std::string str() const + { + std::ostringstream os; + this->print_on(os); + return os.str(); + } }; // fmt class /** @} */ - /** @} */ /** @name Internal details * @{ */ @@ -470,7 +480,7 @@ class clutchlog #if CLUTCHLOG_HAVE_UNIX_SYSIOCTL struct winsize w; ioctl(STDERR_FILENO, TIOCGWINSZ, &w); - _nb_columns = std::min(w.ws_col, default_hfill_max); + _nb_columns = std::min((size_t)w.ws_col, default_hfill_max); #endif } @@ -493,7 +503,7 @@ class clutchlog /** Style of the filling. */ fmt _hfill_fmt; /** Maximum number of fill char. */ - unsigned short _hfill_max; + size_t _hfill_max; #endif /** Standard output. */ std::ostream* _out; @@ -571,13 +581,13 @@ class clutchlog * This version accept style arguments as if they were passed to `clutchlog::fmt`. */ template - void style(FMT... styles) { this->hfill_style(fmt(styles...)); } + void hfill_style(FMT... styles) { this->hfill_style(fmt(styles...)); } //! Get the character for the stretching hfill marker. fmt hfill_style() const {return _hfill_fmt;} //! Set the maximum number of hfill characters. */ void hfill_max(const size_t nmax) {_hfill_max = nmax;} //! Get the maximum number of hfill characters. */ - unsigned short hfill_max() {return _hfill_max;} + size_t hfill_max() {return _hfill_max;} #endif //! Set the log level (below which logs are not printed) with an identifier. @@ -793,7 +803,7 @@ class clutchlog //! Substitute all tags in the format string with the corresponding information and apply the style corresponding to the log level. std::string format( - std::string format, + std::string row, const std::string& what, #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1 const std::string& name, @@ -808,52 +818,65 @@ class clutchlog #endif ) const { - format = replace(format, "\\{msg\\}", what); - format = replace(format, "\\{file\\}", file); - format = replace(format, "\\{func\\}", func); - format = replace(format, "\\{line\\}", line); + row = replace(row, "\\{msg\\}", what); + row = replace(row, "\\{file\\}", file); + row = replace(row, "\\{func\\}", func); + row = replace(row, "\\{line\\}", line); - format = replace(format, "\\{level\\}", _level_word.at(stage)); + row = replace(row, "\\{level\\}", _level_word.at(stage)); std::string letter(1, _level_word.at(stage).at(0)); // char -> string - format = replace(format, "\\{level_letter\\}", letter); + row = replace(row, "\\{level_letter\\}", letter); #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1 - format = replace(format, "\\{name\\}", name); - format = replace(format, "\\{depth\\}", depth - _strip_calls); + row = replace(row, "\\{name\\}", name); + row = replace(row, "\\{depth\\}", depth - _strip_calls); std::ostringstream chevrons; for(size_t i = _strip_calls; i < depth; ++i) { chevrons << _depth_mark; } - format = replace(format, "\\{depth_marks\\}", chevrons.str()); + row = replace(row, "\\{depth_marks\\}", chevrons.str()); #endif + + row = replace(row, "\\{level_fmt\\}", _level_fmt.at(stage).str()); + #if CLUTCHLOG_HAVE_UNIX_SYSIOCTL + // hfill is replaced last to allow for correct line width estimation. + const std::string raw_row = replace(row, "\\x1B\\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]", ""); const std::string hfill_tag = "{hfill}"; - const size_t hfill_pos = format.find(hfill_tag); + const size_t hfill_pos = row.find(hfill_tag); + const size_t raw_hfill_pos = raw_row.find(hfill_tag); + const size_t nb_columns = std::min(_nb_columns, _hfill_max); if(hfill_pos != std::string::npos) { - if(_nb_columns > 0) { - const size_t left_len = hfill_pos; - const size_t right_len = format.size() - hfill_pos - hfill_tag.size(); - if(right_len+left_len > _nb_columns) { - // The right part would go over the terminal width: add a new line. - const std::string hfill(std::max((size_t)0, _nb_columns-right_len), _hfill_char); + assert(raw_hfill_pos != std::string::npos); + if(nb_columns > 0) { + const size_t left_len = raw_hfill_pos; + const size_t right_len = raw_row.size() - raw_hfill_pos - hfill_tag.size(); + if(right_len+left_len > nb_columns) { + // The right part would go over the terminal width: add a new row. + const std::string hfill(std::max((size_t)0, nb_columns-right_len), _hfill_char); const std::string hfill_styled = _hfill_fmt(hfill); - format = replace(format, "\\{hfill\\}", "\n"+hfill_styled); + row = replace(row, "\\{hfill\\}", "\n"+hfill_styled); } else { // There is some space in between left and right parts. - const std::string hfill(std::max((size_t)0, _nb_columns - (right_len+left_len)), _hfill_char); + const std::string hfill(std::max((size_t)0, nb_columns - (right_len+left_len)), _hfill_char); const std::string hfill_styled = _hfill_fmt(hfill); - format = replace(format, "\\{hfill\\}", hfill_styled); + row = replace(row, "\\{hfill\\}", hfill_styled); } } else { // We don't know the terminal width. const std::string hfill(1, _hfill_char); const std::string hfill_styled = _hfill_fmt(hfill); - format = replace(format, "\\{hfill\\}", hfill_styled); + row = replace(row, "\\{hfill\\}", hfill_styled); } } +#else + // We cannot know the terminal width. + const std::string hfill(1, _hfill_char); + const std::string hfill_styled = _hfill_fmt(hfill); + row = replace(row, "\\{hfill\\}", hfill_styled); #endif - return _level_fmt.at(stage)(format); + return _level_fmt.at(stage)(row); } //! Print a log message IF the location matches the given one. @@ -1011,7 +1034,7 @@ class clutchlog void hfill_fmt(fmt) {} fmt hfill_fmt() const {} void hfill_max(const size_t) {} - unsigned short hfill_max() {} + size_t hfill_max() {} #endif void threshold(level) {} diff --git a/tests/t-demo.cpp b/tests/t-demo.cpp index bfc1ea8..fda4b65 100644 --- a/tests/t-demo.cpp +++ b/tests/t-demo.cpp @@ -37,10 +37,42 @@ int main(const int argc, char* argv[]) { auto& log = clutchlog::logger(); + log.style(clutchlog::level::critical, + clutchlog::fmt::fg::red); + log.style(clutchlog::level::error, + clutchlog::fmt::fg::red); + log.style(clutchlog::level::warning, + clutchlog::fmt::fg::magenta); + log.style(clutchlog::level::progress, + clutchlog::fmt::fg::yellow); + log.style(clutchlog::level::note, + clutchlog::fmt::fg::green); + log.style(clutchlog::level::info, + clutchlog::fmt::fg::magenta); + log.style(clutchlog::level::debug, + clutchlog::fmt::fg::cyan); + log.style(clutchlog::level::xdebug, + clutchlog::fmt::fg::blue); + std::ostringstream format; + clutchlog::fmt reset(clutchlog::fmt::typo::reset); + clutchlog::fmt discreet(clutchlog::fmt::fg::black); + clutchlog::fmt bold(clutchlog::fmt::typo::bold); + format << "{level_fmt}" + << "{level_letter}:" + << "{depth_marks} " + << bold("{msg}") + << discreet(" {hfill} ") + << "{level_fmt}{func}" + << discreet(" @ ") + << "{level_fmt}{file}" + << reset << ":" + << "{level_fmt}{line}" + << "\n"; + log.format(format.str()); + + // log.hfill_max(100); log.out(std::clog); - log.depth_mark("\t"); - log.format("[{name}] {level}: {depth_marks} {msg}\n"); - log.style(clutchlog::level::progress,clutchlog::fmt::fg::blue); + log.depth_mark(">"); log.threshold(clutchlog::level::warning); if(argc <= 2) {