New internal-use-only classes `AsStructuredLiteralImpl` and `AsStructuredValueImpl`

PiperOrigin-RevId: 714130164
Change-Id: Ib76de9efeecf5344ed67ca27680937971d9a84e3
diff --git a/absl/log/CMakeLists.txt b/absl/log/CMakeLists.txt
index 085a228..73deb45 100644
--- a/absl/log/CMakeLists.txt
+++ b/absl/log/CMakeLists.txt
@@ -206,6 +206,7 @@
     absl::log_internal_proto
     absl::log_internal_log_sink_set
     absl::log_internal_nullguard
+    absl::log_internal_structured_proto
     absl::log_globals
     absl::log_entry
     absl::log_severity
@@ -666,9 +667,11 @@
   LINKOPTS
     ${ABSL_DEFAULT_LINKOPTS}
   DEPS
+    absl::any_invocable
     absl::config
     absl::core_headers
     absl::log_internal_message
+    absl::log_internal_structured_proto
     absl::strings
 )
 
diff --git a/absl/log/internal/BUILD.bazel b/absl/log/internal/BUILD.bazel
index 4fcbb83..2d74ae9 100644
--- a/absl/log/internal/BUILD.bazel
+++ b/absl/log/internal/BUILD.bazel
@@ -23,7 +23,7 @@
 
 package(
     default_visibility = [
-        "//absl/log:__pkg__",
+        ":internal_users",
     ],
     features = [
         "header_modules",
@@ -35,6 +35,13 @@
 licenses(["notice"])
 
 package_group(
+    name = "internal_users",
+    packages = [
+        "//absl/log",
+    ],
+)
+
+package_group(
     name = "structured_proto_users",
     packages = [
         "//absl/log/...",
@@ -183,6 +190,7 @@
         ":log_sink_set",
         ":nullguard",
         ":proto",
+        ":structured_proto",
         "//absl/base",
         "//absl/base:config",
         "//absl/base:core_headers",
@@ -287,10 +295,16 @@
     hdrs = ["structured.h"],
     copts = ABSL_DEFAULT_COPTS,
     linkopts = ABSL_DEFAULT_LINKOPTS,
+    visibility = [
+        ":internal_users",
+        ":structured_proto_users",
+    ],
     deps = [
         ":log_message",
+        ":structured_proto",
         "//absl/base:config",
         "//absl/base:core_headers",
+        "//absl/functional:any_invocable",
         "//absl/strings",
     ],
 )
@@ -355,6 +369,10 @@
     hdrs = ["test_helpers.h"],
     copts = ABSL_DEFAULT_COPTS,
     linkopts = ABSL_DEFAULT_LINKOPTS,
+    visibility = [
+        ":internal_users",
+        ":structured_proto_users",
+    ],
     deps = [
         ":globals",
         "//absl/base:config",
@@ -372,6 +390,10 @@
     hdrs = ["test_matchers.h"],
     copts = ABSL_DEFAULT_COPTS,
     linkopts = ABSL_DEFAULT_LINKOPTS,
+    visibility = [
+        ":internal_users",
+        ":structured_proto_users",
+    ],
     deps = [
         ":test_helpers",
         "//absl/base:config",
@@ -402,6 +424,10 @@
     hdrs = ["proto.h"],
     copts = ABSL_DEFAULT_COPTS,
     linkopts = ABSL_DEFAULT_LINKOPTS,
+    visibility = [
+        ":internal_users",
+        ":structured_proto_users",
+    ],
     deps = [
         "//absl/base",
         "//absl/base:config",
diff --git a/absl/log/internal/log_message.cc b/absl/log/internal/log_message.cc
index 51961fd..9e7722d 100644
--- a/absl/log/internal/log_message.cc
+++ b/absl/log/internal/log_message.cc
@@ -48,6 +48,7 @@
 #include "absl/log/internal/log_format.h"
 #include "absl/log/internal/log_sink_set.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"
@@ -632,6 +633,47 @@
 template void LogMessage::CopyToEncodedBuffer<
     LogMessage::StringType::kNotLiteral>(char ch, size_t num);
 
+template void LogMessage::CopyToEncodedBufferWithStructuredProtoField<
+    LogMessage::StringType::kLiteral>(StructuredProtoField field,
+                                      absl::string_view str);
+template void LogMessage::CopyToEncodedBufferWithStructuredProtoField<
+    LogMessage::StringType::kNotLiteral>(StructuredProtoField field,
+                                         absl::string_view str);
+
+template <LogMessage::StringType str_type>
+void LogMessage::CopyToEncodedBufferWithStructuredProtoField(
+    StructuredProtoField field, absl::string_view str) {
+  auto encoded_remaining_copy = data_->encoded_remaining();
+  size_t encoded_field_size = BufferSizeForStructuredProtoField(field);
+  constexpr uint8_t tag_value = str_type == StringType::kLiteral
+                                    ? ValueTag::kStringLiteral
+                                    : ValueTag::kString;
+  auto start = EncodeMessageStart(
+      EventTag::kValue,
+      encoded_field_size +
+          BufferSizeFor(tag_value, WireType::kLengthDelimited) + str.size(),
+      &encoded_remaining_copy);
+
+  // Write the encoded proto field.
+  if (!EncodeStructuredProtoField(field, encoded_remaining_copy)) {
+    // The header / field will not fit; zero `encoded_remaining()` so we
+    // don't write anything else later.
+    data_->encoded_remaining().remove_suffix(data_->encoded_remaining().size());
+    return;
+  }
+
+  // Write the string, truncating if necessary.
+  if (!EncodeStringTruncate(ValueTag::kString, str, &encoded_remaining_copy)) {
+    // The length of the string itself did not fit; zero `encoded_remaining()`
+    // so the value is not encoded at all.
+    data_->encoded_remaining().remove_suffix(data_->encoded_remaining().size());
+    return;
+  }
+
+  EncodeMessageLength(start, &encoded_remaining_copy);
+  data_->encoded_remaining() = encoded_remaining_copy;
+}
+
 // We intentionally don't return from these destructors. Disable MSVC's warning
 // about the destructor never returning as we do so intentionally here.
 #if defined(_MSC_VER) && !defined(__clang__)
diff --git a/absl/log/internal/log_message.h b/absl/log/internal/log_message.h
index 474d1da..7d0e403 100644
--- a/absl/log/internal/log_message.h
+++ b/absl/log/internal/log_message.h
@@ -41,6 +41,7 @@
 #include "absl/base/log_severity.h"
 #include "absl/base/nullability.h"
 #include "absl/log/internal/nullguard.h"
+#include "absl/log/internal/structured_proto.h"
 #include "absl/log/log_entry.h"
 #include "absl/log/log_sink.h"
 #include "absl/strings/has_absl_stringify.h"
@@ -52,6 +53,8 @@
 namespace log_internal {
 constexpr int kLogMessageBufferSize = 15000;
 
+enum class StructuredStringType;
+
 class LogMessage {
  public:
   struct InfoTag {};
@@ -217,6 +220,10 @@
   struct LogMessageData;  // Opaque type containing message state
   friend class AsLiteralImpl;
   friend class StringifySink;
+  template <StructuredStringType str_type>
+  friend class AsStructuredStringTypeImpl;
+  template <typename T>
+  friend class AsStructuredValueImpl;
 
   // This streambuf writes directly into the structured logging buffer so that
   // arbitrary types can be encoded as string data (using
@@ -247,6 +254,13 @@
   template <StringType str_type>
   void CopyToEncodedBuffer(char ch, size_t num) ABSL_ATTRIBUTE_NOINLINE;
 
+  // Copies `field` to the encoded buffer, then appends `str` after it
+  // (truncating `str` if necessary to fit).
+  template <StringType str_type>
+  void CopyToEncodedBufferWithStructuredProtoField(StructuredProtoField field,
+                                                   absl::string_view str)
+      ABSL_ATTRIBUTE_NOINLINE;
+
   // Returns `true` if the message is fatal or enabled debug-fatal.
   bool IsFatal() const;
 
diff --git a/absl/log/internal/structured.h b/absl/log/internal/structured.h
index abc0a5a..50783df 100644
--- a/absl/log/internal/structured.h
+++ b/absl/log/internal/structured.h
@@ -20,10 +20,14 @@
 #define ABSL_LOG_INTERNAL_STRUCTURED_H_
 
 #include <ostream>
+#include <string>
 
-#include "absl/base/config.h"
 #include "absl/base/attributes.h"
+#include "absl/base/config.h"
+#include "absl/functional/any_invocable.h"
 #include "absl/log/internal/log_message.h"
+#include "absl/log/internal/structured_proto.h"
+#include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 
 namespace absl {
@@ -54,6 +58,105 @@
   }
 };
 
+enum class StructuredStringType {
+  kLiteral,
+  kNotLiteral,
+};
+
+// Structured log data for a string and associated structured proto field,
+// both of which must outlive this object.
+template <StructuredStringType str_type>
+class ABSL_MUST_USE_RESULT AsStructuredStringTypeImpl final {
+ public:
+  constexpr AsStructuredStringTypeImpl(
+      absl::string_view str ABSL_ATTRIBUTE_LIFETIME_BOUND,
+      StructuredProtoField field ABSL_ATTRIBUTE_LIFETIME_BOUND)
+      : str_(str), field_(field) {}
+
+ private:
+  absl::string_view str_;
+  StructuredProtoField field_;
+
+  friend std::ostream& operator<<(std::ostream& os,
+                                  const AsStructuredStringTypeImpl& impl) {
+    return os << impl.str_;
+  }
+  void AddToMessage(LogMessage& m) const {
+    if (str_type == StructuredStringType::kLiteral) {
+      return m.CopyToEncodedBufferWithStructuredProtoField<
+          log_internal::LogMessage::StringType::kLiteral>(field_, str_);
+    } else {
+      return m.CopyToEncodedBufferWithStructuredProtoField<
+          log_internal::LogMessage::StringType::kNotLiteral>(field_, str_);
+    }
+  }
+  friend LogMessage& operator<<(LogMessage& m,
+                                const AsStructuredStringTypeImpl& impl) {
+    impl.AddToMessage(m);
+    return m;
+  }
+};
+
+using AsStructuredLiteralImpl =
+    AsStructuredStringTypeImpl<StructuredStringType::kLiteral>;
+using AsStructuredNotLiteralImpl =
+    AsStructuredStringTypeImpl<StructuredStringType::kNotLiteral>;
+
+// Structured log data for a stringifyable type T and associated structured
+// proto field, both of which must outlive this object.
+template <typename T>
+class ABSL_MUST_USE_RESULT AsStructuredValueImpl final {
+ public:
+  using ValueFormatter = absl::AnyInvocable<std::string(T) const>;
+
+  constexpr AsStructuredValueImpl(
+      T value ABSL_ATTRIBUTE_LIFETIME_BOUND,
+      StructuredProtoField field ABSL_ATTRIBUTE_LIFETIME_BOUND,
+      ValueFormatter value_formatter =
+          [](T value) { return absl::StrCat(value); })
+      : value_(value),
+        field_(field),
+        value_formatter_(std::move(value_formatter)) {}
+
+ private:
+  T value_;
+  StructuredProtoField field_;
+  ValueFormatter value_formatter_;
+
+  friend std::ostream& operator<<(std::ostream& os,
+                                  const AsStructuredValueImpl& impl) {
+    return os << impl.value_formatter_(impl.value_);
+  }
+  void AddToMessage(LogMessage& m) const {
+    m.CopyToEncodedBufferWithStructuredProtoField<
+        log_internal::LogMessage::StringType::kNotLiteral>(
+        field_, value_formatter_(value_));
+  }
+  friend LogMessage& operator<<(LogMessage& m,
+                                const AsStructuredValueImpl& impl) {
+    impl.AddToMessage(m);
+    return m;
+  }
+};
+
+#ifdef ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION
+
+// Template deduction guide so `AsStructuredValueImpl(42, data)` works
+// without specifying the template type.
+template <typename T>
+AsStructuredValueImpl(T value, StructuredProtoField field)
+    -> AsStructuredValueImpl<T>;
+
+// Template deduction guide so `AsStructuredValueImpl(42, data, formatter)`
+// works without specifying the template type.
+template <typename T>
+AsStructuredValueImpl(
+    T value, StructuredProtoField field,
+    typename AsStructuredValueImpl<T>::ValueFormatter value_formatter)
+    -> AsStructuredValueImpl<T>;
+
+#endif  // ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION
+
 }  // namespace log_internal
 ABSL_NAMESPACE_END
 }  // namespace absl