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;
 };