Danny
Danny

Reputation: 31

Passing boost log message as an argument to function call

I have written C++ code for capturing the various severity levels of messages. I have used https://github.com/gklingler/simpleLogger for this.

So I would send my message as

LOG_INFO << "This is info message"

But this log message I need to send it to some other function as an argument. In that function I will be doing some other changes on the log message i.e "This is info message".

How to send the boost log message as an argument to function? I didn't find relevant source for this.

Thanks in advance

Upvotes: 1

Views: 1454

Answers (1)

sehe
sehe

Reputation: 393134

You can define a "lazy actor" that you can put into a wrapped formatter expression. This is pretty much the rocket science of Boost Log, but perhaps this simple example will help you:

auto reverse_expr = [](auto fmt) {
    return expr::wrap_formatter([fmt](logging::record_view const& rec, logging::formatting_ostream& strm) {
        logging::formatting_ostream tmp;
        std::string text;
        tmp.attach(text);

        fmt(rec, tmp);

        std::reverse(text.begin(), text.end());
        strm << text;
    });
};

// specify the format of the log message
logging::formatter formatter = expr::stream
    << reverse_expr(expr::stream << std::setw(7) << std::setfill('0') << line_id) 
    << " | "
    << expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S.%f") << " "
    << "[" << logging::trivial::severity << "]"
    << " - " 
    << reverse_expr(expr::stream << expr::smessage);

sink->set_formatter(formatter);

Results in:

3000000 | 2020-04-28, 16:15:15.779204 [warning] - egassem gninraw a si siht                         
4000000 | 2020-04-28, 16:15:15.779308 [error] - egassem rorre na si siht                            
5000000 | 2020-04-28, 16:15:15.779324 [fatal] - egassem rorre lataf a si siht                       

Notes: it will not be overly efficient because it involves "double buffering" with a temporary stream, but it is highly flexible, as you can see.

UPDATE Improved method below (see section BONUS)

A slightly more generic implementation might look like:

template <typename F> struct Xfrm {
    Xfrm(F f) : _f(f) {}
    F _f;

    template <typename E> auto operator[](E fmt) const {
        return expr::wrap_formatter(
            [f=_f,fmt](logging::record_view const& rec, logging::formatting_ostream& strm) {
                logging::formatting_ostream tmp;
                std::string text;
                tmp.attach(text);
                fmt(rec, tmp);

                strm << f(text);
            });
    }
};

So you can actually use it for other functions:

std::string reversed(std::string v) {
    std::reverse(v.begin(), v.end());
    return v;
}
std::string to_uppercase(std::string v) {
    for (auto& ch : v) ch = std::toupper(ch);
    return v;
}

Which you could then use like:

logging::formatter formatter = expr::stream
    << Xfrm(reversed)[expr::stream << std::setw(7) << std::setfill('0') << line_id]
    << " | "
    << expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S.%f") << " "
    << "[" << logging::trivial::severity << "]"
    << " - " 
    << Xfrm(to_uppercase)[ expr::stream << expr::smessage ];

Or put the manipulators in your header file:

static inline constexpr Xfrm UC{to_uppercase};
static inline constexpr Xfrm REV{reversed};

So you can use it pre-fab:

logging::formatter formatter = expr::stream
    << REV[ expr::stream << std::setw(7) << std::setfill('0') << line_id ]
    << " | "
    << expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S.%f") << " "
    << "[" << logging::trivial::severity << "]"
    << " - " 
    << UC[ expr::stream << expr::smessage ];

Output

3000000 | 2020-04-28, 16:36:46.184958 [warning] - THIS IS A WARNING MESSAGE
4000000 | 2020-04-28, 16:36:46.185034 [error] - THIS IS AN ERROR MESSAGE
5000000 | 2020-04-28, 16:36:46.185045 [fatal] - THIS IS A FATAL ERROR MESSAGE

Live Demo

Live On Wandbox

  • File simpleLogger.h

     #ifndef _HOME_SEHE_PROJECTS_STACKOVERFLOW_SIMPLELOGGER_H
     #define _HOME_SEHE_PROJECTS_STACKOVERFLOW_SIMPLELOGGER_H
    
     #pragma once
     #define BOOST_LOG_DYN_LINK \
         1 // necessary when linking the boost_log library dynamically
    
     #include <boost/log/sources/global_logger_storage.hpp>
     #include <boost/log/trivial.hpp>
    
     // the logs are also written to LOGFILE
     #define LOGFILE "logfile.log"
    
     // just log messages with severity >= SEVERITY_THRESHOLD are written
     #define SEVERITY_THRESHOLD logging::trivial::warning
    
     // register a global logger
     BOOST_LOG_GLOBAL_LOGGER(logger, boost::log::sources::severity_logger_mt<
                                         boost::log::trivial::severity_level>)
    
     // just a helper macro used by the macros below - don't use it in your code
     #define LOG(severity) \
         BOOST_LOG_SEV(logger::get(), boost::log::trivial::severity)
    
     // ===== log macros =====
     #define LOG_TRACE LOG(trace)
     #define LOG_DEBUG LOG(debug)
     #define LOG_INFO LOG(info)
     #define LOG_WARNING LOG(warning)
     #define LOG_ERROR LOG(error)
     #define LOG_FATAL LOG(fatal)
    
     #endif
    
  • File simpleLogger.cpp

     #include "simpleLogger.h"
    
     #include <boost/core/null_deleter.hpp>
     #include <boost/log/core/core.hpp>
     #include <boost/log/expressions.hpp>
     #include <boost/log/expressions/formatters/char_decorator.hpp>
     #include <boost/log/expressions/formatters/date_time.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/support/date_time.hpp>
     #include <boost/log/trivial.hpp>
     #include <boost/log/utility/setup/common_attributes.hpp>
     #include <boost/make_shared.hpp>
     #include <boost/phoenix.hpp>
     #include <boost/phoenix/function.hpp>
     #include <boost/shared_ptr.hpp>
     #include <fstream>
     #include <ostream>
    
     namespace logging = boost::log;
     namespace src = boost::log::sources;
     namespace expr = boost::log::expressions;
     namespace sinks = boost::log::sinks;
     namespace attrs = boost::log::attributes;
    
     BOOST_LOG_ATTRIBUTE_KEYWORD(line_id, "LineID", unsigned int)
     BOOST_LOG_ATTRIBUTE_KEYWORD(timestamp, "TimeStamp", boost::posix_time::ptime)
     BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity",
                                 logging::trivial::severity_level)
    
     namespace /*extend locally*/ {
         template <typename F> struct Xfrm {
             constexpr Xfrm(F f) : _f(f) {}
             F _f;
    
             template <typename E> auto operator[](E fmt) const {
                 return expr::wrap_formatter(
                     [f = _f, fmt](logging::record_view const& rec,
                                   logging::formatting_ostream& strm) {
                         logging::formatting_ostream tmp;
                         std::string text;
                         tmp.attach(text);
                         fmt(rec, tmp);
    
                         strm << f(text);
                     });
             }
         };
    
         std::string reversed(std::string v) {
             std::reverse(v.begin(), v.end());
             return v;
         }
         std::string to_uppercase(std::string v) {
             for (auto& ch : v) {
                 ch = std::toupper(ch);
             }
             return v;
         }
    
         inline constexpr Xfrm UC{ to_uppercase };
         inline constexpr Xfrm REV{ reversed };
     } // namespace
    
     BOOST_LOG_GLOBAL_LOGGER_INIT(logger, src::severity_logger_mt) {
         src::severity_logger_mt<boost::log::trivial::severity_level> logger;
    
         // add attributes
         logger.add_attribute("LineID", attrs::counter<unsigned int>(
                                            1)); // lines are sequentially numbered
         logger.add_attribute(
             "TimeStamp", attrs::local_clock()); // each log line gets a timestamp
    
         // add a text sink
         using text_sink = sinks::synchronous_sink<sinks::text_ostream_backend>;
         boost::shared_ptr<text_sink> sink = boost::make_shared<text_sink>();
    
         // add a logfile stream to our sink
         sink->locked_backend()->add_stream(
             boost::make_shared<std::ofstream>(LOGFILE));
    
         // add "console" output stream to our sink
         sink->locked_backend()->add_stream(
             boost::shared_ptr<std::ostream>(&std::clog, boost::null_deleter()));
    
         // specify the format of the log message
         logging::formatter formatter =
             expr::stream
             << REV[expr::stream << std::setw(7) << std::setfill('0') << line_id]
             << " | "
             << expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S.%f") << " "
             << "[" << logging::trivial::severity << "]"
             << " - " << UC[expr::stream << expr::smessage];
    
         sink->set_formatter(formatter);
         // only messages with severity >= SEVERITY_THRESHOLD are written
         sink->set_filter(severity >= SEVERITY_THRESHOLD);
    
         // "register" our sink
         logging::core::get()->add_sink(sink);
    
         return logger;
     }
    
  • File test.cpp

     #include "simpleLogger.h"
    
     int main() {
         LOG_TRACE << "this is a trace message";
         LOG_DEBUG << "this is a debug message";
         LOG_WARNING << "this is a warning message";
         LOG_ERROR << "this is an error message";
         LOG_FATAL << "this is a fatal error message";
         return 0;
     }
    

BONUS: More Efficient

To avoid double-buffering, we can avoid using the original formatter. This works best for known attributes:

struct OddEvenId {
    void operator()(logging::record_view const& rec, logging::formatting_ostream& strm) const {
        auto vr = line_id.or_throw()(rec);
        if (!vr.empty()) {
            strm << std::setw(4) << (vr.get<uint32_t>()%2? "Odd":"Even");
        }
    }
};

struct QuotedMessage {
    void operator()(logging::record_view const& rec, logging::formatting_ostream& strm) const {
        auto vr = expr::smessage.or_throw()(rec);
        if (!vr.empty())
            strm << std::quoted(vr.get<std::string>());
    }
};

static inline auto oddeven_id = expr::wrap_formatter(OddEvenId{});
static inline auto qmessage = expr::wrap_formatter(QuotedMessage{});

Now we can simply say qmessage instead of expr::smessage to get the quoted message value:

Live On Wandbox

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

Prints

 Odd | 2020-04-28, 17:21:12.619565 [warning] - "this is a warning message"
Even | 2020-04-28, 17:21:12.619666 [error] - "this is an error message"
 Odd | 2020-04-28, 17:21:12.619684 [fatal] - "this is a fatal error message"

Upvotes: 2

Related Questions