diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp index 1b3cc8f7..05a3c9e4 100644 --- a/example/ex3/ex3.cpp +++ b/example/ex3/ex3.cpp @@ -24,6 +24,7 @@ int main(int argc, char ** argv) { log_config::style = FS_Pretty; log_config::indent_width = 4; + log_config::location_tab = 40; int n = 4; @@ -34,3 +35,5 @@ main(int argc, char ** argv) { log(xtag("n", n)); log("<-", xtag("fib(n)", fn)); } + +/* ex3/ex3.cpp */ diff --git a/include/nestlog/filename.hpp b/include/nestlog/filename.hpp new file mode 100644 index 00000000..ead3b65e --- /dev/null +++ b/include/nestlog/filename.hpp @@ -0,0 +1,70 @@ +/* @file filename.hpp */ + +#pragma once + +#include +#include + +namespace xo { + /* Example: + * os << basename("/path/to/basename.cpp") + * prints + * basename.cpp + * on os + */ + + /* Tag to drive header-only expression */ + template + class basename_impl { + public: + basename_impl(std::string_view path) + : path_{path} {} + + std::string_view const & path() const { return path_; } + + /* /home/roland/proj/nestlog/include/nestlog/filename.hpp + * <-basename-> + */ + static void print_basename(std::ostream & os, std::string_view const & s) { + std::size_t p = exclude_dirname(s); + + os << s.substr(p); + } /*print_basename*/ + + private: + static std::size_t exclude_dirname(std::string_view const & s) { + std::size_t z = s.size(); + + if (z == 0) + return 0; + + if (s[z-1] == '/') { + /* ignore trailing '/' */ + return exclude_dirname(s.substr(0, z-1)); + } + + std::size_t p = s.find_last_of('/'); + + if (p == std::string_view::npos) + return 0; + else + return p + 1; + } /*exclude_dirname*/ + + private: + /* some unix pathname, e.g. [/home/roland/proj/nestlog/include/nestlog/filename.hpp] */ + std::string_view path_; + }; /*basename_impl*/ + + using basename = basename_impl; + + inline std::ostream & + operator<<(std::ostream & os, + basename const & bn) + { + basename::print_basename(os, bn.path()); + return os; + } +} /*xo*/ + +/* end filename.hpp */ diff --git a/include/nestlog/log_config.hpp b/include/nestlog/log_config.hpp index ce28665f..d6da65da 100644 --- a/include/nestlog/log_config.hpp +++ b/include/nestlog/log_config.hpp @@ -13,6 +13,10 @@ namespace xo { static std::uint32_t indent_width; /* display style for function names. FS_Simple|FS_Pretty|FS_Streamlined */ static function_style style; + /* if true, append [file:line] to output */ + static bool location_enabled; + /* when .location_enabled, write [file:line] starting this many chars from left margin */ + static std::uint32_t location_tab; }; /*log_config_impl*/ template @@ -23,6 +27,14 @@ namespace xo { function_style log_config_impl::style = FS_Streamlined; + template + bool + log_config_impl::location_enabled = true; + + template + std::uint32_t + log_config_impl::location_tab = 80; + using log_config = log_config_impl; } /*namespace xo*/ diff --git a/include/nestlog/log_state.hpp b/include/nestlog/log_state.hpp index 59c3b4f6..73b77960 100644 --- a/include/nestlog/log_state.hpp +++ b/include/nestlog/log_state.hpp @@ -5,7 +5,9 @@ #include "log_config.hpp" #include "log_streambuf.hpp" #include "pad.hpp" +#include "filename.hpp" #include +#include #include // for std::unique_ptr namespace xo { @@ -42,6 +44,12 @@ namespace xo { p_sbuf_phase2_->reset_stream(); } + void set_location(std::string_view file, std::uint32_t line) { + this->location_flag_ = true; + this->file_ = std::move(file); + this->line_ = line; + } /*set_location*/ + private: /* common implementation for .preamble(), .postamble() */ void entryexit_aux(function_style style, @@ -59,6 +67,21 @@ namespace xo { */ std::unique_ptr p_sbuf_phase1_; + /* #of characters found in .p_sbuf_phase1 since last \n. + * this value is established+updated in .flush2sbuf(). + * (in particular ignored by stream .ss()) + */ + std::size_t lpos_ = 0; + + /* whenever .set_location() is called: + * - capture (file, line) + * - print them near right margin with next output line + * - ..and reset .location_flag + */ + bool location_flag_ = false; + std::string_view file_; + std::uint32_t line_ = 0; + /* buffer space for handling scope::log() calls that span multiple lines; * inserts extra characters in effort to indent gracefully */ @@ -99,11 +122,6 @@ namespace xo { /* indent to nesting level */ this->ss_ << pad(this->nesting_level_ * log_config::indent_width, pad_char); -#ifdef OBSOLETE - for(uint32_t i = 0, n = this->nesting_level_; iss_ << pad_char; - } -#endif } /*indent*/ template @@ -124,7 +142,7 @@ namespace xo { if (log_config::indent_width > 1) this->ss_ << ' '; - /* scope name */ + /* scope name - note no trailing newline; expect .preamble()/.postamble() caller to supply */ this->ss_ << function_name(style, name1) << name2; } /*entryexit_aux*/ @@ -153,12 +171,15 @@ namespace xo { log_streambuf_type * sbuf1 = this->p_sbuf_phase1_.get(); log_streambuf_type * sbuf2 = this->p_sbuf_phase2_.get(); - /* expecting sbuf to contain one line of output. + /* generally expecting sbuf to contain one line of output. * if it contains multiple newlines, need to indent * after each one. * * will scan output in *sbuf1, post-process to *sbuf2, - * then write *sbuf2 to clog + * then write *sbuf2 to output stream + * + * note: we inherit .lpos from prec call to .flush2sbuf(), + * in the unlikely event that it's non-zero */ char const * s = sbuf1->lo(); char const * e = s + sbuf1->pos(); @@ -177,6 +198,8 @@ namespace xo { /* for indenting, looking for first 'space following non-space, on first line', if any */ + std::size_t lpos_on_newline = 0; + while(p < e) { if(space_after_nonspace) { ; @@ -191,18 +214,50 @@ namespace xo { if(*p == '\n') { ++p; + /* reset .pos on newline */ + lpos_on_newline = this->lpos_; + this->lpos_ = 0; + break; } else { ++p; + + /* increment .lpos on non-newline */ + ++(this->lpos_); } } /* p=e or *p=\n */ /* charseq [s,p) does not contain any newlines, print it */ - sbuf2->sputn(s, p - s); + if (lpos_on_newline > 0) { + /* charseq [s,p) does not contain any newlines, print it */ + sbuf2->sputn(s, p - s - 1); - if(p == e) { + if (this->location_flag_) { + /* 'tab' to position 80 */ + sbuf2->sputc(' '); + for (std::uint32_t i = lpos_on_newline + 1; i < log_config::location_tab; ++i) + sbuf2->sputc(' '); + + std::stringstream ss; + ss << "[" << basename(this->file_) << ":" << this->line_ << "]"; + + std::string ss_str = std::move(ss.str()); /*c++20*/ + sbuf2->sputn(ss_str.c_str(), ss_str.size()); + + this->location_flag_ = false; + this->file_ = ""; + this->line_ = 0; + } + + sbuf2->sputc('\n'); + } else { + /* control here if .flush2sbuf() called without trailing newline in .p_sbuf_phase1 */ + sbuf2->sputn(s, p - s); + } + + if (p == e) { break; } diff --git a/include/nestlog/log_streambuf.hpp b/include/nestlog/log_streambuf.hpp index 02b4b183..3a467b82 100644 --- a/include/nestlog/log_streambuf.hpp +++ b/include/nestlog/log_streambuf.hpp @@ -36,30 +36,30 @@ namespace xo { protected: virtual std::streamsize xsputn(char const * s, std::streamsize n) override { - /* s must be an address in [this->lo() .. this->lo() + capacity()] */ + /* s must be an address in [this->lo() .. this->lo() + capacity()] */ - assert(this->hi() >= this->pptr()); + assert(this->hi() >= this->pptr()); #ifdef NOT_USING_DEBUG - std::cout << "xsputn: pbase=" << (void *)(this->pbase()) - << ", pptr=" << (void*)(this->pptr()) - << "(+" << (this->pptr() - this->lo()) << ")" - << ", n=" << n << " -> (+" << (this->pptr() + n - this->lo()) << ")" - << ", buf_v.size=" << this->buf_v_.size() - << std::endl; + std::cout << "xsputn: pbase=" << (void *)(this->pbase()) + << ", pptr=" << (void*)(this->pptr()) + << "(+" << (this->pptr() - this->lo()) << ")" + << ", n=" << n << " -> (+" << (this->pptr() + n - this->lo()) << ")" + << ", buf_v.size=" << this->buf_v_.size() + << std::endl; #endif - //std::cout << "xsputn: s=" << quoted(string_view(s, n)) << ", n=" << n << std::endl; + //std::cout << "xsputn: s=" << quoted(string_view(s, n)) << ", n=" << n << std::endl; - if (this->pptr() + n > this->hi()) { - n = this->hi() - this->pptr(); - std::memcpy(this->pptr(), s, n); - } else { - std::memcpy(this->pptr(), s, n); - } - this->pbump(n); + if (this->pptr() + n > this->hi()) { + n = this->hi() - this->pptr(); + std::memcpy(this->pptr(), s, n); + } else { + std::memcpy(this->pptr(), s, n); + } + this->pbump(n); - return n; - } /*xsputn*/ + return n; + } /*xsputn*/ virtual int_type overflow(int_type new_ch) override diff --git a/include/nestlog/scope.hpp b/include/nestlog/scope.hpp index 2648f483..04451aaf 100644 --- a/include/nestlog/scope.hpp +++ b/include/nestlog/scope.hpp @@ -1,8 +1,9 @@ -/* @file scopẹhpp */ +/* @file scope.hpp */ #pragma once #include "log_state.hpp" +#include "filename.hpp" #include "tostr.hpp" #include "tag.hpp" @@ -15,38 +16,44 @@ namespace xo { template class state_impl; -# define XO_ENTER0() xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__) -# define XO_ENTER1(debug_flag) xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, debug_flag) +# define XO_ENTER0() xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__) +# define XO_ENTER1(debug_flag) xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__, debug_flag) //# define XO_SSETUP0() xo::scope_setup(__FUNCTION__) -# define XO_SSETUP0() xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__) +# define XO_SSETUP0() xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__) /* throw exception if condition not met*/ # define XO_EXPECT(f,msg) if(!(f)) { throw std::runtime_error(msg); } /* establish scope using current function name */ -# define XO_SCOPE(name) xo::scope name(xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__)) +# define XO_SCOPE(name) xo::scope name(xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__)) /* like XO_SCOPE(name), but also set enabled flag */ -# define XO_SCOPE2(name, debug_flag) xo::scope name(xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, debug_flag)) -# define XO_SCOPE_DISABLED(name) xo::scope name(xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, false)) +# define XO_SCOPE2(name, debug_flag) xo::scope name(xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__, debug_flag)) +# define XO_SCOPE_DISABLED(name) xo::scope name(xo::scope_setup(xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__, false)) # define XO_STUB() { XO_SCOPE(logr); logr.log("STUB"); } /* convenience class for basic_scope<..> construction (see below). * use to disambiguate setup from other arguments */ struct scope_setup { - scope_setup(function_style style, std::string_view name1, std::string_view name2, bool enabled_flag) - : style_{style}, name1_{name1}, name2_{name2}, enabled_flag_{enabled_flag} {} - scope_setup(function_style style, std::string_view name1, bool enabled_flag) - : scope_setup(style, name1, "", enabled_flag) {} - scope_setup(function_style style, std::string_view name1) - : scope_setup(style, name1, true /*enabled_flag*/) {} + scope_setup(function_style style, std::string_view name1, std::string_view name2, + std::string_view file, std::uint32_t line, bool enabled_flag) + : style_{style}, name1_{name1}, name2_{name2}, file_{file}, line_{line}, enabled_flag_{enabled_flag} {} + scope_setup(function_style style, std::string_view name1, std::string_view file, std::uint32_t line, bool enabled_flag) + : scope_setup(style, name1, "", file, line, enabled_flag) {} + scope_setup(function_style style, std::string_view name1, std::string_view file, std::uint32_t line) + : scope_setup(style, name1, file, line, true /*enabled_flag*/) {} - static scope_setup literal(std::string_view name1, bool enabled_flag = true) { return scope_setup(FS_Literal, name1, enabled_flag); } - static scope_setup literal(std::string_view name1, std::string_view name2, bool enabled_flag = true) { return scope_setup(FS_Literal, name1, name2, enabled_flag); } + //static scope_setup literal(std::string_view name1, bool enabled_flag = true) { return scope_setup(FS_Literal, name1, enabled_flag); } + //static scope_setup literal(std::string_view name1, std::string_view name2, bool enabled_flag = true) { return scope_setup(FS_Literal, name1, name2, enabled_flag); } function_style style_ = FS_Pretty; std::string_view name1_ = "<.name1>"; std::string_view name2_ = "<.name2>"; + /* __FILE__ */ + std::string_view file_ = "<.file>"; + /* __LINE__ */ + std::uint32_t line_ = 0; + /* true iff output enabled for this scope */ bool enabled_flag_ = false; }; /*scope_setup*/ @@ -155,6 +162,10 @@ namespace xo { std::string_view name1_ = ""; /* name of this scope (part 2) */ std::string_view name2_ = "::"; + /* captured value of __FILE__ */ + std::string_view file_ = ""; + /* captured value of __LINE__ */ + std::uint32_t line_ = 0; /* set once per scope .finalized=true <-> logging disabled */ bool finalized_ = false; }; /*basic_scope*/ @@ -166,14 +177,23 @@ namespace xo { : style_{setup.style_}, name1_{std::move(setup.name1_)}, name2_{std::move(setup.name2_)}, + file_{std::move(setup.file_)}, + line_{setup.line_}, finalized_{!setup.enabled_flag_} { if(setup.enabled_flag_) { state_impl_type * logstate = basic_scope::require_thread_local_state(); + std::ostream & os = logstate2stream(logstate); logstate->preamble(this->style_, this->name1_, this->name2_); - tosn(logstate2stream(logstate), " ", std::forward(args)...); + tosn(os, " ", std::forward(args)...); + + if (log_config::location_enabled) { + /* prints on next call to flush2sbuf */ + logstate->set_location(this->file_, this->line_); + //tosn(os, " [", basename(this->file_), ":", this->line_, "]"); + } logstate->flush2sbuf(std::clog.rdbuf());