Support logging of user-defined types that implement `AbslStringify()`

If a user-defined type has `AbslStringify()` defined, it will always be used for logging over `operator<<`.

`HasAbslStringify` now uses the empty class `UnimplementedSink` for its checks instead of `StringifySink` in order to make it work in cases involving other sinks.

PiperOrigin-RevId: 485710377
Change-Id: Ibdd916151c7abc3269c35fbe79b772867f3d25e1
pull/1310/head
Phoebe Liang 2 years ago committed by Copybara-Service
parent 1649c037c5
commit e6044634dd
  1. 1
      CMake/AbseilDll.cmake
  2. 1
      absl/log/BUILD.bazel
  3. 1
      absl/log/CMakeLists.txt
  4. 48
      absl/log/internal/log_message.h
  5. 37
      absl/log/log.h
  6. 131
      absl/log/log_format_test.cc
  7. 1
      absl/strings/BUILD.bazel
  8. 1
      absl/strings/CMakeLists.txt
  9. 55
      absl/strings/internal/has_absl_stringify.h
  10. 9
      absl/strings/internal/stringify_sink.h
  11. 1
      absl/strings/str_cat.h

@ -244,6 +244,7 @@ set(ABSL_INTERNAL_DLL_FILES
"strings/internal/string_constant.h"
"strings/internal/stringify_sink.h"
"strings/internal/stringify_sink.cc"
"strings/internal/has_absl_stringify.h"
"strings/match.cc"
"strings/match.h"
"strings/numbers.cc"

@ -324,6 +324,7 @@ cc_test(
"//absl/log/internal:config",
"//absl/log/internal:test_matchers",
"//absl/strings",
"//absl/strings:str_format",
"@com_google_googletest//:gtest_main",
],
)

@ -681,6 +681,7 @@ absl_cc_test(
absl::log_internal_config
absl::log_internal_test_matchers
absl::scoped_mock_log
absl::str_format
absl::strings
GTest::gmock
GTest::gtest_main

@ -41,6 +41,7 @@
#include "absl/log/internal/nullguard.h"
#include "absl/log/log_entry.h"
#include "absl/log/log_sink.h"
#include "absl/strings/internal/has_absl_stringify.h"
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
@ -153,8 +154,17 @@ class LogMessage {
template <int SIZE>
LogMessage& operator<<(char (&buf)[SIZE]) ABSL_ATTRIBUTE_NOINLINE;
// Default: uses `ostream` logging to convert `v` to a string.
template <typename T>
// Types that support `AbslStringify()` are serialized that way.
template <typename T,
typename std::enable_if<
strings_internal::HasAbslStringify<T>::value, int>::type = 0>
LogMessage& operator<<(const T& v) ABSL_ATTRIBUTE_NOINLINE;
// Types that don't support `AbslStringify()` but do support streaming into a
// `std::ostream&` are serialized that way.
template <typename T,
typename std::enable_if<
!strings_internal::HasAbslStringify<T>::value, int>::type = 0>
LogMessage& operator<<(const T& v) ABSL_ATTRIBUTE_NOINLINE;
// Note: We explicitly do not support `operator<<` for non-const references
@ -205,12 +215,44 @@ class LogMessage {
std::ostream stream_;
};
// Helper class so that `AbslStringify()` can modify the LogMessage.
class StringifySink final {
public:
explicit StringifySink(LogMessage& message) : message_(message) {}
void Append(size_t count, char ch) { message_ << std::string(count, ch); }
void Append(absl::string_view v) { message_ << v; }
// For types that implement `AbslStringify` using `absl::Format()`.
friend void AbslFormatFlush(StringifySink* sink, absl::string_view v) {
sink->Append(v);
}
private:
LogMessage& message_;
};
// Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE`
template <typename T,
typename std::enable_if<strings_internal::HasAbslStringify<T>::value,
int>::type>
LogMessage& LogMessage::operator<<(const T& v) {
StringifySink sink(*this);
// Replace with public API.
AbslStringify(sink, v);
return *this;
}
// Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE`
template <typename T>
template <typename T,
typename std::enable_if<!strings_internal::HasAbslStringify<T>::value,
int>::type>
LogMessage& LogMessage::operator<<(const T& v) {
stream_ << log_internal::NullGuard<T>().Guard(v);
return *this;
}
inline LogMessage& LogMessage::operator<<(
std::ostream& (*m)(std::ostream& os)) {
stream_ << m;

@ -132,10 +132,45 @@
// as they would be if streamed into a `std::ostream`, however it should be
// noted that their actual type is unspecified.
//
// To implement a custom formatting operator for a type you own, define
// To implement a custom formatting operator for a type you own, there are two
// options: `AbslStringify()` or `std::ostream& operator<<(std::ostream&, ...)`.
// It is recommended that users make their types loggable through
// `AbslStringify()` as it is a universal stringification extension that also
// enables `absl::StrFormat` and `absl::StrCat` support. If both
// `AbslStringify()` and `std::ostream& operator<<(std::ostream&, ...)` are
// defined, `AbslStringify()` will be used.
//
// To use the `AbslStringify()` API, define a friend function template in your
// type's namespace with the following signature:
//
// template <typename Sink>
// void AbslStringify(Sink& sink, const UserDefinedType& value);
//
// `Sink` has the same interface as `absl::FormatSink`, but without
// `PutPaddedString()`.
//
// Example:
//
// struct Point {
// template <typename Sink>
// friend void AbslStringify(Sink& sink, const Point& p) {
// absl::Format(&sink, "(%v, %v)", p.x, p.y);
// }
//
// int x;
// int y;
// };
//
// To use `std::ostream& operator<<(std::ostream&, ...)`, define
// `std::ostream& operator<<(std::ostream&, ...)` in your type's namespace (for
// ADL) just as you would to stream it to `std::cout`.
//
// Currently `AbslStringify()` ignores output manipulators but this is not
// guaranteed behavior and may be subject to change in the future. If you would
// like guaranteed behavior regarding output manipulators, please use
// `std::ostream& operator<<(std::ostream&, ...)` to make custom types loggable
// instead.
//
// Those macros that support streaming honor output manipulators and `fmtflag`
// changes that output data (e.g. `std::ends`) or control formatting of data
// (e.g. `std::hex` and `std::fixed`), however flushing such a stream is

@ -32,6 +32,7 @@
#include "absl/log/scoped_mock_log.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
namespace {
@ -865,6 +866,111 @@ TEST(LogFormatTest, CustomNonCopyable) {
LOG(INFO) << value;
}
struct Point {
template <typename Sink>
friend void AbslStringify(Sink& sink, const Point& p) {
absl::Format(&sink, "(%d, %d)", p.x, p.y);
}
int x = 10;
int y = 20;
};
TEST(LogFormatTest, AbslStringifyExample) {
absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
Point p;
EXPECT_CALL(
test_sink,
Send(AllOf(
TextMessage(Eq("(10, 20)")), TextMessage(Eq(absl::StrCat(p))),
ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(10, 20)" })pb")))));
test_sink.StartCapturingLogs();
LOG(INFO) << p;
}
struct PointWithAbslStringifiyAndOstream {
template <typename Sink>
friend void AbslStringify(Sink& sink,
const PointWithAbslStringifiyAndOstream& p) {
absl::Format(&sink, "(%d, %d)", p.x, p.y);
}
int x = 10;
int y = 20;
};
ABSL_ATTRIBUTE_UNUSED std::ostream& operator<<(
std::ostream& os, const PointWithAbslStringifiyAndOstream&) {
return os << "Default to AbslStringify()";
}
TEST(LogFormatTest, CustomWithAbslStringifyAndOstream) {
absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
PointWithAbslStringifiyAndOstream p;
EXPECT_CALL(
test_sink,
Send(AllOf(
TextMessage(Eq("(10, 20)")), TextMessage(Eq(absl::StrCat(p))),
ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(10, 20)" })pb")))));
test_sink.StartCapturingLogs();
LOG(INFO) << p;
}
struct PointStreamsNothing {
template <typename Sink>
friend void AbslStringify(Sink&, const PointStreamsNothing&) {}
int x = 10;
int y = 20;
};
TEST(LogFormatTest, AbslStringifyStreamsNothing) {
absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
PointStreamsNothing p;
EXPECT_CALL(
test_sink,
Send(AllOf(TextMessage(Eq("77")), TextMessage(Eq(absl::StrCat(p, 77))),
ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" })pb")))));
test_sink.StartCapturingLogs();
LOG(INFO) << p << 77;
}
struct PointMultipleAppend {
template <typename Sink>
friend void AbslStringify(Sink& sink, const PointMultipleAppend& p) {
sink.Append("(");
sink.Append(absl::StrCat(p.x, ", ", p.y, ")"));
}
int x = 10;
int y = 20;
};
TEST(LogFormatTest, AbslStringifyMultipleAppend) {
absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
PointMultipleAppend p;
EXPECT_CALL(
test_sink,
Send(AllOf(
TextMessage(Eq("(10, 20)")), TextMessage(Eq(absl::StrCat(p))),
ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(" }
value { str: "10, 20)" })pb")))));
test_sink.StartCapturingLogs();
LOG(INFO) << p;
}
TEST(ManipulatorLogFormatTest, BoolAlphaTrue) {
absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
@ -1501,6 +1607,31 @@ TEST(ManipulatorLogFormatTest, CustomClassStreamsNothing) {
LOG(INFO) << value << 77;
}
struct PointPercentV {
template <typename Sink>
friend void AbslStringify(Sink& sink, const PointPercentV& p) {
absl::Format(&sink, "(%v, %v)", p.x, p.y);
}
int x = 10;
int y = 20;
};
TEST(ManipulatorLogFormatTest, IOManipsDoNotAffectAbslStringify) {
absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
PointPercentV p;
EXPECT_CALL(
test_sink,
Send(AllOf(
TextMessage(Eq("(10, 20)")), TextMessage(Eq(absl::StrCat(p))),
ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(10, 20)" })pb")))));
test_sink.StartCapturingLogs();
LOG(INFO) << std::hex << p;
}
// Tests that verify the behavior when more data are streamed into a `LOG`
// statement than fit in the buffer.
// Structured logging scenario is tested in other unit tests since the output is

@ -58,6 +58,7 @@ cc_library(
"charconv.h",
"escaping.h",
"internal/damerau_levenshtein_distance.h",
"internal/has_absl_stringify.h",
"internal/string_constant.h",
"match.h",
"numbers.h",

@ -23,6 +23,7 @@ absl_cc_library(
"escaping.h"
"internal/damerau_levenshtein_distance.h"
"internal/string_constant.h"
"internal/has_absl_stringify.h"
"match.h"
"numbers.h"
"str_cat.h"

@ -0,0 +1,55 @@
// Copyright 2022 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.
#ifndef ABSL_STRINGS_INTERNAL_HAS_ABSL_STRINGIFY_H_
#define ABSL_STRINGS_INTERNAL_HAS_ABSL_STRINGIFY_H_
#include <string>
#include <type_traits>
#include <utility>
#include "absl/strings/string_view.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace strings_internal {
// This is an empty class not intended to be used. It exists so that
// `HasAbslStringify` can reference a universal class rather than needing to be
// copied for each new sink.
class UnimplementedSink {
public:
void Append(size_t count, char ch);
void Append(string_view v);
// Support `absl::Format(&sink, format, args...)`.
friend void AbslFormatFlush(UnimplementedSink* sink, absl::string_view v);
};
template <typename T, typename = void>
struct HasAbslStringify : std::false_type {};
template <typename T>
struct HasAbslStringify<
T, std::enable_if_t<std::is_void<decltype(AbslStringify(
std::declval<strings_internal::UnimplementedSink&>(),
std::declval<const T&>()))>::value>> : std::true_type {};
} // namespace strings_internal
ABSL_NAMESPACE_END
} // namespace absl
#endif // ABSL_STRINGS_INTERNAL_HAS_ABSL_STRINGIFY_H_

@ -49,15 +49,6 @@ string_view ExtractStringification(StringifySink& sink, const T& v) {
return sink.buffer_;
}
template <typename T, typename = void>
struct HasAbslStringify : std::false_type {};
template <typename T>
struct HasAbslStringify<T, std::enable_if_t<std::is_void<decltype(AbslStringify(
std::declval<strings_internal::StringifySink&>(),
std::declval<const T&>()))>::value>>
: std::true_type {};
} // namespace strings_internal
ABSL_NAMESPACE_END

@ -95,6 +95,7 @@
#include <vector>
#include "absl/base/port.h"
#include "absl/strings/internal/has_absl_stringify.h"
#include "absl/strings/internal/stringify_sink.h"
#include "absl/strings/numbers.h"
#include "absl/strings/string_view.h"

Loading…
Cancel
Save