diff --git a/README.md b/README.md index 64f8fdf..639ab28 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ Clutchlog — versatile (de)clutchable logging ***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.*** +- [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) [TOC] @@ -176,33 +179,66 @@ log.format("{msg}"); Available tags are: - `{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_letter}`: the first letter of the current log level, - `{file}`: the current file (absolute path), - `{func}`: the current function, -- `{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. +- `{line}`: the current line number. -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. -The default format of the first line of comment added with the dump macro is +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 `"# [{name}] {level} in {func} (at depth {depth}) @ {file}:{line}"`. It can be edited with the `format_comment` method. 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`. + By default, the separator between items in the container is a new line. To change this behaviour, you can change `CLUTCHDUMP_DEFAULT_SEP` or 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, 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, +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` entrypoint corresponds to a depth of zero. You can change this behaviour by defining the `CLUTCHLOG_STRIP_CALLS` macro. @@ -362,7 +398,7 @@ Limitations ### 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 are only available for operating systems having the following headers: `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 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 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 diff --git a/clutchlog/clutchlog.h b/clutchlog/clutchlog.h index 969e83c..19bd4f8 100644 --- a/clutchlog/clutchlog.h +++ b/clutchlog/clutchlog.h @@ -32,6 +32,16 @@ #define CLUTCHLOG_HAVE_UNIX_SYSINFO 0 #endif +#if __has_include() && __has_include() && __has_include() + #include + #include + #include + #define CLUTCHLOG_HAVE_UNIX_SYSIOCTL 1 +#else + #define CLUTCHLOG_HAVE_UNIX_SYSIOCTL 0 +#endif + + /********************************************************************** * Enable by default in Debug builds. **********************************************************************/ @@ -171,33 +181,52 @@ class clutchlog #ifndef NDEBUG #ifndef CLUTCHLOG_DEFAULT_FORMAT //! Compile-time default format of the messages (debug mode: with absolute location). - #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1 - #define CLUTCHLOG_DEFAULT_FORMAT "[{name}] {level_letter}:{depth_marks} {msg}\t\t\t\t\t{func} @ {file}:{line}\n" + #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" + #endif #else - #define CLUTCHLOG_DEFAULT_FORMAT "{level_letter} {msg}\t\t\t\t\t{func} @ {file}:{line}\n" + #if CLUTCHLOG_HAVE_UNIX_SYSIOCTL == 1 + #define CLUTCHLOG_DEFAULT_FORMAT "{level_letter} {msg} {hfill} {func} @ {file}:{line}\n" + #else + #define CLUTCHLOG_DEFAULT_FORMAT "{level_letter} {msg}\t\t\t\t\t{func} @ {file}:{line}\n" + #endif #endif #endif #else #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 - #define CLUTCHLOG_DEFAULT_FORMAT "[{name}] {level_letter}:{depth_marks} {msg}\n" + #define CLUTCHLOG_DEFAULT_FORMAT "{level_letter}:{depth_marks} {msg} {hfill} {func}\n" #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 //! Default format of the messages. static inline std::string default_format = CLUTCHLOG_DEFAULT_FORMAT; - #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}) @ {file}:{line}" - #else - #define CLUTCHDUMP_DEFAULT_FORMAT "# {level} in {func} @ {file}:{line}" - #endif - #endif // CLUTCHDUMP_DEFAULT_FORMAT + #ifndef NDEBUG + #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}) @ {file}:{line}" + #else + #define CLUTCHDUMP_DEFAULT_FORMAT "# {level} in {func} @ {file}:{line}" + #endif + #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. static inline std::string dump_default_format = CLUTCHDUMP_DEFAULT_FORMAT; @@ -221,6 +250,13 @@ class clutchlog #endif // CLUTCHLOG_STRIP_CALLS //! Number of call stack levels to remove from depth display by default. 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_dump(clutchlog::dump_default_format), + _hfill_char(clutchlog::default_hfill_char), _out(&std::clog), #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1 _depth(std::numeric_limits::max() - _strip_calls), @@ -413,6 +450,11 @@ class clutchlog for(auto& lw : _level_word) { _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: @@ -428,6 +470,8 @@ class clutchlog std::string _format_log; /** Current format of the file output. */ std::string _format_dump; + /** Character for filling. */ + const char _hfill_char; /** Standard output. */ std::ostream* _out; #if CLUTCHLOG_HAVE_UNIX_SYSINFO == 1 @@ -449,6 +493,11 @@ class clutchlog /** Maximum buffer size for backtrace message. */ static const size_t _max_buffer = 4096; #endif + +#if CLUTCHLOG_HAVE_UNIX_SYSIOCTL + /** Current terminal size (for right-alignment). */ + size_t _nb_columns; +#endif /** @}*/ public: @@ -478,10 +527,16 @@ class clutchlog size_t depth() const {return _depth;} //! 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. std::string depth_mark() const {return _depth_mark;} #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. void threshold(level l) {_stage = l;} @@ -730,7 +785,28 @@ class clutchlog } format = replace(format, "\\{depth_marks\\}", chevrons.str()); #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); } @@ -878,9 +954,13 @@ class clutchlog void depth(size_t) {} size_t depth() const {} - void depth_mark(std::string) {} + void depth_mark(const std::string) {} std::string depth_mark() const {} #endif +#if CLUTCHLOG_HAVE_UNIX_SYSIOCTL == 1 + void hfill_mark(const char) {} + char hfill_mark() const {} +#endif void threshold(level) {} void threshold(const std::string&) {}