/* @file scope.hpp */ #pragma once #include "log_state.hpp" #include "print/filename.hpp" #include "print/tostr.hpp" #include "print/tag.hpp" #include #include #include // for std::unique_ptr namespace xo { template class state_impl; # define XO_ENTER0(lvl) xo::scope_setup(xo::log_level::lvl, xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__) # define XO_ENTER1(lvl, debug_flag) XO_ENTER2(lvl, debug_flag, __PRETTY_FUNCTION__) # define XO_ENTER2(lvl, debug_flag, name1) xo::scope_setup((debug_flag ? xo::log_level::lvl : xo::log_level::never), xo::log_config::style, name1, __FILE__, __LINE__) # define XO_DEBUG(debug_flag) XO_ENTER1(always, debug_flag) # define XO_DEBUG2(debug_flag, name1) XO_ENTER2(always, debug_flag, name1) # define XO_LITERAL(lvl, name1, name2) xo::scope_setup(lvl, function_style::literal, name1, name2, __FILE__, __LINE__) //# define XO_SSETUP0() xo::scope_setup(__FUNCTION__) //# define XO_SSETUP0(lvl) xo::scope_setup(xo::log_level::lvl, 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, lvl) xo::scope name(xo::scope_setup(xo::log_level::lvl, 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__, __FILE__, __LINE__, debug_flag)) # define XO_SCOPE_DISABLED(name) xo::scope name(xo::scope_setup(xo::log_level::never, xo::log_config::style, __PRETTY_FUNCTION__, __FILE__, __LINE__)) # 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(log_level level, function_style style, std::string_view name1, std::string_view name2, std::string_view file, std::uint32_t line) : log_level_{level}, style_{style}, name1_{name1}, name2_{name2}, file_{file}, line_{line} {} scope_setup(log_level level, function_style style, std::string_view name1, std::string_view file, std::uint32_t line) : scope_setup(level, style, name1, "" /*name2*/, file, line) {} bool is_enabled() const { return (this->log_level_ >= log_config::min_log_level); } //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); } /* threshold level for logging -- write messages with severity >= this level */ log_level log_level_ = log_level::error; /* FS_Pretty | FS_Streamlined | FS_Simple */ function_style style_ = function_style::pretty; std::string_view name1_ = "<.name1>"; std::string_view name2_ = "<.name2>"; /* __FILE__ */ std::string_view file_ = "<.file>"; /* __LINE__ */ std::uint32_t line_ = 0; }; /*scope_setup*/ /* nesting logger * * Use: * using xo::scope; * * void myfunc() { * XO_SCOPE(log); //or scope x("myfunc") * log(a,b,c); * anotherfunc(); * log(d,e,f); * } * * void anotherfunc() { * XO_SCOPE(x); // or scope x("anotherfunc") * x.log(y); * } * * or: * void myfunc() { * bool log_flag = true; * XO_SCOPE2(log, log_flag); // create local variable 'log' * log && log(a,b,c); // log iff enabled * log.end_scope(); // optional protection against compiler destroying 'log' early * } * * output like: * +myfunc: * a,b,c * +anotherfunc: * y * -anotherfunc: * d,e,f * -myfunc: */ template > class basic_scope { public: using state_impl_type = state_impl; public: //basic_scope(std::string_view name1, bool enabled_flag); template basic_scope(scope_setup setup, Tn&&... rest); ~basic_scope(); bool enabled() const { return !finalized_; } operator bool() const { return this->enabled(); } /* report current nesting level */ std::uint32_t nesting_level() const; void set_dest_sbuf(std::streambuf * x) { this->dest_sbuf_ = x; } template bool log(Tn&&... rest) { if(this->finalized_) { throw std::runtime_error("basic_scope: attempt to use finalized scope"); } else { state_impl_type * logstate = require_indent_thread_local_state(); /* indent for timestamp (not printed on this line) */ logstate->time_indent(); /* log to per-thread stream to prevent data races */ tosn(logstate2stream(logstate), std::forward(rest)...); this->flush2sbuf(logstate); } return true; } /*log*/ template bool operator()(Tn&&... args) { return this->log(std::forward(args)...); } /* call once to end scope before dtor */ template void end_scope(Tn&&... args); private: /* establish stream for logging; use thread-local storage for threadsafetỵ * stream, if recycled (ịẹ after 1st call to scopẹlog() from a particular thread), * will be in 'reset-to-beginning of buffer' statẹ */ static state_impl_type * require_indent_thread_local_state(); /* establish logging state; use thread-local storage for threadsafety */ static state_impl_type * require_thread_local_state(); /* retrieve permanently-associated ostream for logging-state */ static std::ostream & logstate2stream(state_impl_type * logstate); /* write collected output to std::clog, or chosen streambuf */ void flush2sbuf(state_impl_type * logstate); private: /* keep logging state separately for each thread */ static thread_local std::unique_ptr s_threadlocal_state; /* send indented output to this streambuf (e.g. std::clog.rdbuf()) */ std::streambuf * dest_sbuf_ = std::clog.rdbuf(); /* style for displaying .name1 */ function_style style_ = function_style::pretty; /* name of this scope (part 1) */ 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*/ template template basic_scope::basic_scope(scope_setup setup, Tn&&... args) : style_{setup.style_}, name1_{std::move(setup.name1_)}, name2_{std::move(setup.name2_)}, file_{std::move(setup.file_)}, line_{setup.line_}, finalized_{!(setup.is_enabled())} { if(setup.is_enabled()) { state_impl_type * logstate = basic_scope::require_thread_local_state(); std::ostream & os = logstate2stream(logstate); logstate->preamble(this->style_, this->name1_, this->name2_); 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()); ///* next call to scope::log() can reset to beginning of buffer space */ //logstate->ss().seekp(0); logstate->incr_nesting(); } } /*ctor*/ template basic_scope::~basic_scope() { if(!this->finalized_) this->end_scope(); } /*dtor*/ template thread_local std::unique_ptr> basic_scope::s_threadlocal_state; template std::uint32_t basic_scope::nesting_level() const { return require_thread_local_state()->nesting_level(); } /*nesting_level*/ template typename basic_scope::state_impl_type * basic_scope::require_indent_thread_local_state() { state_impl_type * local_state = require_thread_local_state(); local_state->reset_stream(); local_state->indent(' ' /*pad_char*/); return local_state; } /*require_thread_local_stream*/ template typename basic_scope::state_impl_type * basic_scope::require_thread_local_state() { if(!s_threadlocal_state) { s_threadlocal_state.reset(new state_impl_type()); } return s_threadlocal_state.get(); } /*require_thread_local_state*/ template std::ostream & basic_scope::logstate2stream(state_impl_type * logstate) { return logstate->ss(); } /*logstate2stream*/ template void basic_scope::flush2sbuf(state_impl_type * logstate) { logstate->flush2sbuf(this->dest_sbuf_); } /*flush2sbuf*/ template template void basic_scope::end_scope(Tn&&... args) { if(!this->finalized_) { this->finalized_ = true; state_impl_type * logstate = basic_scope::require_thread_local_state(); logstate->decr_nesting(); logstate->postamble(this->style_, this->name1_, this->name2_); tosn(logstate2stream(logstate), " ", std::forward(args)...); logstate->flush2sbuf(std::clog.rdbuf()); } } /*end_scope*/ using scope = basic_scope; } /*namespace xo*/ /* end scope.hpp */