Skip to content

Commit

Permalink
Huge optimizations and refactoring
Browse files Browse the repository at this point in the history
- Using cached formatter to prevent parsing multiple times;
- Caching local time and thread ID
- Have only file name and function without signature in Location;
- {time} have seconds precision since now;
- Add {msec}, {usec} and {nsec} placeholders.

Signed-off-by: Pavel Artsishevsky <polter.rnd@gmail.com>
  • Loading branch information
polter-rnd committed Nov 2, 2024
1 parent ddad57d commit efb81ac
Show file tree
Hide file tree
Showing 11 changed files with 739 additions and 320 deletions.
219 changes: 176 additions & 43 deletions include/log/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
#include <fmt/format.h> // IWYU pragma: keep
#include <fmt/xchar.h> // IWYU pragma: keep
#else
#include <array>
#include <format>
#endif

#include "location.h"
#include "util/buffer.h"
#include "util/types.h"

#include <concepts>
#include <cstddef>
Expand All @@ -43,6 +43,12 @@ template<typename T, typename... Args>
using FormatString = fmt::basic_format_string<T, Args...>;
/** @brief Alias for \a fmt::format_error. */
using FormatError = fmt::format_error;
/** @brief Alias for \a fmt::formatter. */
template<typename T, typename Char>
using Formatter = fmt::formatter<T, Char>;
/** @brief Alias for \a fmt::basic_format_parse_context. */
template<typename Char>
using FormatParseContext = fmt::basic_format_parse_context<Char>;
#else
/**
* @brief Alias for std::basic_format_string.
Expand All @@ -54,6 +60,12 @@ template<typename T, typename... Args>
using FormatString = std::basic_format_string<T, Args...>;
/** @brief Alias for \a std::format_error. */
using FormatError = std::format_error;
/** @brief Alias for \a std::formatter. */
template<typename T, typename Char>
using Formatter = std::formatter<T, Char>;
/** @brief Alias for \a std::basic_format_parse_context. */
template<typename Char>
using FormatParseContext = std::basic_format_parse_context<Char>;
#endif

/**
Expand Down Expand Up @@ -128,6 +140,121 @@ class Format final {
Location m_loc;
};

template<typename T, typename Char>
class CachedFormatter;

/**
* @brief Wrapper for formatting a value with a cached formatter.
*
* Stores the value and reference to the cached formatter (see CachedFormatter).
* Used in combination with `std::formatter<FormatValue<T, Char>, Char>` specialization.
*
* @tparam T Value type.
* @tparam Char Output character type.
*/
template<typename T, typename Char>
class FormatValue final {
public:
/**
* @brief Constructs a new FormatValue object from a reference to the formatter and value.
*
* @param formatter Reference to the formatter.
* @param value Value to be formatted.
*/
constexpr FormatValue(const CachedFormatter<T, Char>& formatter, T value)
: m_formatter(formatter)
, m_value(std::move(value))
{
}

~FormatValue() = default;

FormatValue(const FormatValue&) = delete;
FormatValue(FormatValue&&) = delete;
auto operator=(const FormatValue&) -> FormatValue& = delete;
auto operator=(FormatValue&&) noexcept -> FormatValue& = delete;

/**
* @brief Formats the value with the cached formatter.
*
* @tparam Context Format context type (`std::format_context`).
* @param context Format context.
* @return Output iterator to the resulting buffer.
*/
template<typename Context>
auto format(Context& context) const
{
return m_formatter.format(m_value, context);
}

private:
const CachedFormatter<T, Char>& m_formatter;
T m_value;
};

/**
* @brief Wrapper to parse the format string and store the format context.
*
* Used to parse format string only once and then cache the formatter.
*
* @tparam T Value type.
* @tparam Char Output character type.
*/
template<typename T, typename Char>
class CachedFormatter final : Formatter<T, Char> {
public:
using Formatter<T, Char>::format;

/**
* @brief Constructs a new CachedFormatter object from a format string.
*
* @param fmt Format string.
*/
constexpr explicit CachedFormatter(std::basic_string_view<Char> fmt)
#ifdef ENABLE_FMTLIB
: m_empty(fmt.empty())
#endif
{
FormatParseContext<Char> parse_context(std::move(fmt));
Formatter<T, Char>::parse(parse_context);
}

/**
* @brief Formats the value and writes to the output buffer.
*
* @tparam Out Output buffer type (see MemoryBuffer).
* @param out Output buffer.
* @param value Value to be formatted.
*/
template<typename Out>
void format(Out& out, T value) const
{
#ifdef ENABLE_FMTLIB
if constexpr (std::is_arithmetic_v<T>) {
if (m_empty) [[likely]] {
out.append(fmt::format_int(value));
return;
}
}

using Appender = std::conditional_t<
std::is_same_v<Char, char>,
fmt::appender,
std::back_insert_iterator<std::remove_cvref_t<Out>>>;
fmt::basic_format_context<Appender, Char> fmt_context(Appender(out), {});
Formatter<T, Char>::format(std::move(value), fmt_context);
#else
static constexpr std::array<Char, 3> Fmt{'{', '}', '\0'};
out.format(Fmt.data(), FormatValue(*this, std::move(value)));
#endif
}

#ifdef ENABLE_FMTLIB
private:
bool m_empty;
#endif
};

/**
* @brief Buffer used for log message formatting.
*
Expand All @@ -140,6 +267,35 @@ class FormatBuffer final : public MemoryBuffer<Char, BufferSize, Allocator> {
public:
using MemoryBuffer<Char, BufferSize, Allocator>::MemoryBuffer;

/**
* @brief Appends data to the end of the buffer.
*
* @tparam ContiguousRange Type of the source object.
*
* @param range Source object containing data to be added to the buffer.
*/
template<typename ContiguousRange>
void append(const ContiguousRange& range) // cppcheck-suppress duplInheritedMember
{
append(range.data(), std::next(range.data(), range.size()));
}

/**
* @brief Appends data to the end of the buffer.
*
* @tparam U Input data type.
* @param begin Begin input iterator.
* @param end End input iterator.
*/
template<typename U>
void append(const U* begin, const U* end) // cppcheck-suppress duplInheritedMember
{
const auto buf_size = this->size();
const auto count = static_cast<std::make_unsigned_t<decltype(end - begin)>>(end - begin);
this->resize(buf_size + count);
std::uninitialized_copy_n(begin, count, std::next(this->begin(), buf_size));
}

/**
* @brief Formats a log message with compile-time argument checks.
*
Expand All @@ -154,60 +310,37 @@ class FormatBuffer final : public MemoryBuffer<Char, BufferSize, Allocator> {
if constexpr (std::is_same_v<Char, char>) {
fmt::format_to(fmt::appender(*this), std::move(fmt), std::forward<Args>(args)...);
} else {
#if FMT_VERSION < 110000
fmt::format_to(
std::back_inserter(*this),
#if FMT_VERSION < 110000
static_cast<fmt::basic_string_view<Char>>(std::move(fmt)),
std::forward<Args>(args)...);
#else
fmt::format_to(std::back_inserter(*this), std::move(fmt), std::forward<Args>(args)...);
std::move(fmt),
#endif
std::forward<Args>(args)...);
}
#else
std::format_to(std::back_inserter(*this), std::move(fmt), std::forward<Args>(args)...);
#endif
}
};

/**
* @brief Formats a log message.
*
* @tparam Args Format argument types. Deduced from arguments.
* @param fmt Format string.
* @param args Format arguments.
* @throws FormatError if fmt is not a valid format string for the provided arguments.
*/
template<typename... Args>
auto format_runtime(std::basic_string_view<Char> fmt, Args&... args) -> void
} // namespace PlainCloud::Log

#ifndef ENABLE_FMTLIB
/** @cond */
template<typename T, typename Char>
struct std::formatter<PlainCloud::Log::FormatValue<T, Char>, Char> { // NOLINT (cert-dcl58-cpp)
constexpr auto parse(PlainCloud::Log::FormatParseContext<Char>& context)
{
#ifdef ENABLE_FMTLIB
if constexpr (std::is_same_v<Char, char>) {
fmt::vformat_to(fmt::appender(*this), std::move(fmt), fmt::make_format_args(args...));
} else {
return context.end();
}

fmt::vformat_to(
std::back_inserter(*this),
std::move(fmt),
#if FMT_VERSION < 110000
fmt::make_format_args<fmt::buffer_context<Char>>(args...)
#else
fmt::make_format_args<fmt::buffered_context<Char>>(args...)
#endif
);
}
#else
if constexpr (std::is_same_v<Char, char>) {
std::vformat_to(
std::back_inserter(*this), std::move(fmt), std::make_format_args(args...));
} else if constexpr (std::is_same_v<Char, wchar_t>) {
std::vformat_to(
std::back_inserter(*this), std::move(fmt), std::make_wformat_args(args...));
} else {
static_assert(
Util::Types::AlwaysFalse<Char>{},
"std::vformat_to() supports only `char` or `wchar_t` character types");
}
#endif
template<typename Context>
auto format(const PlainCloud::Log::FormatValue<T, Char>& wrapper, Context& context) const
{
return wrapper.format(context);
}
};

} // namespace PlainCloud::Log
/** @endcond */
#endif
30 changes: 20 additions & 10 deletions include/log/location.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,8 @@

#pragma once

#if __has_include(<source_location>)
#include <source_location>
#endif

namespace PlainCloud::Log {

#ifdef __cpp_lib_source_location
/** @brief Alias for std::source_location. */
using Location = std::source_location;
#else
/**
* @brief Represents a specific location in the source code.
*
Expand Down Expand Up @@ -43,7 +35,7 @@ class Location {
#if __has_builtin(__builtin_FILE) and __has_builtin(__builtin_FUNCTION) \
and __has_builtin(__builtin_LINE) \
or defined(_MSC_VER) and _MSC_VER > 192
const char* file = __builtin_FILE(),
const char* file = extract_file_name(__builtin_FILE()),
const char* function = __builtin_FUNCTION(),
int line = __builtin_LINE()
#else
Expand Down Expand Up @@ -89,9 +81,27 @@ class Location {
}

private:
consteval static auto extract_file_name(const char* path) -> const char*
{
const char* file = path;
const char sep =
#ifdef _WIN32
'\\';
#else
'/';
#endif
while (*path != '\0') {
// NOLINTNEXTLINE (cppcoreguidelines-pro-bounds-pointer-arithmetic)
if (*path++ == sep) {
file = path;
}
}
return file;
}

const char* m_file{""};
const char* m_function{""};
int m_line{};
};
#endif

} // namespace PlainCloud::Log
Loading

0 comments on commit efb81ac

Please sign in to comment.