Skip to content

Commit

Permalink
use custom namespace for bundled libfmt (#315)
Browse files Browse the repository at this point in the history
* add missing header to file

* rename libfmt namespace and macros

* use custom namespace for bundled libfmt

* use custom namespace for bundled libfmt

* fix tests
  • Loading branch information
odygrd authored Jun 24, 2023
1 parent e18443b commit c580d49
Show file tree
Hide file tree
Showing 41 changed files with 1,830 additions and 1,759 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@
- `quill::utility::to_string()` now uses `fmt::to_string()`
- Quill now utilizes a custom namespace (`fmtquill`) for the bundled fmt library. This enables smooth integration with
your own external fmt library, even if it's a different version.
## v3.0.2
- Add missing header on clang when `QUILL_X86ARCH` is defined.
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/backend_throughput/quill_backend_throughput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ int main()
auto const delta = end_time - start_time;
auto delta_d = std::chrono::duration_cast<std::chrono::duration<double>>(delta).count();

std::cout << fmt::format(
std::cout << fmtquill::format(
"Throughput is {:.2f} million msgs/sec average, total time elapsed: {} ms for {} "
"log messages \n",
total_iterations / delta_d / 1e6,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ int main()
auto const delta = end_time - start_time;
auto delta_d = std::chrono::duration_cast<std::chrono::duration<double>>(delta).count();

std::cout << fmt::format(
std::cout << fmtquill::format(
"Throughput is {:.2f} million msgs/sec average, total time elapsed: {} ms for {} "
"log messages \n",
total_iterations / delta_d / 1e6,
Expand Down
2 changes: 1 addition & 1 deletion examples/example_multiple_handlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class VectorHandler final : public quill::Handler
// Called by the logger backend worker thread
// This is not called for each LOG_* invocation like the write function, instead it is called
// periodically or when there are no more LOG_* writes left to process.
std::cout << fmt::format("VectorHandler: {}", _formatted_messages) << std::endl;
std::cout << fmtquill::format("VectorHandler: {}", _formatted_messages) << std::endl;
}

private:
Expand Down
15 changes: 12 additions & 3 deletions examples/example_user_defined_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ class User
uint32_t age;
};

template <> struct fmt::formatter<User> : ostream_formatter {};
template <>
struct fmtquill::formatter<User> : ostream_formatter
{
};

/**
* An other user defined type that is marked as safe to copy
Expand Down Expand Up @@ -53,7 +56,10 @@ class User2
uint32_t age;
};

template <> struct fmt::formatter<User2> : ostream_formatter {};
template <>
struct fmtquill::formatter<User2> : ostream_formatter
{
};

/**
* An other user defined type that is registered as safe to copy via copy_logable
Expand All @@ -76,7 +82,10 @@ class User3
uint32_t age;
};

template <> struct fmt::formatter<User3> : ostream_formatter {};
template <>
struct fmtquill::formatter<User3> : ostream_formatter
{
};

/**
* Specialise copy_loggable to register User3 object as safe to copy.
Expand Down
12 changes: 10 additions & 2 deletions quill/include/quill/Fmt.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,25 @@
#include <fmt/format.h>
#include <fmt/ostream.h>
#include <fmt/ranges.h>

#define QUILL_FMT_VERSION FMT_VERSION
#define QUILL_FMT_STRING FMT_STRING

namespace fmtquill = fmt;
#else
#include "quill/bundled/fmt/chrono.h"
#include "quill/bundled/fmt/format.h"
#include "quill/bundled/fmt/ostream.h"
#include "quill/bundled/fmt/ranges.h"

#define QUILL_FMT_VERSION FMTQUILL_VERSION
#define QUILL_FMT_STRING FMTQUILL_STRING
#endif

/**
* Also include additional files due to recent fmt versions
*/
#if FMT_VERSION > 70103
#if QUILL_FMT_VERSION > 70103
#if defined(QUILL_FMT_EXTERNAL)
#include <fmt/args.h>
#include <fmt/xchar.h>
Expand All @@ -39,7 +47,7 @@
/**
* Also include additional files due to recent fmt versions
*/
#if FMT_VERSION >= 90000
#if QUILL_FMT_VERSION >= 90000
#if defined(QUILL_FMT_EXTERNAL)
#include <fmt/std.h>
#else
Expand Down
26 changes: 13 additions & 13 deletions quill/include/quill/Logger.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,12 @@ class alignas(detail::CACHE_LINE_ALIGNED) Logger
{
assert(!_is_invalidated.load(std::memory_order_acquire) && "Invalidated loggers can not log");

#if FMT_VERSION >= 90000
static_assert(
!detail::has_fmt_stream_view_v<FmtArgs...>,
"fmt::streamed(...) is not supported. In order to make a type formattable via std::ostream "
"you should provide a formatter specialization inherited from ostream_formatter. "
"`template <> struct fmt::formatter<T> : ostream_formatter {};");
#if QUILL_FMT_VERSION >= 90000
static_assert(!detail::has_fmt_stream_view_v<FmtArgs...>,
"fmtquill::streamed(...) is not supported. In order to make a type formattable "
"via std::ostream "
"you should provide a formatter specialization inherited from ostream_formatter. "
"`template <> struct fmtquill::formatter<T> : ostream_formatter {};");
#endif

#if !defined(QUILL_MODE_UNSAFE)
Expand All @@ -129,19 +129,19 @@ class alignas(detail::CACHE_LINE_ALIGNED) Logger
else
{
// fallback to libfmt check
fmt::detail::check_format_string<std::remove_reference_t<FmtArgs>...>(format_string);
fmtquill::detail::check_format_string<std::remove_reference_t<FmtArgs>...>(format_string);
}

detail::ThreadContext* const thread_context =
_thread_context_collection.local_thread_context<QUILL_QUEUE_TYPE>();

// For windows also take wide strings into consideration.
#if defined(_WIN32)
constexpr size_t c_string_count = fmt::detail::count<detail::is_type_of_c_string<FmtArgs>()...>() +
fmt::detail::count<detail::is_type_of_wide_c_string<FmtArgs>()...>() +
fmt::detail::count<detail::is_type_of_wide_string<FmtArgs>()...>();
constexpr size_t c_string_count = fmtquill::detail::count<detail::is_type_of_c_string<FmtArgs>()...>() +
fmtquill::detail::count<detail::is_type_of_wide_c_string<FmtArgs>()...>() +
fmtquill::detail::count<detail::is_type_of_wide_string<FmtArgs>()...>();
#else
constexpr size_t c_string_count = fmt::detail::count<detail::is_type_of_c_string<FmtArgs>()...>();
constexpr size_t c_string_count = fmtquill::detail::count<detail::is_type_of_c_string<FmtArgs>()...>();
#endif

size_t c_string_sizes[(std::max)(c_string_count, static_cast<size_t>(1))];
Expand Down Expand Up @@ -237,7 +237,7 @@ class alignas(detail::CACHE_LINE_ALIGNED) Logger
} anonymous_log_message_info;

// we pass this message to the queue and also pass capacity as arg
this->template log<decltype(anonymous_log_message_info)>(FMT_STRING("{}"), capacity);
this->template log<decltype(anonymous_log_message_info)>(QUILL_FMT_STRING("{}"), capacity);

// Also store the desired flush log level
_logger_details.set_backtrace_flush_level(backtrace_flush_level);
Expand All @@ -263,7 +263,7 @@ class alignas(detail::CACHE_LINE_ALIGNED) Logger
} anonymous_log_message_info;

// we pass this message to the queue and also pass capacity as arg
this->template log<decltype(anonymous_log_message_info)>(FMT_STRING(""));
this->template log<decltype(anonymous_log_message_info)>(QUILL_FMT_STRING(""));
}

private:
Expand Down
11 changes: 6 additions & 5 deletions quill/include/quill/PatternFormatter.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,23 +130,24 @@ class PatternFormatter
template <size_t I, typename T>
void _set_arg(T const& arg)
{
_args[_order_index[I]] = fmt::detail::make_arg<fmt::format_context>(arg);
_args[_order_index[I]] = fmtquill::detail::make_arg<fmtquill::format_context>(arg);
}

template <size_t I, typename T>
QUILL_ALWAYS_INLINE_HOT void _set_arg_val(T const& arg)
{
fmt::detail::value<fmt::format_context>& value_ =
*(reinterpret_cast<fmt::detail::value<fmt::format_context>*>(std::addressof(_args[_order_index[I]])));
fmtquill::detail::value<fmtquill::format_context>& value_ =
*(reinterpret_cast<fmtquill::detail::value<fmtquill::format_context>*>(
std::addressof(_args[_order_index[I]])));

value_ = fmt::detail::arg_mapper<fmt::format_context>().map(arg);
value_ = fmtquill::detail::arg_mapper<fmtquill::format_context>().map(arg);
}

private:
std::string _format;
/** Each named argument in the format_pattern is mapped in order to this array **/
std::array<size_t, Attribute::ATTR_NR_ITEMS> _order_index{};
std::array<fmt::basic_format_arg<fmt::format_context>, Attribute::ATTR_NR_ITEMS> _args{};
std::array<fmtquill::basic_format_arg<fmtquill::format_context>, Attribute::ATTR_NR_ITEMS> _args{};
std::bitset<Attribute::ATTR_NR_ITEMS> _is_set_in_pattern;

/** class responsible for formatting the timestamp */
Expand Down
2 changes: 1 addition & 1 deletion quill/include/quill/Utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,6 @@ QUILL_NODISCARD std::string to_hex(char const* buffer, size_t size) noexcept;
template <typename T>
QUILL_NODISCARD std::string to_string(T const& obj) noexcept
{
return fmt::to_string(obj);
return fmtquill::to_string(obj);
}
} // namespace quill::utility
36 changes: 18 additions & 18 deletions quill/include/quill/bundled/fmt/args.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@
//
// For the license information refer to format.h.

#ifndef FMT_ARGS_H_
#define FMT_ARGS_H_
#ifndef FMTQUILL_ARGS_H_
#define FMTQUILL_ARGS_H_

#include <functional> // std::reference_wrapper
#include <memory> // std::unique_ptr
#include <vector>

#include "core.h"

FMT_BEGIN_NAMESPACE
FMTQUILL_BEGIN_NAMESPACE

namespace detail {

Expand All @@ -40,10 +40,10 @@ class dynamic_arg_list {
T value;

template <typename Arg>
FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {}
FMTQUILL_CONSTEXPR typed_node(const Arg& arg) : value(arg) {}

template <typename Char>
FMT_CONSTEXPR typed_node(const basic_string_view<Char>& arg)
FMTQUILL_CONSTEXPR typed_node(const basic_string_view<Char>& arg)
: value(arg.data(), arg.size()) {}
};

Expand All @@ -62,17 +62,17 @@ class dynamic_arg_list {

/**
\rst
A dynamic version of `fmt::format_arg_store`.
A dynamic version of `fmtquill::format_arg_store`.
It's equipped with a storage to potentially temporary objects which lifetimes
could be shorter than the format arguments object.
It can be implicitly converted into `~fmt::basic_format_args` for passing
into type-erased formatting functions such as `~fmt::vformat`.
It can be implicitly converted into `~fmtquill::basic_format_args` for passing
into type-erased formatting functions such as `~fmtquill::vformat`.
\endrst
*/
template <typename Context>
class dynamic_format_arg_store
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
#if FMTQUILL_GCC_VERSION && FMTQUILL_GCC_VERSION < 409
// Workaround a GCC template argument substitution bug.
: public basic_format_args<Context>
#endif
Expand Down Expand Up @@ -155,11 +155,11 @@ class dynamic_format_arg_store
**Example**::
fmt::dynamic_format_arg_store<fmt::format_context> store;
fmtquill::dynamic_format_arg_store<fmtquill::format_context> store;
store.push_back(42);
store.push_back("abc");
store.push_back(1.5f);
std::string result = fmt::vformat("{} and {} and {}", store);
std::string result = fmtquill::vformat("{} and {} and {}", store);
\endrst
*/
template <typename T> void push_back(const T& arg) {
Expand All @@ -176,11 +176,11 @@ class dynamic_format_arg_store
**Example**::
fmt::dynamic_format_arg_store<fmt::format_context> store;
fmtquill::dynamic_format_arg_store<fmtquill::format_context> store;
char band[] = "Rolling Stones";
store.push_back(std::cref(band));
band[9] = 'c'; // Changing str affects the output.
std::string result = fmt::vformat("{}", store);
std::string result = fmtquill::vformat("{}", store);
// result == "Rolling Scones"
\endrst
*/
Expand All @@ -202,9 +202,9 @@ class dynamic_format_arg_store
dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
if (detail::const_check(need_copy<T>::value)) {
emplace_arg(
fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)));
fmtquill::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)));
} else {
emplace_arg(fmt::arg(arg_name, arg.value));
emplace_arg(fmtquill::arg(arg_name, arg.value));
}
}

Expand All @@ -222,13 +222,13 @@ class dynamic_format_arg_store
\endrst
*/
void reserve(size_t new_cap, size_t new_cap_named) {
FMT_ASSERT(new_cap >= new_cap_named,
FMTQUILL_ASSERT(new_cap >= new_cap_named,
"Set of arguments includes set of named arguments");
data_.reserve(new_cap);
named_info_.reserve(new_cap_named);
}
};

FMT_END_NAMESPACE
FMTQUILL_END_NAMESPACE

#endif // FMT_ARGS_H_
#endif // FMTQUILL_ARGS_H_
Loading

5 comments on commit c580d49

@GJanos
Copy link

@GJanos GJanos commented on c580d49 Jul 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dear @odygrd !
I am working on a logging library in C++, I built my implementation upon Glaze C++ logging library, that uses the Format library. I had a working code, but I think, after refreshing my cmakelists file, which has glaze, and quill (quill has fmt) as fetchcontent, my quill and fmt related macros stopped working. I checked the repositories commit history, and seen some changes inside the quill library that might affected something on my end. I am talking about the last 5 commits, or around that. I am saying this because I downloaded one of my older working commits from git, and was not able to run that either.

LOG_INFO(logger_bar, "{:f}",Trade_data(1, "BTC", 25432.23, 1.23, "C"));

This is the line that gives the errors. For my own type to be loggable I followed quills and fmts instructions and made changes accordingly (glz => copyable object, fmt => specialized formatter):

#include "log_data_info.h"

#include <glaze/glaze.hpp>
#include <iostream>
#include <utility>
#include "quill/Quill.h"
#include "quill/Utility.h"
#include <fmt/core.h>


class Trade_data {
public:
    Trade_data() = default;

    Trade_data(int id,
               std::string name,
               double price,
               double quantity,
               std::string exchange_name);

    Trade_data(const Trade_data& other);
    Trade_data& operator=(const Trade_data& other);
    Trade_data(Trade_data&& other) noexcept;
    Trade_data& operator=(Trade_data&& other) noexcept;

    static std::string serialize_best(const Trade_data &trade_data);
    static Trade_data deserialize_best(const std::string &trade_data_str);

    static Trade_data create_rand_trade_data();
    Trade_data create_price_movements();

public:
    int id;
    std::string name;
    double price;
    double quantity;
    std::string exchange_name;
};

// to be able to serialize Trade_data with glaze
template<>
struct glz::meta<Trade_data> {
    using T = Trade_data;
    static constexpr auto value = glz::object(
            "id", &T::id,
            "name", &T::name,
            "price", &T::price,
            "quantity", &T::quantity,
            "exchange_name", &T::exchange_name);
};

// to be able to log Trade_data with quill
namespace quill {
    template<>
    struct copy_loggable<Trade_data> : std::true_type {
    };
}

template<>
struct fmt::formatter<Trade_data> {
    char presentation = 'f';

    constexpr auto parse(format_parse_context &ctx) -> format_parse_context::iterator {
        auto it = ctx.begin(), end = ctx.end();
        if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++;
        return it;
    }

    auto format(const Trade_data &trade_data, format_context &ctx) const -> format_context::iterator {
        return fmt::format_to(ctx.out(),
                              "{}{}{}",
                              typeid(Trade_data).name(),
                              TYPE_SEPARATOR,
                              glz::write_json(trade_data));
    }
};

This alone, was enough for me previously to log my own Trade_data objects without any problems, but right now I get a lot of errors.
bundled/fmt/core.h related problems and use of deleted function ‘fmtquill::v10::formatter<T, Char, Enable>::formatter() [with T = Trade_data; Char = char; Enable = void]’
2529 | return formatter<mapped_type, char_type>().parse(ctx);
bundled/fmt/core.h:1071:3: note: declared here
1071 | formatter() = delete;
bundled/fmt/core.h:2638:27: error: ‘constexpr’ call flows off the end of the function
2638 | FMTQUILL_CONSTEXPR bool error = (parse_format_string(s, checker(s)), true);
bundled/fmt/core.h:1566:7: error: static assertion failed: Cannot format an argument. To make type T formattable provide a formatter specialization: https://fmt.dev/latest/api.html#udt
1566 | formattable,
| ^~~~~~~~~~~

Could these errors be caused be my local and quill librarys fmt library installation and dependecy?
Thank you for your time!

@odygrd
Copy link
Owner Author

@odygrd odygrd commented on c580d49 Jul 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bundled fmt library namespace got renamed. It used to work fine when your installed fmt library was the same version as the bundled one. But if they are different versions it could previously cause you problems.

when using the bundled one you now have to use
struct fmtquill::formatter for the logger to see it

struct fmtquill::formatter<User> : ostream_formatter

if you’re using quill’s bundled fmt across your whole project you need to use namespace fmtquill or create a namespace alias
namespace fmt = fmtquill
The other option if you don’t wish to rename those is to build using your system installed fmt library if you have one

// #define QUILL_FMT_EXTERNAL

option(QUILL_FMT_EXTERNAL "Use external fmt library instead of bundled" OFF)

@GJanos
Copy link

@GJanos GJanos commented on c580d49 Jul 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dear @odygrd !
I have done the first thing that you suggested. I modified my code, added the fmtquill::formatter and an overloaded << operator as well:

#ifndef TRADE_DATA_H
#define TRADE_DATA_H

#include "log_data_info.h"

#include <glaze/glaze.hpp>
#include <iostream>
#include <utility>
#include "quill/Quill.h"
#include "quill/Utility.h"
#include <fmt/core.h>


class Trade_data {
public:
    Trade_data() = default;

    Trade_data(int id,
               std::string name,
               double price,
               double quantity,
               std::string exchange_name);

    Trade_data(const Trade_data& other);
    Trade_data& operator=(const Trade_data& other);
    Trade_data(Trade_data&& other) noexcept;
    Trade_data& operator=(Trade_data&& other) noexcept;

    static std::string serialize_best(const Trade_data &trade_data);
    static Trade_data deserialize_best(const std::string &trade_data_str);
    //new
    friend std::ostream& operator<<(std::ostream& os, Trade_data const& trade_data)
    {
        os << typeid(Trade_data).name() << TYPE_SEPARATOR << glz::write_json(trade_data);
        return os;
    }

    static Trade_data create_rand_trade_data();
    Trade_data create_price_movements();

public:
    int id;
    std::string name;
    double price;
    double quantity;
    std::string exchange_name;
};

// to be able to serialize Trade_data with glaze
template<>
struct glz::meta<Trade_data> {
    using T = Trade_data;
    static constexpr auto value = glz::object(
            "id", &T::id,
            "name", &T::name,
            "price", &T::price,
            "quantity", &T::quantity,
            "exchange_name", &T::exchange_name);
};

// to be able to log Trade_data with quill
namespace quill {
    template<>
    struct copy_loggable<Trade_data> : std::true_type {
    };
}
//new
template <>
struct fmtquill::formatter<Trade_data> : ostream_formatter
{
};

// I don't know if it is still needed, probably not.
template<>
struct fmt::formatter<Trade_data> {
    char presentation = 'f';

    constexpr auto parse(format_parse_context &ctx) -> format_parse_context::iterator {
        auto it = ctx.begin(), end = ctx.end();
        if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++;
        return it;
    }

    auto format(const Trade_data &trade_data, format_context &ctx) const -> format_context::iterator {
        return fmt::format_to(ctx.out(),
                              "{}{}{}",
                              typeid(Trade_data).name(),
                              TYPE_SEPARATOR,
                              glz::write_json(trade_data));
    }
};

#endif // TRADE_DATA_H

With this, I am able to log Trade_data type objects. But I had to change the Trade_data classes code for it to work, by overriding the << operator. If I would like to be able to log not user defined types, how could I achive that without changing those underlying classes?

Previously providing a fmt::formatter for them as well was enough, but I dont know what to implement override in the fmtquill::formatter case.

#include <glaze/glaze.hpp>
#include <string_view>
#include <boost/json.hpp>

///glz::obj loggable requirements
template<typename... Args>
struct quill::copy_loggable<glz::obj<Args...>> : std::true_type {
};

template<typename... Args>
struct fmtquill::formatter<glz::obj<Args...>> : ostream_formatter
{
};

// This is also probably not needed anymore.
template<typename... Args>
struct fmt::formatter<glz::obj<Args...>> {
    char presentation = 'f';

    constexpr auto parse(format_parse_context &ctx) -> format_parse_context::iterator {
        auto it = ctx.begin(), end = ctx.end();
        if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++;
        return it;
    }

    auto format(glz::obj<Args...> const &obj, format_context &ctx) const -> format_context::iterator {
        //get_container(*ctx.out()).clear();
        return fmt::format_to(ctx.out(), "{}", glz::write_json(obj));
    }
};

But when I want to log a glz::obj type, these are the error messages:

LOG_INFO(logger_bar, "{}", glz::obj{"pi", 3.141, "happy", true});

quillMongo/main.cpp:48:9: required from here
quillMongo/cmake-build-debug/_deps/quill-src/quill/include/quill/bundled/fmt/ostream.h:97:10: error: no match for ‘operator<<’
97 | output << value;
| ~~~~~~~^~~~~~~~
Is there any other way to log user defined types other, than overriding the << operator ? Because if I needed any private members of those classes I would not be able to access them.

After defining an overloaded << operator it works:

template<typename... Args>
std::ostream& operator<<(std::ostream& os, const glz::obj<Args...>& obj) {
    os << glz::write_json(obj);
    return os;
}

And by writing just the "{}" signs LOG_INFO(logger_bar, "{}", Trade_data(...)); logging of user defined data would work?

@odygrd
Copy link
Owner Author

@odygrd odygrd commented on c580d49 Jul 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't want to overload operator<< then you don't need to define

template<typename... Args>
struct fmtquill::formatter<glz::obj<Args...>> : ostream_formatter
{
};

because the reason you define this is to enable formatting via operator<<

You can instead use your own fmt formatter for example if you are using the bundled fmtquill :

class User4
{
public:
  User4(std::string name, std::string surname, uint32_t age)
    : name(std::move(name)), surname(std::move(surname)), age(age){};

  friend struct fmtquill::formatter<User4>;
private:
  std::string name;
  std::string surname;
  uint32_t age;
};

template <>
struct fmtquill::formatter<User4>
{
  template <typename FormatContext>
  auto parse(FormatContext& ctx)
  {
    return ctx.begin();
  }

  template <typename FormatContext>
  auto format(User4 const& user, FormatContext& ctx)
  {
    return fmtquill::format_to(ctx.out(), "User: {} {}, Age: {}", user.name, user.surname, user.age);
  }
};

template <>
struct quill::copy_loggable<User4> : std::true_type
{
};

int main()
{
  quill::start();

  User4 user{"Super", "User", 42};
  LOG_INFO(quill::get_logger(), "The user is {}", user);
}

Also see

  1. https://fmt.dev/latest/api.html#std-ostream-support
  2. https://fmt.dev/latest/api.html#formatting-user-defined-types

Everything is exactly the same as in the above documentation, the only difference is that you need to use namespace fmtquill instead of fmt when you are using the bundled fmt library

Let me know if that works for you

@GJanos
Copy link

@GJanos GJanos commented on c580d49 Jul 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dear @odygrd !
I tried out your solution and it worked! Thank you for your help with figuring out this issue.

Please sign in to comment.