feat: add the {hfill} format tag

and update the readme.
This commit is contained in:
Johann Dreo 2022-09-01 22:45:38 +02:00
commit fc26a8af5a
2 changed files with 156 additions and 24 deletions

View file

@ -4,6 +4,9 @@ Clutchlog — versatile (de)clutchable logging
***Clutchlog is a logging system that targets versatile debugging.*** ***Clutchlog is a logging system that targets versatile debugging.***
***It allows to (de)clutch messages for a given: log level, source code location or call stack depth.*** ***It allows to (de)clutch messages for a given: log level, source code location or call stack depth.***
- [Project page on Github](https://github.com/nojhan/clutchlog)
- [Documentation](https://nojhan.github.io/clutchlog/)
![Clutchlog logo](https://raw.githubusercontent.com/nojhan/clutchlog/master/docs/clutchlog_logo.svg) ![Clutchlog logo](https://raw.githubusercontent.com/nojhan/clutchlog/master/docs/clutchlog_logo.svg)
[TOC] [TOC]
@ -176,33 +179,66 @@ log.format("{msg}");
Available tags are: Available tags are:
- `{msg}`: the logged message, - `{msg}`: the logged message,
- `{name}`: the name of the current binary,
- `{level}`: the current log level (i.e. `Critical`, `Error`, `Warning`, `Progress`, `Note`, `Info`, `Debug` or `XDebug`), - `{level}`: the current log level (i.e. `Critical`, `Error`, `Warning`, `Progress`, `Note`, `Info`, `Debug` or `XDebug`),
- `{level_letter}`: the first letter of the current log level, - `{level_letter}`: the first letter of the current log level,
- `{file}`: the current file (absolute path), - `{file}`: the current file (absolute path),
- `{func}`: the current function, - `{func}`: the current function,
- `{line}`: the current line number, - `{line}`: the current line number.
- `{depth}`: the current depth of the call stack,
- `{depth_marks}`: as many chevrons `>` as there is calls in the stack.
The default log format is `"[{name}] {level_letter}:{depth_marks} {msg}\t\t\t\t\t{func} @ {file}:{line}\n"`, Some tags are only available on POSIX operating systems as of now:
- `{name}`: the name of the current binary,
- `{depth}`: the current depth of the call stack,
- `{depth_marks}`: as many chevrons `>` as there is calls in the stack,
- `{hfill}`: Inserts a sequence of characters that will stretch to fill the space available
in the current terminal, between the rightmost and leftmost part of the log message.
### Log Format
The default log format is `"[{name}] {level_letter}:{depth_marks} {msg} {hfill} {func} @ {file}:{line}\n"`,
it can be overriden at compile time by defining the `CLUTCHLOG_DEFAULT_FORMAT` macro. it can be overriden at compile time by defining the `CLUTCHLOG_DEFAULT_FORMAT` macro.
By default, and if `CLUTCHLOG_DEFAULT_FORMAT` is not defined,
clutchlog will not put the location-related tags in the message formats
(i.e. `{name}`, `{func}`, and `{line}`) when not in Debug builds.
### Dump Format
The default format of the first line of comment added with the dump macro is The default format of the first line of comment added with the dump macro is
`"# [{name}] {level} in {func} (at depth {depth}) @ {file}:{line}"`. `"# [{name}] {level} in {func} (at depth {depth}) @ {file}:{line}"`.
It can be edited with the `format_comment` method. It can be edited with the `format_comment` method.
If it is set to an empty string, then no comment line is added. If it is set to an empty string, then no comment line is added.
The default can be modified at compile time with `CLUTCHDUMP_DEFAULT_FORMAT`. The default can be modified at compile time with `CLUTCHDUMP_DEFAULT_FORMAT`.
By default, the separator between items in the container is a new line. By default, the separator between items in the container is a new line.
To change this behaviour, you can change `CLUTCHDUMP_DEFAULT_SEP` or To change this behaviour, you can change `CLUTCHDUMP_DEFAULT_SEP` or
call the low-level `dump` method. call the low-level `dump` method.
By default, and if `CLUTCHDUMP_DEFAULT_FORMAT` is not defined,
clutchlog will not put the location-related tags in the message formats
(i.e. `{file}` and `{line}`) when not in Debug builds.
### 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 `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,
and its default with the `CLUTCHLOG_DEFAULT_HFILL_MARK` macro:
```cpp
log.hfill_mark(CLUTCHLOG_DEFAULT_HFILL_MARK); // Defaults to '.'.
```
Note: if the system detects no terminal, only a single fill character is inserted.
## Stack Depth
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.
@ -362,7 +398,7 @@ Limitations
### System-dependent stack depth ### System-dependent stack depth
Because the call stack depth and program name access are system-dependent, Because access to the call stack depth and program name are system-dependent,
the features relying on the depth of the call stack and the display of the program name the features relying on the depth of the call stack and the display of the program name
are only available for operating systems having the following headers: are only available for operating systems having the following headers:
`execinfo.h`, `stdlib.h` and `libgen.h` (so far, tested with Linux). `execinfo.h`, `stdlib.h` and `libgen.h` (so far, tested with Linux).
@ -377,6 +413,22 @@ You can make portable code using something like:
``` ```
### System-dependent horizontal fill
Because access to the current terminal width is system-dependent,
the `{hfill}` format tag feature is only available for operating systems having the following headers:
`sys/ioctl.h`, `stdio.h` and `unistd.h` (so far, tested with Linux).
Clutchlog sets the `CLUTCHLOG_HAVE_UNIX_SYSIOCTL` to 1 if the headers are
available, and to 0 if they are not.
You can make portable code using something like:
```cpp
#if CLUTCHLOG_HAVE_UNIX_SYSIOCTL == 1
log.hfill_mark( '_' );
#endif
```
### Dependencies ### Dependencies
Some colors/styles may not be supported by some exotic terminal emulators. Some colors/styles may not be supported by some exotic terminal emulators.
@ -388,7 +440,7 @@ You may need to indicate `-std=c++17 -lstdc++fs` to some compilers.
### Variable names within the CLUTCHLOG macro ### Variable names within the CLUTCHLOG macro
Calling the `CLUTCHLOG` macro with a message using a variable named `clutchlog__msg` will end in Calling the `CLUTCHLOG` macro with a message using a variable named `clutchlog__msg` will end in
an error. Avoid this kind of naming for the logger singleton, also. an error.
### Features ### Features

View file

@ -32,6 +32,16 @@
#define CLUTCHLOG_HAVE_UNIX_SYSINFO 0 #define CLUTCHLOG_HAVE_UNIX_SYSINFO 0
#endif #endif
#if __has_include(<sys/ioctl.h>) && __has_include(<stdio.h>) && __has_include(<unistd.h>)
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>
#define CLUTCHLOG_HAVE_UNIX_SYSIOCTL 1
#else
#define CLUTCHLOG_HAVE_UNIX_SYSIOCTL 0
#endif
/********************************************************************** /**********************************************************************
* Enable by default in Debug builds. * Enable by default in Debug builds.
**********************************************************************/ **********************************************************************/
@ -171,25 +181,34 @@ class clutchlog
#ifndef NDEBUG #ifndef NDEBUG
#ifndef CLUTCHLOG_DEFAULT_FORMAT #ifndef CLUTCHLOG_DEFAULT_FORMAT
//! Compile-time default format of the messages (debug mode: with absolute location). //! Compile-time default format of the messages (debug mode: with absolute location).
#if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1 #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1 // Enables: name, depth and depth_marks
#if CLUTCHLOG_HAVE_UNIX_SYSIOCTL == 1 // Enables: hfill
#define CLUTCHLOG_DEFAULT_FORMAT "[{name}] {level_letter}:{depth_marks} {msg} {hfill} {func} @ {file}:{line}\n"
#else
#define CLUTCHLOG_DEFAULT_FORMAT "[{name}] {level_letter}:{depth_marks} {msg}\t\t\t\t\t{func} @ {file}:{line}\n" #define CLUTCHLOG_DEFAULT_FORMAT "[{name}] {level_letter}:{depth_marks} {msg}\t\t\t\t\t{func} @ {file}:{line}\n"
#endif
#else
#if CLUTCHLOG_HAVE_UNIX_SYSIOCTL == 1
#define CLUTCHLOG_DEFAULT_FORMAT "{level_letter} {msg} {hfill} {func} @ {file}:{line}\n"
#else #else
#define CLUTCHLOG_DEFAULT_FORMAT "{level_letter} {msg}\t\t\t\t\t{func} @ {file}:{line}\n" #define CLUTCHLOG_DEFAULT_FORMAT "{level_letter} {msg}\t\t\t\t\t{func} @ {file}:{line}\n"
#endif #endif
#endif #endif
#endif
#else #else
#ifndef CLUTCHLOG_DEFAULT_FORMAT #ifndef CLUTCHLOG_DEFAULT_FORMAT
//! Compile-time default format of the messages (debug mode: with absolute location). //! Compile-time default format of the messages (non-debug mode: without absolute location).
#if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1 #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1
#define CLUTCHLOG_DEFAULT_FORMAT "[{name}] {level_letter}:{depth_marks} {msg}\n" #define CLUTCHLOG_DEFAULT_FORMAT "{level_letter}:{depth_marks} {msg} {hfill} {func}\n"
#else #else
#define CLUTCHLOG_DEFAULT_FORMAT "{level_letter} {msg}\n" #define CLUTCHLOG_DEFAULT_FORMAT "{level_letter} {msg}\t\t\t\t\t{func}\n"
#endif #endif
#endif #endif
#endif #endif
//! Default format of the messages. //! Default format of the messages.
static inline std::string default_format = CLUTCHLOG_DEFAULT_FORMAT; static inline std::string default_format = CLUTCHLOG_DEFAULT_FORMAT;
#ifndef NDEBUG
#ifndef CLUTCHDUMP_DEFAULT_FORMAT #ifndef CLUTCHDUMP_DEFAULT_FORMAT
//! Compile-time default format of the comment line in file dump. //! Compile-time default format of the comment line in file dump.
#if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1 #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1
@ -198,6 +217,16 @@ class clutchlog
#define CLUTCHDUMP_DEFAULT_FORMAT "# {level} in {func} @ {file}:{line}" #define CLUTCHDUMP_DEFAULT_FORMAT "# {level} in {func} @ {file}:{line}"
#endif #endif
#endif // CLUTCHDUMP_DEFAULT_FORMAT #endif // CLUTCHDUMP_DEFAULT_FORMAT
#else
#ifndef CLUTCHDUMP_DEFAULT_FORMAT
//! Compile-time default format of the comment line in file dump.
#if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1
#define CLUTCHDUMP_DEFAULT_FORMAT "# [{name}] {level} in {func} (at depth {depth})"
#else
#define CLUTCHDUMP_DEFAULT_FORMAT "# {level} in {func}"
#endif
#endif // CLUTCHDUMP_DEFAULT_FORMAT
#endif
//! Default format of the comment line in file dump. //! Default format of the comment line in file dump.
static inline std::string dump_default_format = CLUTCHDUMP_DEFAULT_FORMAT; static inline std::string dump_default_format = CLUTCHDUMP_DEFAULT_FORMAT;
@ -221,6 +250,13 @@ class clutchlog
#endif // CLUTCHLOG_STRIP_CALLS #endif // CLUTCHLOG_STRIP_CALLS
//! Number of call stack levels to remove from depth display by default. //! Number of call stack levels to remove from depth display by default.
static inline unsigned int default_strip_calls = CLUTCHLOG_STRIP_CALLS; static inline unsigned int default_strip_calls = CLUTCHLOG_STRIP_CALLS;
#ifndef CLUTCHLOG_HFILL_MARK
//! Character used as a filling for right-align the right part of messages with "{hfill}".
#define CLUTCHLOG_HFILL_MARK '.'
#endif // CLUTCHLOG_HFILL_MARK
//! Default character used as a filling for right-align the right part of messages with "{hfill}".
static inline char default_hfill_char = CLUTCHLOG_HFILL_MARK;
/* @} */ /* @} */
@ -399,6 +435,7 @@ class clutchlog
}), }),
_format_log(clutchlog::default_format), _format_log(clutchlog::default_format),
_format_dump(clutchlog::dump_default_format), _format_dump(clutchlog::dump_default_format),
_hfill_char(clutchlog::default_hfill_char),
_out(&std::clog), _out(&std::clog),
#if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1 #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1
_depth(std::numeric_limits<size_t>::max() - _strip_calls), _depth(std::numeric_limits<size_t>::max() - _strip_calls),
@ -413,6 +450,11 @@ class clutchlog
for(auto& lw : _level_word) { for(auto& lw : _level_word) {
_word_level[lw.second] = lw.first; _word_level[lw.second] = lw.first;
} }
#if CLUTCHLOG_HAVE_UNIX_SYSIOCTL
struct winsize w;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
_nb_columns = w.ws_col;
#endif
} }
protected: protected:
@ -428,6 +470,8 @@ class clutchlog
std::string _format_log; std::string _format_log;
/** Current format of the file output. */ /** Current format of the file output. */
std::string _format_dump; std::string _format_dump;
/** Character for filling. */
const char _hfill_char;
/** Standard output. */ /** Standard output. */
std::ostream* _out; std::ostream* _out;
#if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1 #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1
@ -449,6 +493,11 @@ class clutchlog
/** Maximum buffer size for backtrace message. */ /** Maximum buffer size for backtrace message. */
static const size_t _max_buffer = 4096; static const size_t _max_buffer = 4096;
#endif #endif
#if CLUTCHLOG_HAVE_UNIX_SYSIOCTL
/** Current terminal size (for right-alignment). */
size_t _nb_columns;
#endif
/** @}*/ /** @}*/
public: public:
@ -478,10 +527,16 @@ class clutchlog
size_t depth() const {return _depth;} size_t depth() const {return _depth;}
//! Set the string mark with which stack depth is indicated. //! Set the string mark with which stack depth is indicated.
void depth_mark(std::string mark) {_depth_mark = mark;} void depth_mark(const std::string mark) {_depth_mark = mark;}
//! Get the string mark with which stack depth is indicated. //! Get the string mark with which stack depth is indicated.
std::string depth_mark() const {return _depth_mark;} std::string depth_mark() const {return _depth_mark;}
#endif #endif
#if CLUTCHLOG_HAVE_UNIX_SYSIOCTL == 1
//! Set the character for the stretching hfill marker.
void hfill_mark(const char mark) {_hfill_char = mark;}
//! Get the character for the stretching hfill marker.
char hfill_mark() const {return _hfill_char;}
#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.
void threshold(level l) {_stage = l;} void threshold(level l) {_stage = l;}
@ -730,7 +785,28 @@ class clutchlog
} }
format = replace(format, "\\{depth_marks\\}", chevrons.str()); format = replace(format, "\\{depth_marks\\}", chevrons.str());
#endif #endif
#if CLUTCHLOG_HAVE_UNIX_SYSIOCTL
const std::string hfill_tag = "{hfill}";
const size_t hfill_pos = format.find(hfill_tag);
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(0, _nb_columns-right_len), _hfill_char);
format = replace(format, "\\{hfill\\}", "\n"+hfill);
} else {
// There is some space in between left and right parts.
const std::string hfill(std::max(0, _nb_columns - (right_len+left_len)), _hfill_char);
format = replace(format, "\\{hfill\\}", hfill);
}
} else {
// We don't know the terminal width.
format = replace(format, "\\{hfill\\}", _hfill_char);
}
}
#endif
return _level_fmt.at(stage)(format); return _level_fmt.at(stage)(format);
} }
@ -878,9 +954,13 @@ class clutchlog
void depth(size_t) {} void depth(size_t) {}
size_t depth() const {} size_t depth() const {}
void depth_mark(std::string) {} void depth_mark(const std::string) {}
std::string depth_mark() const {} std::string depth_mark() const {}
#endif #endif
#if CLUTCHLOG_HAVE_UNIX_SYSIOCTL == 1
void hfill_mark(const char) {}
char hfill_mark() const {}
#endif
void threshold(level) {} void threshold(level) {}
void threshold(const std::string&) {} void threshold(const std::string&) {}