0

I'm working on the basic boost example from here. I'm configuring what I need for my application, but I'm stuck. Here's where I am now:

#include <fstream>
#include <iomanip>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/log/support/date_time.hpp>
#include <boost/smart_ptr/shared_ptr.hpp>
#include <boost/smart_ptr/make_shared_object.hpp>
#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/sinks/text_ostream_backend.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/date_time/posix_time/posix_time_io.hpp>

namespace logging = boost::log;
namespace src = boost::log::sources;
namespace expr = boost::log::expressions;
namespace sinks = boost::log::sinks;
namespace keywords = boost::log::keywords;

void init()
{
    typedef sinks::synchronous_sink<sinks::text_ostream_backend> text_sink;
    boost::log::core::get()->add_global_attribute("TimeStamp", boost::log::attributes::utc_clock());
    boost::shared_ptr<text_sink> sink = boost::make_shared<text_sink>();

    sink->locked_backend()->add_stream(
        boost::make_shared<std::ofstream>("log.html"));

    sink->set_formatter
    (
        expr::stream
            << expr::format_date_time<boost::posix_time::ptime>("TimeStamp", "%Y.%m.%d-%H:%M:%S-UTC")
            << ": <" << logging::trivial::severity << "> " << expr::smessage
    );

    logging::core::get()->add_sink(sink);
}


int main(int, char*[])
{
    std::cout<<"Start"<<std::endl;
    init();
    logging::add_common_attributes();

    using namespace logging::trivial;
    src::severity_logger< severity_level > lg;

    BOOST_LOG_SEV(lg, trace) << "A trace severity message";
    BOOST_LOG_SEV(lg, debug) << "A debug severity message";
    BOOST_LOG_SEV(lg, info) << "An informational severity message";
    BOOST_LOG_SEV(lg, warning) << "A warning severity message";
    BOOST_LOG_SEV(lg, error) << "An error severity message";
    BOOST_LOG_SEV(lg, fatal) << "A fatal severity message";

    std::cout<<"End"<<std::endl;
    return 0;
}

I would like to have the formatting depend on the severity level. This is because my logging output is HTML. For example, the messages I print in the main up there should be output like this to the file:

<font color='black'>A trace severity message</font>
<font color='gray'>A debug severity message</font>
<font color='blue'>An informational severity message</font>
<font color='orange'>A warning severity message</font>
<font color='red'>An error severity message</font>
<strong><font color='red'>A fatal severity message</font></strong>

Is this possible with the simple model I have up there? What does it take?

Inserting an if condition into sink->set_formatter(). I couldn't also use a C++11 lambda in there.

The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189

2 Answers2

2

There are a number of ways to do what you want. The most direct approach is to use the conditional formatter:

sink->set_formatter
(
    expr::stream
        << expr::if_(logging::trivial::severity <= logging::trivial::severity_level::trace)
           [
               expr::stream << "<font color='black'>"
           ]
           .else_
           [
               expr::stream << expr::if_(logging::trivial::severity <= logging::trivial::severity_level::debug)
               [
                    // ...
               ]
           ]
        << expr::format_date_time<boost::posix_time::ptime>("TimeStamp", "%Y.%m.%d-%H:%M:%S-UTC")
        << ": <" << logging::trivial::severity << "> " << expr::smessage
);

However, this approach can be rather tedious and have suboptimal performance.

A better solution is to inject your function into the formatter. Here's an example:

boost::string_view prefix_formatter(
    logging::value_ref< logging::trivial::severity_level, logging::trivial::tag::severity > const& level)
{
    if (level)
    {
        switch (level.get())
        {
        case logging::trivial::severity_level::trace:
            return "<font color='black'>";
        case logging::trivial::severity_level::debug:
            return "<font color='gray'>";
        // ...
        }
    }

    return boost::string_view();
}

boost::string_view suffix_formatter(
    logging::value_ref< logging::trivial::severity_level, logging::trivial::tag::severity > const& level)
{
    if (level)
    {
        switch (level.get())
        {
        case logging::trivial::severity_level::trace:
            return "</font>";
        // ...
        }
    }

    return boost::string_view();
}

sink->set_formatter
(
    expr::stream
        << boost::phoenix::bind(&prefix_formatter, logging::trivial::severity.or_none())
        << expr::format_date_time<boost::posix_time::ptime>("TimeStamp", "%Y.%m.%d-%H:%M:%S-UTC")
        << ": <" << logging::trivial::severity << "> " << expr::smessage
        << boost::phoenix::bind(&suffix_formatter, logging::trivial::severity.or_none())
);

Here, or_none makes the value_ref be empty if the attribute is not present in the log record, hence the need to check it in prefix_formatter and suffix_formatter.

Finally, since you're writing HTML, you will probably want to automatically convert reserved characters in the output to escape sequences. XML character decorator can help you with this.

Andrey Semashev
  • 10,046
  • 1
  • 17
  • 27
  • Thank you for the answer. I'm trying to implement it. That `string_view` is not on boost 1.55, right? I'm on Debian Jessie and couldn't find it with a grep on the whole `/usr/` directory. – The Quantum Physicist Apr 08 '17 at 18:32
  • No, it was added later. You can use `boost::string_ref` or `std::string` instead. – Andrey Semashev Apr 09 '17 at 12:07
  • Thanks a lot. I didn't know that. Actually this morning I read about `string_view` and learned what it's. On the other hand, I found a solution to my problem and wrote it in another answer. I learned much from you, so I'm +1ing this :) Thanks again! – The Quantum Physicist Apr 09 '17 at 12:26
0

I was able to do it like this, with a custom formatter:

#include <fstream>
#include <iomanip>
#include <boost/smart_ptr/shared_ptr.hpp>
#include <boost/smart_ptr/make_shared_object.hpp>
#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/sinks/text_ostream_backend.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/sources/record_ostream.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/date_time/posix_time/posix_time_io.hpp>
#include "boost/log/utility/setup.hpp"
#include "boost/log/support/date_time.hpp"
#include <boost/log/expressions/formatters/xml_decorator.hpp>

namespace b_logging = boost::log;
namespace b_srcs = b_logging::sources;
namespace b_expr = b_logging::expressions;
namespace b_sinks = b_logging::sinks;

b_logging::formatting_ostream& severity_wrap_prefix(b_logging::formatting_ostream& strm, const b_logging::trivial::severity_level& sev)
{
    if     (sev == b_logging::trivial::severity_level::trace)
        strm << "<font color='gray'>";
    else if(sev == b_logging::trivial::severity_level::debug)
        strm << "<font color='#808080'>";
    else if(sev == b_logging::trivial::severity_level::info)
        strm << "<font color='green'>";
    else if(sev == b_logging::trivial::severity_level::warning)
        strm << "<font color='orange'>";
    else if(sev == b_logging::trivial::severity_level::error)
        strm << "<font color='red'>";
    else if(sev == b_logging::trivial::severity_level::fatal)
        strm << "<strong><font color='red'>";
    else
        strm << "<UNKNOWN SEVERITY LEVEL>";
    return strm;
}

b_logging::formatting_ostream& severity_wrap_suffix(b_logging::formatting_ostream& strm, const b_logging::trivial::severity_level& sev)
{
    if     (sev == b_logging::trivial::severity_level::trace)
        strm << "</font><br>";
    else if(sev == b_logging::trivial::severity_level::debug)
        strm << "</font><br>";
    else if(sev == b_logging::trivial::severity_level::info)
        strm << "</font><br>";
    else if(sev == b_logging::trivial::severity_level::warning)
        strm << "</font><br>";
    else if(sev == b_logging::trivial::severity_level::error)
        strm << "</font><br>";
    else if(sev == b_logging::trivial::severity_level::fatal)
        strm << "</strong></font><br>";
    else
        strm << "<UNKNOWN SEVERITY LEVEL>";
    return strm;
}

std::string ptime_to_string(const boost::posix_time::ptime& time)
{
    std::stringstream str;
    boost::posix_time::time_facet *facet = new boost::posix_time::time_facet("%d.%m.%Y-%H:%M:%S-UTC");
    str.imbue(std::locale(str.getloc(), facet));
    str << time;
    return str.str();
}

void init()
{
    b_logging::core::get()->add_global_attribute("TimeStamp", b_logging::attributes::utc_clock());
    typedef b_sinks::synchronous_sink<b_sinks::text_ostream_backend> text_sink;
    boost::shared_ptr<text_sink> sink = boost::make_shared<text_sink>();

    sink->locked_backend()->add_stream(
        boost::make_shared<std::ofstream>("sample.htm"));

    sink->set_formatter([&](b_logging::record_view const& rec, b_logging::formatting_ostream& strm)
    {
        const b_logging::trivial::severity_level& sev = *b_logging::extract<b_logging::trivial::severity_level>("Severity", rec);

        const boost::posix_time::ptime &pt = *b_logging::extract<boost::posix_time::ptime>("TimeStamp", rec);

        severity_wrap_prefix(strm,sev);
        strm << ptime_to_string(pt) << ": "
                     << rec[b_expr::smessage];
        strm << b_expr::xml_decor[b_expr::stream << b_expr::smessage];
        severity_wrap_suffix(strm,sev);
    });

    b_logging::core::get()->add_sink(sink);
}


int main(int, char*[])
{
    std::cout<<"Start"<<std::endl;
    init();
    b_logging::add_common_attributes();

    using namespace b_logging::trivial;
    b_srcs::severity_logger< severity_level > lg;

    BOOST_LOG_SEV(lg, trace) << "A trace severity message";
    BOOST_LOG_SEV(lg, debug) << "A debug severity message";
    BOOST_LOG_SEV(lg, info) << "An informational severity message";
    BOOST_LOG_SEV(lg, warning) << "A warning severity message";
    BOOST_LOG_SEV(lg, error) << "An error severity message";
    BOOST_LOG_SEV(lg, fatal) << "A fatal severity message";

    std::cout<<"End"<<std::endl;
    return 0;
}

Andrey's solution didn't work for me because apparently string_view is something new and I couldn't find it in my boost that comes with Debian Jessie, Boost 1.55.

The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189