/* @file log_state.hpp */ #pragma once #include "log_config.hpp" #include "log_streambuf.hpp" #include "print/pad.hpp" #include "print/filename.hpp" #include "print/code_location.hpp" #include "print/time.hpp" #include #include #include // for std::unique_ptr namespace xo { enum EntryExit { EE_Entry, EE_Exit }; // track per-thread state associated with nesting logger // template class state_impl { public: using log_streambuf_type = log_streambuf>; using utc_nanos = xo::time::utc_nanos; public: state_impl(); std::uint32_t nesting_level() const { return nesting_level_; } void incr_nesting() { ++nesting_level_; } void decr_nesting() { --nesting_level_; } std::ostream & ss() { return ss_; } log_streambuf_type & sbuf() { return *p_sbuf_phase1_.get(); } void check_print_time(utc_nanos now_tm) { using xo::time::timeutil; using xo::time::utc_nanos; using xo::time::hms_msec; using xo::time::hms_usec; if (log_config::time_enabled) { if (log_config::time_local_flag) { if (log_config::time_usec_flag) this->ss_ << hms_usec::local(now_tm) << " "; else this->ss_ << hms_msec::local(now_tm) << " "; } else { if (log_config::time_usec_flag) this->ss_ << hms_usec::utc(now_tm) << " "; else this->ss_ << hms_msec::utc(now_tm) << " "; } } } /*check_print_time*/ /* space budget for time-of-day */ std::size_t calc_time_indent() const { if (log_config::time_enabled) { if (log_config::time_usec_flag) { /*strlen("14:38:19.123456 ")*/ return 16; } else { /*strlen("14:38:19.974 ")*/ return 13; } } else { return 0; } } /*calc_time_indent*/ void time_indent() { if (log_config::time_enabled) this->ss_ << pad(this->calc_time_indent(), ' '); } /*time_indent*/ /* call on entry to new scope */ void preamble(function_style style, std::string_view name1, std::string_view name2); /* call before each new log entry */ void indent(char pad_char); /* call on exit from scope */ void postamble(function_style style, std::string_view name1, std::string_view name2); /* write collected output to *p_sbuf */ void flush2sbuf(std::streambuf * p_sbuf); /* discard output, reset write pointer to beginning of buffer */ void reset_stream() { p_sbuf_phase1_->reset_stream(); 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, std::string_view name1, std::string_view name2, EntryExit entryexit); private: /* current nesting level for this thread */ std::uint32_t nesting_level_ = 0; /* buffer space for logging * (before pretty-printing for scope::log() calls that span multiple lines) * reused across tos() and scope::log() calls */ std::unique_ptr p_sbuf_phase1_; /* 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 */ std::unique_ptr p_sbuf_phase2_; /* output stream -- always attached to .p_sbuf_phase1 * stream inserters for application datatypes will target this stream */ std::ostream ss_; }; /*state_impl*/ constexpr uint32_t c_default_buf_size = 1024*1024; template state_impl::state_impl() : p_sbuf_phase1_(new log_streambuf_type(c_default_buf_size)), p_sbuf_phase2_(new log_streambuf_type(c_default_buf_size)), ss_(p_sbuf_phase1_.get()) { assert(p_sbuf_phase1_.get() == ss_.rdbuf()); } /*ctor*/ template void state_impl::indent(char pad_char) { //log_streambuf * sbuf = this->p_sbuf_phase1_.get(); #ifdef NOT_IN_USE { char buf[80]; ::snprintf(buf, sizeof(buf), "[%02d] ", this->nesting_level_); this->ss_ << buf; //this->p_sbuf_->sputn(buf, strlen(buf)); } #endif /* indent to nesting level. * * note: see also flush2sbuf(), need special indent handling for continuation lines * (when application sends explicit newlines to this logger) */ this->ss_ << pad(std::min(this->nesting_level_ * log_config::indent_width, log_config::max_indent_width), pad_char); } /*indent*/ template void state_impl::entryexit_aux(function_style style, std::string_view name1, std::string_view name2, EntryExit entryexit) { log_streambuf_type * sbuf = this->p_sbuf_phase1_.get(); sbuf->reset_stream(); this->check_print_time(xo::time::timeutil::now()); this->indent(' '); char ee_label = '\0'; color_spec_type fn_color; /* mnemonic for scope entry/exit */ switch(entryexit) { case EE_Entry: ee_label = '+'; fn_color = log_config::function_entry_color; break; case EE_Exit: ee_label = '-'; fn_color = log_config::function_exit_color; break; } this->ss_ << ee_label; if (log_config::nesting_level_enabled) { /* e.g. * (^[[38;5;195m7^[[0m) * <-----a---->b<-c-> * * a = color on * b = level - displayed in color * c = color off */ this->ss_ << "(" << with_color(log_config::nesting_level_color, this->nesting_level_) << ")"; } if (log_config::indent_width > 0) this->ss_ << ' '; /* scope name - note no trailing newline; expect .preamble()/.postamble() caller to supply */ this->ss_ << function_name(style, fn_color, name1) << name2; } /*entryexit_aux*/ template void state_impl::preamble(function_style style, std::string_view name1, std::string_view name2) { this->entryexit_aux(style, name1, name2, EE_Entry); } /*preamble*/ template void state_impl::postamble(function_style style, std::string_view name1, std::string_view name2) { this->entryexit_aux(style, name1, name2, EE_Exit); } /*postamble*/ template void state_impl::flush2sbuf(std::streambuf * p_sbuf) { log_streambuf_type * sbuf1 = this->p_sbuf_phase1_.get(); // instead of post-processing, rely on newline-aware log_streambuf // to indent in advance p_sbuf->sputn(sbuf1->lo(), sbuf1->pos()); /* reset streams for next message */ this->reset_stream(); } /*flush2sbuf*/ } /*namespace xo*/ /* end log_state.hpp */