1

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

  • File simpleLogger.cpp

    #include "simpleLogger.h"
    
    #include <boost/log/core/core.hpp>
    #include <boost/log/expressions/formatters/date_time.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/support/date_time.hpp>
    #include <boost/log/trivial.hpp>
    #include <boost/core/null_deleter.hpp>
    #include <boost/log/utility/setup/common_attributes.hpp>
    #include <boost/make_shared.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)
    
    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
        typedef sinks::synchronous_sink<sinks::text_ostream_backend> text_sink;
        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
            << std::setw(7) << std::setfill('0') << line_id << std::setfill(' ') << " | "
            << expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S.%f") << " "
            << "[" << logging::trivial::severity << "]"
            << " - " << 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 simpleLogger.h

    #ifndef simpleLogger_h__
    #define simpleLogger_h__
    
    #define BOOST_LOG_DYN_LINK // necessary when linking the boost_log library dynamically
    
    #include <boost/log/trivial.hpp>
    #include <boost/log/sources/global_logger_storage.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 app.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;
    }
    

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

sehe
  • 374,641
  • 47
  • 450
  • 633
Danny
  • 31
  • 2

1 Answers1

2

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"
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Added a **BONUS** take that is more efficient if you want to operate on specific attributes. A more full demo here: https://wandbox.org/permlink/8S661XoTb8qXKkLZ – sehe Apr 28 '20 at 15:24
  • Symbols in the `aux` namespace are implementation details. I wouldn't recommend using those, so probably not the best idea to use them in the answer. – Andrey Semashev Jul 29 '20 at 22:01
  • @AndreySemashev thanks for letting me know. I'll leave the answer as really I've spent 100x too much time on it anyways for precisely no purpose, because no one will find it on their own (the Q is <0). (_I bet you also found it because I linked to it from my recent answer :)_) – sehe Jul 29 '20 at 22:37
  • Oh. Ah, I was misreading what part was from `aux` earlier. Is there a better way to get the default attribute names? I think I've looked for it back then. – sehe Jul 29 '20 at 22:48
  • 1
    You could use `log::expressions::tag::message::get_name()`, but really the better solution is just use the `log::expressions::message` and other keywords to begin with. – Andrey Semashev Jul 29 '20 at 23:53
  • @AndreySemashev Phew. 38 minutes. As far as I can tell none of this is documented or very discoverable :| I borrowed from an understanding of how Proto is usually used, and made some leaps of faith, basically stumbling on the ways to use the keywords functionally with `record_view. It does sit better with me now, but I'm not sure how I'd have found it without your hint that the keywords are useable outside their intended proto-expressions :). Answer improved though! – sehe Jul 30 '20 at 00:35
  • The use of keywords with records is described here (https://www.boost.org/doc/libs/1_73_0/libs/log/doc/html/log/detailed.html#log.detailed.core.record) and here (https://www.boost.org/doc/libs/1_73_0/libs/log/doc/html/log/detailed/expressions.html#log.detailed.expressions.attr_keywords). The same works with record views. So, what I meant was using `rec[expr::smessage]` syntax to extract values without having to specify either the name or value type of the attribute. The way you did it works, but I wouldn't say it is the intended way. – Andrey Semashev Jul 30 '20 at 06:57
  • @AndreySemashev Oh my. Well. I stand corrected. When first learning the library and reading the docs the myriad of new concepts makes it so that I could never absorb these nuggets. Mind you, not complaining about it (much) more musing about the inherent difficulties of understanding highly generic/flexible libraries. Thanks for pointing me to the right bits! We need more "how it's intended" bits, the tutorials and samples seem to focus on the setup/formatter config angle of things, which is good for 80% of use cases of course. – sehe Jul 30 '20 at 10:47