Skip to content

Commit

Permalink
add StringRef
Browse files Browse the repository at this point in the history
  • Loading branch information
odygrd committed Jun 19, 2024
1 parent dfdd86d commit a5e421f
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 5 deletions.
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);
}

0 comments on commit a5e421f

Please sign in to comment.