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.
- *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__) }

View file

@ -1,5 +1,6 @@
#pragma once
#ifndef CLUTCHLOG_H
//! Header guard.
#define CLUTCHLOG_H
/** @file */
@ -22,7 +23,7 @@
#include <regex>
#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>)
#include <execinfo.h> // execinfo
#include <stdlib.h> // getenv
@ -32,6 +33,7 @@
#define CLUTCHLOG_HAVE_UNIX_SYSINFO 0
#endif
//! True if the system can handle the `hfill` feature.
#if __has_include(<sys/ioctl.h>) && __has_include(<stdio.h>) && __has_include(<unistd.h>)
#include <sys/ioctl.h>
#include <stdio.h>
@ -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<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.
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) {}

View file

@ -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) {