gabime / spdlog

Fast C++ logging library.
Other
24.37k stars 4.55k forks source link

Formatting Custom Objects Example Doesn't work #3097

Closed cuppajoeman closed 5 months ago

cuppajoeman commented 5 months ago

In the quickstart page 1, we have:

#include "spdlog/spdlog.h"
#include "spdlog/fmt/ostr.h" // must be included
#include "spdlog/sinks/stdout_sinks.h"

class some_class {};
std::ostream& operator<<(std::ostream& os, const some_class& c)
{ 
    return os << "some_class"; 
}

void custom_class_example()
{
    some_class c;
    auto console = spdlog::stdout_logger_mt("console");
    console->info("custom class with operator<<: {}..", c);
}

building this example yields the following error:

cdIn file included from /home/ccn/temp/spdlog/external_libraries/spdlog/include/spdlog/fmt/bundled/format.h:49,
                 from /home/ccn/temp/spdlog/external_libraries/spdlog/include/spdlog/fmt/bundled/ostream.h:21,
                 from /home/ccn/temp/spdlog/external_libraries/spdlog/include/spdlog/fmt/ostr.h:19,
                 from /home/ccn/temp/spdlog/main.cpp:1:
/home/ccn/temp/spdlog/external_libraries/spdlog/include/spdlog/fmt/bundled/core.h: In instantiation of ‘fmt::v10::detail::value<Context> fmt::v10::detail::make_arg(T&) [with bool PACKED = true; Context = fmt::v10::basic_format_context<fmt::v10::appender, char>; T = some_class; typename std::enable_if<PACKED, int>::type <anonymous> = 0]’:
/home/ccn/temp/spdlog/external_libraries/spdlog/include/spdlog/fmt/bundled/core.h:1842:51:   required from ‘fmt::v10::format_arg_store<Context, Args>::format_arg_store(T& ...) [with T = {some_class}; Context = fmt::v10::basic_format_context<fmt::v10::appender, char>; Args = {some_class}]’
/home/ccn/temp/spdlog/external_libraries/spdlog/include/spdlog/fmt/bundled/core.h:1860:18:   required from ‘constexpr fmt::v10::format_arg_store<Context, typename std::remove_cv<typename std::remove_reference<T>::type>::type ...> fmt::v10::make_format_args(T& ...) [with Context = basic_format_context<appender, char>; T = {some_class}]’
/home/ccn/temp/spdlog/external_libraries/spdlog/include/spdlog/logger.h:328:75:   required from ‘void spdlog::logger::log_(spdlog::source_loc, spdlog::level::level_enum, spdlog::string_view_t, Args&& ...) [with Args = {some_class&}; spdlog::string_view_t = fmt::v10::basic_string_view<char>]’
/home/ccn/temp/spdlog/external_libraries/spdlog/include/spdlog/logger.h:80:13:   required from ‘void spdlog::logger::log(spdlog::source_loc, spdlog::level::level_enum, fmt::v10::format_string<T ...>, Args&& ...) [with Args = {some_class&}; fmt::v10::format_string<T ...> = fmt::v10::basic_format_string<char, some_class&>]’
/home/ccn/temp/spdlog/external_libraries/spdlog/include/spdlog/logger.h:85:12:   required from ‘void spdlog::logger::log(spdlog::level::level_enum, fmt::v10::format_string<T ...>, Args&& ...) [with Args = {some_class&}; fmt::v10::format_string<T ...> = fmt::v10::basic_format_string<char, some_class&>]’
/home/ccn/temp/spdlog/external_libraries/spdlog/include/spdlog/logger.h:140:12:   required from ‘void spdlog::logger::info(fmt::v10::format_string<T ...>, Args&& ...) [with Args = {some_class&}; fmt::v10::format_string<T ...> = fmt::v10::basic_format_string<char, some_class&>]’
/home/ccn/temp/spdlog/main.cpp:13:16:   required from here
/home/ccn/temp/spdlog/external_libraries/spdlog/include/spdlog/fmt/bundled/core.h:1604: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
 1604 |       formattable,
      |       ^~~~~~~~~~~
/home/ccn/temp/spdlog/external_libraries/spdlog/include/spdlog/fmt/bundled/core.h:1604:7: note: ‘formattable’ evaluates to false
 make[2]: *** [CMakeFiles/MyProject.dir/build.make:76: CMakeFiles/MyProject.dir/main.cpp.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:100: CMakeFiles/MyProject.dir/all] Error 2
make: *** [Makefile:91: all] Error 2
tt4g commented 5 months ago

Have you tried another sample?

#include <iterator>

#include "spdlog/spdlog.h"
#include "spdlog/fmt/ostr.h" // must be included
#include "spdlog/sinks/stdout_sinks.h"

class some_class {
    int code;
};

template<typename OStream>
friend OStream &operator<<(OStream &os, const some_class& to_log)
{
    fmt::format_to(std::ostream_iterator<char>(os), "{:04X}", to_log.code);
    return os;
}

void custom_class_example()
{
    some_class c; c.code = 17;
    auto console = spdlog::stdout_logger_mt("console");
    console->info("custom class with operator<< using fmt: {}..", c);
}

Also, did you check the link to the official fmt documentation (https://fmt.dev/latest/api.html#udt) in the error message?

cuppajoeman commented 5 months ago

Have you tried another sample?

#include <iterator>

#include "spdlog/spdlog.h"
#include "spdlog/fmt/ostr.h" // must be included
#include "spdlog/sinks/stdout_sinks.h"

class some_class {
    int code;
};

template<typename OStream>
friend OStream &operator<<(OStream &os, const some_class& to_log)
{
    fmt::format_to(std::ostream_iterator<char>(os), "{:04X}", to_log.code);
    return os;
}

void custom_class_example()
{
    some_class c; c.code = 17;
    auto console = spdlog::stdout_logger_mt("console");
    console->info("custom class with operator<< using fmt: {}..", c);
}

Also, did you check the link to the official fmt documentation (https://fmt.dev/latest/api.html#udt) in the error message?

I tried the example you provided which also errors out the same way. I'm using

CMakeLists.txt:

cmake_minimum_required(VERSION 3.10)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # allows for language servers to understand the codebase
set(CMAKE_LINKER_TYPE MOLD) # use a modern linker
project(MyProject)

# Specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# Add spdlog directory
add_subdirectory(external_libraries/spdlog)
# include_directories(external_libraries/spdlog/include)

# Add executable
add_executable(MyProject main.cpp)

# Link spdlog
target_link_libraries(MyProject PRIVATE spdlog::spdlog)

I looked at the fmt documentation. Note that my question issue is in relation to the first example which has the following language:

image

Due to the usage of "or" here I assumed that the first example wouldn't have to do anything much with fmt.

Either way let me know if you see any incorrect usage of spdlog or if this is an actual problem.

tt4g commented 5 months ago

I noticed that this is a breaking change in fmt version 10 (fmtlib/fmt#3318). The Wiki example seems to be out of date.

The following changes should work, please try it.

 #include "spdlog/spdlog.h"
 #include "spdlog/fmt/ostr.h" // must be included
 #include "spdlog/sinks/stdout_sinks.h"

 class some_class {};
 std::ostream& operator<<(std::ostream& os, const some_class& c)
 { 
     return os << "some_class"; 
 }
+
+template <> struct fmt::formatter<some_class> : fmt::ostream_formatter {};

 void custom_class_example()
 {
     some_class c;
     auto console = spdlog::stdout_logger_mt("console");
     console->info("custom class with operator<<: {}..", c);
 }
ChangXiaoxin commented 5 months ago

+template <> struct fmt::formatter<some_class> : fmt::ostream_formatter {};

Hi, I got the same compile issue, and fixed with this change in my case, thank you so much!

cuppajoeman commented 5 months ago

Just writing this to confirm that the newest information in the docs fixes it

harryjwright commented 1 week ago

Can I create a template out of the, uh, template fix?

I assumed

template <typename T> struct formatter<T>: ostream_formatter{};

would work, but it doesn't. I'm sick of having to append it to every custom class.

tt4g commented 1 week ago

@harryjwright This feature is provided by fmt. Please ask in the fmt repository.

[!Tip] From the latest documentation, it seems that fmt::formatter<T> can define several types of formats with std::enable_if_t: https://fmt.dev/11.0/api/#formatting-user-defined-types