Cast a boost::log::expressions::attr< std> to std:

2020-02-09 22:28发布

While using Boost.Log, I am trying to keep my TimeStamp formatter such as:

  logging::add_file_log
    (
     keywords::file_name = "my.log",
     keywords::format =
     (
      expr::stream
      << expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S")
      << "," << expr::attr< int >("Line")
      << " " << expr::attr< std::string >("File")
      << " " << logging::trivial::severity
      << " - " << expr::smessage
     )
    );

It is said that I cannot use the other form of formatter since I'll have much difficulties turning "TimeStamp" into my custom format:

static void my_formatter(logging::record_view const& rec, logging::formatting_ostream& strm)
{
  strm << logging::extract< boost::posix_time::ptime >("TimeStamp", rec);

will output something like: 2015-Jul-01 16:06:31.514053, while I am only interested in: "%Y-%m-%d %H:%M:%S". However the first form is extremely hard to use, for instance I am not able to cast an expr::attr< std::string > to a simple std::string for instance:

  logging::add_file_log
    (
     keywords::file_name = "my.log",
     keywords::format =
     (
      expr::stream
      << expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S")
      << "," << expr::attr< int >("Line")
      << " " << boost::filesystem::path(expr::attr< std::string >("File"))
        .filename().string()
      << " " << logging::trivial::severity
      << " - " << expr::smessage
     )
    );

The above code does not even compile.

Is there an easy way to both print TimeStamp using my custom format and at the same time use a custom cast to string to be able to use boost::filesystem::path::filename() ?

2条回答
欢心
2楼-- · 2020-02-09 23:08

There are multiple ways to achieve what you want. The key point to understand is that Boost.Log formatting expressions (as well as filters, by the way) are Boost.Phoenix lambda functions. As such you can inject your own functions in them using Boost.Phoenix constructs such as boost::phoenix::bind. See an example here, for instance. Your code would look something like this:

std::string file_basename(logging::value_ref< std::string > const& filename)
{
  // Check to see if the attribute value has been found
  if (filename)
    return boost::filesystem::path(filename.get()).filename().string();
  else
    return std::string();
}

// ...

logging::add_file_log
(
  keywords::file_name = "my.log",
  keywords::format =
  (
    expr::stream
      << expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S")
      << "," << expr::attr< int >("Line")
      << " " << boost::phoenix::bind(&file_basename, expr::attr< std::string >("File"))
      << " " << logging::trivial::severity
      << " - " << expr::smessage
  )
);

Another way to do that is to use attribute keywords and define an operator<< specific for the File attribute. You can find an example here.

BOOST_LOG_ATTRIBUTE_KEYWORD(a_timestamp, "TimeStamp", boost::posix_time::ptime)
BOOST_LOG_ATTRIBUTE_KEYWORD(a_line, "Line", int)
BOOST_LOG_ATTRIBUTE_KEYWORD(a_file, "File", std::string)

namespace std {

logging::formatting_ostream& operator<<
(
  logging::formatting_ostream& strm,
  logging::to_log_manip< std::string, tag::a_file > const& manip
)
{
  strm << boost::filesystem::path(manip.get()).filename().string();
  return strm;
}

} // namespace std

// ...

logging::add_file_log
(
  keywords::file_name = "my.log",
  keywords::format =
  (
    expr::stream
      << expr::format_date_time(a_timestamp, "%Y-%m-%d %H:%M:%S")
      << "," << a_line
      << " " << a_file
      << " " << logging::trivial::severity
      << " - " << expr::smessage
  )
);

Notice that attribute keywords simplify expressions significantly.

Lastly, you can use wrap_formatter to inject your own function into the streaming expression. When it comes to formatting, wrap_formatter invokes your function providing it with the log record being formatted and the formatting stream. When your function returns the wrapper automatically returns the reference to the formatting stream so that the rest of the formatting expression can proceed.

BOOST_LOG_ATTRIBUTE_KEYWORD(a_timestamp, "TimeStamp", boost::posix_time::ptime)
BOOST_LOG_ATTRIBUTE_KEYWORD(a_line, "Line", int)
BOOST_LOG_ATTRIBUTE_KEYWORD(a_file, "File", std::string)

void file_basename(logging::record_view const& record, logging::formatting_ostream& strm)
{
  // Check to see if the attribute value has been found
  logging::value_ref< std::string, tag::a_file > filename = record[a_file];
  if (filename)
    strm << boost::filesystem::path(filename.get()).filename().string();
}

// ...

logging::add_file_log
(
  keywords::file_name = "my.log",
  keywords::format =
  (
    expr::stream
      << expr::format_date_time(a_timestamp, "%Y-%m-%d %H:%M:%S")
      << "," << a_line
      << " " << expr::wrap_formatter(&file_basename)
      << " " << logging::trivial::severity
      << " - " << expr::smessage
  )
);

The above is similar to the first variant with boost::phoenix::bind but allows for more flexibility in the file_basename implementation.

查看更多
\"骚年 ilove
3楼-- · 2020-02-09 23:22

In custom formatter you can easily build the timestamp in "%Y-%m-%d %H:%M:%S" format:

void my_formatter(logging::record_view const& rec, logging::formatting_ostream& strm)
{
    const boost::posix_time::ptime &pt = *logging::extract< boost::posix_time::ptime >("TimeStamp", rec);
    strm << pt.date() << " " << pt.time_of_day().hours() << ":" << pt.time_of_day().minutes() << ":" << pt.time_of_day().seconds()
    ...
    << rec[expr::smessage];
}
查看更多
登录 后发表回答