Naju
Naju

Reputation: 315

How to insert boost::log::expressions formatter object into stream to format timestamp

I am following the tutorial on Boost.log record formatting and I'm trying to format a timestamp to only show 2 digits on the fractional seconds part. The same question was asked 9 years ago here how to customize "TimeStamp" format of Boost.Log, but the solution doesn't seem to work with more recent versions of Boost. Here's what I tried which is similar to said solution, but with format_date_time< boost::posix_time::ptime > instead of date_time<boost::posix_time::ptime>:

namespace expr = boost::log::expressions;

auto ts = expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S.");
auto ts_fractional = expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%f");
auto ts_fractional_short = (expr::format("%.2s") % ts_fractional);
  
auto strm = expr::stream
    << '[' << ts << '.'
    << ts_fractional_short << ']'   //error here. ts_fractional would work
    << '[' << logging::trivial::severity << ']'
    << " " << expr::smessage;

sink->set_formatter(strm);

What seems to be the relevant error is:

/path/to/boost_1_73_0/boost/log/utility/formatting_ostream.hpp:921:19: note:   'boost::log::v2s_mt_posix::basic_formatting_ostream<char>::ostream_type {aka std::basic_ostream<char>}' is not derived from 'boost::log::v2s_mt_posix::basic_formatting_ostream<CharT, TraitsT, AllocatorT>'
     strm.stream() << value;
...
/path/to/boost_1_73_0/boost/log/utility/formatting_ostream.hpp:921:19: note:   cannot convert 'value' (type 'const boost::log::v2s_mt_posix::aux::basic_format<char>::pump') to type 'const id& {aka const boost::log::v2s_mt_posix::aux::id<boost::log::v2s_mt_posix::aux::process>&}'
     strm.stream() << value;

Is there a way to convert the object returned by expr::format() to something that can be injected into the expr::stream?

UPDATE

I have found a solution, which is to use a custom formatter which takes a formatting_ostream and a record_view, and extract the timestamp from the record. Then I do the formatting using a local_date_time object and output_facets, and finally write it back to the ostream using boost::format on the fractional seconds string. It is pretty ugly; There must be a better way.

void formatter(const boost::log::record_view &rec, boost::log::formatting_ostream &os)
{
  auto pt = logging::extract< boost::posix_time::ptime >("TimeStamp", rec);

  using namespace boost::local_time;
  using namespace boost::gregorian;

  stringstream ss;
  auto output_facet = new local_time_facet();
  auto input_facet = new local_time_input_facet();
  ss.imbue(locale(locale::classic(), output_facet));
  ss.imbue(locale(ss.getloc(), input_facet));

  local_date_time ldt(not_a_date_time);
  ss << pt;
  ss >> ldt;    //yuck...

  output_facet->format("%Y-%m-%d %H:%M:%S");
  ss.str("");
  ss << ldt;
  auto ts = ss.str();
  output_facet->format("%f");
  ss.str("");
  ss << ldt;
  auto ts_fractional = ss.str();

  os << boost::format("[%1%.%2%][%3%] %4%")
        % ts
        % (boost::format("%.3s") % ts_fractional)
        % logging::extract< boost::log::trivial::severity_level >("Severity", rec)
        % rec[expr::smessage];
}

int main(int argc, char *argv[]) {
...
  sink->set_formatter(&formatter);
...
}

UPDATE 2

Just in case someone stumbles upon this and wants to use @sehe's format_f with a C++11 compiler:

struct format_f {
  std::string format_str;

  template<typename T1, typename... Ts>
  string operator()(T1 const& t1, Ts const&... ts) const {
    return consume_arg((boost::format(format_str) % t1), ts...).str();
  }
  template<typename T1>
  T1 consume_arg(T1 &t1) const {
    return t1;
  }
  template<typename T1, typename T2, typename... Ts>
  T1 consume_arg(T1 &t1, T2 const& t2, Ts const&... ts) const {
    return consume_arg(t1 % t2, ts...);
  }
};

Upvotes: 1

Views: 1066

Answers (1)

sehe
sehe

Reputation: 393064

You want to have deferred calleables.

Borrowing Xfrm loosely from this older answer of mine I would suggest doing a substring of the full datetime:

static constexpr Xfrm Left24 { [](std::string const& s) { return s.substr(0, 24); } };

logging::formatter formatter = expr::stream
    << line_id
    << " | "
    << Left24 [ expr::stream << expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S.%f") ]
    << " [" << logging::trivial::severity << "]"
    << " - " << expr::smessage;

Which prints Live On Wandbox

3 | 2020-08-15, 11:37:30.128 [warning] - this is a warning message
4 | 2020-08-15, 11:37:30.129 [error] - this is an error message
5 | 2020-08-15, 11:37:30.129 [fatal] - this is a fatal error message

Extracting Values

Instead of post-processing textually, you might want to extract true data. I found this harder than I hoped but showing anyways, in case it helps anyone:

boost::phoenix::function<milliseconds_f> milliseconds;

logging::formatter formatter = expr::format(
        "%1% | %2%.%3% [ %4% ] - %5%")
    % line_id
    % expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S")
    % milliseconds(expr::attr<boost::posix_time::ptime>("TimeStamp").or_throw())
    % logging::trivial::severity
    % expr::smessage;

Now, milliseconds_f is defined as:

struct milliseconds_f {
    auto operator()(logging::value_ref<boost::posix_time::ptime> const& v) const {
        auto f = v.get().time_of_day().fractional_seconds();
        std::string s = std::to_string(f / 1000);
        while (s.length()<3) s += '0';
        return s;
    }
};

See it Live On Wandbox

3 | 2020-08-15, 12:27:38.870 [ warning ] - this is a warning message
4 | 2020-08-15, 12:27:38.870 [ error ] - this is an error message
5 | 2020-08-15, 12:27:38.870 [ fatal ] - this is a fatal error message

Alternative: Closer To Your Expectation

You could make a lazy function to do formatting that works:

boost::phoenix::function<format_ex> format;

logging::formatter formatter = expr::format(
        "%1% | %2%.%3% [ %4% ] - %5%")
    % line_id
    % expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S")
    % format(std::string("%.3s"), expr::format_date_time(timestamp, "%f"))
    % logging::trivial::severity
    % expr::smessage;

Where

struct format_ex {
    template<typename... T>
    auto operator()(std::string const& format_str, T const&... v) const {
        return (boost::format(format_str) % ... % v);
    }
};

Due to leaky abstractions, you need to make sure the format-string is not a char[] literal by reference. You can also force decay (ugly, but less verbose):

    % format(+"%.3s", expr::format_date_time(timestamp, "%f"))

To sidestep the whole issue, one could use Phoenix Bind with the formatter separate:

using boost::phoenix::bind;

logging::formatter formatter = expr::format(
        "%1% | %2%.%3% [ %4% ] - %5%")
    % line_id
    % expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S")
    % bind(format_f{"%.3s"}, expr::format_date_time(timestamp, "%f"))
    % logging::trivial::severity
    % expr::smessage;

With

struct format_f {
    std::string format_str;
    
    template<typename... T>
    std::string operator()(T const&... v) const {
        return (boost::format(format_str) % ... % v).str();
    }
};

See both these Live On Wandbox

Hybrid

Trying to mix expr::stream with format this way:

boost::phoenix::function<format_f> left24 = format_f{"%.24s"};

logging::formatter formatter = expr::stream
    << line_id << " | "
    << left24(expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S.%f"))
    << " [ " << logging::trivial::severity
    << " ] - " << expr::smessage;

With the same format_f as above: Live On Wandbox, prints:

3 | 2020-08-15, 12:55:39.426 [ warning ] - this is a warning message
4 | 2020-08-15, 12:55:39.426 [ error ] - this is an error message
5 | 2020-08-15, 12:55:39.426 [ fatal ] - this is a fatal error message

Alternatively

boost::phoenix::function<format_ex> format;

logging::formatter formatter = expr::stream
    << line_id << " | "
    << format(+"%.24s", expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S.%f"))
    << " [ " << logging::trivial::severity
    << " ] - " << expr::smessage;

Also Live On Wandbox

3 | 2020-08-15, 12:59:35.964 [ warning ] - this is a warning message
4 | 2020-08-15, 12:59:35.965 [ error ] - this is an error message
5 | 2020-08-15, 12:59:35.965 [ fatal ] - this is a fatal error message

Upvotes: 1

Related Questions