Coder Social home page Coder Social logo

Comments (10)

tt4g avatar tt4g commented on June 2, 2024

Thank you for reporting.
Could you send PR?

from spdlog.

gabime avatar gabime commented on June 2, 2024

This is kind of expected. If there are no args spdlog does’t treat the passed string as format string for performance reasons, and bypass the fmt formatting.

from spdlog.

tt4g avatar tt4g commented on June 2, 2024

@gabime Should I add a section to the FAQ page (https://github.com/gabime/spdlog/wiki/0.-FAQ) about this design?

from spdlog.

Luffbee avatar Luffbee commented on June 2, 2024

Is this the desired behavior? It seems this overload can be removed, as there is another overload that can cover the use cases of it:

// T cannot be statically converted to format string (including string_view/wstring_view)
template <class T,
typename std::enable_if<!is_convertible_to_any_format_string<const T &>::value,
int>::type = 0>
void log(source_loc loc, level::level_enum lvl, const T &msg) {

Firstly, my apology, I misunderstood the above overload, it is used to handle cases like SPDLOG_INFO(type_can_fmt{}).
The std::string_view fits the requirement of is_convertible_to_any_format_string.

I tried to fix the problem by adding an overload of logger::log_(), which will bypass the fmt formatting if there are no arguments, like the following:

    ...
    void log_(source_loc loc, level::level_enum lvl, string_view_t msg) {
        log_no_fmt_(loc, lvl, msg); 
    }
    // copy from the `log(..., string_view_t)`
    void log_no_fmt_(source_loc loc, level::level_enum lvl, string_view_t msg) {
        bool log_enabled = should_log(lvl);
        bool traceback_enabled = tracer_.enabled();
        if (!log_enabled && !traceback_enabled) {
            return;
        }

        details::log_msg log_msg(loc, name_, lvl, msg);
        log_it_(log_msg, log_enabled, traceback_enabled);
    }

However, it fails to compile with many errors of "error: the value of ‘msg’ is not usable in a constant expression", which is caused by using non-literal strings, e.g.:

// bench/latency.cpp
void bench_c_string(benchmark::State &state, std::shared_ptr<spdlog::logger> logger) {
    const char *msg =
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum pharetra metus cursus "
        "lacus placerat congue. Nulla egestas, mauris a tincidunt tempus, enim lectus volutpat mi, "
        "eu consequat sem "
        "libero nec massa. In dapibus ipsum a diam rhoncus gravida. Etiam non dapibus eros. Donec "
        "fringilla dui sed "
        "augue pretium, nec scelerisque est maximus. Nullam convallis, sem nec blandit maximus, "
        "nisi turpis ornare "
        "nisl, sit amet volutpat neque massa eu odio. Maecenas malesuada quam ex, posuere congue "
        "nibh turpis duis.";

    for (auto _ : state) {
        logger->info(msg);
    }
}

So the problem now is how to correctly detect non-literal strings and dispatch them to log_no_fmt_?
Is there any effort to solve this problem before?

from spdlog.

tt4g avatar tt4g commented on June 2, 2024

Use fmt::runtime() for non-literal characters: #2786


It is returns runtime object basic_runtime<char> in fmt 9.1.0 (https://fmt.dev/9.1.0/api.html#_CPPv4N3fmt7runtimeE11string_view).

Note fmt::runtime() returns runtime_format_string<> fmt 10 and above (https://fmt.dev/10.0.0/api.html#_CPPv4N3fmt7runtimeE11string_view).

The template function log(source_loc loc, level::level_enum lvl, const T &msg) does not fit because is_convertible_to_any_format_string<const T &>::value == true when using fmt::runtime().

template <class T>
struct is_convertible_to_any_format_string
: std::integral_constant<bool,
is_convertible_to_basic_format_string<T, char>::value ||
is_convertible_to_basic_format_string<T, wchar_t>::value> {};

template <typename Char>
#if FMT_VERSION >= 90101
using fmt_runtime_string = fmt::runtime_format_string<Char>;
#else
using fmt_runtime_string = fmt::basic_runtime<Char>;
#endif
// clang doesn't like SFINAE disabled constructor in std::is_convertible<> so have to repeat the
// condition from basic_format_string here, in addition, fmt::basic_runtime<Char> is only
// convertible to basic_format_string<Char> but not basic_string_view<Char>
template <class T, class Char = char>
struct is_convertible_to_basic_format_string
: std::integral_constant<bool,
std::is_convertible<T, fmt::basic_string_view<Char>>::value ||
std::is_same<remove_cvref_t<T>, fmt_runtime_string<Char>>::value> {
};

from spdlog.

tt4g avatar tt4g commented on June 2, 2024

Looking at the logging template function signatures, it seems that it would be very difficult to change the current implementation.

The only way to make the compile time check work with just a literal string would be to directly call log(source_loc loc, level::level_enum lvl, format_string_t<Args...> fmt, Args &&...args) that calls log_(source_loc loc, level::level_enum lvl, string_view_t fmt, Args &&...args).

template <typename... Args>
void log(source_loc loc, level::level_enum lvl, format_string_t<Args...> fmt, Args &&...args) {
log_(loc, lvl, details::to_string_view(fmt), std::forward<Args>(args)...);
}

Since info(const T &msg) cannot call log(source_loc loc, level::level_enum lvl, format_string_t<Args...> fmt, Args &&...args), try calling log(source_loc loc, level::level_enum lvl, format_string_t<Args...> fmt, Args &&...args) directly.

template <typename T>
void info(const T &msg) {
log(level::info, msg);
}

from spdlog.

Luffbee avatar Luffbee commented on June 2, 2024

Since info(const T &msg) cannot call log(source_loc loc, level::level_enum lvl, format_string_t<Args...> fmt, Args &&...args), try calling log(source_loc loc, level::level_enum lvl, format_string_t<Args...> fmt, Args &&...args) directly.

template <typename T>
void info(const T &msg) {
log(level::info, msg);
}

There is another similar overload can achieve the compile-time check

template <typename... Args>
void info(format_string_t<Args...> fmt, Args &&...args) {
log(level::info, fmt, std::forward<Args>(args)...);
}

The problem is info("{}") will resolve to the info(const T &msg) overload.
Therefore, I tried to add the !is_convertible_to_any_format_string requirement to info(const T &msg), which makes the info("{}") resolve to info(format_string_t<Args...> fmt, Args &&...args), and compile-time check works for info("{}").

template <class T, 
           typename std::enable_if<!is_convertible_to_any_format_string<const T &>::value, 
                                   int>::type = 0> 
void info(const T &msg) { 
     log(level::info, msg); 
} 

However, this modification makes non-literal strings resolve to the compile-time check overload, too.

std::string s = fmt::format("{}", 123);
// without modification: resolves to `info(const T&msg)`, compile ok
// with modification: resolves to `info(format_string_t<Args...> fmt, Args &&...args)`, compile fail (c++20) due to `s` is not constexpr
spdlog::info(s);

The problem here is there is no way to do different dispatch for constexpr argument and non-constexpr argument before c++23.
With c++23, the if consteval feature seems can help in this problem.

from spdlog.

tt4g avatar tt4g commented on June 2, 2024

Yes, this problem cannot be avoided because if consteval is not available in C++11.
If you really want to do a compile-time check when passing only string, you will have to call log(source_loc loc, level::level_enum lvl, format_string_t<Args...> fmt, Args &&...args).

from spdlog.

gabime avatar gabime commented on June 2, 2024

@gabime Should I add a section to the FAQ page (https://github.com/gabime/spdlog/wiki/0.-FAQ) about this design?

If this is actually a FAQ then yes please

from spdlog.

tt4g avatar tt4g commented on June 2, 2024

If this is actually a FAQ then yes please

Added: https://github.com/gabime/spdlog/wiki/0.-FAQ#format-string-compile-time-check-does-not-work-when-there-are-no-args

from spdlog.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.