[log] Prepare helper for streaming container contents to strings.

PiperOrigin-RevId: 846769302
Change-Id: Ice80fd61edaf7fa4b97286444251abccb204679f
diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake
index 91e220a..b8b137c 100644
--- a/CMake/AbseilDll.cmake
+++ b/CMake/AbseilDll.cmake
@@ -179,6 +179,7 @@
   "log/internal/conditions.cc"
   "log/internal/conditions.h"
   "log/internal/config.h"
+  "log/internal/container.h"
   "log/internal/fnmatch.h"
   "log/internal/fnmatch.cc"
   "log/internal/globals.cc"
diff --git a/absl/log/CMakeLists.txt b/absl/log/CMakeLists.txt
index 51f399e..9d097ab 100644
--- a/absl/log/CMakeLists.txt
+++ b/absl/log/CMakeLists.txt
@@ -1204,3 +1204,36 @@
     absl::log_internal_fnmatch
     GTest::gmock_main
 )
+
+absl_cc_library(
+  NAME
+    log_internal_container
+  HDRS
+    "internal/container.h"
+  COPTS
+    ${ABSL_DEFAULT_COPTS}
+  LINKOPTS
+    ${ABSL_DEFAULT_LINKOPTS}
+  DEPS
+    absl::config
+    absl::requires_internal
+    absl::strings
+)
+
+absl_cc_test(
+  NAME
+    internal_container_test
+  SRCS
+    "internal/container_test.cc"
+  COPTS
+    ${ABSL_TEST_COPTS}
+  LINKOPTS
+    ${ABSL_DEFAULT_LINKOPTS}
+  DEPS
+    absl::config
+    absl::log_internal_container
+    absl::span
+    absl::strings
+    absl::str_format
+    GTest::gmock_main
+)
diff --git a/absl/log/internal/BUILD.bazel b/absl/log/internal/BUILD.bazel
index bb20a95..c5bdd67 100644
--- a/absl/log/internal/BUILD.bazel
+++ b/absl/log/internal/BUILD.bazel
@@ -548,3 +548,31 @@
         "@google_benchmark//:benchmark_main",
     ],
 )
+
+cc_library(
+    name = "container",
+    hdrs = ["container.h"],
+    copts = ABSL_DEFAULT_COPTS,
+    linkopts = ABSL_DEFAULT_LINKOPTS,
+    deps = [
+        "//absl/base:config",
+        "//absl/meta:requires",
+        "//absl/strings",
+    ],
+)
+
+cc_test(
+    name = "container_test",
+    srcs = ["container_test.cc"],
+    copts = ABSL_TEST_COPTS,
+    linkopts = ABSL_DEFAULT_LINKOPTS,
+    deps = [
+        ":container",
+        "//absl/base:config",
+        "//absl/strings",
+        "//absl/strings:str_format",
+        "//absl/types:span",
+        "@googletest//:gtest",
+        "@googletest//:gtest_main",
+    ],
+)
diff --git a/absl/log/internal/container.h b/absl/log/internal/container.h
new file mode 100644
index 0000000..1144652
--- /dev/null
+++ b/absl/log/internal/container.h
@@ -0,0 +1,312 @@
+// Copyright 2025 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// The typical use looks like this:
+//
+//   LOG(INFO) << LogContainer(container);
+//
+// By default, LogContainer() uses the LogShortUpTo100 policy: comma-space
+// separation, no newlines, and with limit of 100 items.
+//
+// Policies can be specified:
+//
+//   LOG(INFO) << LogContainer(container, LogMultiline());
+//
+// The above example will print the container using newlines between elements,
+// enclosed in [] braces.
+//
+// See below for further details on policies.
+
+#ifndef ABSL_LOG_INTERNAL_CONTAINER_H_
+#define ABSL_LOG_INTERNAL_CONTAINER_H_
+
+#include <cstdint>
+#include <limits>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <type_traits>
+
+#include "absl/base/config.h"
+#include "absl/meta/internal/requires.h"
+#include "absl/strings/str_cat.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+
+// Several policy classes below determine how LogRangeToStream will
+// format a range of items.  A Policy class should have these methods:
+//
+// Called to print an individual container element.
+//   void Log(ostream &out, const ElementT &element) const;
+//
+// Called before printing the set of elements:
+//   void LogOpening(ostream &out) const;
+//
+// Called after printing the set of elements:
+//   void LogClosing(ostream &out) const;
+//
+// Called before printing the first element:
+//   void LogFirstSeparator(ostream &out) const;
+//
+// Called before printing the remaining elements:
+//   void LogSeparator(ostream &out) const;
+//
+// Returns the maximum number of elements to print:
+//   int64 MaxElements() const;
+//
+// Called to print an indication that MaximumElements() was reached:
+//   void LogEllipsis(ostream &out) const;
+
+namespace internal {
+
+struct LogBase {
+  template <typename ElementT>
+  void Log(std::ostream &out, const ElementT &element) const {  // NOLINT
+    // Fallback to `AbslStringify` if the type does not have `operator<<`.
+    if constexpr (meta_internal::Requires<std::ostream, ElementT>(
+                      [](auto&& x, auto&& y) -> decltype(x << y) {})) {
+      out << element;
+    } else {
+      out << absl::StrCat(element);
+    }
+  }
+  void LogEllipsis(std::ostream &out) const {  // NOLINT
+    out << "...";
+  }
+};
+
+struct LogShortBase : public LogBase {
+  void LogOpening(std::ostream &out) const { out << "["; }        // NOLINT
+  void LogClosing(std::ostream &out) const { out << "]"; }        // NOLINT
+  void LogFirstSeparator(std::ostream &out) const { out << ""; }  // NOLINT
+  void LogSeparator(std::ostream &out) const { out << ", "; }     // NOLINT
+};
+
+struct LogMultilineBase : public LogBase {
+  void LogOpening(std::ostream &out) const { out << "["; }          // NOLINT
+  void LogClosing(std::ostream &out) const { out << "\n]"; }        // NOLINT
+  void LogFirstSeparator(std::ostream &out) const { out << "\n"; }  // NOLINT
+  void LogSeparator(std::ostream &out) const { out << "\n"; }       // NOLINT
+};
+
+struct LogLegacyBase : public LogBase {
+  void LogOpening(std::ostream &out) const { out << ""; }         // NOLINT
+  void LogClosing(std::ostream &out) const { out << ""; }         // NOLINT
+  void LogFirstSeparator(std::ostream &out) const { out << ""; }  // NOLINT
+  void LogSeparator(std::ostream &out) const { out << " "; }      // NOLINT
+};
+
+}  // namespace internal
+
+// LogShort uses [] braces and separates items with comma-spaces.  For
+// example "[1, 2, 3]".
+struct LogShort : public internal::LogShortBase {
+  int64_t MaxElements() const { return (std::numeric_limits<int64_t>::max)(); }
+};
+
+// LogShortUpToN(max_elements) formats the same as LogShort but prints no more
+// than the max_elements elements.
+class LogShortUpToN : public internal::LogShortBase {
+ public:
+  explicit LogShortUpToN(int64_t max_elements) : max_elements_(max_elements) {}
+  int64_t MaxElements() const { return max_elements_; }
+
+ private:
+  int64_t max_elements_;
+};
+
+// LogShortUpTo100 formats the same as LogShort but prints no more
+// than 100 elements.
+struct LogShortUpTo100 : public LogShortUpToN {
+  LogShortUpTo100() : LogShortUpToN(100) {}
+};
+
+// LogMultiline uses [] braces and separates items with
+// newlines.  For example "[
+// 1
+// 2
+// 3
+// ]".
+struct LogMultiline : public internal::LogMultilineBase {
+  int64_t MaxElements() const { return (std::numeric_limits<int64_t>::max)(); }
+};
+
+// LogMultilineUpToN(max_elements) formats the same as LogMultiline but
+// prints no more than max_elements elements.
+class LogMultilineUpToN : public internal::LogMultilineBase {
+ public:
+  explicit LogMultilineUpToN(int64_t max_elements)
+      : max_elements_(max_elements) {}
+  int64_t MaxElements() const { return max_elements_; }
+
+ private:
+  int64_t max_elements_;
+};
+
+// LogMultilineUpTo100 formats the same as LogMultiline but
+// prints no more than 100 elements.
+struct LogMultilineUpTo100 : public LogMultilineUpToN {
+  LogMultilineUpTo100() : LogMultilineUpToN(100) {}
+};
+
+// The legacy behavior of LogSequence() does not use braces and
+// separates items with spaces.  For example "1 2 3".
+struct LogLegacyUpTo100 : public internal::LogLegacyBase {
+  int64_t MaxElements() const { return 100; }
+};
+struct LogLegacy : public internal::LogLegacyBase {
+  int64_t MaxElements() const { return (std::numeric_limits<int64_t>::max)(); }
+};
+
+// The default policy for new code.
+typedef LogShortUpTo100 LogDefault;
+
+// LogRangeToStream should be used to define operator<< for
+// STL and STL-like containers.  For example, see stl_logging.h.
+template <typename IteratorT, typename PolicyT>
+inline void LogRangeToStream(std::ostream &out,  // NOLINT
+                             IteratorT begin, IteratorT end,
+                             const PolicyT &policy) {
+  policy.LogOpening(out);
+  for (int64_t i = 0; begin != end && i < policy.MaxElements(); ++i, ++begin) {
+    if (i == 0) {
+      policy.LogFirstSeparator(out);
+    } else {
+      policy.LogSeparator(out);
+    }
+    policy.Log(out, *begin);
+  }
+  if (begin != end) {
+    policy.LogSeparator(out);
+    policy.LogEllipsis(out);
+  }
+  policy.LogClosing(out);
+}
+
+namespace detail {
+
+// RangeLogger is a helper class for LogRange and LogContainer; do not use it
+// directly.  This object captures iterators into the argument of the LogRange
+// and LogContainer functions, so its lifetime should be confined to a single
+// logging statement.  Objects of this type should not be assigned to local
+// variables.
+template <typename IteratorT, typename PolicyT>
+class RangeLogger {
+ public:
+  RangeLogger(const IteratorT &begin, const IteratorT &end,
+              const PolicyT &policy)
+      : begin_(begin), end_(end), policy_(policy) {}
+
+  friend std::ostream &operator<<(std::ostream &out, const RangeLogger &range) {
+    LogRangeToStream<IteratorT, PolicyT>(out, range.begin_, range.end_,
+                                         range.policy_);
+    return out;
+  }
+
+  // operator<< above is generally recommended. However, some situations may
+  // require a string, so a convenience str() method is provided as well.
+  std::string str() const {
+    std::stringstream ss;
+    ss << *this;
+    return ss.str();
+  }
+
+ private:
+  IteratorT begin_;
+  IteratorT end_;
+  PolicyT policy_;
+};
+
+template <typename E>
+class EnumLogger {
+ public:
+  explicit EnumLogger(E e) : e_(e) {}
+
+  friend std::ostream &operator<<(std::ostream &out, const EnumLogger &v) {
+    using I = typename std::underlying_type<E>::type;
+    return out << static_cast<I>(v.e_);
+  }
+
+ private:
+  E e_;
+};
+
+}  // namespace detail
+
+// Log a range using "policy".  For example:
+//
+//   LOG(INFO) << LogRange(start_pos, end_pos, LogMultiline());
+//
+// The above example will print the range using newlines between
+// elements, enclosed in [] braces.
+template <typename IteratorT, typename PolicyT>
+detail::RangeLogger<IteratorT, PolicyT> LogRange(const IteratorT &begin,
+                                                 const IteratorT &end,
+                                                 const PolicyT &policy) {
+  return detail::RangeLogger<IteratorT, PolicyT>(begin, end, policy);
+}
+
+// Log a range.  For example:
+//
+//   LOG(INFO) << LogRange(start_pos, end_pos);
+//
+// By default, Range() uses the LogShortUpTo100 policy: comma-space
+// separation, no newlines, and with limit of 100 items.
+template <typename IteratorT>
+detail::RangeLogger<IteratorT, LogDefault> LogRange(const IteratorT &begin,
+                                                    const IteratorT &end) {
+  return LogRange(begin, end, LogDefault());
+}
+
+// Log a container using "policy".  For example:
+//
+//   LOG(INFO) << LogContainer(container, LogMultiline());
+//
+// The above example will print the container using newlines between
+// elements, enclosed in [] braces.
+template <typename ContainerT, typename PolicyT>
+auto LogContainer(const ContainerT& container, const PolicyT& policy)
+    -> decltype(LogRange(container.begin(), container.end(), policy)) {
+  return LogRange(container.begin(), container.end(), policy);
+}
+
+// Log a container.  For example:
+//
+//   LOG(INFO) << LogContainer(container);
+//
+// By default, Container() uses the LogShortUpTo100 policy: comma-space
+// separation, no newlines, and with limit of 100 items.
+template <typename ContainerT>
+auto LogContainer(const ContainerT& container)
+    -> decltype(LogContainer(container, LogDefault())) {
+  return LogContainer(container, LogDefault());
+}
+
+// Log a (possibly scoped) enum.  For example:
+//
+//   enum class Color { kRed, kGreen, kBlue };
+//   LOG(INFO) << LogEnum(kRed);
+template <typename E>
+detail::EnumLogger<E> LogEnum(E e) {
+  static_assert(std::is_enum<E>::value, "must be an enum");
+  return detail::EnumLogger<E>(e);
+}
+
+}  // namespace log_internal
+ABSL_NAMESPACE_END
+}  // namespace absl
+
+#endif  // ABSL_LOG_INTERNAL_CONTAINER_H_
diff --git a/absl/log/internal/container_test.cc b/absl/log/internal/container_test.cc
new file mode 100644
index 0000000..0a5a058
--- /dev/null
+++ b/absl/log/internal/container_test.cc
@@ -0,0 +1,254 @@
+// Copyright 2025 The Abseil Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "absl/log/internal/container.h"
+
+#include <cstdint>
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "absl/base/config.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/str_join.h"
+#include "absl/types/span.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace log_internal {
+namespace {
+
+class ContainerLoggingTest : public ::testing::Test {
+ protected:
+  ContainerLoggingTest() : stream_(new std::stringstream) {}
+  std::ostream& stream() { return *stream_; }
+  std::string logged() {
+    std::string r = stream_->str();
+    stream_ = std::make_unique<std::stringstream>();
+    return r;
+  }
+
+ private:
+  std::unique_ptr<std::stringstream> stream_;
+};
+
+TEST_F(ContainerLoggingTest, ShortRange) {
+  std::vector<std::string> words = {"hi", "hello"};
+  LogRangeToStream(stream(), words.begin(), words.end(), LogMultiline());
+  EXPECT_EQ("[\nhi\nhello\n]", logged());
+}
+
+TEST_F(ContainerLoggingTest, LegacyRange) {
+  std::vector<int> lengths = {1, 2};
+  LogRangeToStream(stream(), lengths.begin(), lengths.end(),
+                   LogLegacyUpTo100());
+  EXPECT_EQ("1 2", logged());
+}
+
+TEST_F(ContainerLoggingTest, ToString) {
+  std::vector<int> lengths = {1, 2, 3, 4, 5};
+  EXPECT_EQ(LogContainer(lengths).str(), "[1, 2, 3, 4, 5]");
+}
+
+class UserDefFriend {
+ public:
+  explicit UserDefFriend(int i) : i_(i) {}
+
+ private:
+  friend std::ostream& operator<<(std::ostream& str, const UserDefFriend& i) {
+    return str << i.i_;
+  }
+  int i_;
+};
+
+TEST_F(ContainerLoggingTest, RangeOfUserDefined) {
+  std::vector<UserDefFriend> ints = {UserDefFriend(1), UserDefFriend(2),
+                                     UserDefFriend(3)};
+  LogRangeToStream(stream(), ints.begin(), ints.begin() + 1, LogDefault());
+  LogRangeToStream(stream(), ints.begin() + 1, ints.begin() + 2,
+                   LogMultiline());
+  LogRangeToStream(stream(), ints.begin() + 2, ints.begin() + 3, LogDefault());
+  LogRangeToStream(stream(), ints.begin(), ints.begin(), LogMultiline());
+
+  EXPECT_EQ("[1][\n2\n][3][\n]", logged());
+}
+
+TEST_F(ContainerLoggingTest, FullContainer) {
+  std::vector<int> ints;
+  std::vector<int> ints100;
+  std::vector<int> ints123;
+  int64_t max_elements = 123;
+  std::string expected1;
+  std::string expected2;
+  std::string expected3;
+  std::string expected4;
+  std::string expected5;
+  std::string expected6;
+  std::string expected7;
+  std::string expected8;
+  std::string expected9;
+  for (int i = 0; i < 1000; ++i) {
+    ints.push_back(i);
+    if (i < 100) {
+      ints100.push_back(i);
+    }
+    if (i < max_elements) {
+      ints123.push_back(i);
+    }
+  }
+  expected1 = "[\n" + absl::StrJoin(ints, "\n") + "\n]";
+  expected2 = "[" + absl::StrJoin(ints, ", ") + "]";
+  expected3 = "[\n" + absl::StrJoin(ints100, "\n") + "\n...\n]";
+  expected4 = "[" + absl::StrJoin(ints100, ", ") + ", ...]";
+  expected5 = absl::StrJoin(ints100, " ") + " ...";
+  expected6 = "[\n" + absl::StrJoin(ints, "\n") + "\n]";
+  expected7 = "[\n" + absl::StrJoin(ints123, "\n") + "\n...\n]";
+  expected8 = "[" + absl::StrJoin(ints, ", ") + "]";
+  expected9 = "[" + absl::StrJoin(ints123, ", ") + ", ...]";
+
+  LogRangeToStream(stream(), ints.begin(), ints.end(), LogMultiline());
+  EXPECT_EQ(expected1, logged());
+  LogRangeToStream(stream(), ints.begin(), ints.end(), LogShort());
+  EXPECT_EQ(expected2, logged());
+  LogRangeToStream(stream(), ints.begin(), ints.end(), LogMultilineUpTo100());
+  EXPECT_EQ(expected3, logged());
+  LogRangeToStream(stream(), ints.begin(), ints.end(), LogShortUpTo100());
+  EXPECT_EQ(expected4, logged());
+  LogRangeToStream(stream(), ints.begin(), ints.end(), LogLegacyUpTo100());
+  EXPECT_EQ(expected5, logged());
+
+  LogRangeToStream(stream(), ints.begin(), ints.end(),
+                   LogMultilineUpToN(ints.size()));
+  EXPECT_EQ(expected6, logged());
+  LogRangeToStream(stream(), ints.begin(), ints.end(),
+                   LogMultilineUpToN(max_elements));
+  EXPECT_EQ(expected7, logged());
+  LogRangeToStream(stream(), ints.begin(), ints.end(),
+                   LogShortUpToN(ints.size()));
+  EXPECT_EQ(expected8, logged());
+  LogRangeToStream(stream(), ints.begin(), ints.end(),
+                   LogShortUpToN(max_elements));
+  EXPECT_EQ(expected9, logged());
+}
+
+TEST_F(ContainerLoggingTest, LogContainer) {
+  std::set<int> ints = {1, 2, 3};
+  stream() << LogContainer(ints, LogMultiline());
+  EXPECT_EQ("[\n1\n2\n3\n]", logged());
+
+  stream() << LogContainer(ints);
+  EXPECT_EQ("[1, 2, 3]", logged());
+
+  stream() << LogContainer(std::vector<int>(ints.begin(), ints.end()),
+                           LogLegacyUpTo100());
+  EXPECT_EQ("1 2 3", logged());
+}
+
+TEST_F(ContainerLoggingTest, LogMutableSpan) {
+  std::vector<int> ints = {1, 2, 3};
+  absl::Span<int> int_span(ints);
+  stream() << LogContainer(int_span);
+  EXPECT_EQ("[1, 2, 3]", logged());
+}
+
+TEST_F(ContainerLoggingTest, LogRange) {
+  std::set<int> ints = {1, 2, 3};
+  stream() << LogRange(ints.begin(), ints.end(), LogMultiline());
+  EXPECT_EQ("[\n1\n2\n3\n]", logged());
+
+  stream() << LogRange(ints.begin(), ints.end());
+  EXPECT_EQ("[1, 2, 3]", logged());
+}
+
+// Some class with a custom Stringify
+class C {
+ public:
+  explicit C(int x) : x_(x) {}
+
+ private:
+  // This is intentionally made private for the purposes of the test;
+  //` AbslStringify` isn't meant to be called directly, and instead invoked
+  // via `StrCat` and friends.
+  template <typename Sink>
+  friend void AbslStringify(Sink& sink, const C& p) {
+    absl::Format(&sink, "C(%d)", p.x_);
+  }
+
+  int x_;
+};
+
+TEST_F(ContainerLoggingTest, LogContainerWithCustomStringify) {
+  std::vector<C> c = {C(1), C(2), C(3)};
+  stream() << LogContainer(c);
+  EXPECT_EQ("[C(1), C(2), C(3)]", logged());
+}
+
+class LogEnumTest : public ContainerLoggingTest {
+ protected:
+  enum Unscoped { kUnscoped0, kUnscoped1, kUnscoped2 };
+
+  enum StreamableUnscoped {
+    kStreamableUnscoped0,
+    kStreamableUnscoped1,
+    kStreamableUnscoped2
+  };
+
+  enum class Scoped { k0, k1, k2 };
+
+  enum class StreamableScoped { k0, k1, k2 };
+
+  friend std::ostream& operator<<(std::ostream& os, StreamableUnscoped v) {
+    return os << LogEnum(v);
+  }
+
+  friend std::ostream& operator<<(std::ostream& os, StreamableScoped v) {
+    return os << LogEnum(v);
+  }
+};
+
+TEST_F(LogEnumTest, Unscoped) {
+  stream() << LogEnum(kUnscoped0) << "," << LogEnum(kUnscoped1) << ","
+           << LogEnum(kUnscoped2);
+  EXPECT_EQ("0,1,2", logged());
+}
+
+TEST_F(LogEnumTest, StreamableUnscoped) {
+  stream() << kStreamableUnscoped0 << "," << kStreamableUnscoped1 << ","
+           << kStreamableUnscoped2;
+  EXPECT_EQ("0,1,2", logged());
+}
+
+TEST_F(LogEnumTest, Scoped) {
+  stream() << LogEnum(Scoped::k0) << "," << LogEnum(Scoped::k1) << ","
+           << LogEnum(Scoped::k2);
+  EXPECT_EQ("0,1,2", logged());
+}
+
+TEST_F(LogEnumTest, StreamableScoped) {
+  // Test using LogEnum to implement an operator<<.
+  stream() << StreamableScoped::k0 << "," << StreamableScoped::k1 << ","
+           << StreamableScoped::k2;
+  EXPECT_EQ("0,1,2", logged());
+}
+
+}  // namespace
+}  // namespace log_internal
+ABSL_NAMESPACE_END
+}  // namespace absl