如何将 boost::log::expressions::smessage 传递给 nlohmann::json 构造函数?

How to pass boost::log::expressions::smessage to nlohmann::json constructor?

我有一个类似这样的代码:
const auto jsonFormatter = boost::log::expressions::stream << boost::log::expressions::smessage;

我想使用 nlohmann::json 转义消息,例如:
nlohmann::json json{boost::log::expressions::smessage};

我可以执行以下操作将 boost::log::expressions::smessage 转换为 std::string:

std::stringstream ss;
ss << boost::log::expressions::smessage;
std::string message = ss.str();
nlohmann::json json{message};

,但我需要将它放在格式化程序中,因为
const auto jsonFormatter = boost::log::expressions::stream << nlohmann::json{boost::log::expressions::smessage}; 无法将 boost::log::expressions::smessage 参数转换为任何 nlohmann::json 构造函数。

有什么让它发挥作用的建议吗?

日志格式化程序看起来像普通的 C++,但表达式模板组成了执行相应操作的延迟调用。

下面是如何创建一个知道如何执行此操作的包装器表达式:

namespace {
    struct as_json_t {
        template <typename E> auto operator[](E fmt) const {
            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);

                    strm << nlohmann::json{text};
                });
        }
    };

    inline constexpr as_json_t as_json;
} // namespace

现在您可以制作格式化程序,例如

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

结果是例如

2021-01-15, 23:34:08.489173 error - ["this is an error message"]

现场演示

Live On Wandbox

  • 文件simpleLogger.h

     #ifndef _HOME_SEHE_PROJECTS_Whosebug_SIMPLELOGGER_H
     #define _HOME_SEHE_PROJECTS_Whosebug_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
    
  • 文件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 <nlohmann/json.hpp>
     #include <fstream>
    
     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(timestamp, "TimeStamp", boost::posix_time::ptime)
     BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", logging::trivial::severity_level)
    
     namespace {
         struct as_json_t {
             template <typename E> auto operator[](E fmt) const {
                 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);
    
                         strm << nlohmann::json{text};
                     });
             }
         };
    
         inline constexpr as_json_t as_json;
     } // 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("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
             << expr::format_date_time(timestamp, "%Y-%m-%d, %H:%M:%S.%f") << " "
             << logging::trivial::severity
             << " - " << as_json[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;
     }
    
  • 文件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;
     }
    

版画

2021-01-15, 23:50:03.130250 warning - ["this is a warning message"]
2021-01-15, 23:50:03.130327 error - ["this is an error message"]
2021-01-15, 23:50:03.130354 fatal - ["this is a fatal error message"]

除了 sehe 的答案之外,您还可以使用 Boost.Log 组件实现类似 JSON 的格式。本质部分是 c_decor character decorator,它确保其输出可以用作 C 风格的字符串文字。

namespace expr = boost::log::expressions;

const auto jsonFormatter =
    expr::stream << "[\""
       << expr::c_decor[ expr::stream << expr::smessage ]
       << "\"]";

首先,c_decor 会将消息中的任何控制字符转义为 C 样式的转义序列,如 \n、\t。它还将转义双引号字符。然后,加上括号和双引号使输出兼容 JSON 格式。

如果您的日志消息中有非 ASCII 字符,并且希望格式化的日志记录严格为 ASCII,则可以使用 c_ascii_decor 而不是 c_decor。除了 c_decor 所做的之外,它还会将任何大于 127 的字节转义为它们的十六进制转义序列,例如\x8c.

我非常感谢帮助和其他答案,这在其他情况下可能会更好,但长话短说,我尝试了很多解决方案并最终选择了这个(如果可以的话我可以更新它改进):

#include <boost/phoenix/bind/bind_function.hpp>
..
nlohmann::json EscapeMessage(
    boost::log::value_ref<std::string, boost::log::expressions::tag::smessage> const& message)
{
    return message ? nlohmann::json(message.get()) : nlohmann::json();
}
..
const auto jsonFormatter = boost::log::expressions::stream << boost::phoenix::bind(&EscapeMessage, boost::log::expressions::smessage.or_none())
boost::log::add_console_log(std::cout, boost::log::keywords::format = jsonFormatter);