Add support for logging wide strings in `absl::log`. The logged strings are truncated to fit the available buffer in the same way as regular strings. Invalid characters are replaced by the replacement char (`U+FFFD`). Wide string literals are recorded as literals in `logging.proto.Value`. PiperOrigin-RevId: 756052375 Change-Id: If3960b4230f923061028bd738eb209b5a82a31eb
diff --git a/absl/log/CMakeLists.txt b/absl/log/CMakeLists.txt index 6aae05d..130897f 100644 --- a/absl/log/CMakeLists.txt +++ b/absl/log/CMakeLists.txt
@@ -218,6 +218,7 @@ absl::span absl::strerror absl::strings + absl::strings_internal absl::time ) @@ -395,6 +396,7 @@ DEPS absl::config absl::strings + absl::strings_internal absl::span )
diff --git a/absl/log/internal/BUILD.bazel b/absl/log/internal/BUILD.bazel index 44ec71b..953b690 100644 --- a/absl/log/internal/BUILD.bazel +++ b/absl/log/internal/BUILD.bazel
@@ -205,6 +205,7 @@ "//absl/log:log_sink_registry", "//absl/memory", "//absl/strings", + "//absl/strings:internal", "//absl/time", "//absl/types:span", ], @@ -218,6 +219,7 @@ deps = [ "//absl/base:config", "//absl/strings", + "//absl/strings:internal", "//absl/types:span", ], )
diff --git a/absl/log/internal/append_truncated.h b/absl/log/internal/append_truncated.h index f0e7912..d420a8b 100644 --- a/absl/log/internal/append_truncated.h +++ b/absl/log/internal/append_truncated.h
@@ -17,8 +17,10 @@ #include <cstddef> #include <cstring> +#include <string_view> #include "absl/base/config.h" +#include "absl/strings/internal/utf8.h" #include "absl/strings/string_view.h" #include "absl/types/span.h" @@ -33,6 +35,32 @@ dst.remove_prefix(src.size()); return src.size(); } +// Likewise, but it also takes a wide character string and transforms it into a +// UTF-8 encoded byte string regardless of the current locale. +// - On platforms where `wchar_t` is 2 bytes (e.g., Windows), the input is +// treated as UTF-16. +// - On platforms where `wchar_t` is 4 bytes (e.g., Linux, macOS), the input +// is treated as UTF-32. +inline size_t AppendTruncated(std::wstring_view src, absl::Span<char> &dst) { + absl::strings_internal::ShiftState state; + size_t total_bytes_written = 0; + for (const wchar_t wc : src) { + // If the destination buffer might not be large enough to write the next + // character, stop. + if (dst.size() < absl::strings_internal::kMaxEncodedUTF8Size) break; + size_t bytes_written = + absl::strings_internal::WideToUtf8(wc, dst.data(), state); + if (bytes_written == static_cast<size_t>(-1)) { + // Invalid character. Encode REPLACEMENT CHARACTER (U+FFFD) instead. + constexpr wchar_t kReplacementCharacter = L'\uFFFD'; + bytes_written = absl::strings_internal::WideToUtf8(kReplacementCharacter, + dst.data(), state); + } + dst.remove_prefix(bytes_written); + total_bytes_written += bytes_written; + } + return total_bytes_written; +} // Likewise, but `n` copies of `c`. inline size_t AppendTruncated(char c, size_t n, absl::Span<char> &dst) { if (n > dst.size()) n = dst.size();
diff --git a/absl/log/internal/log_message.cc b/absl/log/internal/log_message.cc index aaaaf03..07d17a0 100644 --- a/absl/log/internal/log_message.cc +++ b/absl/log/internal/log_message.cc
@@ -27,10 +27,12 @@ #include <algorithm> #include <array> #include <atomic> +#include <cwchar> #include <ios> #include <memory> #include <ostream> #include <string> +#include <string_view> #include <tuple> #include "absl/base/attributes.h" @@ -47,12 +49,14 @@ #include "absl/log/internal/globals.h" #include "absl/log/internal/log_format.h" #include "absl/log/internal/log_sink_set.h" +#include "absl/log/internal/nullguard.h" #include "absl/log/internal/proto.h" #include "absl/log/internal/structured_proto.h" #include "absl/log/log_entry.h" #include "absl/log/log_sink.h" #include "absl/log/log_sink_registry.h" #include "absl/memory/memory.h" +#include "absl/strings/internal/utf8.h" #include "absl/strings/string_view.h" #include "absl/time/clock.h" #include "absl/time/time.h" @@ -403,6 +407,35 @@ CopyToEncodedBuffer<StringType::kNotLiteral>(v); return *this; } + +LogMessage& LogMessage::operator<<(const std::wstring& v) { + CopyToEncodedBuffer<StringType::kNotLiteral>(v); + return *this; +} + +LogMessage& LogMessage::operator<<(std::wstring_view v) { + CopyToEncodedBuffer<StringType::kNotLiteral>(v); + return *this; +} + +template <> +LogMessage& LogMessage::operator<< <const wchar_t*>( + const wchar_t* absl_nullable const& v) { + if (v == nullptr) { + CopyToEncodedBuffer<StringType::kNotLiteral>( + absl::string_view(kCharNull.data(), kCharNull.size() - 1)); + } else { + CopyToEncodedBuffer<StringType::kNotLiteral>( + std::wstring_view(v, wcsnlen(v, data_->encoded_remaining().size()))); + } + return *this; +} + +LogMessage& LogMessage::operator<<(wchar_t v) { + CopyToEncodedBuffer<StringType::kNotLiteral>(std::wstring_view(&v, 1)); + return *this; +} + LogMessage& LogMessage::operator<<(std::ostream& (*m)(std::ostream& os)) { OstreamView view(*data_); data_->manipulated << m; @@ -625,6 +658,37 @@ template void LogMessage::CopyToEncodedBuffer< LogMessage::StringType::kNotLiteral>(char ch, size_t num); +template <LogMessage::StringType str_type> +void LogMessage::CopyToEncodedBuffer(std::wstring_view str) { + auto encoded_remaining_copy = data_->encoded_remaining(); + constexpr uint8_t tag_value = str_type == StringType::kLiteral + ? ValueTag::kStringLiteral + : ValueTag::kString; + size_t max_str_byte_length = + absl::strings_internal::kMaxEncodedUTF8Size * str.length(); + auto value_start = + EncodeMessageStart(EventTag::kValue, + BufferSizeFor(tag_value, WireType::kLengthDelimited) + + max_str_byte_length, + &encoded_remaining_copy); + auto str_start = EncodeMessageStart(tag_value, max_str_byte_length, + &encoded_remaining_copy); + if (str_start.data()) { + log_internal::AppendTruncated(str, encoded_remaining_copy); + EncodeMessageLength(str_start, &encoded_remaining_copy); + EncodeMessageLength(value_start, &encoded_remaining_copy); + data_->encoded_remaining() = encoded_remaining_copy; + } else { + // The field header(s) did not fit; zero `encoded_remaining()` so we don't + // write anything else later. + data_->encoded_remaining().remove_suffix(data_->encoded_remaining().size()); + } +} +template void LogMessage::CopyToEncodedBuffer<LogMessage::StringType::kLiteral>( + std::wstring_view str); +template void LogMessage::CopyToEncodedBuffer< + LogMessage::StringType::kNotLiteral>(std::wstring_view str); + template void LogMessage::CopyToEncodedBufferWithStructuredProtoField< LogMessage::StringType::kLiteral>(StructuredProtoField field, absl::string_view str); @@ -682,17 +746,13 @@ *this << "Check failed: " << failure_msg << " "; } -LogMessageFatal::~LogMessageFatal() { - FailWithoutStackTrace(); -} +LogMessageFatal::~LogMessageFatal() { FailWithoutStackTrace(); } LogMessageDebugFatal::LogMessageDebugFatal(const char* absl_nonnull file, int line) : LogMessage(file, line, absl::LogSeverity::kFatal) {} -LogMessageDebugFatal::~LogMessageDebugFatal() { - FailWithoutStackTrace(); -} +LogMessageDebugFatal::~LogMessageDebugFatal() { FailWithoutStackTrace(); } LogMessageQuietlyDebugFatal::LogMessageQuietlyDebugFatal( const char* absl_nonnull file, int line) @@ -700,9 +760,7 @@ SetFailQuietly(); } -LogMessageQuietlyDebugFatal::~LogMessageQuietlyDebugFatal() { - FailQuietly(); -} +LogMessageQuietlyDebugFatal::~LogMessageQuietlyDebugFatal() { FailQuietly(); } LogMessageQuietlyFatal::LogMessageQuietlyFatal(const char* absl_nonnull file, int line) @@ -717,9 +775,7 @@ *this << "Check failed: " << failure_msg << " "; } -LogMessageQuietlyFatal::~LogMessageQuietlyFatal() { - FailQuietly(); -} +LogMessageQuietlyFatal::~LogMessageQuietlyFatal() { FailQuietly(); } #if defined(_MSC_VER) && !defined(__clang__) #pragma warning(pop) #endif
diff --git a/absl/log/internal/log_message.h b/absl/log/internal/log_message.h index e7eff47..1aaf05e 100644 --- a/absl/log/internal/log_message.h +++ b/absl/log/internal/log_message.h
@@ -27,12 +27,15 @@ #ifndef ABSL_LOG_INTERNAL_LOG_MESSAGE_H_ #define ABSL_LOG_INTERNAL_LOG_MESSAGE_H_ +#include <wchar.h> + #include <cstddef> #include <ios> #include <memory> #include <ostream> #include <streambuf> #include <string> +#include <string_view> #include <type_traits> #include "absl/base/attributes.h" @@ -158,6 +161,13 @@ LogMessage& operator<<(const std::string& v); LogMessage& operator<<(absl::string_view v); + // Wide string overloads (since std::ostream does not provide them). + LogMessage& operator<<(const std::wstring& v); + LogMessage& operator<<(std::wstring_view v); + // `const wchar_t*` is handled by `operator<< <const wchar_t*>`. + LogMessage& operator<<(wchar_t* absl_nullable v); + LogMessage& operator<<(wchar_t v); + // Handle stream manipulators e.g. std::endl. LogMessage& operator<<(std::ostream& (*absl_nonnull m)(std::ostream& os)); LogMessage& operator<<(std::ios_base& (*absl_nonnull m)(std::ios_base& os)); @@ -169,17 +179,20 @@ // this template for every value of `SIZE` encountered in each source code // file. That significantly increases linker input sizes. Inlining is cheap // because the argument to this overload is almost always a string literal so - // the call to `strlen` can be replaced at compile time. The overload for - // `char[]` below should not be inlined. The compiler typically does not have - // the string at compile time and cannot replace the call to `strlen` so - // inlining it increases the binary size. See the discussion on + // the call to `strlen` can be replaced at compile time. The overloads for + // `char[]`/`wchar_t[]` below should not be inlined. The compiler typically + // does not have the string at compile time and cannot replace the call to + // `strlen` so inlining it increases the binary size. See the discussion on // cl/107527369. template <int SIZE> LogMessage& operator<<(const char (&buf)[SIZE]); + template <int SIZE> + LogMessage& operator<<(const wchar_t (&buf)[SIZE]); // This prevents non-const `char[]` arrays from looking like literals. template <int SIZE> LogMessage& operator<<(char (&buf)[SIZE]) ABSL_ATTRIBUTE_NOINLINE; + // `wchar_t[SIZE]` is handled by `operator<< <const wchar_t*>`. // Types that support `AbslStringify()` are serialized that way. // Types that don't support `AbslStringify()` but do support streaming into a @@ -243,6 +256,8 @@ void CopyToEncodedBuffer(absl::string_view str) ABSL_ATTRIBUTE_NOINLINE; template <StringType str_type> void CopyToEncodedBuffer(char ch, size_t num) ABSL_ATTRIBUTE_NOINLINE; + template <StringType str_type> + void CopyToEncodedBuffer(std::wstring_view str) ABSL_ATTRIBUTE_NOINLINE; // Copies `field` to the encoded buffer, then appends `str` after it // (truncating `str` if necessary to fit). @@ -273,6 +288,22 @@ absl_nonnull std::unique_ptr<LogMessageData> data_; }; +// Explicitly specializes the generic operator<< for `const wchar_t*` +// arguments. +// +// This method is used instead of a non-template `const wchar_t*` overload, +// as the latter was found to take precedence over the array template +// (`operator<<(const wchar_t(&)[SIZE])`) when handling string literals. +// This specialization ensures the array template now correctly processes +// literals. +template <> +LogMessage& LogMessage::operator<< <const wchar_t*>( + const wchar_t* absl_nullable const& v); + +inline LogMessage& LogMessage::operator<<(wchar_t* absl_nullable v) { + return operator<<(const_cast<const wchar_t*>(v)); +} + // Helper class so that `AbslStringify()` can modify the LogMessage. class StringifySink final { public: @@ -317,6 +348,12 @@ return *this; } +template <int SIZE> +LogMessage& LogMessage::operator<<(const wchar_t (&buf)[SIZE]) { + CopyToEncodedBuffer<StringType::kLiteral>(buf); + return *this; +} + // Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE` template <int SIZE> LogMessage& LogMessage::operator<<(char (&buf)[SIZE]) { @@ -358,6 +395,10 @@ size_t num); extern template void LogMessage::CopyToEncodedBuffer< LogMessage::StringType::kNotLiteral>(char ch, size_t num); +extern template void LogMessage::CopyToEncodedBuffer< + LogMessage::StringType::kLiteral>(std::wstring_view str); +extern template void LogMessage::CopyToEncodedBuffer< + LogMessage::StringType::kNotLiteral>(std::wstring_view str); // `LogMessageFatal` ensures the process will exit in failure after logging this // message.
diff --git a/absl/log/log_format_test.cc b/absl/log/log_format_test.cc index ecd6968..f4e33c9 100644 --- a/absl/log/log_format_test.cc +++ b/absl/log/log_format_test.cc
@@ -15,12 +15,14 @@ #include <math.h> +#include <cstring> #include <iomanip> #include <ios> #include <limits> #include <ostream> #include <sstream> #include <string> +#include <string_view> #include <type_traits> #ifdef __ANDROID__ @@ -28,6 +30,7 @@ #endif #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/base/config.h" #include "absl/log/check.h" #include "absl/log/internal/test_matchers.h" #include "absl/log/log.h" @@ -44,6 +47,7 @@ using ::absl::log_internal::RawEncodedMessage; using ::absl::log_internal::TextMessage; using ::absl::log_internal::TextPrefix; +using ::testing::_; using ::testing::AllOf; using ::testing::AnyOf; using ::testing::Each; @@ -124,6 +128,33 @@ LOG(INFO) << value; } +TEST(WideCharLogFormatTest, Printable) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send(AllOf(TextMessage(Eq("€")), + ENCODED_MESSAGE(HasValues( + ElementsAre(ValueWithStr(Eq("€")))))))); + + test_sink.StartCapturingLogs(); + const wchar_t value = L'\u20AC'; + LOG(INFO) << value; +} + +TEST(WideCharLogFormatTest, Unprintable) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + // Using NEL (Next Line) Unicode character (U+0085). + // It is encoded as "\xC2\x85" in UTF-8. + constexpr wchar_t wide_value = L'\u0085'; + constexpr char value[] = "\xC2\x85"; + EXPECT_CALL(test_sink, Send(AllOf(TextMessage(Eq(value)), + ENCODED_MESSAGE(HasValues(ElementsAre( + ValueWithStr(Eq(value)))))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << wide_value; +} + template <typename T> class UnsignedIntLogFormatTest : public testing::Test {}; using UnsignedIntTypes = Types<unsigned short, unsigned int, // NOLINT @@ -635,7 +666,7 @@ template <typename T> class VoidPtrLogFormatTest : public testing::Test {}; -using VoidPtrTypes = Types<void *, const void *>; +using VoidPtrTypes = Types<void*, const void*>; TYPED_TEST_SUITE(VoidPtrLogFormatTest, VoidPtrTypes); TYPED_TEST(VoidPtrLogFormatTest, Null) { @@ -676,11 +707,10 @@ template <typename T> class VolatilePtrLogFormatTest : public testing::Test {}; -using VolatilePtrTypes = - Types<volatile void*, const volatile void*, volatile char*, - const volatile char*, volatile signed char*, - const volatile signed char*, volatile unsigned char*, - const volatile unsigned char*>; +using VolatilePtrTypes = Types< + volatile void*, const volatile void*, volatile char*, const volatile char*, + volatile signed char*, const volatile signed char*, volatile unsigned char*, + const volatile unsigned char*, volatile wchar_t*, const volatile wchar_t*>; TYPED_TEST_SUITE(VolatilePtrLogFormatTest, VolatilePtrTypes); TYPED_TEST(VolatilePtrLogFormatTest, Null) { @@ -784,6 +814,38 @@ LOG(INFO) << value; } +template <typename T> +class WideCharPtrLogFormatTest : public testing::Test {}; +using WideCharPtrTypes = Types<wchar_t, const wchar_t>; +TYPED_TEST_SUITE(WideCharPtrLogFormatTest, WideCharPtrTypes); + +TYPED_TEST(WideCharPtrLogFormatTest, Null) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + TypeParam* const value = nullptr; + + EXPECT_CALL(test_sink, Send(AllOf(TextMessage(Eq("(null)")), + ENCODED_MESSAGE(HasValues(ElementsAre( + ValueWithStr(Eq("(null)")))))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(WideCharPtrLogFormatTest, NonNull) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + TypeParam data[] = {'v', 'a', 'l', 'u', 'e', '\0'}; + TypeParam* const value = data; + + EXPECT_CALL(test_sink, Send(AllOf(TextMessage(Eq("value")), + ENCODED_MESSAGE(HasValues(ElementsAre( + ValueWithStr(Eq("value")))))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + TEST(BoolLogFormatTest, True) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); @@ -836,6 +898,17 @@ LOG(INFO) << "value"; } +TEST(LogFormatTest, WideStringLiteral) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send(AllOf(TextMessage(Eq("value")), + ENCODED_MESSAGE(HasValues(ElementsAre( + ValueWithLiteral(Eq("value")))))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << L"value"; +} + TEST(LogFormatTest, CharArray) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); @@ -854,6 +927,125 @@ LOG(INFO) << value; } +TEST(LogFormatTest, WideCharArray) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + wchar_t value[] = L"value"; + + EXPECT_CALL(test_sink, Send(AllOf(TextMessage(Eq("value")), + ENCODED_MESSAGE(HasValues(ElementsAre( + ValueWithStr(Eq("value")))))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +// Comprehensive test string for validating wchar_t to UTF-8 conversion. +// See details in absl/strings/internal/utf8_test.cc. +// +// clang-format off +#define ABSL_LOG_INTERNAL_WIDE_LITERAL L"Holá €1 你好 שָׁלוֹם 👍🏻🇺🇸👩❤️💋👨 中" +#define ABSL_LOG_INTERNAL_UTF8_LITERAL u8"Holá €1 你好 שָׁלוֹם 👍🏻🇺🇸👩❤️💋👨 中" +// clang-format on + +absl::string_view GetUtf8TestString() { + // `u8""` forces UTF-8 encoding; MSVC will default to e.g. CP1252 (and warn) + // without it. However, the resulting character type differs between pre-C++20 + // (`char`) and C++20 (`char8_t`). So we reinterpret_cast to `char*` and wrap + // it in a `string_view`. + static const absl::string_view kUtf8TestString( + reinterpret_cast<const char*>(ABSL_LOG_INTERNAL_UTF8_LITERAL), + sizeof(ABSL_LOG_INTERNAL_UTF8_LITERAL) - 1); + return kUtf8TestString; +} + +template <typename T> +class WideStringLogFormatTest : public testing::Test {}; +using StringTypes = + Types<std::wstring, const std::wstring, wchar_t[], const wchar_t*>; +TYPED_TEST_SUITE(WideStringLogFormatTest, StringTypes); + +TYPED_TEST(WideStringLogFormatTest, NonLiterals) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + TypeParam value = ABSL_LOG_INTERNAL_WIDE_LITERAL; + absl::string_view utf8_value = GetUtf8TestString(); + + EXPECT_CALL(test_sink, Send(AllOf(TextMessage(Eq(utf8_value)), + ENCODED_MESSAGE(HasValues(ElementsAre( + ValueWithStr(Eq(utf8_value)))))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TEST(WideStringLogFormatTest, StringView) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + std::wstring_view value = ABSL_LOG_INTERNAL_WIDE_LITERAL; + absl::string_view utf8_value = GetUtf8TestString(); + + EXPECT_CALL(test_sink, Send(AllOf(TextMessage(Eq(utf8_value)), + ENCODED_MESSAGE(HasValues(ElementsAre( + ValueWithStr(Eq(utf8_value)))))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TEST(WideStringLogFormatTest, Literal) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + absl::string_view utf8_value = GetUtf8TestString(); + + EXPECT_CALL(test_sink, Send(AllOf(TextMessage(Eq(utf8_value)), + ENCODED_MESSAGE(HasValues(ElementsAre( + ValueWithLiteral(Eq(utf8_value)))))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << ABSL_LOG_INTERNAL_WIDE_LITERAL; +} + +#undef ABSL_LOG_INTERNAL_WIDE_LITERAL +#undef ABSL_LOG_INTERNAL_UTF8_LITERAL + +TYPED_TEST(WideStringLogFormatTest, InvalidCharactersAreReplaced) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + TypeParam value = L"AAA \xDC00 BBB"; + // NOLINTNEXTLINE(readability/utf8) + absl::string_view utf8_value = "AAA � BBB"; + + EXPECT_CALL(test_sink, Send(AllOf(TextMessage(Eq(utf8_value)), + ENCODED_MESSAGE(HasValues(ElementsAre( + ValueWithStr(Eq(utf8_value)))))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(WideStringLogFormatTest, EmptyWideString) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + TypeParam value = L""; + + EXPECT_CALL(test_sink, Send(AllOf(TextMessage(Eq("")), + ENCODED_MESSAGE(HasValues( + ElementsAre(ValueWithStr(Eq("")))))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TEST(WideStringLogFormatTest, MixedNarrowAndWideStrings) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Log(_, _, "1234")); + + test_sink.StartCapturingLogs(); + LOG(INFO) << "1" << L"2" << "3" << L"4"; +} + class CustomClass {}; std::ostream& operator<<(std::ostream& os, const CustomClass&) { return os << "CustomClass{}"; @@ -1675,6 +1867,29 @@ LOG(INFO) << std::string(2 * absl::log_internal::kLogMessageBufferSize, 'x'); } +TEST(StructuredLoggingOverflowTest, TruncatesWideStrings) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + // This message is too long and should be truncated to some unspecified size + // no greater than the buffer size but not too much less either. It should be + // truncated rather than discarded. + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(AllOf( + SizeIs(AllOf(Ge(absl::log_internal::kLogMessageBufferSize - 256), + Le(absl::log_internal::kLogMessageBufferSize))), + Each(Eq('x')))), + ENCODED_MESSAGE(HasOneStrThat(AllOf( + SizeIs(AllOf(Ge(absl::log_internal::kLogMessageBufferSize - 256), + Le(absl::log_internal::kLogMessageBufferSize))), + Each(Eq('x')))))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::wstring(2 * absl::log_internal::kLogMessageBufferSize, + L'x'); +} + struct StringLike { absl::string_view data; };