Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON custom formatter #2861

Closed
maordadush opened this issue Aug 21, 2023 · 4 comments
Closed

JSON custom formatter #2861

maordadush opened this issue Aug 21, 2023 · 4 comments

Comments

@maordadush
Copy link

          > > 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 #1797 (comment)

@tt4g
Copy link
Contributor

tt4g commented Aug 21, 2023

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

@maordadush
Copy link
Author

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
Copy link
Contributor

tt4g commented Aug 21, 2023

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
Copy link
Author

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants