gabime / spdlog

Fast C++ logging library.
Other
24.3k stars 4.54k forks source link

JSON custom formatter #2861

Closed maordadush closed 1 year ago

maordadush commented 1 year ago
          > > In case anyone else is searching for this, here's what I'm using. I wrote a custom formatter to help, that looks like this (requires C++17):
namespace {
        template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
    template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
}

struct SimpleJSON
{
    using json_val = std::variant<std::int64_t, int, double, std::string, bool>;
    std::unordered_map<std::string, json_val> members;

    SimpleJSON(std::initializer_list<std::pair<const std::string, json_val>> il) : members{il} {}

    template<typename OStream>
    friend OStream &operator<<(OStream &os, const SimpleJSON &j)
    {
        for (const auto &kv : j.members) {
            os << ", " << std::quoted(kv.first) << ":";
            std::visit(overloaded {
                [&](std::int64_t arg) { os << arg; },
                [&](int arg) { os << arg; },
                [&](double arg) { os << arg; },
                [&](const std::string& arg) { os << std::quoted(arg); },
                [&](bool arg) { os << (arg ? "true" : "false"); }
            }, kv.second);
        }
        return os;
    }
};

Then the usage looks like this:

using J = SimpleJSON;
spdlog::set_pattern(
                "{\"timestamp\":\"%Y-%m-%dT%H:%M:%S.%e%z\",\"logger\":\"%n\",\"log_"
                "level\":\"%l\",\"process_id\":%P,\"thread_id\":%t %v}");

spdlog::info("{}", J({{"key1","value1"},{"key2",true},{"key3",99}}));

Which results in single lines that look like:

{"timestamp":"2022-01-14T09:00:00Z","logger":"1","log_level":"info","process_id":123,"thread_id":123, "key1":"value1","key2":true,"key3":99}

It doesn't handle nested JSON, but with a bit more template magic, it probably could. My needs are just for various key/value pairs in JSON format, so this works for me. I'm happy about the pretty compact syntax on the actual log lines.

in newer versions, make sure to include:

#include "spdlog/fmt/ostr.h"

otherwise there'll be fmt compilation errors due to requiring a formatter

I'm still getting a fmt compilation errors due to requiring a formatter

Originally posted by @maordadush in https://github.com/gabime/spdlog/issues/1797#issuecomment-1686064127

tt4g commented 1 year ago

Neither compile errors nor source code are provided, so we have no idea what we are dealing with.

maordadush commented 1 year ago

oh sorry, I'm trying to set up JSON logging with spdlog, so I'm using the following custom formatter

#include <spdlog/spdlog.h>
#include <variant>
#include <iomanip>
#include <spdlog/fmt/ostr.h>

namespace {
        template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
    template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
}

struct SimpleJSON
{
    using json_val = std::variant<std::int64_t, int, double, std::string, bool>;
    std::unordered_map<std::string, json_val> members;

    SimpleJSON(std::initializer_list<std::pair<const std::string, json_val>> il) : members{il} {}

    template<typename OStream>
    friend OStream &operator<<(OStream &os, const SimpleJSON &j)
    {
        for (const auto &kv : j.members) {
            os << ", " << std::quoted(kv.first) << ":";
            std::visit(overloaded {
                [&](std::int64_t arg) { os << arg; },
                [&](int arg) { os << arg; },
                [&](double arg) { os << arg; },
                [&](const std::string& arg) { os << std::quoted(arg); },
                [&](bool arg) { os << (arg ? "true" : "false"); }
            }, kv.second);
        }
        return os;
    }
};

Then the usage looks like this:

using J = SimpleJSON;
spdlog::set_pattern(
                "{\"timestamp\":\"%Y-%m-%dT%H:%M:%S.%e%z\",\"logger\":\"%n\",\"log_"
                "level\":\"%l\",\"process_id\":%P,\"thread_id\":%t %v}");

spdlog::info("{}", J({{"key1","value1"},{"key2",true},{"key3",99}}));

but I got an fmt compilation error due to requiring a formatter

In file included from /usr/local/include/spdlog/fmt/bundled/format.h:48,
                 from /usr/local/include/spdlog/fmt/bundled/ranges.h:19,
                 from /home/user/test/src/common/log.h:3,
                 from /home/user/test/src/log.cpp:1:
/usr/local/include/spdlog/fmt/bundled/core.h: In instantiation of ‘constexpr fmt::v9::detail::value<Context> fmt::v9::detail::make_value(T&&) [with Context = fmt::v9::basic_format_context<fmt::v9::appender, char>; T = logging::SimpleJSON&]’:
/usr/local/include/spdlog/fmt/bundled/core.h:1777:29:   required from ‘constexpr fmt::v9::detail::value<Context> fmt::v9::detail::make_arg(T&&) [with bool IS_PACKED = true; Context = fmt::v9::basic_format_context<fmt::v9::appender, char>; fmt::v9::detail::type <anonymous> = fmt::v9::detail::type::custom_type; T = logging::SimpleJSON&; typename std::enable_if<IS_PACKED, int>::type <anonymous> = 0]’
/usr/local/include/spdlog/fmt/bundled/core.h:1901:77:   required from ‘constexpr fmt::v9::format_arg_store<Context, Args>::format_arg_store(T&& ...) [with T = {logging::SimpleJSON&}; Context = fmt::v9::basic_format_context<fmt::v9::appender, char>; Args = {logging::SimpleJSON}]’
/usr/local/include/spdlog/fmt/bundled/core.h:1918:31:   required from ‘constexpr fmt::v9::format_arg_store<Context, typename std::remove_cv<typename std::remove_reference<Args>::type>::type ...> fmt::v9::make_format_args(Args&& ...) [with Context = fmt::v9::basic_format_context<fmt::v9::appender, char>; Args = {logging::SimpleJSON&}]’
/usr/local/include/spdlog/logger.h:374:75:   required from ‘void spdlog::logger::log_(spdlog::source_loc, spdlog::level::level_enum, spdlog::string_view_t, Args&& ...) [with Args = {logging::SimpleJSON}; spdlog::string_view_t = fmt::v9::basic_string_view<char>]’
/usr/local/include/spdlog/logger.h:90:13:   required from ‘void spdlog::logger::log(spdlog::source_loc, spdlog::level::level_enum, fmt::v9::format_string<T ...>, Args&& ...) [with Args = {logging::SimpleJSON}; fmt::v9::format_string<T ...> = fmt::v9::basic_format_string<char, logging::SimpleJSON>]’
/usr/local/include/spdlog/logger.h:96:12:   required from ‘void spdlog::logger::log(spdlog::level::level_enum, fmt::v9::format_string<T ...>, Args&& ...) [with Args = {logging::SimpleJSON}; fmt::v9::format_string<T ...> = fmt::v9::basic_format_string<char, logging::SimpleJSON>]’
/usr/local/include/spdlog/logger.h:158:12:   required from ‘void spdlog::logger::info(fmt::v9::format_string<T ...>, Args&& ...) [with Args = {logging::SimpleJSON}; fmt::v9::format_string<T ...> = fmt::v9::basic_format_string<char, logging::SimpleJSON>]’
/usr/local/include/spdlog/spdlog.h:170:31:   required from ‘void spdlog::info(fmt::v9::format_string<T ...>, Args&& ...) [with Args = {logging::SimpleJSON}; fmt::v9::format_string<T ...> = fmt::v9::basic_format_string<char, logging::SimpleJSON>]’
/home/david/rdp-proxy/src/log.cpp:116:25:   required from here
/usr/local/include/spdlog/fmt/bundled/core.h:1757:7: error: static assertion failed: Cannot format an argument. To make type T formattable provide a formatter<T> specialization: https://fmt.dev/latest/api.html#udt
 1757 |       formattable,
      |       ^~~~~~~~~~~
/usr/local/include/spdlog/fmt/bundled/core.h:1757:7: note: ‘formattable’ evaluates to false

Thanks!

tt4g commented 1 year ago

You need to define a fmt::ostream<SimpleJSON> specialization that extends fmt::ostream_formatter.

https://github.com/fmtlib/fmt/blob/9.0.0/ChangeLog.rst#900---2022-07-04

Disabled automatic std::ostream insertion operator (operator<<) discovery when fmt/ostream.h is included to prevent ODR violations. You can get the old behavior by defining FMT_DEPRECATED_OSTREAM but this will be removed in the next major release. Use fmt::streamed or fmt::ostream_formatter to enable formatting via std::ostream instead.

See: https://fmt.dev/9.1.0/api.html#std-ostream-support


template <> struct fmt::ostream<SimpleJSON> : fmt::ostream_formatter {}
}

Another way: https://fmt.dev/9.1.0/api.html#formatting-user-defined-types
maordadush commented 1 year ago

Thanks!