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

add StringRef #477

Merged
merged 2 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@
or disable this feature through the backend options by modifying the `check_printable_char` callback
in `BackendOptions`.

- Added `StringRef`, a utility for passing string arguments by reference without copying. Suitable for string literals
or immutable strings with a guaranteed persistent lifetime. For example

```c++
#include "quill/StringRef.h"

static constexpr std::string_view sv {"string_view"};
LOG_INFO(logger, "{} {}", quill::utility::StringRef{sv}, quill::utility::StringRef{"string_literal"});
```

## v4.4.1

- Fixed multiple definitions of `quill::detail::get_error_message` ([#469](https://github.com/odygrd/quill/issues/469))
Expand Down
8 changes: 3 additions & 5 deletions examples/advanced/user_quill_codec.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

// Always required
#include "quill/bundled/fmt/format.h"
#include "quill/core/Codec.h"
#include "quill/core/DynamicFormatArgStore.h"

Expand All @@ -15,14 +16,11 @@
template <>
struct fmtquill::formatter<User>
{
template <typename FormatContext>
constexpr auto parse(FormatContext& ctx)
{
constexpr auto parse(format_parse_context& ctx) {
return ctx.begin();
}

template <typename FormatContext>
auto format(::User const& user, FormatContext& ctx) const
auto format(::User const& user, format_context& ctx) const
{
return fmtquill::format_to(ctx.out(), "Name: {}, Surname: {}, Age: {}, Favorite Colors: {}",
user.name, user.surname, user.age, user.favorite_colors);
Expand Down
1 change: 1 addition & 0 deletions quill/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ set(HEADER_FILES
include/quill/Frontend.h
include/quill/Logger.h
include/quill/LogMacros.h
include/quill/StringRef.h
include/quill/UserClockSource.h
include/quill/Utility.h
)
Expand Down
88 changes: 88 additions & 0 deletions quill/include/quill/StringRef.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* Copyright(c) 2020-present, Odysseas Georgoudis & quill contributors.
* Distributed under the MIT License (http://opensource.org/licenses/MIT)
*/

#pragma once

#include <cstddef>
#include <cstring>
#include <string>
#include <string_view>

#include "quill/core/Attributes.h"
#include "quill/core/Codec.h"
#include "quill/core/DynamicFormatArgStore.h"

namespace quill::utility
{
/**
* StringRef is used to specify that a string argument should be passed by reference instead of by
* value, ensuring that no copy of the string is made.
*
* Note that by default, all strings, including C strings and std::string_view, are copied before
* being passed to the backend. To pass strings by reference, they must be wrapped in a StringRef.
*
* Use this with caution, as the backend will parse the string asynchronously. The wrapped string
* must have a valid lifetime and should not be modified.
*/
class StringRef
{
public:
explicit StringRef(std::string const& str) : _str_view(str){};
explicit StringRef(std::string_view str) : _str_view(str){};
explicit StringRef(char const* str) : _str_view(str, strlen(str)){};
StringRef(char const* str, size_t size) : _str_view(str, size){};

QUILL_NODISCARD std::string_view const& get_string_view() const noexcept { return _str_view; }

private:
std::string_view _str_view;
};
} // namespace quill::utility

/***/
template <>
struct quill::detail::ArgSizeCalculator<quill::utility::StringRef>
{
static size_t calculate(std::vector<size_t>& conditional_arg_size_cache,
quill::utility::StringRef const& no_copy) noexcept
{
return sizeof(size_t) + sizeof(uintptr_t);
}
};

/***/
template <>
struct quill::detail::Encoder<quill::utility::StringRef>
{
static void encode(std::byte*& buffer, std::vector<size_t> const& conditional_arg_size_cache,
uint32_t& conditional_arg_size_cache_index, quill::utility::StringRef const& no_copy) noexcept
{
char const* data = no_copy.get_string_view().data();
std::memcpy(buffer, &data, sizeof(uintptr_t));
buffer += sizeof(uintptr_t);

size_t const size = no_copy.get_string_view().size();
std::memcpy(buffer, &size, sizeof(size_t));
buffer += sizeof(size_t);
}
};

/***/
template <>
struct quill::detail::Decoder<quill::utility::StringRef>
{
static void decode(std::byte*& buffer, DynamicFormatArgStore* args_store)
{
char const* data;
std::memcpy(&data, buffer, sizeof(uintptr_t));
buffer += sizeof(uintptr_t);

size_t size;
std::memcpy(&size, buffer, sizeof(size_t));
buffer += sizeof(size_t);

args_store->push_back(std::string_view{data, size});
}
};
1 change: 1 addition & 0 deletions quill/test/integration_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ quill_add_test(TEST_StringLogging StringLoggingTest.cpp)
quill_add_test(TEST_StringRandomLargeLogging StringRandomLargeLoggingTest.cpp)
quill_add_test(TEST_StringRandomLogging StringRandomLoggingTest.cpp)
quill_add_test(TEST_StringRandomSmallLogging StringRandomSmallLoggingTest.cpp)
quill_add_test(TEST_StringRefTest StringRefTest.cpp)
quill_add_test(TEST_TagsLogging TagsLoggingTest.cpp)
quill_add_test(TEST_UserClockSource UserClockSourceTest.cpp)
quill_add_test(TEST_UserDefinedTypeLogging UserDefinedTypeLoggingTest.cpp)
Expand Down
89 changes: 89 additions & 0 deletions quill/test/integration_tests/StringRefTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#include "doctest/doctest.h"

#include "misc/TestUtilities.h"
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/StringRef.h"
#include "quill/sinks/FileSink.h"

#include <cstdio>
#include <string>
#include <string_view>
#include <vector>

using namespace quill;

/***/
TEST_CASE("string_no_copy_logging")
{
static constexpr char const* filename = "string_no_copy_logging.log";
static std::string const logger_name = "logger";

// Start the logging backend thread
Backend::start();

// Set writing logging to a file
auto file_sink = Frontend::create_or_get_sink<FileSink>(
filename,
[]()
{
FileSinkConfig cfg;
cfg.set_open_mode('w');
return cfg;
}(),
FileEventNotifier{});

Logger* logger = Frontend::create_or_get_logger(logger_name, std::move(file_sink));

static std::string s = "adipiscing";
static std::string_view sv = "string_view";
static char const* c_style_string_empty = "";
static const char* c_style_string = "Lorem ipsum";
static const char* npcs = "Example\u0003String\u0004";

std::string s1 = "adipiscing_1";
char const* s2 = "adipiscing_2";

LOG_INFO(logger, "static string [{}]", quill::utility::StringRef{s});
LOG_INFO(logger, "static string_view [{}]", quill::utility::StringRef{sv});
LOG_INFO(logger, "static c_style_string_empty [{}]", quill::utility::StringRef{c_style_string_empty});
LOG_INFO(logger, "static c_style_string [{}]", quill::utility::StringRef{c_style_string});
LOG_INFO(logger, "static npcs [{}]", quill::utility::StringRef{npcs});
LOG_INFO(logger, "string literal [{}]", quill::utility::StringRef{"test string literal"});
LOG_INFO(logger, "mix strings [{}] [{}] [{}] [{}]",
quill::utility::StringRef{"test string literal"}, s1, quill::utility::StringRef{s}, s2);

logger->flush_log();
Frontend::remove_logger(logger);

// Wait until the backend thread stops for test stability
Backend::stop();

// Read file and check
std::vector<std::string> const file_contents = quill::testing::file_contents(filename);
REQUIRE_EQ(file_contents.size(), 7);

REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " static string [adipiscing]"}));

REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " static string_view [string_view]"}));

REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " static c_style_string_empty []"}));

REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " static c_style_string [Lorem ipsum]"}));

REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " static npcs [Example\\x03String\\x04]"}));

REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " string literal [test string literal]"}));

REQUIRE(quill::testing::file_contains(
file_contents, std::string{"LOG_INFO " + logger_name + " mix strings [test string literal] [adipiscing_1] [adipiscing] [adipiscing_2]"}));

testing::remove_file(filename);
}