include/nestlog -> include/indentlog

This commit is contained in:
Roland Conybeare 2023-09-16 15:24:25 -04:00
commit 61441f2513
21 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,25 @@
/* @file array.hpp */
#pragma once
#include <iostream>
#include <array>
namespace std {
template<typename T, size_t N>
inline std::ostream &
operator<<(std::ostream & os,
std::array<T, N> const & v)
{
os << "[";
for(size_t i = 0; i < N; ++i) {
if(i > 0)
os << " ";
os << v[i];
}
os << "]";
return os;
} /*operator<<*/
} /*namespace std*/
/* end array.hpp */

View file

@ -0,0 +1,56 @@
/* @file code_location.hpp */
#pragma once
#include "filename.hpp"
#include "color.hpp"
namespace xo {
/* Example:
* os << code_location("/path/to/foo.cpp", 123)
* writes
* foo.cpp:123
* on stream os
*/
/* Tag to drive header-only expression */
template <typename Tag>
class code_location_impl {
public:
code_location_impl(std::string_view file,
std::uint32_t line,
color_encoding encoding = CE_Ansi,
std::uint32_t color = 31 /*red*/)
: file_{file}, line_{line}, encoding_{encoding}, color_{color} {}
void print_code_location(std::ostream & os) const {
os << "["
<< with_color(encoding_, color_, basename(file_))
<< ":"
<< line_
<< "]";
} /*print_code_location*/
private:
/* __FILE__ */
std::string_view file_;
/* __LINE__ */
std::uint32_t line_ = 0;
/* color encoding for file,line */
color_encoding encoding_ = CE_Ansi;
/* color for file,line */
std::uint32_t color_ = 0;
}; /*code_location_impl*/
using code_location = code_location_impl<class code_location_impl_tag>;
inline std::ostream &
operator<<(std::ostream & os,
code_location const & x)
{
x.print_code_location(os);
return os;
}
} /*namespace xo*/
/* end code_location.hpp */

133
include/indentlog/color.hpp Normal file
View file

@ -0,0 +1,133 @@
/* color.hpp */
#pragma once
#include <ostream>
//#include <utility> // for std::move
#include <cstdint>
namespace xo {
enum color_encoding {
CE_None,
CE_Ansi,
CE_Xterm,
};
enum color_flags {
CF_None = 0x0,
CF_ColorOn = 0x01,
CF_Contents = 0x02,
CF_ColorOff = 0x04,
CF_All = 0x07
};
template <typename Contents>
class color_impl {
public:
color_impl(color_flags flags, color_encoding encoding, std::uint32_t color, Contents && contents)
: flags_{flags}, encoding_{encoding}, color_{color}, contents_{std::forward<Contents>(contents)} {}
std::uint32_t color() const { return color_; }
Contents const & contents() const { return contents_; }
void print(std::ostream & os) const {
if ((flags_ & CF_ColorOn) && (color_ > 0)) {
switch(encoding_) {
case CE_None:
break;
case CE_Ansi:
os << "\033[" << color_ << "m";
break;
case CE_Xterm:
os << "\033[38;5;" << color_ << "m";
break;
}
}
if (flags_ & CF_Contents)
os << contents_;
if ((flags_ & CF_ColorOff) && (color_ > 0)) {
switch(encoding_) {
case CE_None:
break;
case CE_Ansi:
case CE_Xterm:
os << "\033[0m";
break;
}
}
} /*print*/
private:
color_flags flags_ = CF_None;
color_encoding encoding_ = CE_Ansi;
/* .encoding = CE_Ansi:
* 0 = no color
* 30 = black
* 31 = red
* 32 = green
* 33 = yellow
* 34 = blue
* 35 = magenta
* 36 = cyan
*
* .encoding = CE_Xterm:
* see [[https://i.stack.imgur.com/KTSQa.png]]
* 0..7 standard colors (muted: grey, red, green, yellow, blue, pink, cyan, white)
* 8..15 high-intensity colors (grey, red, green, yellow, blue, pink, cyan, white)
* 16..51 chooses hue
* 16..51 + (0..5)x36 increases whiteness
*/
std::uint32_t color_ = 0;
Contents contents_;
}; /*color_impl*/
template <typename Contents>
color_impl<Contents> with_ansi_color(std::uint32_t color, Contents && contents) {
return color_impl<Contents>(CF_All, CE_Ansi, color, std::forward(contents));
} /*with_ansi_color*/
template <typename Contents>
color_impl<Contents> with_xterm_color(std::uint32_t color, Contents && contents) {
return color_impl<Contents>(CF_All, CE_Xterm, color, std::forward(contents));
} /*with_ansi_color*/
template <typename Contents>
color_impl<Contents> with_color(color_encoding encoding, std::uint32_t color, Contents && contents) {
return color_impl<Contents>(CF_All, encoding, color, std::forward<Contents>(contents));
} /*with_color*/
inline color_impl<int>
color_on_ansi(std::uint32_t color) {
return color_impl<int>(CF_ColorOn, CE_Ansi, color, 0);
} /*color_on_ansi*/
inline color_impl<int>
color_on_xterm(std::uint32_t color) {
return color_impl<int>(CF_ColorOn, CE_Xterm, color, 0);
} /*color_on_xterm*/
inline color_impl<int>
color_on(color_encoding encoding, std::uint32_t color) {
return color_impl<int>(CF_ColorOn, encoding, color, 0);
} /*color_on*/
inline color_impl<int>
color_off() {
/* any non-zero value works here for color */
return color_impl<int>(CF_ColorOff, CE_Ansi, 1 /*color*/, 0);
} /*color_off*/
template <typename Contents>
inline std::ostream &
operator<<(std::ostream & os, color_impl<Contents> const & x) {
x.print(os);
return os;
} /*operator<<*/
} /*namespace xo*/
/* end color.hpp */

View file

@ -0,0 +1,37 @@
/* @file concat.hpp */
#pragma once
#include <ostream>
#include <utility> // for std::move()
namespace xo {
template <typename T1, typename T2>
struct concat_impl {
public:
concat_impl(T1 && x1, T2 && x2)
: x1_{std::forward<T1>(x1)}, x2_{std::forward<T2>(x2)} {}
T1 const & x1() const { return x1_; }
T2 const & x2() const { return x2_; }
private:
T1 x1_;
T2 x2_;
}; /*concat_impl*/
template <typename T1, typename T2>
concat_impl<T1,T2> concat(T1 && x1, T2 && x2) {
return concat_impl<T1,T2>(std::move(x1), std::move(x2));
} /*concat*/
template <typename T1, typename T2>
inline std::ostream &
operator<<(std::ostream & os, concat_impl<T1, T2> const & x) {
os << x.x1() << x.x2();
return os;
} /*operator<<*/
} /*namespace xo*/
/* end concat.hpp */

View file

@ -0,0 +1,70 @@
/* @file filename.hpp */
#pragma once
#include <iostream>
#include <cstdint>
namespace xo {
/* Example:
* os << basename("/path/to/basename.cpp")
* prints
* basename.cpp
* on os
*/
/* Tag to drive header-only expression */
template <typename Tag>
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<class basename_impl_tag>;
inline std::ostream &
operator<<(std::ostream & os,
basename const & bn)
{
basename::print_basename(os, bn.path());
return os;
}
} /*xo*/
/* end filename.hpp */

View file

@ -0,0 +1,45 @@
/* @file fixed.hpp */
#pragma once
#include <iostream>
namespace xo {
/* use:
* ostream os = ...;
*
* os << fixed(3.1415926, 3)
*
* writes
* 3.142
*
* on os, restoring stream's formatting+precision state
*/
class fixed {
public:
fixed(double x, uint16_t prec) : x_{x}, prec_{prec} {}
/* print this value */
double x_;
/* precision */
uint16_t prec_ = 0;
}; /*fixed*/
inline std::ostream &
operator<<(std::ostream & s, fixed const & fx)
{
std::ios::fmtflags orig_flags = s.flags();
std::streamsize orig_p = s.precision();
s.flags(std::ios::fixed);
s.precision(fx.prec_);
s << fx.x_;
s.flags(orig_flags);
s.precision(orig_p);
return s;
} /*operator<<*/
} /*namespace xo*/
/* end fixed.hpp */

View file

@ -0,0 +1,211 @@
/* @file function.hpp */
#include "color.hpp"
#include <iostream>
#include <cstdint>
namespace xo {
enum function_style {
/* literal: print given name, no alterations */
FS_Literal,
/* pretty: print name, surrounded by [] */
FS_Pretty,
/* streamlined: remove extraneous detail, try to print something like class::method */
FS_Streamlined,
/* simple: remove everything except function/method name */
FS_Simple
};
/* Tag to drive header-only expression */
template <typename Tag>
class function_name_impl {
public:
/* color: ANSI escape color (lookup Select Graphic Rendition subset)
* 0 = none
* 31 = red
*/
function_name_impl(function_style style,
color_encoding encoding,
std::uint32_t color,
std::string_view pretty)
: style_{style}, encoding_{encoding}, color_{color}, pretty_{pretty} {}
function_style style() const { return style_; }
color_encoding encoding() const { return encoding_; }
std::uint32_t color() const { return color_; }
std::string_view const & pretty() const { return pretty_; }
/* e.g.
* std::vector<std::pair<int, xo::bar> xo::sometemplateclass<T,U>::fib(int, char**)
* ^ ^
* p q
*/
static void print_simple(std::ostream & os, std::string_view const & s) {
std::size_t p = exclude_return_type(s);
std::string_view s2 = s.substr(p);
std::size_t q = find_toplevel_sep(s2, true /*last_flag*/);
print_aux(os, s2.substr(q));
} /*print_simple*/
/* e.g.
* std::vector<std::pair<int, xo::bar> xo::sometemplateclass<T,U>::fib(int, char**)
* ^ ^
* p q
*/
static void print_streamlined(std::ostream & os, std::string_view const & s) {
std::size_t p = exclude_return_type(s);
std::string_view s2 = s.substr(p);
std::size_t q = find_toplevel_sep(s2, false /*!last_flag*/);
print_aux(os, s2.substr(q));
} /*print_streamlined*/
private:
static std::size_t exclude_return_type(std::string_view const & s) {
/* strategy:
* - scan right-to-left
* - ignore anything between matching <>, () pairs (i.e. anything nested)
* - stop at rightmost toplevel space --> return suffix following that space
*/
std::size_t nesting_level = 0;
std::size_t z = s.size();
for (std::size_t rp = 0; rp < z; ++rp) {
std::size_t p = z-1-rp;
char ch = s[p];
if (ch == '<' || ch == '(')
++nesting_level;
if (nesting_level == 0) {
if (ch == ' ')
return p + 1;
}
if (ch == '>' || ch == ')')
--nesting_level;
}
return 0;
} /*exclude_return_type*/
/* e.g.
* xo::ns::someclass<xo::foo, xo::bar>::somemethod(xo::enum1, std::vector<xo::blah>)
* ^
* return this pos
*
* last_flag: return pos after last ::
* !last_flag: return pos after 2nd-last ::
*/
static std::size_t find_toplevel_sep(std::string_view const & s, bool last_flag) {
/* strategy:
* - scan left-to-right
* - ignore anything between matching <>, () pairs (i.e. anything nested)
* - count :: pairs
* - remember 2nd-last :: pair; reports pos just after it
*
* note:
* - if no :: pairs, or only one such pair, return 0
*/
std::size_t nesting_level = 0;
std::size_t pos_after_last_sep = 0;
std::size_t pos_after_2ndlast_sep = 0;
for (std::size_t p = 0; p < s.size(); ++p) {
char ch = s[p];
if (ch == '<' || ch == '(')
++nesting_level;
if (nesting_level == 0) {
if ((ch == ':')
&& (p+1 < s.size())
&& s[p+1] == ':')
{
pos_after_2ndlast_sep = pos_after_last_sep;
pos_after_last_sep = p+2;
++p; /* skipping 1st : in separator */
}
}
if (ch == '>' || ch == ')')
--nesting_level;
}
return last_flag ? pos_after_last_sep : pos_after_2ndlast_sep;
} /*find_toplevel_sep*/
/* fib(int, char **) --> fib
* quux(std::vector<std::size_t, std::allocator<std::size_t>>) -> quux
* foo::bar<std::vector<char>>() -> foo::bar
*/
static void print_aux(std::ostream & os, std::string_view const & s) {
//std::cerr << "print_aux: s=" << s << std::endl;
/* strategy:
* - print left-to-right, omit anything between matching <> or () pairs.
* - don't keep track of which is which, so would also match < with ) etc;
* this acceptable since pretty functions won't visit this corner case
*/
std::size_t nesting_level = 0;
for (char ch : s) {
if (ch == '<' || ch == '(')
++nesting_level;
if (nesting_level == 0)
os << ch;
if (ch == '>' || ch == ')')
--nesting_level;
}
} /*print_aux*/
private:
/* FS_Simple | FS_Pretty (= FS_Literal) | FS_Streamlined */
function_style style_;
/* CE_Ansi | CE_Xterm */
color_encoding encoding_;
/* color, if non-zero */
std::uint32_t color_;
/* e.g. __PRETTY_FUNCTION__ */
std::string_view pretty_;
}; /*function_name_impl*/
using function_name = function_name_impl<class function_name_impl_tag>;
inline std::ostream &
operator<<(std::ostream & os,
function_name const & fn)
{
/* set text color */
switch(fn.style()) {
case FS_Literal:
os << with_color(fn.encoding(), fn.color(), fn.pretty());
break;
case FS_Pretty:
os << "[" << with_color(fn.encoding(), fn.color(), fn.pretty()) << "]";
break;
case FS_Simple:
os << color_on(fn.encoding(), fn.color());
function_name::print_simple(os, fn.pretty());
os << color_off();
break;
case FS_Streamlined:
/* omit namespace qualifiers and template arguments */
os << color_on(fn.encoding(), fn.color());
function_name::print_streamlined(os, fn.pretty());
os << color_off();
break;
}
return os;
} /*operator<<*/
} /*namespace xo*/
/* end function.hpp */

View file

@ -0,0 +1,110 @@
/* @file log_config.hpp */
#pragma once
#include "log_level.hpp"
#include "function.hpp"
#include "color.hpp"
#include <cstdint>
namespace xo {
/* Tag here b/c we want header-only library */
template <typename Tag>
struct log_config_impl {
/* display log messages with severity >= .log_level */
static log_level min_log_level;
/* true to log local time */
static bool time_enabled;
/* true to log time-of-day in local coords; false for UTC coords */
static bool time_local_flag;
/* true to log time-of-day with microsecond precision; false for millisecond precision */
static bool time_usec_flag;
/* spaces per nesting level. 0 -> no indenting */
static std::uint32_t indent_width;
/* max #of spaces to introduce when indenting */
static std::uint32_t max_indent_width;
/* if true enable explicit nesting level display [nnn] */
static bool nesting_level_enabled;
/* color to use for explicit nesting level */
static std::uint32_t nesting_level_color;
/* display style for function names. FS_Simple|FS_Pretty|FS_Streamlined */
static function_style style;
/* color encoding */
static color_encoding encoding;
/* color to use for function name, on entry/exit (xo::scope creation/destruction)
* (ansi color codes, see Select Graphics Rendition subset)
*/
static std::uint32_t function_entry_color;
static std::uint32_t function_exit_color;
/* 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;
/* color to use for code location */
static std::uint32_t code_location_color;
}; /*log_config_impl*/
template <typename Tag>
log_level
log_config_impl<Tag>::min_log_level = log_level::default_level;
template <typename Tag>
bool
log_config_impl<Tag>::time_enabled = 1;
template <typename Tag>
bool
log_config_impl<Tag>::time_local_flag = true;
template <typename Tag>
bool
log_config_impl<Tag>::time_usec_flag = true;
template <typename Tag>
std::uint32_t
log_config_impl<Tag>::indent_width = 2;
template <typename Tag>
std::uint32_t
log_config_impl<Tag>::max_indent_width = 32;
template <typename Tag>
bool
log_config_impl<Tag>::nesting_level_enabled = true;
template <typename Tag>
std::uint32_t
log_config_impl<Tag>::nesting_level_color = 195;
template <typename Tag>
function_style
log_config_impl<Tag>::style = FS_Streamlined;
template <typename Tag>
color_encoding
log_config_impl<Tag>::encoding = CE_Ansi;
template <typename Tag>
std::uint32_t
log_config_impl<Tag>::function_entry_color = 34;
template <typename Tag>
std::uint32_t
log_config_impl<Tag>::function_exit_color = 32;
template <typename Tag>
bool
log_config_impl<Tag>::location_enabled = true;
template <typename Tag>
std::uint32_t
log_config_impl<Tag>::location_tab = 80;
template <typename Tag>
std::uint32_t
log_config_impl<Tag>::code_location_color = 31;
using log_config = log_config_impl<class log_config_tag>;
} /*namespace xo*/
/* end log_config.hpp */

View file

@ -0,0 +1,57 @@
/* @file log_level.hpp */
#include <cstdint>
namespace xo {
enum class log_level : std::uint32_t {
/* control log message severity
* silent > always > severe > error > warning > info > chatty > never
*
* never:
* used internally e.g. by XO_ENTER1()
* a log message with this severity will never be printed
*
* always:
* use with XO_ENTER1():
* scope log(XO_ENTER1(always, mydebug_flag));
* to log message whenever mydebug_flag is true (for any .min_log_level except silent)
*
* silent:
* use in log_config to suppress all log messages
*/
never,
verbose,
chatty,
info,
warning,
error,
severe,
always,
silent,
default_level = error
}; /*log_level*/
inline bool
operator>(log_level x, log_level y) {
return (static_cast<std::uint32_t>(x) > static_cast<std::uint32_t>(y));
}
inline bool
operator>=(log_level x, log_level y) {
return (static_cast<std::uint32_t>(x) >= static_cast<std::uint32_t>(y));
}
inline bool
operator<(log_level x, log_level y) {
return (static_cast<std::uint32_t>(x) < static_cast<std::uint32_t>(y));
}
inline bool
operator<=(log_level x, log_level y) {
return (static_cast<std::uint32_t>(x) <= static_cast<std::uint32_t>(y));
}
} /*namespace xo*/
/* end log_level.hpp */

View file

@ -0,0 +1,406 @@
/* @file log_state.hpp */
#pragma once
#include "log_config.hpp"
#include "log_streambuf.hpp"
#include "pad.hpp"
#include "filename.hpp"
#include "code_location.hpp"
#include "time.hpp"
#include <ostream>
#include <sstream>
#include <memory> // for std::unique_ptr
namespace xo {
enum EntryExit {
EE_Entry,
EE_Exit
};
// track per-thread state associated with nesting logger
//
template <typename CharT, typename Traits>
class state_impl {
public:
using log_streambuf_type = log_streambuf<char, std::char_traits<char>>;
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_; }
void check_print_time(utc_nanos now_tm) {
using xo::time::time;
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<log_streambuf_type> 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
*/
std::unique_ptr<log_streambuf_type> 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;
template <typename CharT, typename Traits>
state_impl<CharT, Traits>::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 <typename CharT, typename Traits>
void
state_impl<CharT, Traits>::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 <typename CharT, typename Traits>
void
state_impl<CharT, Traits>::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::time::now());
this->indent(' ');
char ee_label = '\0';
std::uint32_t fn_color = 0;
color_encoding encoding = log_config::encoding;
/* 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::encoding,
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, encoding, fn_color, name1) << name2;
} /*entryexit_aux*/
template <typename CharT, typename Traits>
void
state_impl<CharT, Traits>::preamble(function_style style,
std::string_view name1,
std::string_view name2)
{
this->entryexit_aux(style, name1, name2, EE_Entry);
} /*preamble*/
template <typename CharT, typename Traits>
void
state_impl<CharT, Traits>::postamble(function_style style,
std::string_view name1,
std::string_view name2)
{
this->entryexit_aux(style, name1, name2, EE_Exit);
} /*postamble*/
template <typename CharT, typename Traits>
void
state_impl<CharT, Traits>::flush2sbuf(std::streambuf * p_sbuf)
{
log_streambuf_type * sbuf1 = this->p_sbuf_phase1_.get();
log_streambuf_type * sbuf2 = this->p_sbuf_phase2_.get();
/* 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 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();
char const * p = s;
/* point to first space following a non-space character.
* will indent to just after this space
*/
char const * space_after_nonspace = nullptr;
/* true on VT100 color escape (\033); in which case false on terminating char (m)
* don't advance lpos during escape
*/
bool in_color_escape = false;
while(true) {
bool have_nonspace = false;
/* invariant: s<=p<=e */
/* 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) {
;
} else {
if(*p != ' ')
have_nonspace = true;
if(have_nonspace && (*p == ' ')) {
space_after_nonspace = p;
}
}
if (in_color_escape && (*p != '\n')) {
/* in color escape -> don't advance .lpos */
if (*p == 'm')
in_color_escape = false;
++p;
} else if (*p == '\033') {
/* begin color escape sequence */
in_color_escape = true;
++p;
} else if (*p == '\n') {
/* reset .pos on newline; also drop any (incomplete + ill-formed) color escape */
in_color_escape = false;
lpos_on_newline = this->lpos_;
this->lpos_ = 0;
++p;
break;
} else {
/* increment .lpos on non-newline */
++(this->lpos_);
++p;
}
}
/* p=e or *p=\n */
/* charseq [s,p) does not contain any newlines, print it */
if (lpos_on_newline > 0) {
/* charseq [s,p) does not contain any newlines, print it */
sbuf2->sputn(s, p - s - 1);
if (this->location_flag_) {
/* 'tab' to position lpos for [file:line] */
sbuf2->sputc(' ');
for (std::uint32_t i = lpos_on_newline + 1; i < log_config::location_tab; ++i)
sbuf2->sputc(' ');
std::stringstream ss;
ss << code_location(this->file_, this->line_,
log_config::encoding, log_config::code_location_color);
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;
// {
// char buf[80];
// snprintf(buf, sizeof(buf), "*** indent=[%d] next=[%c]", this->nesting_level_, *(p+1));
//
// std::clog.rdbuf()->sputn(buf, strlen(buf));
//}
/* control here only for continuation lines (application logging code embedding its own newlines)
* - minimum indent = nesting level;
* - however if space_after_nonspace defined, also indent for that
*/
std::uint32_t n_indent = 0;
n_indent += this->calc_time_indent();
n_indent += std::min(this->nesting_level_ * log_config::indent_width,
log_config::max_indent_width);
/* this is just to indent for per-line entry/exit label */
if(space_after_nonspace)
n_indent += (space_after_nonspace - s);
for(std::uint32_t i = 0; i < n_indent; ++i)
sbuf2->sputc(' ');
s = p;
}
/* now write entire contents of *sbuf2 to clog */
p_sbuf->sputn(sbuf2->lo(), sbuf2->pos());
/* reset streams for next message */
this->reset_stream();
} /*flush2sbuf*/
} /*namespace xo*/
/* end log_state.hpp */

View file

@ -0,0 +1,123 @@
/* @file log_streambuf.hpp */
#pragma once
#include <iostream>
#include <vector>
#include <cstring> // e.g. for std::memcpy()
#include <cstdint>
#include <cassert>
namespace xo {
/* recycling buffer for logging.
* write to self-extending storage array;
*/
template <typename CharT, typename Traits>
class log_streambuf : public std::streambuf {
public:
log_streambuf(std::uint32_t buf_z) {
this->buf_v_.resize(buf_z);
this->reset_stream();
} /*ctor*/
std::streamsize capacity() const { return this->buf_v_.size(); }
char const * lo() const { return this->pbase(); }
char const * hi() const { return this->lo() + this->capacity(); }
std::uint32_t pos() const { return this->pptr() - this->pbase(); }
void reset_stream() {
char * p_lo = &(this->buf_v_[0]);
char * p_hi = p_lo + this->capacity();
/* tells parent our buffer extent */
this->setp(p_lo, p_hi);
} /*reset_stream*/
protected:
virtual std::streamsize
xsputn(char const * s, std::streamsize n) override {
/* s must be an address in [this->lo() .. this->lo() + capacity()] */
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;
#endif
//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);
return n;
} /*xsputn*/
virtual int_type
overflow(int_type new_ch) override
{
char * old_pptr = this->pptr();
std::streamsize old_n = old_pptr - this->pbase();
assert(old_n <= static_cast<std::streamsize>(this->buf_v_.size()));
std::size_t new_z = 2 * this->buf_v_.size();
this->buf_v_.resize(new_z);
this->buf_v_[old_n] = new_ch;
/* 'buffered range' will now be .buf_v[old_n .. new_z] */
char * p_base = &(this->buf_v_[0]);
//char * p_lo = &(this->buf_v_[old_n+1]);
char * p_hi = p_base + this->buf_v_.capacity();
this->setp(p_base, p_hi);
this->pbump(old_n + 1);
return new_ch;
} /*overflow*/
/* off. offset, relative to starting point dir.
* dir.
* which. in|out|both
*/
virtual pos_type seekoff(off_type off,
std::ios_base::seekdir dir,
std::ios_base::openmode which) override {
//std::cout << "seekoff: off=" << off << ", dir=" << dir << ", which=" << which << std::endl;
// Only output stream is supported
if (which != std::ios_base::out)
throw std::runtime_error("log_streambuf: only output mode supported");
if (dir == std::ios_base::cur) {
this->pbump(off);
} else if (dir == std::ios_base::end) {
/* .setp(): using for side effect: sets .pptr to .pbase */
this->setp(this->pbase(), this->epptr());
this->pbump(off);
} else if (dir == std::ios_base::beg) {
/* .setp(): using for side effect: sets .pptr to .pbase */
this->setp(this->pbase(), this->epptr());
this->pbump(this->capacity() + off);
}
return this->pptr() - this->pbase();
} /*seekoff*/
private:
/* buffered output stored here */
std::vector<char> buf_v_;
}; /*log_streambuf*/
} /*namespace xo*/
/* end log_streambuf.hpp */

52
include/indentlog/pad.hpp Normal file
View file

@ -0,0 +1,52 @@
/* @file pad.hpp */
#pragma once
#include <iostream>
#include <cstdint>
namespace xo {
/* use:
* ostream os = ...;
*
* 1.
* os << ":" << pad(8) << ":"
*
* writes
* : :
*
* 2.
* os << pad(16, '-')
*
* writes
* ----------------
*
* on os
*/
class pad_impl {
public:
pad_impl(std::uint32_t n, char pad_char) : n_pad_{n}, pad_char_{pad_char} {}
std::uint32_t n_pad() const { return n_pad_; }
char pad_char() const { return pad_char_; }
private:
std::uint32_t n_pad_ = 0;
char pad_char_ = '\0';
}; /*pad_impl*/
inline pad_impl
pad(std::uint32_t n, char pad_char = ' ') { return pad_impl(n, pad_char); }
inline std::ostream &
operator<<(std::ostream & s,
pad_impl const & pad)
{
for(std::uint32_t i=0; i<pad.n_pad(); ++i)
s << pad.pad_char();
return s;
} /*operator<<*/
} /*namespace xo*/
/* end pad.hpp */

View file

@ -0,0 +1,28 @@
/* @file printer.hpp */
#pragma once
#include <utility>
namespace xo {
namespace print {
/* print an event to a logfile
* intended to be usable as EventSink argument
* to RealizationSimSource<T, EventSink>
*/
template<typename T, typename Stream>
class printer {
public:
printer(Stream && os) : os_{std::move(os)} {}
void operator()(T const & x) {
this->os_ << x;
}
private:
Stream os_;
}; /*printer*/
} /*namespace print*/
} /*namespace xo*/
/* end printer.hpp */

View file

@ -0,0 +1,147 @@
/* file quoted.hpp
*
* author: Roland Conybeare, Sep 2022
*/
#pragma once
#include "nestlog/tostr.hpp"
#include <sstream>
#include <string_view>
#include <utility>
#include <type_traits>
namespace xo {
namespace print {
/* use this to avoid template conversion hassles
* since literal strings get treated as arrays
*/
template<typename T>
char const * ccs(T x) { return x; }
/* Printing cases:
* 1. T&&:
* move into quoted_impl<T>. T must be moveable!
* 2. T&:
* copy reference into quoted_impl<T&>.
* similarly for T const &, copy reference into quoted_impl<T const &>
*/
template<typename T>
class quoted_impl {
public:
quoted_impl(bool unq_flag, T const & x) : unq_flag_{unq_flag}, value_{x} {}
quoted_impl(bool unq_flag, T && x) : unq_flag_{unq_flag}, value_{std::move(x)} {}
bool unq_flag() const { return unq_flag_; }
T const & value() const { return value_; }
void print(std::ostream & os) const {
std::string xs = xo::tostr(value_);
if (xs.empty()) {
/* print empty string as "" */
os << "\"\"";
} else if ((xs.at(0) == '<') && (xs.at(xs.size() - 1) == '>')) {
/* assume string represents output of a well-formed object printer,
* and already self-escapes
*/
os << xs;
} else if (xs.find_first_of(" \"\n\r\\") == std::string::npos) {
/* no escapes needed, just print xs */
if (unq_flag_)
os << xs;
else
os << "\"" << xs << "\"";
} else {
/* printed value contains a space
* and/or a must-be-escaped character.
* in any case, need quotes
*/
os << "\"";
/* print contents of ss, with escapes:
* \ => \\
* " => \"
* newline => \n
* cr => \r
*/
for (char ch : xs) {
switch (ch) {
case '"':
/* " => \" */
os << "\\\"";
break;
case '\n':
/* newline -> \n */
os << "\\\n";
break;
case '\r':
/* cr -> \r */
os << "\\\r";
break;
case '\\':
/* \ => \\ (mind c++ requires we escape \) */
os << "\\\\";
break;
default:
os << ch;
break;
}
}
os << "\"";
}
} /*print*/
private:
/* .unq_flag: if true, omit surrounding " chars
* if printed value satisfies both:
* - no escaped chars
* - no spaces
*/
bool unq_flag_ = false;
/* .value: value to be printed */
T value_;
}; /*quoted_impl*/
template<typename T>
std::ostream &
operator<<(std::ostream & os, quoted_impl<T> const & x) {
x.print(os);
return os;
} /*operator*/
/* writing out std::forward<T> behavior for completeness' sake:
*
* 1. call quoted(x) with rvalue std::string x, then:
* - T will be deduced to [std::string]
* (in particular: _not_ std::string &, std::string const &, std::string &&)
* - rvalue std::string passed to quoted_impl ctor
*
* 2a. call quoted(x) with std::string & x, then:
* - T deduced to [std::string &]
* - std::string & passed to quoted_impl ctor
*
* 2b. call quoted(x) with std::string const & x, then:
* - T deduced to [std::string const &]
* - std::string const & passed to quoted_impl ctor
*/
template<typename T>
auto quoted(T && x) {
return quoted_impl(false /*unq_flag*/, std::forward<T>(x));
}
inline auto qcstr(char const * x) {
return quoted(x);
} /*qcstr*/
template<typename T>
auto unq(T && x) {
return quoted_impl(true /*unq_flag*/, std::forward<T>(x));
}
} /*namespace print*/
} /*namespace xo*/
/* end quoted.hpp */

View file

@ -0,0 +1,43 @@
/* @file quoted_char.hpp */
#pragma once
#include <iostream>
namespace xo {
template<typename CharT>
class quoted_char {
public:
quoted_char(CharT ch) : ch_{ch} {}
void print(std::ostream & os) const {
switch(ch_) {
case '\033':
os << "\\033";
break;
case '\n':
os << "\\n";
break;
case '\r':
os << "\\r";
break;
default:
os << ch_;
}
}
private:
CharT ch_;
}; /*quoted_char*/
template<typename CharT>
inline std::ostream &
operator<<(std::ostream & os, quoted_char<CharT> const & x) {
x.print(os);
return os;
} /*operator<<*/
} /*namespace xo*/
/* end quoted_char.hpp */

297
include/indentlog/scope.hpp Normal file
View file

@ -0,0 +1,297 @@
/* @file scope.hpp */
#pragma once
#include "log_state.hpp"
#include "filename.hpp"
#include "tostr.hpp"
#include "tag.hpp"
#include <stdexcept>
#include <cstdint>
#include <memory> // for std::unique_ptr
namespace xo {
template <typename ChartT, typename Traits>
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(xo::log_level::lvl, FS_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_ = FS_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 <typename CharT, typename Traits = std::char_traits<CharT>>
class basic_scope {
public:
using state_impl_type = state_impl<CharT, Traits>;
public:
//basic_scope(std::string_view name1, bool enabled_flag);
template <typename... Tn>
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<typename... Tn>
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<Tn>(rest)...);
this->flush2sbuf(logstate);
}
return true;
} /*log*/
template<typename... Tn>
bool operator()(Tn&&... args) { return this->log(std::forward<Tn>(args)...); }
/* call once to end scope before dtor */
template<typename... Tn>
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<state_impl_type> 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_ = FS_Pretty;
/* name of this scope (part 1) */
std::string_view name1_ = "<name1>";
/* name of this scope (part 2) */
std::string_view name2_ = "::<name2>";
/* captured value of __FILE__ */
std::string_view file_ = "<file>";
/* captured value of __LINE__ */
std::uint32_t line_ = 0;
/* set once per scope .finalized=true <-> logging disabled */
bool finalized_ = false;
}; /*basic_scope*/
template <typename CharT, typename Traits>
template <typename... Tn>
basic_scope<CharT, Traits>::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<Tn>(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 <typename CharT, typename Traits>
basic_scope<CharT, Traits>::~basic_scope() {
if(!this->finalized_)
this->end_scope();
} /*dtor*/
template <typename CharT, typename Traits>
thread_local std::unique_ptr<state_impl<CharT, Traits>>
basic_scope<CharT, Traits>::s_threadlocal_state;
template <typename CharT, typename Traits>
std::uint32_t
basic_scope<CharT, Traits>::nesting_level() const {
return require_thread_local_state()->nesting_level();
} /*nesting_level*/
template <typename CharT, typename Traits>
basic_scope<CharT, Traits>::state_impl_type *
basic_scope<CharT, Traits>::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 CharT, typename Traits>
basic_scope<CharT, Traits>::state_impl_type *
basic_scope<CharT, Traits>::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 <typename CharT, typename Traits>
std::ostream &
basic_scope<CharT, Traits>::logstate2stream(state_impl_type * logstate)
{
return logstate->ss();
} /*logstate2stream*/
template <typename CharT, typename Traits>
void
basic_scope<CharT, Traits>::flush2sbuf(state_impl_type * logstate)
{
logstate->flush2sbuf(this->dest_sbuf_);
} /*flush2sbuf*/
template <typename CharT, typename Traits>
template <typename... Tn>
void
basic_scope<CharT, Traits>::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<Tn>(args)...);
logstate->flush2sbuf(std::clog.rdbuf());
}
} /*end_scope*/
using scope = basic_scope<char>;
} /*namespace xo*/
/* end scope.hpp */

106
include/indentlog/tag.hpp Normal file
View file

@ -0,0 +1,106 @@
/* @file tag.hpp */
#pragma once
#include "tag_config.hpp"
#include "concat.hpp"
#include "quoted.hpp"
#include "color.hpp"
#include <iostream>
// STRINGIFY(xyz) -> "xyz"
#define STRINGIFY(x) #x
// TAG(xyz) -> tag("xyz", xyz)
#define TAG(x) xo::make_tag(STRINGIFY(x), x)
#define TAG2(x, y) xo::make_tag(x, y)
namespace xo {
// associate a name with a value
//
// will print like
// :name value
//
// NOTE: will search for operator<< overloads in the logutil
// namespace
//*/
template <bool PrefixSpace, typename Name, typename Value>
struct tag_impl {
tag_impl(Name const & n, Value const & v)
: name_{n}, value_{v} {}
tag_impl(Name && n, Value && v)
: name_{std::move(n)}, value_{std::move(v)} {}
Name const & name() const { return name_; }
Value const & value() const { return value_; }
private:
Name name_;
Value value_;
}; /*tag_impl*/
/* deduce tag template-type from arguments */
template<typename Name, typename Value>
tag_impl<false, Name, Value>
make_tag(Name && n, Value && v)
{
return tag_impl<false, Name, Value>(n, v);
} /*make_tag*/
template<typename Value>
tag_impl<false, char const *, Value>
make_tag(char const * n, Value && v) {
return tag_impl<false, char const *, Value>(n, v);
} /*make_tag*/
template<typename Name, typename Value>
tag_impl<true, Name, Value>
xtag(Name && n, Value && v)
{
return tag_impl<true, Name, Value>(n, v);
} /*xtag*/
template<typename Value>
tag_impl<true, char const *, Value>
xtag(char const * n, Value && v) {
return tag_impl<true, char const *, Value>(n, v);
} /*xtag*/
inline
tag_impl<true, char const *, char const *>
xtag_pre(char const * n) {
return tag_impl<true, char const *, char const *>(n, "");
} /*xtag_pre*/
template<typename Name, typename Value>
tag_impl<false, Name, Value>
tag(Name && n, Value && v)
{
return tag_impl<false, Name, Value>(n, v);
} /*tag*/
template<typename Value>
tag_impl<false, char const *, Value>
tag(char const * n, Value && v)
{
return tag_impl<false, char const *, Value>(n, v);
} /*tag*/
template <bool PrefixSpace, typename Name, typename Value>
inline std::ostream &
operator<<(std::ostream &s,
tag_impl<PrefixSpace, Name, Value> const & tag)
{
using xo::print::unq;
if(PrefixSpace)
s << " ";
s << with_color(tag_config::encoding, tag_config::tag_color, concat((char const *)":", tag.name()))
<< " " << unq(tag.value());
return s;
} /*operator<<*/
} /*namespace xo*/
/* end tag.hpp */

View file

@ -0,0 +1,34 @@
/* @file tag_config.hpp */
#pragma once
#include "color.hpp"
#include <cstdint>
namespace xo {
/* Tag here b/c we want header-only library */
template <typename Tag>
struct tag_config_impl {
/* color encoding */
static color_encoding encoding;
/* color to use for tags
* os << tag("foo", foovalue)
* to produces output like
* :foo foovalue
* with :foo using .tag_color
*/
static std::uint32_t tag_color;
}; /*tag_config_impl*/
template <typename Tag>
color_encoding
tag_config_impl<Tag>::encoding = CE_Xterm;
template <typename Tag>
std::uint32_t
tag_config_impl<Tag>::tag_color = 245;
using tag_config = tag_config_impl<class tag_config_tag>;
} /*namespace xo*/
/* end tag_config.hpp */

362
include/indentlog/time.hpp Normal file
View file

@ -0,0 +1,362 @@
/* @file time.hpp */
#pragma once
#include <iostream>
#include <iomanip>
#ifdef NOT_YET
# include <format>
#endif
#include <chrono>
#include <cstdint>
#include <time.h>
namespace xo {
namespace time {
using utc_nanos = std::chrono::time_point<std::chrono::system_clock,
std::chrono::nanoseconds>;
using nanos = std::chrono::nanoseconds;
using microseconds = std::chrono::microseconds;
using milliseconds = std::chrono::milliseconds;
using seconds = std::chrono::seconds;
using hours = std::chrono::hours;
using days = std::chrono::days;
struct time {
static utc_nanos now() {
return utc_nanos(std::chrono::system_clock::now());
}
static utc_nanos epoch() {
return utc_nanos(std::chrono::system_clock::from_time_t(0));
} /*epoch*/
static utc_nanos ymd_hms(uint32_t ymd, uint32_t hms) {
/* e.g. ymd=20220610 -> n_yr=2022, n_mon=06, n_dy=10 */
uint32_t n_yr = ymd / 10000;
uint32_t n_mon = (ymd % 10000) / 100;
uint32_t n_dy = ymd % 100;
uint32_t n_hr = hms / 10000;
uint32_t n_min = (hms % 10000) / 100;
uint32_t n_sec = hms % 100;
struct tm t;
t.tm_year = n_yr - 1900; /* 0 means 1900 */
t.tm_mon = n_mon - 1; /* 0 means january */
t.tm_mday = n_dy;
t.tm_hour = n_hr; /* 24 hour clock */
t.tm_min = n_min;
t.tm_sec = n_sec;
/* time since epoch */
time_t epoch_time = timegm(&t);
return std::chrono::system_clock::from_time_t(epoch_time);
} /*ymd_hms*/
/* midnight UTC on date ymd.
* e.g. ymd_midnight(20220707) -> midnight UTC on 7jul22
*/
static utc_nanos ymd_midnight(uint32_t ymd) {
return ymd_hms(ymd, 0);
} /*ymd_midnight*/
static utc_nanos ymd_hms_usec(uint32_t ymd, uint32_t hms, uint32_t usec) {
utc_nanos s = ymd_hms(ymd, hms);
return s + microseconds(usec);
} /*ymd_hms_usec*/
/* .first: UTC midnight on same calendar day as t0
* .second: elapsed time from .first to t0 (i.e. UTC time-of-day for t0)
*/
static std::pair<utc_nanos, nanos> utc_split_vs_midnight(utc_nanos t0) {
using xo::time::microseconds;
using xo::time::utc_nanos;
/* use yyyymmdd.hh:mm:ss.nnnnnn */
time_t t0_time_t = (std::chrono::system_clock::to_time_t
(std::chrono::time_point_cast<xo::time::microseconds>(t0)));
/* convert to std::tm,
* only provides 1-second precision
*/
std::tm t0_tm;
::gmtime_r(&t0_time_t, &t0_tm);
/* midnight on the same calendar day as t0_tm */
std::tm midnight_tm = t0_tm;
{
midnight_tm.tm_hour = 0;
midnight_tm.tm_min = 0;
midnight_tm.tm_sec = 0;
}
/* convert to UTC epoch seconds */
time_t midnight_time_t = ::timegm(&midnight_tm);
utc_nanos t0_midnight =
(std::chrono::time_point_cast<xo::time::microseconds>(
std::chrono::system_clock::from_time_t(midnight_time_t)));
nanos t0_tdy = t0 - t0_midnight;
return std::pair<utc_nanos, nanos>(t0_midnight, t0_tdy);
} /*utc_split_vs_midnight*/
/* .first: LOCAL midnight on same calendar day as t0 (but in UTC coords)
* .second: elapsed time from .first to t0 (i.e. LOCAL time-of-day for t0)
*/
static std::pair<utc_nanos, nanos> local_split_vs_midnight(utc_nanos t0) {
using xo::time::microseconds;
using xo::time::utc_nanos;
/* use yyyymmdd.hh:mm:ss.nnnnnn */
time_t t0_time_t = (std::chrono::system_clock::to_time_t
(std::chrono::time_point_cast<xo::time::microseconds>(t0)));
/* convert to std::tm,
* only provides 1-second precision
*/
std::tm t0_tm;
::localtime_r(&t0_time_t, &t0_tm);
/* midnight on the same calendar day as t0_tm */
std::tm midnight_tm = t0_tm;
{
midnight_tm.tm_hour = 0;
midnight_tm.tm_min = 0;
midnight_tm.tm_sec = 0;
}
/* convert local midnight to UTC epoch seconds */
time_t midnight_time_t = ::timelocal(&midnight_tm);
utc_nanos t0_midnight =
(std::chrono::time_point_cast<xo::time::microseconds>(
std::chrono::system_clock::from_time_t(midnight_time_t)));
nanos t0_tdy = t0 - t0_midnight;
return std::pair<utc_nanos, nanos>(t0_midnight, t0_tdy);
} /*local_split_vs_midnight*/
/* split utc_nanos into
* std::tm
* .tm_year
* .tm_mon (1-12)
* .tm_mday (1-31)
* .tm_hour (0-23)
* .tm_min (0-59)
* .tm_sec (0-59)
* .tm_wday (0=sunday .. 6=saturday)
* .tm_yday (0=1jan .. 365)
* .tm_isdst (daylight savings time flag)
* usec (0-999999)
*/
static std::pair<std::tm, uint32_t> split_tm(utc_nanos t0) {
using xo::time::microseconds;
using xo::time::utc_nanos;
/* use yyyymmdd.hh:mm:ss.nnnnnn */
time_t t0_time_t = (std::chrono::system_clock::to_time_t
(std::chrono::time_point_cast<xo::time::microseconds>(t0)));
/* convert to std::tm, un UTC coords,
* only provides 1-second precision
*/
std::tm t0_tm;
::gmtime_r(&t0_time_t, &t0_tm);
/* midnight on the same calendar day as t0_tm */
std::tm midnight_tm = t0_tm;
midnight_tm.tm_isdst = 0;
midnight_tm.tm_hour = 0;
midnight_tm.tm_min = 0;
midnight_tm.tm_sec = 0;
/* convert back to epoch seconds */
time_t midnight_time_t = ::mktime(&midnight_tm);
utc_nanos t0_midnight =
(std::chrono::time_point_cast<xo::time::microseconds>(
std::chrono::system_clock::from_time_t(midnight_time_t)));
uint32_t usec =
(std::chrono::duration_cast<microseconds>(
std::chrono::hh_mm_ss(t0 - t0_midnight).subseconds()))
.count();
return std::make_pair(t0_tm, usec);
} /*split_tm*/
static void print_hms_msec(nanos dt, std::ostream & os) {
/* use hhmmss.nnn */
using std::int32_t;
auto hms = std::chrono::hh_mm_ss(dt);
int32_t h = hms.hours().count();
int32_t m = hms.minutes().count();
int32_t s = hms.seconds().count();
int32_t msec = std::chrono::duration_cast<milliseconds>(hms.subseconds()).count();
char buf[32];
snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%03d", h, m, s, msec);
os << buf;
} /*print_hms_msec*/
static void print_utc_hms_msec(utc_nanos t0, std::ostream & os) {
print_hms_msec(utc_split_vs_midnight(t0).second, os);
} /*print_utc_hms_usec*/
static void print_hms_usec(nanos dt, std::ostream & os) {
/* use hhmmss.uuuuuu */
using std::int32_t;
auto hms = std::chrono::hh_mm_ss(dt);
int32_t h = hms.hours().count();
int32_t m = hms.minutes().count();
int32_t s = hms.seconds().count();
int32_t usec = std::chrono::duration_cast<microseconds>(hms.subseconds()).count();
char buf[32];
snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%06d", h, m, s, usec);
os << buf;
} /*print_hms_usec*/
static void print_utc_ymd_hms_usec(utc_nanos t0, std::ostream & os) {
using xo::time::microseconds;
using xo::time::utc_nanos;
/* use yyyymmdd.hh:mm:ss.nnnnnn */
//std::tm t0_tm;
//uint32_t t0_usec;
/* (structured binding ftw!) */
auto [t0_tm, t0_usec] = split_tm(t0);
/* no std::format in clang11 afaict */
char usec_buf[7];
snprintf(usec_buf, sizeof(usec_buf), "%06d", t0_usec);
/* control string | example
* ----------------------------+--------------------------
* %c - locale-specific string | Fri Jun 10 16:29:05 2022
* %Y - year | 2022
* %m - month | 06
* %d - day of month | 10
* %H - hour | 16
* %M - minute | 29
* %S - second | 05
* %Z - timezone | UTC
*/
os << std::put_time(&t0_tm, "%Y%m%d:%H:%M:%S.") << usec_buf;
} /*print_utc_ymd_hms_usec*/
/* print datetime in format compatible with ISO 8601.
* copying the format javascript uses, e.g:
* 2012-04-23T18:25:43.511Z
*/
static void print_iso8601(utc_nanos t0, std::ostream & os) {
auto [t0_tm, t0_usec] = split_tm(t0);
char msec_buf[8];
snprintf(msec_buf, sizeof(msec_buf), "%03d", t0_usec / 1000);
os << std::put_time(&t0_tm, "%Y-%m-%dT%H:%M:%S.") << msec_buf << "Z";
} /*print_iso8601*/
}; /*time*/
/* stream inserter that displays time in ISO 8601 format:
* 2012-04-23T18:25:43.511Z
*/
struct iso8601 {
iso8601(utc_nanos t0) : t0_{t0} {}
utc_nanos t0_;
}; /*iso8601*/
inline std::ostream &
operator<<(std::ostream & os,
iso8601 x)
{
time::print_iso8601(x.t0_, os);
return os;
} /*operator<<*/
/* stream inserter that display time like:
* hh:mm:ss.nnn
*/
struct hms_msec {
hms_msec(nanos dt) : dt_{dt} {}
static hms_msec utc(utc_nanos t0) { return hms_msec(time::utc_split_vs_midnight(t0).second); }
static hms_msec local(utc_nanos t0) { return hms_msec(time::local_split_vs_midnight(t0).second); }
nanos dt_;
}; /*hms_msec*/
inline std::ostream &
operator<<(std::ostream & os, hms_msec x)
{
time::print_hms_msec(x.dt_, os);
return os;
} /*operator<<*/
/* stream inserter that display time like:
* hh:mm:ss.nnnnnn
*/
struct hms_usec {
hms_usec(nanos dt) : dt_{dt} {}
static hms_usec utc(utc_nanos t0) { return hms_usec(time::utc_split_vs_midnight(t0).second); }
static hms_usec local(utc_nanos t0) { return hms_usec(time::local_split_vs_midnight(t0).second); }
nanos dt_;
}; /*hms_msec*/
inline std::ostream &
operator<<(std::ostream & os, hms_usec x)
{
time::print_hms_usec(x.dt_, os);
return os;
} /*operator<<*/
} /*namespace time*/
} /*namespace xo*/
namespace std {
namespace chrono {
inline std::ostream & operator<<(std::ostream & os,
xo::time::utc_nanos t0)
{
xo::time::time::print_utc_ymd_hms_usec(t0, os);
return os;
} /*operator<<*/
inline std::ostream & operator<<(std::ostream & os,
xo::time::nanos dt)
{
xo::time::time::print_hms_usec(dt, os);
return os;
} /*operator<<*/
} /*namespace chrono*/
} /*namespace std*/
/* end time.hpp */

105
include/indentlog/tostr.hpp Normal file
View file

@ -0,0 +1,105 @@
/* file tostr.hpp
*
* author: Roland Conybeare, Sep 2022
*/
#pragma once
#include <iostream>
#include <sstream>
#include <string>
namespace xo {
/*
* write x to stream s
* note: here x is a universal reference, since
* (a) it's a template type
* (b) requires deduction to establish x's type
* this means:
* x will be an r-value reference or an l-value reference
* depending on calling context
*
* see:
* https://eli.thegreenplace.net/2014/variadic-templates-in-c/
* http://bajamircea.github.io/coding/cpp/2016/04/07/move-forward.html
* https://en.cppreference.com/w/cpp/language/value_category
*
* has identity == has address
*
* /- has identity -----------------\
* | |
* | lvalue |
* | glvalue /------------------------------\
* | | | |
* | | xvalue | |
* | | rvalue | |
* | | glvalue | |
* | | | |
* \--------------------------------/ |
* | rvalue |
* | prvalue |
* | |
* \- can be moved ---------------/
*
* 1. has identity, but cannot be moved -> it's an lvalue; otherwise it's an rvalue
* e.g: local variable name
*
* 2. can be moved, but no identity -> it's a prvalue (pure right-value);
* otherwise it's a glvalue (generalized left-value)
* e.g: non-reference function return value, or literal constant
*
* 3. has identity and can be moved -> it's an xvalue (strange value)
* e.g: std::move(a)
*
* reminder:
* - std::move() does not move: it converts lvalue to rvalue, so compiler can select
* desired overload
* - std::forward() does not forward: it recovers original value category
* (when starting with a universal reference), so compiler can select
* desired ctor
*/
/* no-op terminal case */
template<class Stream>
Stream & tos(Stream & s) {
return s;
}
// Use:
// tos(s,a,b,c)
// is the same as
// s << a << b << c;
//
template<class Stream, typename T>
Stream & tos(Stream & s, T && x) {
s << x;
return s;
} /*tos*/
template <class Stream, typename T, typename... Tn>
Stream &tos(Stream &s, T &&x, Tn &&...rest) {
s << x;
return tos(s, rest...);
} /*tos*/
// like tos(..), but append newline
//
template <class Stream, typename... Tn>
Stream &tosn(Stream &s, Tn &&...args) {
tos(s, args...);
s << std::endl;
return s;
} /*tosn*/
// tostr(args..) writes arguments to temporary stingstream,
// returns its contents
//
template <typename... Tn> std::string tostr(Tn &&...args) {
std::stringstream ss;
tos(ss, args...);
//ss << std::ends;
return ss.str();
} /*tostr*/
} /*namespace xo*/
/* end tostr.hpp */

View file

@ -0,0 +1,28 @@
/* file vector.hpp
*
* author: Roland Conybeare, Sep 2022
*/
#pragma once
#include <iostream>
#include <vector>
namespace std {
template<typename T>
inline std::ostream &
operator<<(std::ostream & os,
std::vector<T> const & v)
{
os << "[";
for(size_t i=0, z=v.size(); i<z; ++i) {
if(i > 0)
os << " ";
os << v[i];
}
os << "]";
return os;
} /*operator<<*/
} /*namespace std*/
/* end vector.hpp */