Commit ebab79b5 by Andy Getzendanner Committed by Copybara-Service

Release structured logging.

This stores the operands to LOG (and CHECK) as separate fields in a serialized protobuf.  The protobuf format is not yet published.

PiperOrigin-RevId: 489275799
Change-Id: I86d83671a6b1feb2bddd5bee51552907897ca8eb
parent 0c048353
......@@ -196,6 +196,18 @@ cc_library(
],
)
cc_library(
name = "structured",
hdrs = ["structured.h"],
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
"//absl/base:config",
"//absl/log/internal:structured",
"//absl/strings",
],
)
# Test targets
cc_test(
name = "basic_log_test",
......@@ -458,6 +470,23 @@ cc_test(
],
)
cc_test(
name = "structured_test",
size = "small",
srcs = ["structured_test.cc"],
copts = ABSL_TEST_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":log",
":scoped_mock_log",
":structured",
"//absl/base:core_headers",
"//absl/log/internal:test_helpers",
"//absl/log/internal:test_matchers",
"@com_google_googletest//:gtest_main",
],
)
cc_binary(
name = "log_benchmark",
testonly = 1,
......
......@@ -128,6 +128,25 @@ absl_cc_library(
absl_cc_library(
NAME
log_internal_proto
SRCS
"internal/proto.cc"
HDRS
"internal/proto.h"
COPTS
${ABSL_DEFAULT_COPTS}
LINKOPTS
${ABSL_DEFAULT_LINKOPTS}
DEPS
absl::base
absl::config
absl::core_headers
absl::strings
absl::span
)
absl_cc_library(
NAME
log_internal_message
SRCS
"internal/log_message.cc"
......@@ -147,6 +166,7 @@ absl_cc_library(
absl::log_internal_append_truncated
absl::log_internal_format
absl::log_internal_globals
absl::log_internal_proto
absl::log_internal_log_sink_set
absl::log_internal_nullguard
absl::log_globals
......@@ -251,6 +271,7 @@ absl_cc_library(
${ABSL_DEFAULT_LINKOPTS}
DEPS
absl::config
absl::core_headers
absl::log_entry
absl::log_severity
absl::strings
......@@ -292,6 +313,7 @@ absl_cc_library(
${ABSL_DEFAULT_LINKOPTS}
DEPS
absl::config
absl::core_headers
absl::log_entry
absl::log_internal_test_helpers
absl::log_severity
......@@ -553,6 +575,37 @@ absl_cc_library(
TESTONLY
)
absl_cc_library(
NAME
log_internal_structured
HDRS
"internal/structured.h"
COPTS
${ABSL_DEFAULT_COPTS}
LINKOPTS
${ABSL_DEFAULT_LINKOPTS}
DEPS
absl::config
absl::log_internal_message
absl::strings
)
absl_cc_library(
NAME
log_structured
HDRS
"structured.h"
COPTS
${ABSL_DEFAULT_COPTS}
LINKOPTS
${ABSL_DEFAULT_LINKOPTS}
DEPS
absl::config
absl::log_internal_structured
absl::strings
PUBLIC
)
# Test targets
absl_cc_test(
NAME
......@@ -852,3 +905,23 @@ absl_cc_test(
GTest::gmock
GTest::gtest_main
)
absl_cc_test(
NAME
log_structured_test
SRCS
"structured_test.cc"
COPTS
${ABSL_TEST_COPTS}
LINKOPTS
${ABSL_DEFAULT_LINKOPTS}
DEPS
absl::core_headers
absl::log
absl::log_internal_test_helpers
absl::log_internal_test_matchers
absl::log_structured
absl::scoped_mock_log
GTest::gmock
GTest::gtest_main
)
......@@ -138,6 +138,7 @@ cc_library(
":globals",
":log_sink_set",
":nullguard",
":proto",
"//absl/base",
"//absl/base:config",
"//absl/base:core_headers",
......@@ -233,6 +234,18 @@ cc_library(
)
cc_library(
name = "structured",
hdrs = ["structured.h"],
copts = ABSL_DEFAULT_COPTS,
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":log_message",
"//absl/base:config",
"//absl/strings",
],
)
cc_library(
name = "test_actions",
testonly = True,
srcs = ["test_actions.cc"],
......@@ -246,7 +259,11 @@ cc_library(
"//absl/log:log_entry",
"//absl/strings",
"//absl/time",
],
] + select({
"//absl:msvc_compiler": [],
"//conditions:default": [
],
}),
)
cc_library(
......@@ -275,14 +292,18 @@ cc_library(
linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [
":test_helpers",
"@com_google_googletest//:gtest",
"//absl/base:config",
"//absl/base:core_headers",
"//absl/base:log_severity",
"//absl/log:log_entry",
"//absl/strings",
"//absl/time",
"@com_google_googletest//:gtest",
],
] + select({
"//absl:msvc_compiler": [],
"//conditions:default": [
],
}),
)
cc_library(
......@@ -293,6 +314,19 @@ cc_library(
deps = ["//absl/base:config"],
)
cc_library(
name = "proto",
srcs = ["proto.cc"],
hdrs = ["proto.h"],
deps = [
"//absl/base",
"//absl/base:config",
"//absl/base:core_headers",
"//absl/strings",
"//absl/types:span",
],
)
# Test targets
cc_test(
name = "stderr_log_sink_test",
......
......@@ -47,6 +47,7 @@
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace log_internal {
class AsLiteralImpl;
constexpr int kLogMessageBufferSize = 15000;
......@@ -130,6 +131,10 @@ class LogMessage {
LogMessage& operator<<(bool v) { return operator<< <bool>(v); }
// clang-format on
// These overloads are more efficient since no `ostream` is involved.
LogMessage& operator<<(const std::string& v);
LogMessage& operator<<(absl::string_view v);
// Handle stream manipulators e.g. std::endl.
LogMessage& operator<<(std::ostream& (*m)(std::ostream& os));
LogMessage& operator<<(std::ios_base& (*m)(std::ios_base& os));
......@@ -189,6 +194,27 @@ class LogMessage {
private:
struct LogMessageData; // Opaque type containing message state
friend class AsLiteralImpl;
// This streambuf writes directly into the structured logging buffer so that
// arbitrary types can be encoded as string data (using
// `operator<<(std::ostream &, ...)` without any extra allocation or copying.
// Space is reserved before the data to store the length field, which is
// filled in by `~OstreamView`.
class OstreamView final : public std::streambuf {
public:
explicit OstreamView(LogMessageData& message_data);
~OstreamView() override;
OstreamView(const OstreamView&) = delete;
OstreamView& operator=(const OstreamView&) = delete;
std::ostream& stream();
private:
LogMessageData& data_;
absl::Span<char> encoded_remaining_copy_;
absl::Span<char> message_start_;
absl::Span<char> string_start_;
};
// Returns `true` if the message is fatal or enabled debug-fatal.
bool IsFatal() const;
......@@ -202,6 +228,9 @@ class LogMessage {
// Checks `FLAGS_log_backtrace_at` and appends a backtrace if appropriate.
void LogBacktraceIfNeeded();
LogMessage& LogString(bool literal,
absl::string_view str) ABSL_ATTRIBUTE_NOINLINE;
// This should be the first data member so that its initializer captures errno
// before any other initializers alter it (e.g. with calls to new) and so that
// no other destructors run afterward an alter it (e.g. with calls to delete).
......@@ -210,8 +239,6 @@ class LogMessage {
// We keep the data in a separate struct so that each instance of `LogMessage`
// uses less stack space.
std::unique_ptr<LogMessageData> data_;
std::ostream stream_;
};
// Helper class so that `AbslStringify()` can modify the LogMessage.
......@@ -233,10 +260,9 @@ class StringifySink final {
};
// Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE`
template <
typename T,
typename std::enable_if<strings_internal::HasAbslStringify<T>::value,
int>::type>
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.
......@@ -245,35 +271,26 @@ LogMessage& LogMessage::operator<<(const T& v) {
}
// Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE`
template <
typename T,
typename std::enable_if<!strings_internal::HasAbslStringify<T>::value,
int>::type>
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);
OstreamView view(*data_);
view.stream() << log_internal::NullGuard<T>().Guard(v);
return *this;
}
inline LogMessage& LogMessage::operator<<(
std::ostream& (*m)(std::ostream& os)) {
stream_ << m;
return *this;
}
inline LogMessage& LogMessage::operator<<(
std::ios_base& (*m)(std::ios_base& os)) {
stream_ << m;
return *this;
}
template <int SIZE>
LogMessage& LogMessage::operator<<(const char (&buf)[SIZE]) {
stream_ << buf;
return *this;
const bool literal = true;
return LogString(literal, buf);
}
// Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE`
template <int SIZE>
LogMessage& LogMessage::operator<<(char (&buf)[SIZE]) {
stream_ << buf;
return *this;
const bool literal = false;
return LogString(literal, buf);
}
// We instantiate these specializations in the library's TU to save space in
// other TUs. Since the template is marked `ABSL_ATTRIBUTE_NOINLINE` we will be
......@@ -299,9 +316,6 @@ extern template LogMessage& LogMessage::operator<<(const void* const& v);
extern template LogMessage& LogMessage::operator<<(const float& v);
extern template LogMessage& LogMessage::operator<<(const double& v);
extern template LogMessage& LogMessage::operator<<(const bool& v);
extern template LogMessage& LogMessage::operator<<(const std::string& v);
extern template LogMessage& LogMessage::operator<<(
const absl::string_view& v);
// `LogMessageFatal` ensures the process will exit in failure after logging this
// message.
......
// Copyright 2020 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/proto.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include "absl/base/attributes.h"
#include "absl/base/config.h"
#include "absl/types/span.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace log_internal {
namespace {
void EncodeRawVarint(uint64_t value, size_t size, absl::Span<char> *buf) {
for (size_t s = 0; s < size; s++) {
(*buf)[s] = (value & 0x7f) | (s + 1 == size ? 0 : 0x80);
value >>= 7;
}
buf->remove_prefix(size);
}
constexpr uint64_t MakeTagType(uint64_t tag, WireType type) {
return tag << 3 | static_cast<uint64_t>(type);
}
} // namespace
bool EncodeVarint(uint64_t tag, uint64_t value, absl::Span<char> *buf) {
const uint64_t tag_type = MakeTagType(tag, WireType::kVarint);
const uint64_t tag_type_size = VarintSize(tag_type);
const uint64_t value_size = VarintSize(value);
if (tag_type_size + value_size > buf->size()) {
buf->remove_suffix(buf->size());
return false;
}
EncodeRawVarint(tag_type, tag_type_size, buf);
EncodeRawVarint(value, value_size, buf);
return true;
}
bool Encode64Bit(uint64_t tag, uint64_t value, absl::Span<char> *buf) {
const uint64_t tag_type = MakeTagType(tag, WireType::k64Bit);
const uint64_t tag_type_size = VarintSize(tag_type);
if (tag_type_size + sizeof(value) > buf->size()) {
buf->remove_suffix(buf->size());
return false;
}
EncodeRawVarint(tag_type, tag_type_size, buf);
for (size_t s = 0; s < sizeof(value); s++) {
(*buf)[s] = value & 0xff;
value >>= 8;
}
buf->remove_prefix(sizeof(value));
return true;
}
bool Encode32Bit(uint64_t tag, uint32_t value, absl::Span<char> *buf) {
const uint64_t tag_type = MakeTagType(tag, WireType::k32Bit);
const uint64_t tag_type_size = VarintSize(tag_type);
if (tag_type_size + sizeof(value) > buf->size()) {
buf->remove_suffix(buf->size());
return false;
}
EncodeRawVarint(tag_type, tag_type_size, buf);
for (size_t s = 0; s < sizeof(value); s++) {
(*buf)[s] = value & 0xff;
value >>= 8;
}
buf->remove_prefix(sizeof(value));
return true;
}
bool EncodeBytes(uint64_t tag, absl::Span<const char> value,
absl::Span<char> *buf) {
const uint64_t tag_type = MakeTagType(tag, WireType::kLengthDelimited);
const uint64_t tag_type_size = VarintSize(tag_type);
uint64_t length = value.size();
const uint64_t length_size = VarintSize(length);
if (tag_type_size + length_size + value.size() > buf->size()) {
buf->remove_suffix(buf->size());
return false;
}
EncodeRawVarint(tag_type, tag_type_size, buf);
EncodeRawVarint(length, length_size, buf);
memcpy(buf->data(), value.data(), value.size());
buf->remove_prefix(value.size());
return true;
}
bool EncodeBytesTruncate(uint64_t tag, absl::Span<const char> value,
absl::Span<char> *buf) {
const uint64_t tag_type = MakeTagType(tag, WireType::kLengthDelimited);
const uint64_t tag_type_size = VarintSize(tag_type);
uint64_t length = value.size();
const uint64_t length_size = VarintSize(length);
if (tag_type_size + length_size <= buf->size() &&
tag_type_size + length_size + value.size() > buf->size()) {
value.remove_suffix(tag_type_size + length_size + value.size() -
buf->size());
length = value.size();
}
if (tag_type_size + length_size + value.size() > buf->size()) {
buf->remove_suffix(buf->size());
return false;
}
EncodeRawVarint(tag_type, tag_type_size, buf);
EncodeRawVarint(length, length_size, buf);
memcpy(buf->data(), value.data(), value.size());
buf->remove_prefix(value.size());
return true;
}
ABSL_MUST_USE_RESULT absl::Span<char> EncodeMessageStart(
uint64_t tag, uint64_t max_size, absl::Span<char> *buf) {
const uint64_t tag_type = MakeTagType(tag, WireType::kLengthDelimited);
const uint64_t tag_type_size = VarintSize(tag_type);
max_size = std::min<uint64_t>(max_size, buf->size());
const uint64_t length_size = VarintSize(max_size);
if (tag_type_size + length_size > buf->size()) {
buf->remove_suffix(buf->size());
return absl::Span<char>();
}
EncodeRawVarint(tag_type, tag_type_size, buf);
const absl::Span<char> ret = buf->subspan(0, length_size);
EncodeRawVarint(0, length_size, buf);
return ret;
}
void EncodeMessageLength(absl::Span<char> msg, const absl::Span<char> *buf) {
if (!msg.data()) return;
const uint64_t length_size = msg.size();
EncodeRawVarint(buf->data() - msg.data() - length_size, length_size, &msg);
}
namespace {
uint64_t DecodeVarint(absl::Span<const char> *buf) {
uint64_t value = 0;
size_t s = 0;
while (s < buf->size()) {
value |= static_cast<uint64_t>(static_cast<unsigned char>((*buf)[s]) & 0x7f)
<< 7 * s;
if (!((*buf)[s++] & 0x80)) break;
}
buf->remove_prefix(s);
return value;
}
uint64_t Decode64Bit(absl::Span<const char> *buf) {
uint64_t value = 0;
size_t s = 0;
while (s < buf->size()) {
value |= static_cast<uint64_t>(static_cast<unsigned char>((*buf)[s]))
<< 8 * s;
if (++s == sizeof(value)) break;
}
buf->remove_prefix(s);
return value;
}
uint32_t Decode32Bit(absl::Span<const char> *buf) {
uint32_t value = 0;
size_t s = 0;
while (s < buf->size()) {
value |= static_cast<uint32_t>(static_cast<unsigned char>((*buf)[s]))
<< 8 * s;
if (++s == sizeof(value)) break;
}
buf->remove_prefix(s);
return value;
}
} // namespace
bool ProtoField::DecodeFrom(absl::Span<const char> *data) {
if (data->empty()) return false;
const uint64_t tag_type = DecodeVarint(data);
tag_ = tag_type >> 3;
type_ = static_cast<WireType>(tag_type & 0x07);
switch (type_) {
case WireType::kVarint:
value_ = DecodeVarint(data);
break;
case WireType::k64Bit:
value_ = Decode64Bit(data);
break;
case WireType::kLengthDelimited: {
value_ = DecodeVarint(data);
data_ = data->subspan(0, std::min<size_t>(value_, data->size()));
data->remove_prefix(data_.size());
break;
}
case WireType::k32Bit:
value_ = Decode32Bit(data);
break;
}
return true;
}
} // namespace log_internal
ABSL_NAMESPACE_END
} // namespace absl
// 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.
//
// -----------------------------------------------------------------------------
// File: log/internal/structured.h
// -----------------------------------------------------------------------------
#ifndef ABSL_LOG_INTERNAL_STRUCTURED_H_
#define ABSL_LOG_INTERNAL_STRUCTURED_H_
#include <ostream>
#include "absl/base/config.h"
#include "absl/log/internal/log_message.h"
#include "absl/strings/string_view.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace log_internal {
class ABSL_MUST_USE_RESULT AsLiteralImpl final {
public:
explicit AsLiteralImpl(absl::string_view str) : str_(str) {}
AsLiteralImpl(const AsLiteralImpl&) = default;
AsLiteralImpl& operator=(const AsLiteralImpl&) = default;
private:
absl::string_view str_;
friend std::ostream& operator<<(std::ostream& os, AsLiteralImpl as_literal) {
return os << as_literal.str_;
}
log_internal::LogMessage& AddToMessage(log_internal::LogMessage& m) {
return m.LogString(/* literal = */ true, str_);
}
friend log_internal::LogMessage& operator<<(log_internal::LogMessage& m,
AsLiteralImpl as_literal) {
return as_literal.AddToMessage(m);
}
};
} // namespace log_internal
ABSL_NAMESPACE_END
} // namespace absl
#endif // ABSL_LOG_INTERNAL_STRUCTURED_H_
......@@ -40,16 +40,19 @@ void WriteEntryToStderr::operator()(const absl::LogEntry& entry) const {
const std::string source_filename = absl::CHexEscape(entry.source_filename());
const std::string source_basename = absl::CHexEscape(entry.source_basename());
const std::string text_message = absl::CHexEscape(entry.text_message());
std::cerr << "LogEntry{\n" //
<< " source_filename: \"" << source_filename << "\"\n" //
<< " source_basename: \"" << source_basename << "\"\n" //
<< " source_line: " << entry.source_line() << "\n" //
<< " prefix: " << (entry.prefix() ? "true\n" : "false\n") //
<< " log_severity: " << entry.log_severity() << "\n" //
<< " timestamp: " << entry.timestamp() << "\n" //
<< " text_message: \"" << text_message << "\"\n" //
<< " verbosity: " << entry.verbosity()
<< "\n" //
const std::string encoded_message = absl::CHexEscape(entry.encoded_message());
std::string encoded_message_str;
std::cerr << "LogEntry{\n" //
<< " source_filename: \"" << source_filename << "\"\n" //
<< " source_basename: \"" << source_basename << "\"\n" //
<< " source_line: " << entry.source_line() << "\n" //
<< " prefix: " << (entry.prefix() ? "true\n" : "false\n") //
<< " log_severity: " << entry.log_severity() << "\n" //
<< " timestamp: " << entry.timestamp() << "\n" //
<< " text_message: \"" << text_message << "\"\n" //
<< " verbosity: " << entry.verbosity() << "\n" //
<< " encoded_message (raw): \"" << encoded_message << "\"\n" //
<< encoded_message_str //
<< "}\n";
}
......
......@@ -79,7 +79,6 @@ namespace log_internal {
const std::ostringstream& stream);
::testing::Matcher<const std::string&> DeathTestValidateExpectations();
// This feature coming soon =).
#define ENCODED_MESSAGE(message_matcher) ::testing::_
} // namespace log_internal
......
......@@ -169,6 +169,15 @@ class LogEntry final {
return text_message_with_prefix_and_newline_and_nul_.data();
}
// Returns a serialized protobuf holding the operands streamed into this
// log message. The message definition is not yet published.
//
// The buffer does not outlive the entry; if you need the data later, you must
// copy them.
absl::string_view encoded_message() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
return encoding_;
}
// LogEntry::stacktrace()
//
// Optional stacktrace, e.g. for `FATAL` logs and failed `CHECK`s.
......@@ -198,6 +207,7 @@ class LogEntry final {
tid_t tid_;
absl::Span<const char> text_message_with_prefix_and_newline_and_nul_;
size_t prefix_len_;
absl::string_view encoding_;
std::string stacktrace_;
friend class log_internal::LogEntryTestPeer;
......
......@@ -16,7 +16,9 @@
#include <math.h>
#include <iomanip>
#include <ios>
#include <limits>
#include <ostream>
#include <sstream>
#include <string>
#include <type_traits>
......@@ -41,15 +43,16 @@ using ::absl::log_internal::TextPrefix;
using ::testing::AllOf;
using ::testing::AnyOf;
using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::Truly;
using ::testing::Types;
using ::testing::Each;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Ge;
using ::testing::IsEmpty;
using ::testing::Le;
using ::testing::ResultOf;
using ::testing::SizeIs;
using ::testing::Truly;
using ::testing::Types;
// Some aspects of formatting streamed data (e.g. pointer handling) are
// implementation-defined. Others are buggy in supported implementations.
......@@ -611,10 +614,11 @@ TYPED_TEST(FloatingPointLogFormatTest, NegativeNaN) {
test_sink,
Send(AllOf(
TextMessage(MatchesOstream(comparison_stream)),
TextMessage(AnyOf(Eq("-nan"), Eq("nan"), Eq("NaN"),
Eq("-nan(ind)"))), ENCODED_MESSAGE(EqualsProto(R"pb(value { str:
"-nan" })pb")))));
TextMessage(AnyOf(Eq("-nan"), Eq("nan"), Eq("NaN"), Eq("-nan(ind)"))),
ENCODED_MESSAGE(
AnyOf(EqualsProto(R"pb(value { str: "-nan" })pb"),
EqualsProto(R"pb(value { str: "nan" })pb"),
EqualsProto(R"pb(value { str: "-nan(ind)" })pb"))))));
test_sink.StartCapturingLogs();
LOG(INFO) << value;
}
......@@ -1372,9 +1376,12 @@ TEST(ManipulatorLogFormatTest, Endl) {
auto comparison_stream = ComparisonStream();
comparison_stream << std::endl;
EXPECT_CALL(test_sink,
Send(AllOf(TextMessage(MatchesOstream(comparison_stream)),
TextMessage(Eq("\n")))));
EXPECT_CALL(
test_sink,
Send(AllOf(
TextMessage(MatchesOstream(comparison_stream)),
TextMessage(Eq("\n")),
ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "\n" })pb")))));
test_sink.StartCapturingLogs();
LOG(INFO) << std::endl;
......@@ -1640,26 +1647,32 @@ TEST(ManipulatorLogFormatTest, IOManipsDoNotAffectAbslStringify) {
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 significantly different.
TEST(OverflowTest, TruncatesStrings) {
TEST(StructuredLoggingOverflowTest, TruncatesStrings) {
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.
constexpr size_t buffer_size = 15000;
EXPECT_CALL(test_sink,
Send(TextMessage(
AllOf(SizeIs(AllOf(Ge(buffer_size - 256),
Le(buffer_size))),
Each(Eq('x'))))));
test_sink.StartCapturingLogs();
LOG(INFO) << std::string(2 * buffer_size, 'x');
// 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(ResultOf(
[](const logging::proto::Event& e) { return e.value(); },
ElementsAre(ResultOf(
[](const logging::proto::Value& v) {
return std::string(v.str());
},
AllOf(SizeIs(AllOf(
Ge(absl::log_internal::kLogMessageBufferSize - 256),
Le(absl::log_internal::kLogMessageBufferSize))),
Each(Eq('x'))))))))));
test_sink.StartCapturingLogs();
LOG(INFO) << std::string(2 * absl::log_internal::kLogMessageBufferSize, 'x');
}
} // namespace
// 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.
//
// -----------------------------------------------------------------------------
// File: log/structured.h
// -----------------------------------------------------------------------------
//
// This header declares APIs supporting structured logging, allowing log
// statements to be more easily parsed, especially by automated processes.
//
// When structured logging is in use, data streamed into a `LOG` statement are
// encoded as `Value` fields in a `logging.proto.Event` protocol buffer message.
// The individual data are exposed programmatically to `LogSink`s and to the
// user via some log reading tools which are able to query the structured data
// more usefully than would be possible if each message was a single opaque
// string. These helpers allow user code to add additional structure to the
// data they stream.
#ifndef ABSL_LOG_STRUCTURED_H_
#define ABSL_LOG_STRUCTURED_H_
#include <ostream>
#include "absl/base/config.h"
#include "absl/log/internal/structured.h"
#include "absl/strings/string_view.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
// LogAsLiteral()
//
// Annotates its argument as a string literal so that structured logging
// captures it as a `literal` field instead of a `str` field (the default).
// This does not affect the text representation, only the structure.
//
// Streaming `LogAsLiteral(s)` into a `std::ostream` behaves just like streaming
// `s` directly.
//
// Using `LogAsLiteral()` is occasionally appropriate and useful when proxying
// data logged from another system or another language. For example:
//
// void Logger::LogString(absl::string_view str, absl::LogSeverity severity,
// const char *file, int line) {
// LOG(LEVEL(severity)).AtLocation(file, line) << str;
// }
// void Logger::LogStringLiteral(absl::string_view str,
// absl::LogSeverity severity, const char *file,
// int line) {
// LOG(LEVEL(severity)).AtLocation(file, line) << absl::LogAsLiteral(str);
// }
inline log_internal::AsLiteralImpl LogAsLiteral(absl::string_view s) {
return log_internal::AsLiteralImpl(s);
}
ABSL_NAMESPACE_END
} // namespace absl
#endif // ABSL_LOG_STRUCTURED_H_
//
// 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.
#include "absl/log/structured.h"
#include <ios>
#include <sstream>
#include <string>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/attributes.h"
#include "absl/log/internal/test_helpers.h"
#include "absl/log/internal/test_matchers.h"
#include "absl/log/log.h"
#include "absl/log/scoped_mock_log.h"
namespace {
using ::absl::log_internal::MatchesOstream;
using ::absl::log_internal::TextMessage;
using ::testing::Eq;
auto *test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment(
new absl::log_internal::LogTestEnvironment);
// Abseil Logging library uses these by default, so we set them on the
// `std::ostream` we compare against too.
std::ios &LoggingDefaults(std::ios &str) {
str.setf(std::ios_base::showbase | std::ios_base::boolalpha |
std::ios_base::internal);
return str;
}
TEST(StreamingFormatTest, LogAsLiteral) {
std::ostringstream stream;
const std::string not_a_literal("hello world");
stream << LoggingDefaults << absl::LogAsLiteral(not_a_literal);
absl::ScopedMockLog sink;
EXPECT_CALL(sink,
Send(AllOf(TextMessage(MatchesOstream(stream)),
TextMessage(Eq("hello world")),
ENCODED_MESSAGE(EqualsProto(
R"pb(value { literal: "hello world" })pb")))));
sink.StartCapturingLogs();
LOG(INFO) << absl::LogAsLiteral(not_a_literal);
}
} // namespace
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment