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",
......
......@@ -45,6 +45,7 @@
#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/proto.h"
#include "absl/log/log_entry.h"
#include "absl/log/log_sink.h"
#include "absl/log/log_sink_registry.h"
......@@ -64,6 +65,40 @@ ABSL_NAMESPACE_BEGIN
namespace log_internal {
namespace {
// message `logging.proto.Event`
enum EventTag : uint8_t {
kValue = 7,
};
// message `logging.proto.Value`
enum ValueTag : uint8_t {
kString = 1,
kStringLiteral = 6,
};
// Decodes a `logging.proto.Value` from `buf` and writes a string representation
// into `dst`. The string representation will be truncated if `dst` is not
// large enough to hold it. Returns false if `dst` has size zero or one (i.e.
// sufficient only for a nul-terminator) and no decoded data could be written.
// This function may or may not write a nul-terminator into `dst`, and it may or
// may not truncate the data it writes in order to do make space for that nul
// terminator. In any case, `dst` will be advanced to point at the byte where
// subsequent writes should begin.
bool PrintValue(absl::Span<char>& dst, absl::Span<const char> buf) {
if (dst.size() <= 1) return false;
ProtoField field;
while (field.DecodeFrom(&buf)) {
switch (field.tag()) {
case ValueTag::kString:
case ValueTag::kStringLiteral:
if (field.type() == WireType::kLengthDelimited)
if (log_internal::AppendTruncated(field.string_value(), dst) <
field.string_value().size())
return false;
}
}
return true;
}
absl::string_view Basename(absl::string_view filepath) {
#ifdef _WIN32
......@@ -84,93 +119,6 @@ void WriteToStream(const char* data, void* os) {
}
} // namespace
// A write-only `std::streambuf` that writes into an `absl::Span<char>`.
//
// This class is responsible for writing a metadata prefix just before the
// first data are streamed in. The metadata are subject to change (cf.
// `LogMessage::AtLocation`) until then, so we wait as long as possible.
//
// This class is also responsible for reserving space for a trailing newline
// so that one can be added later by `Finalize` no matter how many data are
// streamed in.
class LogEntryStreambuf final : public std::streambuf {
public:
explicit LogEntryStreambuf(absl::Span<char> buf,
const absl::LogEntry& entry)
: buf_(buf), entry_(entry), prefix_len_(0), finalized_(false) {
// To detect when data are first written, we leave the put area null,
// override `overflow`, and check ourselves in `xsputn`.
}
LogEntryStreambuf(LogEntryStreambuf&&) = delete;
LogEntryStreambuf& operator=(LogEntryStreambuf&&) = delete;
absl::Span<const char> Finalize() {
assert(!finalized_);
// If no data were ever streamed in, this is where we must write the
// prefix.
if (pbase() == nullptr) Initialize();
// Here we reclaim the two bytes we reserved.
ptrdiff_t idx = pptr() - pbase();
setp(buf_.data(), buf_.data() + buf_.size());
pbump(static_cast<int>(idx));
sputc('\n');
sputc('\0');
finalized_ = true;
return absl::Span<const char>(pbase(),
static_cast<size_t>(pptr() - pbase()));
}
size_t prefix_len() const { return prefix_len_; }
protected:
std::streamsize xsputn(const char* s, std::streamsize n) override {
if (n < 0) return 0;
if (pbase() == nullptr) Initialize();
return static_cast<std::streamsize>(
Append(absl::string_view(s, static_cast<size_t>(n))));
}
int overflow(int ch = EOF) override {
if (pbase() == nullptr) Initialize();
if (ch == EOF) return 0;
if (pptr() == epptr()) return EOF;
*pptr() = static_cast<char>(ch);
pbump(1);
return 1;
}
private:
void Initialize() {
// Here we reserve two bytes in our buffer to guarantee `Finalize` space
// to add a trailing "\n\0".
assert(buf_.size() >= 2);
setp(buf_.data(), buf_.data() + buf_.size() - 2);
if (entry_.prefix()) {
absl::Span<char> remaining = buf_;
prefix_len_ = log_internal::FormatLogPrefix(
entry_.log_severity(), entry_.timestamp(), entry_.tid(),
entry_.source_basename(), entry_.source_line(),
log_internal::ThreadIsLoggingToLogSink() ? PrefixFormat::kRaw
: PrefixFormat::kNotRaw,
remaining);
pbump(static_cast<int>(prefix_len_));
}
}
size_t Append(absl::string_view data) {
absl::Span<char> remaining(pptr(),
static_cast<size_t>(epptr() - pptr()));
const size_t written = log_internal::AppendTruncated(data, remaining);
pbump(static_cast<int>(written));
return written;
}
const absl::Span<char> buf_;
const absl::LogEntry& entry_;
size_t prefix_len_;
bool finalized_;
};
struct LogMessage::LogMessageData final {
LogMessageData(const char* file, int line, absl::LogSeverity severity,
absl::Time timestamp);
......@@ -193,18 +141,31 @@ struct LogMessage::LogMessageData final {
// non-sink targets (e.g. stderr, log files).
bool extra_sinks_only;
std::ostream manipulated; // ostream with IO manipulators applied
// A `logging.proto.Event` proto message is built into `encoded_buf`.
std::array<char, kLogMessageBufferSize> encoded_buf;
// `encoded_remaining` is the suffix of `encoded_buf` that has not been filled
// yet. If a datum to be encoded does not fit into `encoded_remaining` and
// cannot be truncated to fit, the size of `encoded_remaining` will be zeroed
// to prevent encoding of any further data. Note that in this case its data()
// pointer will not point past the end of `encoded_buf`.
absl::Span<char> encoded_remaining;
// A formatted string message is built in `string_buf`.
std::array<char, kLogMessageBufferSize> string_buf;
// A `std::streambuf` that stores into `string_buf`.
LogEntryStreambuf streambuf_;
void FinalizeEncodingAndFormat();
};
LogMessage::LogMessageData::LogMessageData(const char* file, int line,
absl::LogSeverity severity,
absl::Time timestamp)
: extra_sinks_only(false),
streambuf_(absl::MakeSpan(string_buf), entry) {
manipulated(nullptr),
encoded_remaining(encoded_buf) {
// Legacy defaults for LOG's ostream:
manipulated.setf(std::ios_base::showbase | std::ios_base::boolalpha);
entry.full_filename_ = file;
entry.base_filename_ = Basename(file);
entry.line_ = line;
......@@ -215,20 +176,57 @@ LogMessage::LogMessageData::LogMessageData(const char* file, int line,
entry.tid_ = absl::base_internal::GetCachedTID();
}
void LogMessage::LogMessageData::FinalizeEncodingAndFormat() {
// Note that `encoded_remaining` may have zero size without pointing past the
// end of `encoded_buf`, so the difference between `data()` pointers is used
// to compute the size of `encoded_data`.
absl::Span<const char> encoded_data(
encoded_buf.data(),
static_cast<size_t>(encoded_remaining.data() - encoded_buf.data()));
// `string_remaining` is the suffix of `string_buf` that has not been filled
// yet.
absl::Span<char> string_remaining(string_buf);
// We may need to write a newline and nul-terminator at the end of the decoded
// string data. Rather than worry about whether those should overwrite the
// end of the string (if the buffer is full) or be appended, we avoid writing
// into the last two bytes so we always have space to append.
string_remaining.remove_suffix(2);
entry.prefix_len_ =
entry.prefix() ? log_internal::FormatLogPrefix(
entry.log_severity(), entry.timestamp(), entry.tid(),
entry.source_basename(), entry.source_line(),
log_internal::ThreadIsLoggingToLogSink()
? PrefixFormat::kRaw
: PrefixFormat::kNotRaw,
string_remaining)
: 0;
// Decode data from `encoded_buf` until we run out of data or we run out of
// `string_remaining`.
ProtoField field;
while (field.DecodeFrom(&encoded_data)) {
switch (field.tag()) {
case EventTag::kValue:
if (field.type() != WireType::kLengthDelimited) continue;
if (PrintValue(string_remaining, field.bytes_value())) continue;
break;
}
break;
}
auto chars_written =
static_cast<size_t>(string_remaining.data() - string_buf.data());
string_buf[chars_written++] = '\n';
string_buf[chars_written++] = '\0';
entry.text_message_with_prefix_and_newline_and_nul_ =
absl::MakeSpan(string_buf).subspan(0, chars_written);
}
LogMessage::LogMessage(const char* file, int line, absl::LogSeverity severity)
: data_(
absl::make_unique<LogMessageData>(file, line, severity, absl::Now()))
, stream_(&data_->streambuf_)
{
: data_(absl::make_unique<LogMessageData>(file, line, severity,
absl::Now())) {
data_->first_fatal = false;
data_->is_perror = false;
data_->fail_quietly = false;
// Legacy defaults for LOG's ostream:
stream_.setf(std::ios_base::showbase | std::ios_base::boolalpha);
// `fill('0')` is omitted here because its effects are very different
// without structured logging. Resolution is tracked in b/111310488.
// This logs a backtrace even if the location is subsequently changed using
// AtLocation. This quirk, and the behavior when AtLocation is called twice,
// are fixable but probably not worth fixing.
......@@ -346,6 +344,23 @@ void LogMessage::FailQuietly() {
_exit(1);
}
LogMessage& LogMessage::operator<<(const std::string& v) {
return LogString(false, v);
}
LogMessage& LogMessage::operator<<(absl::string_view v) {
return LogString(false, v);
}
LogMessage& LogMessage::operator<<(std::ostream& (*m)(std::ostream& os)) {
OstreamView view(*data_);
data_->manipulated << m;
return *this;
}
LogMessage& LogMessage::operator<<(std::ios_base& (*m)(std::ios_base& os)) {
OstreamView view(*data_);
data_->manipulated << m;
return *this;
}
template LogMessage& LogMessage::operator<<(const char& v);
template LogMessage& LogMessage::operator<<(const signed char& v);
template LogMessage& LogMessage::operator<<(const unsigned char& v);
......@@ -363,8 +378,6 @@ template LogMessage& LogMessage::operator<<(const void* const& v);
template LogMessage& LogMessage::operator<<(const float& v);
template LogMessage& LogMessage::operator<<(const double& v);
template LogMessage& LogMessage::operator<<(const bool& v);
template LogMessage& LogMessage::operator<<(const std::string& v);
template LogMessage& LogMessage::operator<<(const absl::string_view& v);
void LogMessage::Flush() {
if (data_->entry.log_severity() < absl::MinLogLevel())
......@@ -388,14 +401,54 @@ void LogMessage::Flush() {
}
}
data_->entry.text_message_with_prefix_and_newline_and_nul_ =
data_->streambuf_.Finalize();
data_->entry.prefix_len_ = data_->streambuf_.prefix_len();
data_->FinalizeEncodingAndFormat();
data_->entry.encoding_ =
absl::string_view(data_->encoded_buf.data(),
static_cast<size_t>(data_->encoded_remaining.data() -
data_->encoded_buf.data()));
SendToLog();
}
void LogMessage::SetFailQuietly() { data_->fail_quietly = true; }
LogMessage::OstreamView::OstreamView(LogMessageData& message_data)
: data_(message_data), encoded_remaining_copy_(data_.encoded_remaining) {
// This constructor sets the `streambuf` up so that streaming into an attached
// ostream encodes string data in-place. To do that, we write appropriate
// headers into the buffer using a copy of the buffer view so that we can
// decide not to keep them later if nothing is ever streamed in. We don't
// know how much data we'll get, but we can use the size of the remaining
// buffer as an upper bound and fill in the right size once we know it.
message_start_ =
EncodeMessageStart(EventTag::kValue, encoded_remaining_copy_.size(),
&encoded_remaining_copy_);
string_start_ =
EncodeMessageStart(ValueTag::kString, encoded_remaining_copy_.size(),
&encoded_remaining_copy_);
setp(encoded_remaining_copy_.data(),
encoded_remaining_copy_.data() + encoded_remaining_copy_.size());
data_.manipulated.rdbuf(this);
}
LogMessage::OstreamView::~OstreamView() {
const absl::Span<const char> contents(pbase(),
static_cast<size_t>(pptr() - pbase()));
encoded_remaining_copy_.remove_prefix(contents.size());
if (!string_start_.data()) {
// The headers didn't fit; we won't write anything to the buffer, but we
// also need to zero the size of `data_->encoded_remaining` so that no more
// data is encoded.
data_.encoded_remaining.remove_suffix(data_.encoded_remaining.size());
} else if (!contents.empty()) {
EncodeMessageLength(string_start_, &encoded_remaining_copy_);
EncodeMessageLength(message_start_, &encoded_remaining_copy_);
data_.encoded_remaining = encoded_remaining_copy_;
}
data_.manipulated.rdbuf(nullptr);
}
std::ostream& LogMessage::OstreamView::stream() { return data_.manipulated; }
bool LogMessage::IsFatal() const {
return data_->entry.log_severity() == absl::LogSeverity::kFatal &&
absl::log_internal::ExitOnDFatal();
......@@ -449,11 +502,30 @@ void LogMessage::LogBacktraceIfNeeded() {
if (!absl::log_internal::ShouldLogBacktraceAt(data_->entry.source_basename(),
data_->entry.source_line()))
return;
stream_ << " (stacktrace:\n";
OstreamView view(*data_);
view.stream() << " (stacktrace:\n";
debugging_internal::DumpStackTrace(
1, log_internal::MaxFramesInLogStackTrace(),
log_internal::ShouldSymbolizeLogStackTrace(), WriteToStream, &stream_);
stream_ << ") ";
log_internal::ShouldSymbolizeLogStackTrace(), WriteToStream,
&view.stream());
view.stream() << ") ";
}
// Encodes a partial `logging.proto.Event` containing the specified string data
// into `data_->encoded_remaining`.
LogMessage& LogMessage::LogString(bool literal, absl::string_view str) {
// Don't commit the MessageStart if the String tag_type and length don't fit.
auto encoded_remaining_copy = data_->encoded_remaining;
auto start = EncodeMessageStart(
EventTag::kValue, BufferSizeFor(WireType::kLengthDelimited) + str.size(),
&encoded_remaining_copy);
if (EncodeStringTruncate(
literal ? ValueTag::kStringLiteral : ValueTag::kString, str,
&encoded_remaining_copy)) {
EncodeMessageLength(start, &encoded_remaining_copy);
data_->encoded_remaining = encoded_remaining_copy;
}
return *this;
}
LogMessageFatal::LogMessageFatal(const char* file, int line)
......
......@@ -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 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.
// -----------------------------------------------------------------------------
// File: internal/proto.h
// -----------------------------------------------------------------------------
//
// Declares functions for serializing and deserializing data to and from memory
// buffers in protocol buffer wire format. This library takes no steps to
// ensure that the encoded data matches with any message specification.
#ifndef ABSL_LOG_INTERNAL_PROTO_H_
#define ABSL_LOG_INTERNAL_PROTO_H_
#include <cstddef>
#include <cstdint>
#include <limits>
#include "absl/base/attributes.h"
#include "absl/base/casts.h"
#include "absl/base/config.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace log_internal {
// absl::Span<char> represents a view into the available space in a mutable
// buffer during encoding. Encoding functions shrink the span as they go so
// that the same view can be passed to a series of Encode functions. If the
// data do not fit, nothing is encoded, the view is set to size zero (so that
// all subsequent encode calls fail), and false is returned. Otherwise true is
// returned.
// In particular, attempting to encode a series of data into an insufficient
// buffer has consistent and efficient behavior without any caller-side error
// checking. Individual values will be encoded in their entirety or not at all
// (unless one of the `Truncate` functions is used). Once a value is omitted
// because it does not fit, no subsequent values will be encoded to preserve
// ordering; the decoded sequence will be a prefix of the original sequence.
// There are two ways to encode a message-typed field:
//
// * Construct its contents in a separate buffer and use `EncodeBytes` to copy
// it into the primary buffer with type, tag, and length.
// * Use `EncodeMessageStart` to write type and tag fields and reserve space for
// the length field, then encode the contents directly into the buffer, then
// use `EncodeMessageLength` to write the actual length into the reserved
// bytes. This works fine if the actual length takes fewer bytes to encode
// than were reserved, although you don't get your extra bytes back.
// This approach will always produce a valid encoding, but your protocol may
// require that the whole message field by omitted if the buffer is too small
// to contain all desired subfields. In this case, operate on a copy of the
// buffer view and assign back only if everything fit, i.e. if the last
// `Encode` call returned true.
// Encodes the specified integer as a varint field and returns true if it fits.
// Used for int32_t, int64_t, uint32_t, uint64_t, bool, and enum field types.
// Consumes up to kMaxVarintSize * 2 bytes (20).
bool EncodeVarint(uint64_t tag, uint64_t value, absl::Span<char> *buf);
// Encodes the specified integer as a varint field using ZigZag encoding and
// returns true if it fits.
// Used for sint32 and sint64 field types.
// Consumes up to kMaxVarintSize * 2 bytes (20).
inline bool EncodeVarintZigZag(uint64_t tag, int64_t value,
absl::Span<char> *buf) {
if (value < 0)
return EncodeVarint(tag, 2 * static_cast<uint64_t>(-(value + 1)) + 1, buf);
return EncodeVarint(tag, 2 * static_cast<uint64_t>(value), buf);
}
// Encodes the specified integer as a 64-bit field and returns true if it fits.
// Used for fixed64 and sfixed64 field types.
// Consumes up to kMaxVarintSize + 8 bytes (18).
bool Encode64Bit(uint64_t tag, uint64_t value, absl::Span<char> *buf);
// Encodes the specified double as a 64-bit field and returns true if it fits.
// Used for double field type.
// Consumes up to kMaxVarintSize + 8 bytes (18).
inline bool EncodeDouble(uint64_t tag, double value, absl::Span<char> *buf) {
return Encode64Bit(tag, absl::bit_cast<uint64_t>(value), buf);
}
// Encodes the specified integer as a 32-bit field and returns true if it fits.
// Used for fixed32 and sfixed32 field types.
// Consumes up to kMaxVarintSize + 4 bytes (14).
bool Encode32Bit(uint64_t tag, uint32_t value, absl::Span<char> *buf);
// Encodes the specified float as a 32-bit field and returns true if it fits.
// Used for float field type.
// Consumes up to kMaxVarintSize + 4 bytes (14).
inline bool EncodeFloat(uint64_t tag, float value, absl::Span<char> *buf) {
return Encode32Bit(tag, absl::bit_cast<uint32_t>(value), buf);
}
// Encodes the specified bytes as a length-delimited field and returns true if
// they fit.
// Used for string, bytes, message, and packed-repeated field type.
// Consumes up to kMaxVarintSize * 2 + value.size() bytes (20 + value.size()).
bool EncodeBytes(uint64_t tag, absl::Span<const char> value,
absl::Span<char> *buf);
// Encodes as many of the specified bytes as will fit as a length-delimited
// field and returns true as long as the field header (`tag_type` and `length`)
// fits.
// Used for string, bytes, message, and packed-repeated field type.
// Consumes up to kMaxVarintSize * 2 + value.size() bytes (20 + value.size()).
bool EncodeBytesTruncate(uint64_t tag, absl::Span<const char> value,
absl::Span<char> *buf);
// Encodes the specified string as a length-delimited field and returns true if
// it fits.
// Used for string, bytes, message, and packed-repeated field type.
// Consumes up to kMaxVarintSize * 2 + value.size() bytes (20 + value.size()).
inline bool EncodeString(uint64_t tag, absl::string_view value,
absl::Span<char> *buf) {
return EncodeBytes(tag, value, buf);
}
// Encodes as much of the specified string as will fit as a length-delimited
// field and returns true as long as the field header (`tag_type` and `length`)
// fits.
// Used for string, bytes, message, and packed-repeated field type.
// Consumes up to kMaxVarintSize * 2 + value.size() bytes (20 + value.size()).
inline bool EncodeStringTruncate(uint64_t tag, absl::string_view value,
absl::Span<char> *buf) {
return EncodeBytesTruncate(tag, value, buf);
}
// Encodes the header for a length-delimited field containing up to `max_size`
// bytes or the number remaining in the buffer, whichever is less. If the
// header fits, a non-nullptr `Span` is returned; this must be passed to
// `EncodeMessageLength` after all contents are encoded to finalize the length
// field. If the header does not fit, a nullptr `Span` is returned which is
// safe to pass to `EncodeMessageLength` but need not be.
// Used for string, bytes, message, and packed-repeated field type.
// Consumes up to kMaxVarintSize * 2 bytes (20).
ABSL_MUST_USE_RESULT absl::Span<char> EncodeMessageStart(uint64_t tag,
uint64_t max_size,
absl::Span<char> *buf);
// Finalizes the length field in `msg` so that it encompasses all data encoded
// since the call to `EncodeMessageStart` which returned `msg`. Does nothing if
// `msg` is a `nullptr` `Span`.
void EncodeMessageLength(absl::Span<char> msg, const absl::Span<char> *buf);
enum class WireType : uint64_t {
kVarint = 0,
k64Bit = 1,
kLengthDelimited = 2,
k32Bit = 5,
};
constexpr uint64_t VarintSize(uint64_t value) {
return value < 128 ? 1 : 1 + VarintSize(value >> 7);
}
constexpr uint64_t MinVarintSize() {
return VarintSize((std::numeric_limits<uint64_t>::min)());
}
constexpr uint64_t MaxVarintSize() {
return VarintSize((std::numeric_limits<uint64_t>::max)());
}
constexpr uint64_t MaxVarintForSize(size_t size) {
return size >= 10 ? (std::numeric_limits<uint64_t>::max)()
: (static_cast<uint64_t>(1) << size * 7) - 1;
}
// `BufferSizeFor` returns a number of bytes guaranteed to be sufficient to
// store encoded fields of the specified WireTypes regardless of tag numbers and
// data values. This only makes sense for `WireType::kLengthDelimited` if you
// add in the length of the contents yourself, e.g. for string and bytes fields
// by adding the lengths of any encoded strings to the return value or for
// submessage fields by enumerating the fields you may encode into their
// contents.
constexpr size_t BufferSizeFor() { return 0; }
template <typename... T>
constexpr size_t BufferSizeFor(WireType type, T... tail) {
// tag_type + data + ...
return MaxVarintSize() +
(type == WireType::kVarint ? MaxVarintSize() : //
type == WireType::k64Bit ? 8 : //
type == WireType::k32Bit ? 4 : MaxVarintSize()) + //
BufferSizeFor(tail...);
}
// absl::Span<const char> represents a view into the un-processed space in a
// buffer during decoding. Decoding functions shrink the span as they go so
// that the same view can be decoded iteratively until all data are processed.
// In general, if the buffer is exhausted but additional bytes are expected by
// the decoder, it will return values as if the additional bytes were zeros.
// Length-delimited fields are an exception - if the encoded length field
// indicates more data bytes than are available in the buffer, the `bytes_value`
// and `string_value` accessors will return truncated views.
class ProtoField final {
public:
// Consumes bytes from `data` and returns true if there were any bytes to
// decode.
bool DecodeFrom(absl::Span<const char> *data);
uint64_t tag() const { return tag_; }
WireType type() const { return type_; }
// These value accessors will return nonsense if the data were not encoded in
// the corresponding wiretype from the corresponding C++ (or other language)
// type.
double double_value() const { return absl::bit_cast<double>(value_); }
float float_value() const {
return absl::bit_cast<float>(static_cast<uint32_t>(value_));
}
int32_t int32_value() const { return static_cast<int32_t>(value_); }
int64_t int64_value() const { return static_cast<int64_t>(value_); }
int32_t sint32_value() const {
if (value_ % 2) return static_cast<int32_t>(0 - ((value_ - 1) / 2) - 1);
return static_cast<int32_t>(value_ / 2);
}
int64_t sint64_value() const {
if (value_ % 2) return 0 - ((value_ - 1) / 2) - 1;
return value_ / 2;
}
uint32_t uint32_value() const { return static_cast<uint32_t>(value_); }
uint64_t uint64_value() const { return value_; }
bool bool_value() const { return value_ != 0; }
// To decode an enum, call int32_value() and cast to the appropriate type.
// Note that the official C++ proto compiler treats enum fields with values
// that do not correspond to a defined enumerator as unknown fields.
// To decode fields within a submessage field, call
// `DecodeNextField(field.BytesValue())`.
absl::Span<const char> bytes_value() const { return data_; }
absl::string_view string_value() const {
const auto data = bytes_value();
return absl::string_view(data.data(), data.size());
}
// Returns the encoded length of a length-delimited field. This equals
// `bytes_value().size()` except when the latter has been truncated due to
// buffer underrun.
uint64_t encoded_length() const { return value_; }
private:
uint64_t tag_;
WireType type_;
// For `kTypeVarint`, `kType64Bit`, and `kType32Bit`, holds the decoded value.
// For `kTypeLengthDelimited`, holds the decoded length.
uint64_t value_;
absl::Span<const char> data_;
};
} // namespace log_internal
ABSL_NAMESPACE_END
} // namespace absl
#endif // ABSL_LOG_INTERNAL_PROTO_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.
//
// -----------------------------------------------------------------------------
// 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