feat: add ANSI color/style formatting
- at some log levels by defaults, - can be used within format
This commit is contained in:
parent
3a4bdc0d57
commit
a93411c4f6
3 changed files with 266 additions and 9 deletions
63
README.md
63
README.md
|
|
@ -167,6 +167,62 @@ log.depth_mark(CLUTCHLOG_DEFAULT_DEPTH_MARK); // Defaults to ">".
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Output style
|
||||||
|
------------
|
||||||
|
|
||||||
|
The output can be colored differently depending on the log level.
|
||||||
|
```cpp
|
||||||
|
// Print error messages in bold red:
|
||||||
|
log.style(clutchlog::level::error,
|
||||||
|
clutchlog::fmt(
|
||||||
|
clutchlog::fmt::fg::red,
|
||||||
|
clutchlog::fmt::typo::bold));
|
||||||
|
```
|
||||||
|
|
||||||
|
Using the `clutchlog::fmt` class, you can style:
|
||||||
|
|
||||||
|
- the foreground color, passing a `clutchlog::fmt::fg`,
|
||||||
|
- the background color, passing a `clutchlog::fmt::bg`,
|
||||||
|
- some typographic style, passing a `clutchlog::fmt::typo`.
|
||||||
|
|
||||||
|
Any of the three arguments may be passed, in any order,
|
||||||
|
if an argument is omitted, it defaults to no color/style.
|
||||||
|
|
||||||
|
Available colors are:
|
||||||
|
|
||||||
|
- black,
|
||||||
|
- red,
|
||||||
|
- green,
|
||||||
|
- yellow,
|
||||||
|
- blue,
|
||||||
|
- magenta,
|
||||||
|
- cyan,
|
||||||
|
- white,
|
||||||
|
- none.
|
||||||
|
|
||||||
|
Available typographies:
|
||||||
|
|
||||||
|
- reset (remove any style),
|
||||||
|
- bold,
|
||||||
|
- underline,
|
||||||
|
- inverse,
|
||||||
|
- none.
|
||||||
|
|
||||||
|
You may use styling within the format message template itself, to add even more colors:
|
||||||
|
```cpp
|
||||||
|
using fmt = clutchlog::fmt;
|
||||||
|
std::ostringstream format;
|
||||||
|
fmt discreet(fmt::fg::blue);
|
||||||
|
format << "{level}: "
|
||||||
|
<< discreet("{file}:") // Used as a function (inserts a reset at the end).
|
||||||
|
<< fmt(fmt::fg::yellow) << "{line}" // Used as a tag (no reset inserted).
|
||||||
|
<< fmt(fmt::typo::reset) << " {msg}" << std::endl; // This is a reset.
|
||||||
|
log.format(format.str());
|
||||||
|
```
|
||||||
|
Note: messages at the "quiet", "error" and "warning" log levels are colored by default.
|
||||||
|
You may want to set their style to `none` if you want to cleanly insert colors in the format template.
|
||||||
|
|
||||||
|
|
||||||
Disabled calls
|
Disabled calls
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
|
@ -229,6 +285,8 @@ the features relying on the depth of the call stack and the display of the progr
|
||||||
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).
|
||||||
|
|
||||||
|
Some colors/styles may not be supported by some exotic terminal emulators.
|
||||||
|
|
||||||
Clutchlog needs `C++-17` with the `filesystem` feature.
|
Clutchlog needs `C++-17` with the `filesystem` feature.
|
||||||
You may need to indicate `-std=c++17 -lstdc++fs` to your compiler.
|
You may need to indicate `-std=c++17 -lstdc++fs` to your compiler.
|
||||||
|
|
||||||
|
|
@ -256,3 +314,8 @@ ctest
|
||||||
|
|
||||||
There's a script which tests all the build types combinations: `./build_all.sh`.
|
There's a script which tests all the build types combinations: `./build_all.sh`.
|
||||||
|
|
||||||
|
|
||||||
|
How to
|
||||||
|
======
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,121 @@ class clutchlog
|
||||||
|
|
||||||
/** }@ High-level API */
|
/** }@ High-level API */
|
||||||
|
|
||||||
|
/** Formating API @{ */
|
||||||
|
|
||||||
|
/** Color and style formatter for ANSI terminal escape sequences.
|
||||||
|
*
|
||||||
|
* @note All styles may not be supported by a given terminal/operating system.
|
||||||
|
*/
|
||||||
|
class fmt {
|
||||||
|
public:
|
||||||
|
//! Foreground color codes.
|
||||||
|
enum class fg {
|
||||||
|
black = 30,
|
||||||
|
red = 31,
|
||||||
|
green = 32,
|
||||||
|
yellow = 33,
|
||||||
|
blue = 34,
|
||||||
|
magenta = 35,
|
||||||
|
cyan = 36,
|
||||||
|
white = 37,
|
||||||
|
none
|
||||||
|
} fore;
|
||||||
|
|
||||||
|
//! Background color codes.
|
||||||
|
enum class bg {
|
||||||
|
black = 40,
|
||||||
|
red = 41,
|
||||||
|
green = 42,
|
||||||
|
yellow = 43,
|
||||||
|
blue = 44,
|
||||||
|
magenta = 45,
|
||||||
|
cyan = 46,
|
||||||
|
white = 47,
|
||||||
|
none
|
||||||
|
} back;
|
||||||
|
|
||||||
|
//! Typographic style codes.
|
||||||
|
enum class typo {
|
||||||
|
reset = 0,
|
||||||
|
bold = 1,
|
||||||
|
underline = 4,
|
||||||
|
inverse = 7,
|
||||||
|
none
|
||||||
|
} style;
|
||||||
|
|
||||||
|
//! Empty constructor, only useful for a no-op formatter.
|
||||||
|
fmt() : fore(fg::none), back(bg::none), style(typo::none) { }
|
||||||
|
|
||||||
|
/** All combination of constructors with different parameters orders. @{ */
|
||||||
|
fmt( fg f, bg b = bg::none, typo s = typo::none) : fore(f), back(b), style(s) { }
|
||||||
|
fmt( fg f, typo s , bg b = bg::none) : fore(f), back(b), style(s) { }
|
||||||
|
fmt( bg b, fg f = fg::none, typo s = typo::none) : fore(f), back(b), style(s) { }
|
||||||
|
fmt( bg b, typo s , fg f = fg::none) : fore(f), back(b), style(s) { }
|
||||||
|
fmt(typo s, fg f = fg::none, bg b = bg::none) : fore(f), back(b), style(s) { }
|
||||||
|
fmt(typo s, bg b , fg f = fg::none) : fore(f), back(b), style(s) { }
|
||||||
|
/** @} */
|
||||||
|
|
||||||
|
protected:
|
||||||
|
//! Print the currently encoded format escape code on the given output stream.
|
||||||
|
std::ostream& print_on( std::ostream& os) const
|
||||||
|
{
|
||||||
|
std::vector<int> codes; codes.reserve(3);
|
||||||
|
if(this->fore != fg::none) { codes.push_back(static_cast<int>(this->fore ));}
|
||||||
|
if(this->back != bg::none) { codes.push_back(static_cast<int>(this->back ));}
|
||||||
|
if(this->style != typo::none) { codes.push_back(static_cast<int>(this->style));}
|
||||||
|
if(codes.size() == 0) {return os;}
|
||||||
|
|
||||||
|
os << "\033[";
|
||||||
|
assert(codes.size() > 0);
|
||||||
|
os << codes[0];
|
||||||
|
for(size_t i=1; i < codes.size(); ++i) {
|
||||||
|
os << ";" << codes[i];
|
||||||
|
}
|
||||||
|
os << "m";
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Output stream overload.
|
||||||
|
*
|
||||||
|
* Allow to use a formatter as a tag within a stream:
|
||||||
|
* @code
|
||||||
|
* clutchlog::fmt end(clutchlog::fmt::typo::reset);
|
||||||
|
* clutchlog::fmt error(clutchlog::fmt::fg::red, clutchlog::fmt::typo::bold);
|
||||||
|
* std::cout << error << "ERROR" << end << std::endl;
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* @note An formatter called this way will NOT output a reset escape code.
|
||||||
|
*/
|
||||||
|
friend std::ostream& operator<<(std::ostream& os, const fmt& fmt)
|
||||||
|
{
|
||||||
|
return fmt.print_on(os);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Format the given string with the currently encoded format.
|
||||||
|
*
|
||||||
|
* Allow to use a formatter as a function:
|
||||||
|
* @code
|
||||||
|
* clutchlog::fmt error(clutchlog::fmt::fg::red, clutchlog::fmt::typo::bold);
|
||||||
|
* std::cout << error("ERROR") << std::endl;
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* @note A formatter called this way WILL output a reset escape code at the end.
|
||||||
|
*/
|
||||||
|
std::string operator()( const std::string& msg ) const
|
||||||
|
{
|
||||||
|
std::ostringstream os;
|
||||||
|
this->print_on(os);
|
||||||
|
fmt end(fmt::typo::reset);
|
||||||
|
os << msg;
|
||||||
|
end.print_on(os);
|
||||||
|
return os.str();
|
||||||
|
}
|
||||||
|
}; // fmt class
|
||||||
|
|
||||||
|
/** @} Formating API */
|
||||||
|
|
||||||
/** Internal details @{ */
|
/** Internal details @{ */
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
@ -147,15 +262,24 @@ class clutchlog
|
||||||
private:
|
private:
|
||||||
clutchlog() :
|
clutchlog() :
|
||||||
// system, main, log
|
// system, main, log
|
||||||
_strip_calls(3),
|
_strip_calls(5),
|
||||||
_level_words({
|
_level_words({
|
||||||
{level::quiet,"Quiet"},
|
{level::quiet ,"Quiet"},
|
||||||
{level::error,"Error"},
|
{level::error ,"Error"},
|
||||||
{level::warning,"Warning"},
|
{level::warning ,"Warning"},
|
||||||
{level::progress,"Progress"},
|
{level::progress,"Progress"},
|
||||||
{level::info,"Info"},
|
{level::info ,"Info"},
|
||||||
{level::debug,"Debug"},
|
{level::debug ,"Debug"},
|
||||||
{level::xdebug,"XDebug"}
|
{level::xdebug ,"XDebug"}
|
||||||
|
}),
|
||||||
|
_level_fmt({
|
||||||
|
{level::quiet ,fmt(fmt::bg::red, fmt::typo::underline)},
|
||||||
|
{level::error ,fmt(fmt::fg::red, fmt::typo::bold)},
|
||||||
|
{level::warning ,fmt(fmt::fg::magenta, fmt::typo::bold)},
|
||||||
|
{level::progress,fmt()},
|
||||||
|
{level::info ,fmt()},
|
||||||
|
{level::debug ,fmt()},
|
||||||
|
{level::xdebug ,fmt()}
|
||||||
}),
|
}),
|
||||||
_format_log(CLUTCHLOG_DEFAULT_FORMAT),
|
_format_log(CLUTCHLOG_DEFAULT_FORMAT),
|
||||||
_format_dump(CLUTCHDUMP_DEFAULT_FORMAT),
|
_format_dump(CLUTCHDUMP_DEFAULT_FORMAT),
|
||||||
|
|
@ -173,6 +297,7 @@ class clutchlog
|
||||||
protected:
|
protected:
|
||||||
const size_t _strip_calls;
|
const size_t _strip_calls;
|
||||||
const std::map<level,std::string> _level_words;
|
const std::map<level,std::string> _level_words;
|
||||||
|
std::map<level,fmt> _level_fmt;
|
||||||
std::string _format_log;
|
std::string _format_log;
|
||||||
std::string _format_dump;
|
std::string _format_dump;
|
||||||
std::ostream* _out;
|
std::ostream* _out;
|
||||||
|
|
@ -202,6 +327,7 @@ class clutchlog
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
//! Gather information on the current location of the call.
|
//! Gather information on the current location of the call.
|
||||||
scope_t locate(
|
scope_t locate(
|
||||||
const level& stage,
|
const level& stage,
|
||||||
|
|
@ -290,6 +416,9 @@ class clutchlog
|
||||||
line(in_line);
|
line(in_line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void style(level stage, fmt style) { _level_fmt.at(stage) = style; }
|
||||||
|
fmt style(level stage) const { return _level_fmt.at(stage); }
|
||||||
|
|
||||||
/** }@ Configuration */
|
/** }@ Configuration */
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
@ -389,9 +518,9 @@ class clutchlog
|
||||||
format = replace(format, "\\{msg\\}", what);
|
format = replace(format, "\\{msg\\}", what);
|
||||||
format = replace(format, "\\{file\\}", file);
|
format = replace(format, "\\{file\\}", file);
|
||||||
format = replace(format, "\\{func\\}", func);
|
format = replace(format, "\\{func\\}", func);
|
||||||
format = replace(format, "\\{level\\}", _level_words.at(stage));
|
|
||||||
format = replace(format, "\\{line\\}", line);
|
format = replace(format, "\\{line\\}", line);
|
||||||
|
|
||||||
|
format = replace(format, "\\{level\\}", _level_words.at(stage));
|
||||||
std::string letter(1, _level_words.at(stage).at(0)); // char -> string
|
std::string letter(1, _level_words.at(stage).at(0)); // char -> string
|
||||||
format = replace(format, "\\{level_letter\\}", letter);
|
format = replace(format, "\\{level_letter\\}", letter);
|
||||||
|
|
||||||
|
|
@ -406,7 +535,7 @@ class clutchlog
|
||||||
format = replace(format, "\\{depth_marks\\}", chevrons.str());
|
format = replace(format, "\\{depth_marks\\}", chevrons.str());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return format;
|
return _level_fmt.at(stage)(format);
|
||||||
}
|
}
|
||||||
|
|
||||||
void log(
|
void log(
|
||||||
|
|
@ -498,6 +627,24 @@ class clutchlog
|
||||||
public:
|
public:
|
||||||
static clutchlog& logger() { }
|
static clutchlog& logger() { }
|
||||||
enum level {quiet=0, error=1, warning=2, progress=3, info=4, debug=5, xdebug=6};
|
enum level {quiet=0, error=1, warning=2, progress=3, info=4, debug=5, xdebug=6};
|
||||||
|
class fmt {
|
||||||
|
public:
|
||||||
|
enum class fg { black, red, green, yellow, blue, magenta, cyan, white, none } fore;
|
||||||
|
enum class bg { black, red, green, yellow, blue, magenta, cyan, white, none } back;
|
||||||
|
enum class typo { reset, bold, underline, inverse, none } style;
|
||||||
|
fmt() : fore(fg::none), back(bg::none), style(typo::none) { }
|
||||||
|
fmt( fg f, bg b = bg::none, typo s = typo::none) : fore(f), back(b), style(s) { }
|
||||||
|
fmt( fg f, typo s , bg b = bg::none) : fore(f), back(b), style(s) { }
|
||||||
|
fmt( bg b, fg f = fg::none, typo s = typo::none) : fore(f), back(b), style(s) { }
|
||||||
|
fmt( bg b, typo s , fg f = fg::none) : fore(f), back(b), style(s) { }
|
||||||
|
fmt(typo s, fg f = fg::none, bg b = bg::none) : fore(f), back(b), style(s) { }
|
||||||
|
fmt(typo s, bg b , fg f = fg::none) : fore(f), back(b), style(s) { }
|
||||||
|
protected:
|
||||||
|
std::ostream& print_on(std::ostream&) const { }
|
||||||
|
public:
|
||||||
|
friend std::ostream& operator<<(std::ostream&, const fmt&) { }
|
||||||
|
std::string operator()(const std::string&) const { }
|
||||||
|
};
|
||||||
public:
|
public:
|
||||||
clutchlog(clutchlog const&) = delete;
|
clutchlog(clutchlog const&) = delete;
|
||||||
void operator=(clutchlog const&) = delete;
|
void operator=(clutchlog const&) = delete;
|
||||||
|
|
@ -546,6 +693,8 @@ class clutchlog
|
||||||
)
|
)
|
||||||
{ }
|
{ }
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
|
void style(level, fmt) { }
|
||||||
|
fmt style(level) const { }
|
||||||
public:
|
public:
|
||||||
std::string replace(
|
std::string replace(
|
||||||
const std::string&,
|
const std::string&,
|
||||||
|
|
|
||||||
45
tests/t-color.cpp
Normal file
45
tests/t-color.cpp
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
#include <iostream>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include "../clutchlog/clutchlog.h"
|
||||||
|
|
||||||
|
int main(/*const int argc, char* argv[]*/)
|
||||||
|
{
|
||||||
|
using typo = clutchlog::fmt::typo;
|
||||||
|
using fg = clutchlog::fmt::fg;
|
||||||
|
using bg = clutchlog::fmt::bg;
|
||||||
|
|
||||||
|
clutchlog::fmt none;
|
||||||
|
clutchlog::fmt end(typo::reset);
|
||||||
|
clutchlog::fmt note(typo::bold);
|
||||||
|
clutchlog::fmt info(fg::green);
|
||||||
|
clutchlog::fmt warning(fg::magenta, typo::bold);
|
||||||
|
clutchlog::fmt error(fg::red, typo::bold);
|
||||||
|
clutchlog::fmt critical(bg::red, typo::underline, fg::black);
|
||||||
|
|
||||||
|
auto& log = clutchlog::logger();
|
||||||
|
log.threshold(clutchlog::level::info);
|
||||||
|
|
||||||
|
// Change a style.
|
||||||
|
log.style(clutchlog::level::quiet, error);
|
||||||
|
CLUTCHLOG(quiet,"Styles demo");
|
||||||
|
|
||||||
|
CLUTCHLOG(info,"Either using functions...");
|
||||||
|
std::cout << none("No style: ") << std::endl;
|
||||||
|
std::cout << note("NOTE: bold") << std::endl;
|
||||||
|
std::cout << info("INFO: green") << std::endl;
|
||||||
|
|
||||||
|
CLUTCHLOG(info,"... or tags.");
|
||||||
|
std::cout << warning << "WARNING" << end << ": bold magenta" << std::endl;
|
||||||
|
std::cout << error << "ERROR" << end << ": bold red" << std::endl;
|
||||||
|
std::cout << critical << "CRITICAL" << end << ": underlined black over red background" << std::endl;
|
||||||
|
|
||||||
|
std::ostringstream format;
|
||||||
|
clutchlog::fmt discreet(clutchlog::fmt::fg::white);
|
||||||
|
format << "{level}: "
|
||||||
|
<< discreet("{file}:")
|
||||||
|
<< clutchlog::fmt(clutchlog::fmt::fg::yellow) << "{line}"
|
||||||
|
<< clutchlog::fmt(clutchlog::fmt::typo::reset) << " {msg} ! " << std::endl;
|
||||||
|
log.format(format.str());
|
||||||
|
CLUTCHLOG(quiet,"After having inserted styles within a new format template");
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue