feats: adds {level_fmt} and fix hfill accordingly

Update t-demo to show more formatting.
This commit is contained in:
Johann Dreo 2023-01-18 22:30:27 +01:00
commit 1c6a6d8507
3 changed files with 119 additions and 58 deletions

View file

@ -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. - *Debug*: data that would help debugging the program if there was a bug later on.
- *XDebug*: debugging information that would be heavy to read. - *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 Location filtering
@ -174,12 +174,12 @@ Note that the case of the log levels strings matters (see below).
Output Configuration Output Configuration
-------------------- --------------------
The output stream can be configured using the `out` method: The output stream can be configured using the `clutchlog::out` method:
```cpp ```cpp
log.out(std::clog); // Defaults to clog. 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 ```cpp
log.format("{msg}"); log.format("{msg}");
``` ```
@ -213,7 +213,7 @@ clutchlog will not put the location-related tags in the message formats
Output style 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 ```cpp
// Print error messages in bold red: // Print error messages in bold red:
log.style(clutchlog::level::error, // First, the log level. 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); 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: Using the `clutchlog::fmt` class, you can style:
- the foreground color, passing a `clutchlog::fmt::fg`, - 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. 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. 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: for example:
```cpp ```cpp
log.hfill_style(clutchlog::fmt::fg::black); 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 Advanced Usage
@ -304,13 +310,13 @@ clutchlog will not put the location-related tags in the message formats
### Marks ### 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: and its default with the `CLUTCHLOG_DEFAULT_DEPTH_MARK` macro:
```cpp ```cpp
log.depth_mark(CLUTCHLOG_DEFAULT_DEPTH_MARK); // Defaults to ">". 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: and its default with the `CLUTCHLOG_DEFAULT_HFILL_MARK` macro:
```cpp ```cpp
log.hfill_mark(CLUTCHLOG_DEFAULT_HFILL_MARK); // Defaults to '.'. 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. Clutchlog measures the width of the standard error channel.
If it is redirected, it may be measured as very large. 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 ```cpp
log.hfill_max(CLUTCHLOG_DEFAULT_HFILL_MAX); // Defaults to 300. 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, 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. in any cases.
@ -333,7 +339,7 @@ in any cases.
By default, clutchlog removes 5 levels of the calls stack, so that your `main` By default, clutchlog removes 5 levels of the calls stack, so that your `main`
entrypoint corresponds to a depth of zero. entrypoint corresponds to a depth of zero.
You can change this behaviour by defining the `CLUTCHLOG_STRIP_CALLS` macro, You can change this behaviour by defining the `CLUTCHLOG_STRIP_CALLS` macro,
or calling `strip_calls`. or calling `clutchlog::strip_calls`.
```cpp ```cpp
log.strip_calls(CLUTCHLOG_STRIP_CALLS); // Defaults to 5. log.strip_calls(CLUTCHLOG_STRIP_CALLS); // Defaults to 5.
``` ```
@ -378,7 +384,7 @@ for example:
std::string mark = log.depth_mark(); 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 ```cpp
log.log(clutchlog::level::xdebug, "hello world", "main.cpp", "main", 122); 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"); 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 ```cpp
log.threshold( log.level_of("XDebug") ); // You have to know the exact string. 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"); CLUTCHDUMP(note, my_list, "my_list_{n}.dat");
// Function call. // 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. // Declutchable asserts.
#define ASSERT(...) { CLUTCHFUNC(critical, assert, __VA_ARGS__) } #define ASSERT(...) { CLUTCHFUNC(critical, assert, __VA_ARGS__) }

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#ifndef CLUTCHLOG_H #ifndef CLUTCHLOG_H
//! Header guard.
#define CLUTCHLOG_H #define CLUTCHLOG_H
/** @file */ /** @file */
@ -22,7 +23,7 @@
#include <regex> #include <regex>
#include <map> #include <map>
//! POSIX headers necessary for stack depth management are available. //! True if POSIX headers necessary for stack depth management are available.
#if __has_include(<execinfo.h>) && __has_include(<stdlib.h>) && __has_include(<libgen.h>) #if __has_include(<execinfo.h>) && __has_include(<stdlib.h>) && __has_include(<libgen.h>)
#include <execinfo.h> // execinfo #include <execinfo.h> // execinfo
#include <stdlib.h> // getenv #include <stdlib.h> // getenv
@ -32,6 +33,7 @@
#define CLUTCHLOG_HAVE_UNIX_SYSINFO 0 #define CLUTCHLOG_HAVE_UNIX_SYSINFO 0
#endif #endif
//! True if the system can handle the `hfill` feature.
#if __has_include(<sys/ioctl.h>) && __has_include(<stdio.h>) && __has_include(<unistd.h>) #if __has_include(<sys/ioctl.h>) && __has_include(<stdio.h>) && __has_include(<unistd.h>)
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <stdio.h> #include <stdio.h>
@ -57,7 +59,7 @@
**********************************************************************/ **********************************************************************/
#ifdef WITH_CLUTCHLOG #ifdef WITH_CLUTCHLOG
/** @addtogroup DefaultConfigMacros Default configuration macros /** @defgroup DefaultConfig Default configuration management
* @{ **/ * @{ **/
#ifndef CLUTCHLOG_DEFAULT_DEPTH_BUILT_NODEBUG #ifndef CLUTCHLOG_DEFAULT_DEPTH_BUILT_NODEBUG
@ -65,10 +67,10 @@
#define CLUTCHLOG_DEFAULT_DEPTH_BUILT_NODEBUG clutchlog::level::progress #define CLUTCHLOG_DEFAULT_DEPTH_BUILT_NODEBUG clutchlog::level::progress
#endif // CLUTCHLOG_DEFAULT_DEPTH_BUILT #endif // CLUTCHLOG_DEFAULT_DEPTH_BUILT
/** @} */ /** @} DefaultConfig */
/** @addtogroup UseMacros High-level API macros /** @defgroup UseMacros High-level API macros
* @{ */ * @{ */
//! Handy shortcuts to location. //! Handy shortcuts to location.
@ -150,7 +152,7 @@
} while(0) } while(0)
#endif // NDEBUG #endif // NDEBUG
/** @} */ /** @} UseMacros */
#else // not WITH_CLUTCHLOG #else // not WITH_CLUTCHLOG
// Disabled macros can still be called in Release builds. // Disabled macros can still be called in Release builds.
@ -165,18 +167,20 @@
**********************************************************************/ **********************************************************************/
#ifdef WITH_CLUTCHLOG #ifdef WITH_CLUTCHLOG
/** @defgroup Main Main class
* @{
*/
/** The single class which holds everything. /** The single class which holds everything.
* *
* This is a Singleton class. * This is a Singleton class.
*
* @addtogroup Main Main class
* @{
*/ */
class clutchlog class clutchlog
{ {
protected: protected:
/** @addtogroup UseMacros High-level API macros /** @name Default configuration members
* @{ */
/** @ingroup DefaultConfig
* @{ */ * @{ */
#ifndef NDEBUG #ifndef NDEBUG
#ifndef CLUTCHLOG_DEFAULT_FORMAT #ifndef CLUTCHLOG_DEFAULT_FORMAT
@ -265,10 +269,11 @@ class clutchlog
#endif #endif
#endif #endif
//! Default maximum number of character used as a filling for right-align the right part of messages with "{hfill}". //! 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, // NOTE: there is no CLUTCHLOG_HFILL_STYLE for defaulting,
// but you can still set `hfill_style(...)` on the logger singleton. // but you can still set `hfill_style(...)` on the logger singleton.
/* @} DefaultConfig */
/* @} */ /* @} */
@ -297,9 +302,6 @@ class clutchlog
/** @addtogroup Formating Formating tools /** @addtogroup Formating Formating tools
* @{ */ * @{ */
/** @name Formating API
* @{ */
/** Color and style formatter for ANSI terminal escape sequences. /** Color and style formatter for ANSI terminal escape sequences.
* *
* @note All styles may not be supported by a given terminal/operating system. * @note All styles may not be supported by a given terminal/operating system.
@ -410,10 +412,18 @@ class clutchlog
reset.print_on(os); reset.print_on(os);
return os.str(); 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 }; // fmt class
/** @} */ /** @} */
/** @} */
/** @name Internal details /** @name Internal details
* @{ */ * @{ */
@ -470,7 +480,7 @@ class clutchlog
#if CLUTCHLOG_HAVE_UNIX_SYSIOCTL #if CLUTCHLOG_HAVE_UNIX_SYSIOCTL
struct winsize w; struct winsize w;
ioctl(STDERR_FILENO, TIOCGWINSZ, &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 #endif
} }
@ -493,7 +503,7 @@ class clutchlog
/** Style of the filling. */ /** Style of the filling. */
fmt _hfill_fmt; fmt _hfill_fmt;
/** Maximum number of fill char. */ /** Maximum number of fill char. */
unsigned short _hfill_max; size_t _hfill_max;
#endif #endif
/** Standard output. */ /** Standard output. */
std::ostream* _out; std::ostream* _out;
@ -571,13 +581,13 @@ class clutchlog
* This version accept style arguments as if they were passed to `clutchlog::fmt`. * This version accept style arguments as if they were passed to `clutchlog::fmt`.
*/ */
template<class ... FMT> template<class ... FMT>
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. //! Get the character for the stretching hfill marker.
fmt hfill_style() const {return _hfill_fmt;} fmt hfill_style() const {return _hfill_fmt;}
//! Set the maximum number of hfill characters. */ //! Set the maximum number of hfill characters. */
void hfill_max(const size_t nmax) {_hfill_max = nmax;} void hfill_max(const size_t nmax) {_hfill_max = nmax;}
//! Get the maximum number of hfill characters. */ //! Get the maximum number of hfill characters. */
unsigned short hfill_max() {return _hfill_max;} size_t hfill_max() {return _hfill_max;}
#endif #endif
//! Set the log level (below which logs are not printed) with an identifier. //! 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. //! 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 format, std::string row,
const std::string& what, const std::string& what,
#if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1 #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1
const std::string& name, const std::string& name,
@ -808,52 +818,65 @@ class clutchlog
#endif #endif
) const ) const
{ {
format = replace(format, "\\{msg\\}", what); row = replace(row, "\\{msg\\}", what);
format = replace(format, "\\{file\\}", file); row = replace(row, "\\{file\\}", file);
format = replace(format, "\\{func\\}", func); row = replace(row, "\\{func\\}", func);
format = replace(format, "\\{line\\}", line); 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 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 #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1
format = replace(format, "\\{name\\}", name); row = replace(row, "\\{name\\}", name);
format = replace(format, "\\{depth\\}", depth - _strip_calls); row = replace(row, "\\{depth\\}", depth - _strip_calls);
std::ostringstream chevrons; std::ostringstream chevrons;
for(size_t i = _strip_calls; i < depth; ++i) { for(size_t i = _strip_calls; i < depth; ++i) {
chevrons << _depth_mark; chevrons << _depth_mark;
} }
format = replace(format, "\\{depth_marks\\}", chevrons.str()); row = replace(row, "\\{depth_marks\\}", chevrons.str());
#endif #endif
row = replace(row, "\\{level_fmt\\}", _level_fmt.at(stage).str());
#if CLUTCHLOG_HAVE_UNIX_SYSIOCTL #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 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(hfill_pos != std::string::npos) {
if(_nb_columns > 0) { assert(raw_hfill_pos != std::string::npos);
const size_t left_len = hfill_pos; if(nb_columns > 0) {
const size_t right_len = format.size() - hfill_pos - hfill_tag.size(); const size_t left_len = raw_hfill_pos;
if(right_len+left_len > _nb_columns) { const size_t right_len = raw_row.size() - raw_hfill_pos - hfill_tag.size();
// The right part would go over the terminal width: add a new line. if(right_len+left_len > nb_columns) {
const std::string hfill(std::max((size_t)0, _nb_columns-right_len), _hfill_char); // 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); const std::string hfill_styled = _hfill_fmt(hfill);
format = replace(format, "\\{hfill\\}", "\n"+hfill_styled); row = replace(row, "\\{hfill\\}", "\n"+hfill_styled);
} else { } else {
// There is some space in between left and right parts. // 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); const std::string hfill_styled = _hfill_fmt(hfill);
format = replace(format, "\\{hfill\\}", hfill_styled); row = replace(row, "\\{hfill\\}", hfill_styled);
} }
} else { } else {
// We don't know the terminal width. // We don't know the terminal width.
const std::string hfill(1, _hfill_char); const std::string hfill(1, _hfill_char);
const std::string hfill_styled = _hfill_fmt(hfill); 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 #endif
return _level_fmt.at(stage)(format); return _level_fmt.at(stage)(row);
} }
//! Print a log message IF the location matches the given one. //! Print a log message IF the location matches the given one.
@ -1011,7 +1034,7 @@ class clutchlog
void hfill_fmt(fmt) {} void hfill_fmt(fmt) {}
fmt hfill_fmt() const {} fmt hfill_fmt() const {}
void hfill_max(const size_t) {} void hfill_max(const size_t) {}
unsigned short hfill_max() {} size_t hfill_max() {}
#endif #endif
void threshold(level) {} void threshold(level) {}

View file

@ -37,10 +37,42 @@ int main(const int argc, char* argv[])
{ {
auto& log = clutchlog::logger(); 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.out(std::clog);
log.depth_mark("\t"); log.depth_mark(">");
log.format("[{name}] {level}: {depth_marks} {msg}\n");
log.style(clutchlog::level::progress,clutchlog::fmt::fg::blue);
log.threshold(clutchlog::level::warning); log.threshold(clutchlog::level::warning);
if(argc <= 2) { if(argc <= 2) {