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( ...@@ -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 # Test targets
cc_test( cc_test(
name = "basic_log_test", name = "basic_log_test",
...@@ -458,6 +470,23 @@ cc_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( cc_binary(
name = "log_benchmark", name = "log_benchmark",
testonly = 1, testonly = 1,
......
...@@ -128,6 +128,25 @@ absl_cc_library( ...@@ -128,6 +128,25 @@ absl_cc_library(
absl_cc_library( absl_cc_library(
NAME 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 log_internal_message
SRCS SRCS
"internal/log_message.cc" "internal/log_message.cc"
...@@ -147,6 +166,7 @@ absl_cc_library( ...@@ -147,6 +166,7 @@ absl_cc_library(
absl::log_internal_append_truncated absl::log_internal_append_truncated
absl::log_internal_format absl::log_internal_format
absl::log_internal_globals absl::log_internal_globals
absl::log_internal_proto
absl::log_internal_log_sink_set absl::log_internal_log_sink_set
absl::log_internal_nullguard absl::log_internal_nullguard
absl::log_globals absl::log_globals
...@@ -251,6 +271,7 @@ absl_cc_library( ...@@ -251,6 +271,7 @@ absl_cc_library(
${ABSL_DEFAULT_LINKOPTS} ${ABSL_DEFAULT_LINKOPTS}
DEPS DEPS
absl::config absl::config
absl::core_headers
absl::log_entry absl::log_entry
absl::log_severity absl::log_severity
absl::strings absl::strings
...@@ -292,6 +313,7 @@ absl_cc_library( ...@@ -292,6 +313,7 @@ absl_cc_library(
${ABSL_DEFAULT_LINKOPTS} ${ABSL_DEFAULT_LINKOPTS}
DEPS DEPS
absl::config absl::config
absl::core_headers
absl::log_entry absl::log_entry
absl::log_internal_test_helpers absl::log_internal_test_helpers
absl::log_severity absl::log_severity
...@@ -553,6 +575,37 @@ absl_cc_library( ...@@ -553,6 +575,37 @@ absl_cc_library(
TESTONLY 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 # Test targets
absl_cc_test( absl_cc_test(
NAME NAME
...@@ -852,3 +905,23 @@ absl_cc_test( ...@@ -852,3 +905,23 @@ absl_cc_test(
GTest::gmock GTest::gmock
GTest::gtest_main 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( ...@@ -138,6 +138,7 @@ cc_library(
":globals", ":globals",
":log_sink_set", ":log_sink_set",
":nullguard", ":nullguard",
":proto",
"//absl/base", "//absl/base",
"//absl/base:config", "//absl/base:config",
"//absl/base:core_headers", "//absl/base:core_headers",
...@@ -233,6 +234,18 @@ cc_library( ...@@ -233,6 +234,18 @@ cc_library(
) )
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", name = "test_actions",
testonly = True, testonly = True,
srcs = ["test_actions.cc"], srcs = ["test_actions.cc"],
...@@ -246,7 +259,11 @@ cc_library( ...@@ -246,7 +259,11 @@ cc_library(
"//absl/log:log_entry", "//absl/log:log_entry",
"//absl/strings", "//absl/strings",
"//absl/time", "//absl/time",
], ] + select({
"//absl:msvc_compiler": [],
"//conditions:default": [
],
}),
) )
cc_library( cc_library(
...@@ -275,14 +292,18 @@ cc_library( ...@@ -275,14 +292,18 @@ cc_library(
linkopts = ABSL_DEFAULT_LINKOPTS, linkopts = ABSL_DEFAULT_LINKOPTS,
deps = [ deps = [
":test_helpers", ":test_helpers",
"@com_google_googletest//:gtest",
"//absl/base:config", "//absl/base:config",
"//absl/base:core_headers", "//absl/base:core_headers",
"//absl/base:log_severity", "//absl/base:log_severity",
"//absl/log:log_entry", "//absl/log:log_entry",
"//absl/strings", "//absl/strings",
"//absl/time", "//absl/time",
"@com_google_googletest//:gtest", ] + select({
], "//absl:msvc_compiler": [],
"//conditions:default": [
],
}),
) )
cc_library( cc_library(
...@@ -293,6 +314,19 @@ cc_library( ...@@ -293,6 +314,19 @@ cc_library(
deps = ["//absl/base:config"], 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 # Test targets
cc_test( cc_test(
name = "stderr_log_sink_test", name = "stderr_log_sink_test",
......
...@@ -47,6 +47,7 @@ ...@@ -47,6 +47,7 @@
namespace absl { namespace absl {
ABSL_NAMESPACE_BEGIN ABSL_NAMESPACE_BEGIN
namespace log_internal { namespace log_internal {
class AsLiteralImpl;
constexpr int kLogMessageBufferSize = 15000; constexpr int kLogMessageBufferSize = 15000;
...@@ -130,6 +131,10 @@ class LogMessage { ...@@ -130,6 +131,10 @@ class LogMessage {
LogMessage& operator<<(bool v) { return operator<< <bool>(v); } LogMessage& operator<<(bool v) { return operator<< <bool>(v); }
// clang-format on // 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. // Handle stream manipulators e.g. std::endl.
LogMessage& operator<<(std::ostream& (*m)(std::ostream& os)); LogMessage& operator<<(std::ostream& (*m)(std::ostream& os));
LogMessage& operator<<(std::ios_base& (*m)(std::ios_base& os)); LogMessage& operator<<(std::ios_base& (*m)(std::ios_base& os));
...@@ -189,6 +194,27 @@ class LogMessage { ...@@ -189,6 +194,27 @@ class LogMessage {
private: private:
struct LogMessageData; // Opaque type containing message state 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. // Returns `true` if the message is fatal or enabled debug-fatal.
bool IsFatal() const; bool IsFatal() const;
...@@ -202,6 +228,9 @@ class LogMessage { ...@@ -202,6 +228,9 @@ class LogMessage {
// Checks `FLAGS_log_backtrace_at` and appends a backtrace if appropriate. // Checks `FLAGS_log_backtrace_at` and appends a backtrace if appropriate.
void LogBacktraceIfNeeded(); 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 // 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 // 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). // no other destructors run afterward an alter it (e.g. with calls to delete).
...@@ -210,8 +239,6 @@ class LogMessage { ...@@ -210,8 +239,6 @@ class LogMessage {
// We keep the data in a separate struct so that each instance of `LogMessage` // We keep the data in a separate struct so that each instance of `LogMessage`
// uses less stack space. // uses less stack space.
std::unique_ptr<LogMessageData> data_; std::unique_ptr<LogMessageData> data_;
std::ostream stream_;
}; };
// Helper class so that `AbslStringify()` can modify the LogMessage. // Helper class so that `AbslStringify()` can modify the LogMessage.
...@@ -233,10 +260,9 @@ class StringifySink final { ...@@ -233,10 +260,9 @@ class StringifySink final {
}; };
// Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE` // Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE`
template < template <typename T,
typename T, typename std::enable_if<strings_internal::HasAbslStringify<T>::value,
typename std::enable_if<strings_internal::HasAbslStringify<T>::value, int>::type>
int>::type>
LogMessage& LogMessage::operator<<(const T& v) { LogMessage& LogMessage::operator<<(const T& v) {
StringifySink sink(*this); StringifySink sink(*this);
// Replace with public API. // Replace with public API.
...@@ -245,35 +271,26 @@ LogMessage& LogMessage::operator<<(const T& v) { ...@@ -245,35 +271,26 @@ LogMessage& LogMessage::operator<<(const T& v) {
} }
// Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE` // Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE`
template < template <typename T,
typename T, typename std::enable_if<!strings_internal::HasAbslStringify<T>::value,
typename std::enable_if<!strings_internal::HasAbslStringify<T>::value, int>::type>
int>::type>
LogMessage& LogMessage::operator<<(const T& v) { 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; 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> template <int SIZE>
LogMessage& LogMessage::operator<<(const char (&buf)[SIZE]) { LogMessage& LogMessage::operator<<(const char (&buf)[SIZE]) {
stream_ << buf; const bool literal = true;
return *this; return LogString(literal, buf);
} }
// Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE` // Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE`
template <int SIZE> template <int SIZE>
LogMessage& LogMessage::operator<<(char (&buf)[SIZE]) { LogMessage& LogMessage::operator<<(char (&buf)[SIZE]) {
stream_ << buf; const bool literal = false;
return *this; return LogString(literal, buf);
} }
// We instantiate these specializations in the library's TU to save space in // 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 // 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); ...@@ -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 float& v);
extern template LogMessage& LogMessage::operator<<(const double& v); extern template LogMessage& LogMessage::operator<<(const double& v);
extern template LogMessage& LogMessage::operator<<(const bool& 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 // `LogMessageFatal` ensures the process will exit in failure after logging this
// message. // 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 { ...@@ -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_filename = absl::CHexEscape(entry.source_filename());
const std::string source_basename = absl::CHexEscape(entry.source_basename()); const std::string source_basename = absl::CHexEscape(entry.source_basename());
const std::string text_message = absl::CHexEscape(entry.text_message()); const std::string text_message = absl::CHexEscape(entry.text_message());
std::cerr << "LogEntry{\n" // const std::string encoded_message = absl::CHexEscape(entry.encoded_message());
<< " source_filename: \"" << source_filename << "\"\n" // std::string encoded_message_str;
<< " source_basename: \"" << source_basename << "\"\n" // std::cerr << "LogEntry{\n" //
<< " source_line: " << entry.source_line() << "\n" // << " source_filename: \"" << source_filename << "\"\n" //
<< " prefix: " << (entry.prefix() ? "true\n" : "false\n") // << " source_basename: \"" << source_basename << "\"\n" //
<< " log_severity: " << entry.log_severity() << "\n" // << " source_line: " << entry.source_line() << "\n" //
<< " timestamp: " << entry.timestamp() << "\n" // << " prefix: " << (entry.prefix() ? "true\n" : "false\n") //
<< " text_message: \"" << text_message << "\"\n" // << " log_severity: " << entry.log_severity() << "\n" //
<< " verbosity: " << entry.verbosity() << " timestamp: " << entry.timestamp() << "\n" //
<< "\n" // << " text_message: \"" << text_message << "\"\n" //
<< " verbosity: " << entry.verbosity() << "\n" //
<< " encoded_message (raw): \"" << encoded_message << "\"\n" //
<< encoded_message_str //
<< "}\n"; << "}\n";
} }
......
...@@ -79,7 +79,6 @@ namespace log_internal { ...@@ -79,7 +79,6 @@ namespace log_internal {
const std::ostringstream& stream); const std::ostringstream& stream);
::testing::Matcher<const std::string&> DeathTestValidateExpectations(); ::testing::Matcher<const std::string&> DeathTestValidateExpectations();
// This feature coming soon =).
#define ENCODED_MESSAGE(message_matcher) ::testing::_ #define ENCODED_MESSAGE(message_matcher) ::testing::_
} // namespace log_internal } // namespace log_internal
......
...@@ -169,6 +169,15 @@ class LogEntry final { ...@@ -169,6 +169,15 @@ class LogEntry final {
return text_message_with_prefix_and_newline_and_nul_.data(); 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() // LogEntry::stacktrace()
// //
// Optional stacktrace, e.g. for `FATAL` logs and failed `CHECK`s. // Optional stacktrace, e.g. for `FATAL` logs and failed `CHECK`s.
...@@ -198,6 +207,7 @@ class LogEntry final { ...@@ -198,6 +207,7 @@ class LogEntry final {
tid_t tid_; tid_t tid_;
absl::Span<const char> text_message_with_prefix_and_newline_and_nul_; absl::Span<const char> text_message_with_prefix_and_newline_and_nul_;
size_t prefix_len_; size_t prefix_len_;
absl::string_view encoding_;
std::string stacktrace_; std::string stacktrace_;
friend class log_internal::LogEntryTestPeer; friend class log_internal::LogEntryTestPeer;
......
...@@ -16,7 +16,9 @@ ...@@ -16,7 +16,9 @@
#include <math.h> #include <math.h>
#include <iomanip> #include <iomanip>
#include <ios>
#include <limits> #include <limits>
#include <ostream>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
...@@ -41,15 +43,16 @@ using ::absl::log_internal::TextPrefix; ...@@ -41,15 +43,16 @@ using ::absl::log_internal::TextPrefix;
using ::testing::AllOf; using ::testing::AllOf;
using ::testing::AnyOf; using ::testing::AnyOf;
using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::Truly;
using ::testing::Types;
using ::testing::Each; using ::testing::Each;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Ge; using ::testing::Ge;
using ::testing::IsEmpty;
using ::testing::Le; using ::testing::Le;
using ::testing::ResultOf;
using ::testing::SizeIs; using ::testing::SizeIs;
using ::testing::Truly;
using ::testing::Types;
// Some aspects of formatting streamed data (e.g. pointer handling) are // Some aspects of formatting streamed data (e.g. pointer handling) are
// implementation-defined. Others are buggy in supported implementations. // implementation-defined. Others are buggy in supported implementations.
...@@ -611,10 +614,11 @@ TYPED_TEST(FloatingPointLogFormatTest, NegativeNaN) { ...@@ -611,10 +614,11 @@ TYPED_TEST(FloatingPointLogFormatTest, NegativeNaN) {
test_sink, test_sink,
Send(AllOf( Send(AllOf(
TextMessage(MatchesOstream(comparison_stream)), TextMessage(MatchesOstream(comparison_stream)),
TextMessage(AnyOf(Eq("-nan"), Eq("nan"), Eq("NaN"), TextMessage(AnyOf(Eq("-nan"), Eq("nan"), Eq("NaN"), Eq("-nan(ind)"))),
Eq("-nan(ind)"))), ENCODED_MESSAGE(EqualsProto(R"pb(value { str: ENCODED_MESSAGE(
"-nan" })pb"))))); 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(); test_sink.StartCapturingLogs();
LOG(INFO) << value; LOG(INFO) << value;
} }
...@@ -1372,9 +1376,12 @@ TEST(ManipulatorLogFormatTest, Endl) { ...@@ -1372,9 +1376,12 @@ TEST(ManipulatorLogFormatTest, Endl) {
auto comparison_stream = ComparisonStream(); auto comparison_stream = ComparisonStream();
comparison_stream << std::endl; comparison_stream << std::endl;
EXPECT_CALL(test_sink, EXPECT_CALL(
Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), test_sink,
TextMessage(Eq("\n"))))); Send(AllOf(
TextMessage(MatchesOstream(comparison_stream)),
TextMessage(Eq("\n")),
ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "\n" })pb")))));
test_sink.StartCapturingLogs(); test_sink.StartCapturingLogs();
LOG(INFO) << std::endl; LOG(INFO) << std::endl;
...@@ -1640,26 +1647,32 @@ TEST(ManipulatorLogFormatTest, IOManipsDoNotAffectAbslStringify) { ...@@ -1640,26 +1647,32 @@ TEST(ManipulatorLogFormatTest, IOManipsDoNotAffectAbslStringify) {
LOG(INFO) << std::hex << p; LOG(INFO) << std::hex << p;
} }
// Tests that verify the behavior when more data are streamed into a `LOG` TEST(StructuredLoggingOverflowTest, TruncatesStrings) {
// statement than fit in the buffer.
// Structured logging scenario is tested in other unit tests since the output
// is significantly different.
TEST(OverflowTest, TruncatesStrings) {
absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected);
// This message is too long and should be truncated to some unspecified // This message is too long and should be truncated to some unspecified size
// size no greater than the buffer size but not too much less either. It // no greater than the buffer size but not too much less either. It should be
// should be truncated rather than discarded. // truncated rather than discarded.
constexpr size_t buffer_size = 15000; EXPECT_CALL(
test_sink,
EXPECT_CALL(test_sink, Send(AllOf(
Send(TextMessage( TextMessage(AllOf(
AllOf(SizeIs(AllOf(Ge(buffer_size - 256), SizeIs(AllOf(Ge(absl::log_internal::kLogMessageBufferSize - 256),
Le(buffer_size))), Le(absl::log_internal::kLogMessageBufferSize))),
Each(Eq('x')))))); Each(Eq('x')))),
ENCODED_MESSAGE(ResultOf(
test_sink.StartCapturingLogs(); [](const logging::proto::Event& e) { return e.value(); },
LOG(INFO) << std::string(2 * buffer_size, 'x'); 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 } // 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