diff --git a/CMakeLists.txt b/CMakeLists.txt index dfda08c..e2ddb63 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ mark_as_advanced(VERSION_MAJOR VERSION_MINOR VERSION_PATCH) # There's no point building clutchlog tests in Release set(CMAKE_BUILD_TYPE Debug) +set(CMAKE_CXX_STANDARD_LIBRARIES -lstdc++fs) ###################################################################################### # Configurable user settings diff --git a/README.md b/README.md index 011bdd3..6ac331f 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,9 @@ allowing for the fast tracking of a bug across the execution. API documentation ================= +Calls +----- + The main entrypoint is the `CLUTCHLOG` macro, which takes the desired log level and message. The message can be anything which can be output in an `ostringstream`. ```cpp @@ -76,11 +79,61 @@ CLUTCHLOG(error, value); CLUTCHLOG(debug, "hello " << value << " world"); ``` +There is also a macro to dump the content of an iterable within a separate file: `CLUTCHDUMP`. +This function takes care of incrementing a numeric suffix in the file name, +if an existing file exists. +```cpp +std::vector v(10); +std::generate(v.begin(), v.end(), std::rand); +CLUTCHLOG(debug, vec, "test_{n}.dat"); +/* Will output in cat "rand_0.dat" +* # [t-dump] Info in main (at depth 5) @ /home/nojhan/code/clutchlog/tests/t-dump.cpp:22 +* 1804289383 +* 846930886 +* 1681692777 +*/ +``` +Note that if you pass a file name without the `{n}` tag, the file will be overwritten as is. + + +Location filtering +------------------ + To configure the global behaviour of the logger, you must first get a reference on its instance: ```cpp auto& log = clutchlog::logger(); ``` +One can configure the location(s) at which messages should actually be logged: +```cpp +log.depth(3); // Depth of the call stack, defaults to the maximum possible value. +log.threshold(clutchlog::level::error); // Log level, defaults to error. +``` +Current levels are defined in an enumeration as `clutchlog::level`: +```cpp +enum level {quiet=0, error=1, warning=2, progress=3, info=4, debug=5, xdebug=6}; +``` + +File, function and line are indicated using regular expression: +```cpp +log.file(".*"); // File location, defaults to any. +log.func(".*"); // Function location, defaults to any. +log.line(".*"); // Line location, defaults to any. +``` +A shortcut function can be used to indicates file, function and line regular expression at once: +```cpp +log.location(file, func, line); // Defaults to any, second and last parameters being optional. +``` + + +Output Configuration +-------------------- + +The output stream can be configured using the `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 `{}`: ```cpp log.format("{msg}"); @@ -97,29 +150,16 @@ Available tags are: - `{depth}`: the current depth of the call stack, - `{depth_marks}`: as many chevrons `>` as there is calls in the stack. -The default format is `"[{name}] {level_letter}:{depth_marks} {msg}\t\t\t\t\t{func} @ {file}:{line}\n"`, +The default log format is `"[{name}] {level_letter}:{depth_marks} {msg}\t\t\t\t\t{func} @ {file}:{line}\n"`, it can be overriden at compile time by defining the `CLUTCHLOG_DEFAULT_FORMAT` macro. -The output stream can be configured using the `out` method: -```cpp -log.out(std::clog); // Defaults to clog. -``` - -One can configure the location(s) at which messages should actually be logged: -```cpp -log.depth(3); // Depth of the call stack, defaults to the maximum possible value. -log.threshold(clutchlog::level::error); // Log level, defaults to error. -``` -File, function and line are indicated using regular expression: -```cpp -log.file(".*"); // File location, defaults to any. -log.func(".*"); // Function location, defaults to any. -log.line(".*"); // Line location, defaults to any. -``` -A shortcut function can be used to indicates file, function and line regular expression at once: -```cpp -log.location(file, func, line); // Defaults to any, second and last parameters being optional. -``` +The default format of the comment added with the dump macro is +`"# [{name}] {level} in {func} (at depth {depth}) @ {file}:{line}"`. +It can be modified at compile time with `CLUTCHDUMP_DEFAULT_FORMAT`. +If it is set to an empty string, then no comment line is added. +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. 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: @@ -127,6 +167,9 @@ and its default with the `CLUTCHLOG_DEFAULT_DEPTH_MARK` macro: log.depth_mark(CLUTCHLOG_DEFAULT_DEPTH_MARK); // Defaults to ">". ``` +Low-level API +------------- + All configuration setters have a getters counterpart, with the same name but taking no parameter, for example: ```cpp @@ -141,6 +184,11 @@ A helper macro can helps to fill in the location with the actual one, as seen by ```cpp log.log(clutchlog::level::xdebug, "hello world", CLUTCHLOC); ``` +A similar `dump` method exists: +```cpp +log.dump(clutchlog::level::xdebug, cont.begin(), cont.end(), CLUTCHLOC, "dumped_{n}.dat", "\n"); +log.dump(clutchlog::level::xdebug, cont.begin(), cont.end(), "main.cpp", "main", 122, "dumped.dat", "\n\n"); +``` Limitations @@ -153,11 +201,14 @@ Clutchlog is only implemented for Linux at the moment. Build and tests =============== +To use clutchlog, just include its header in your code. + To build and run the tests, just use a classical CMake workflow: ```sh mkdir build cd build -cmake .. +# There's no point building in Release mode, at clutchlog is declutched. +cmake -DCMAKE_BUILD_TYPE=Debug .. make ctest ``` diff --git a/clutchlog/clutchlog.h b/clutchlog/clutchlog.h index af5de90..f25d299 100644 --- a/clutchlog/clutchlog.h +++ b/clutchlog/clutchlog.h @@ -1,11 +1,13 @@ #ifndef __CLUTCHLOG_H__ #define __CLUTCHLOG_H__ +#include #include #include +#include #include #include -#include +// #include #include #include #include @@ -36,6 +38,16 @@ #define CLUTCHLOG_DEFAULT_FORMAT "[{name}] {level_letter}:{depth_marks} {msg}\t\t\t\t\t{func} @ {file}:{line}\n" #endif +#ifndef CLUTCHDUMP_DEFAULT_FORMAT +//! Default format of the comment line in file dump. +#define CLUTCHDUMP_DEFAULT_FORMAT "# [{name}] {level} in {func} (at depth {depth}) @ {file}:{line}" +#endif + +#ifndef CLUTCHDUMP_DEFAULT_SEP +//! Default item separator for dump. +#define CLUTCHDUMP_DEFAULT_SEP "\n" +#endif + #ifndef CLUTCHLOG_DEFAULT_DEPTH_MARK #define CLUTCHLOG_DEFAULT_DEPTH_MARK ">" #endif @@ -51,15 +63,15 @@ } //! Dump the given container. -#define CLUTCHDUMP( LEVEL, CONTAINER ) { \ +#define CLUTCHDUMP( LEVEL, CONTAINER, FILENAME ) { \ auto& logger = clutchlog::logger(); \ - logger.dump(clutchlog::level::LEVEL, std::begin(CONTAINER), std::end(CONTAINER), CLUTCHLOC, "\n"); \ + logger.dump(clutchlog::level::LEVEL, std::begin(CONTAINER), std::end(CONTAINER), CLUTCHLOC, FILENAME, CLUTCHDUMP_DEFAULT_SEP); \ } #else // Disabled macros can still be used in Release builds. #define CLUTCHLOG ( LEVEL, WHAT ) { do {/*nothing*/} while(false); } -#define CLUTCHDUMP ( LEVEL, WHAT ) { do {/*nothing*/} while(false); } +#define CLUTCHDUMP ( LEVEL, CONTAINER, FILENAME ) { do {/*nothing*/} while(false); } #endif /********************************************************************** @@ -85,7 +97,7 @@ class clutchlog } //! Available log levels. - enum level {quiet=0, error=1, warning=2, info=3, debug=4, xdebug=5}; + enum level {quiet=0, error=1, warning=2, progress=3, info=4, debug=5, xdebug=6}; /** }@ High-level API */ @@ -103,11 +115,13 @@ class clutchlog {level::quiet,"Quiet"}, {level::error,"Error"}, {level::warning,"Warning"}, + {level::progress,"Progress"}, {level::info,"Info"}, {level::debug,"Debug"}, {level::xdebug,"XDebug"} }), - _format(CLUTCHLOG_DEFAULT_FORMAT), + _format_log(CLUTCHLOG_DEFAULT_FORMAT), + _format_dump(CLUTCHDUMP_DEFAULT_FORMAT), _out(&std::clog), _depth(std::numeric_limits::max() - _strip_calls), _stage(level::error), @@ -120,7 +134,8 @@ class clutchlog protected: const size_t _strip_calls; const std::map _level_words; - std::string _format; + std::string _format_log; + std::string _format_dump; std::ostream* _out; size_t _depth; level _stage; @@ -172,8 +187,11 @@ class clutchlog /** Configuration accessors @{ */ - void format(const std::string& format) {_format = format;} - std::string format() const {return _format;} + void format(const std::string& format) {_format_log = format;} + std::string format() const {return _format_log;} + + void format_comment(const std::string& format) {_format_dump = format;} + std::string format_comment() const {return _format_dump;} void out(std::ostream& out) {_out = &out;} std::ostream& out() {return *_out;} @@ -268,10 +286,20 @@ class clutchlog // throw; // } // catch - std::regex re(mark); + const std::regex re(mark); return std::regex_replace(form, re, tag); } + std::string replace( + const std::string& form, + const std::string& mark, + const size_t tag + ) const + { + std::ostringstream stag; stag << tag; + return replace(form, mark, stag.str()); + } + std::string format( std::string format, const std::string& what, @@ -288,19 +316,16 @@ class clutchlog format = replace(format, "\\{file\\}", file); format = replace(format, "\\{func\\}", func); format = replace(format, "\\{level\\}", _level_words.at(stage)); + format = replace(format, "\\{line\\}", line); + format = replace(format, "\\{depth\\}", depth); - std::ostringstream sline; sline << line; - format = replace(format, "\\{line\\}", sline.str()); - - std::string letter(1, _level_words.at(stage).at(0)); + std::string letter(1, _level_words.at(stage).at(0)); // char -> string format = replace(format, "\\{level_letter\\}", letter); - std::ostringstream sdepth; sdepth << depth; - format = replace(format, "\\{depth\\}", sdepth.str()); - std::ostringstream chevrons; for(size_t i = _strip_calls; i < depth; ++i) { - chevrons << ">"; } + chevrons << ">"; + } format = replace(format, "\\{depth_marks\\}", chevrons.str()); return format; @@ -315,7 +340,7 @@ class clutchlog scope_t scope = locate(stage, file, func, line); if(scope.matches) { - *_out << format(_format, what, basename(getenv("_")), + *_out << format(_format_log, what, basename(getenv("_")), stage, file, func, line, scope.depth ); _out->flush(); @@ -327,21 +352,44 @@ class clutchlog const level& stage, const In container_begin, const In container_end, const std::string& file, const std::string& func, size_t line, - const std::string sep="\n" + const std::string& filename_template="dump_{n}.dat", + const std::string sep=CLUTCHDUMP_DEFAULT_SEP ) const - // FIXME use a file name template as input { scope_t scope = locate(stage, file, func, line); if(scope.matches) { - *_out << "#" // FIXME add a _format_dump parameter? - << format(_format, "", basename(getenv("_")), - stage, file, func, - line, scope.depth ); + const std::string tag = "\\{n\\}"; + const std::regex re(tag); + std::string outfile = ""; + + // If the file name template has the {n} tag. + if(std::regex_search(filename_template, re)) { + // Increment n until a free one is found. + size_t n = 0; + do { + outfile = replace(filename_template, tag, n); + n++; + } while( std::filesystem::exists( outfile ) ); + + } else { + // Use the parameter as is. + outfile = filename_template; + } + + std::ofstream fd(outfile); + + if(_format_dump.size() > 0) { + fd << format(_format_dump, "", basename(getenv("_")), + stage, file, func, + line, scope.depth ); + fd << sep; // sep after comment line. + } std::copy(container_begin, container_end, - std::ostream_iterator(*_out, sep.c_str())); - // No flush + std::ostream_iterator(fd, sep.c_str())); + + fd.close(); } // if scopes.matches } diff --git a/tests/t-dump.cpp b/tests/t-dump.cpp index 3df4b86..0dcee47 100644 --- a/tests/t-dump.cpp +++ b/tests/t-dump.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include "../clutchlog/clutchlog.h" @@ -13,6 +15,10 @@ int main(/*const int argc, char* argv[]*/) std::vector msg = {"hello", "world", "!"}; - CLUTCHDUMP(xdebug, msg); + CLUTCHDUMP(xdebug, msg, "test_{n}.dat"); + + std::vector v(3); + std::generate(v.begin(), v.end(), std::rand); + CLUTCHDUMP(info, v, "rand_{n}.dat"); #endif }