Skip to content

Commit

Permalink
Split the JSON (string) formatter to a Struct (proto) formatter and a…
Browse files Browse the repository at this point in the history
… wrapper that generates a string

Signed-off-by: Itamar Kaminski <itamark@google.com>
  • Loading branch information
itamarkam committed Dec 10, 2020
1 parent f6cc741 commit 6fe09ac
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 36 deletions.
26 changes: 13 additions & 13 deletions source/common/formatter/substitution_formatter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ const std::regex& getStartTimeNewlinePattern() {
}
const std::regex& getNewlinePattern() { CONSTRUCT_ON_FIRST_USE(std::regex, "\n"); }

template <class... Ts> struct JsonFormatMapVisitor : Ts... { using Ts::operator()...; };
template <class... Ts> JsonFormatMapVisitor(Ts...) -> JsonFormatMapVisitor<Ts...>;
template <class... Ts> struct StructFormatMapVisitor : Ts... { using Ts::operator()...; };
template <class... Ts> StructFormatMapVisitor(Ts...) -> StructFormatMapVisitor<Ts...>;

} // namespace

Expand Down Expand Up @@ -136,16 +136,16 @@ std::string JsonFormatterImpl::format(const Http::RequestHeaderMap& request_head
const StreamInfo::StreamInfo& stream_info,
absl::string_view local_reply_body) const {
const auto output_struct =
toStruct(request_headers, response_headers, response_trailers, stream_info, local_reply_body);
struct_formatter_.format(request_headers, response_headers, response_trailers, stream_info, local_reply_body);

const std::string log_line = MessageUtil::getJsonStringFromMessage(output_struct, false, true);
return absl::StrCat(log_line, "\n");
}

JsonFormatterImpl::JsonFormatMapWrapper
JsonFormatterImpl::toFormatMap(const ProtobufWkt::Struct& json_format) const {
auto output = std::make_unique<JsonFormatMap>();
for (const auto& pair : json_format.fields()) {
StructFormatter::StructFormatMapWrapper
StructFormatter::toFormatMap(const ProtobufWkt::Struct& struct_format) const {
auto output = std::make_unique<StructFormatMap>();
for (const auto& pair : struct_format.fields()) {
switch (pair.second.kind_case()) {
case ProtobufWkt::Value::kStringValue:
output->emplace(pair.first, SubstitutionFormatParser::parse(pair.second.string_value()));
Expand All @@ -155,13 +155,13 @@ JsonFormatterImpl::toFormatMap(const ProtobufWkt::Struct& json_format) const {
break;
default:
throw EnvoyException(
"Only string values or nested structs are supported in the JSON access log format.");
"Only string values or nested structs are supported in the Struct/JSON access log format.");
}
}
return {std::move(output)};
};

ProtobufWkt::Struct JsonFormatterImpl::toStruct(const Http::RequestHeaderMap& request_headers,
ProtobufWkt::Struct StructFormatter::format(const Http::RequestHeaderMap& request_headers,
const Http::ResponseHeaderMap& response_headers,
const Http::ResponseTrailerMap& response_trailers,
const StreamInfo::StreamInfo& stream_info,
Expand Down Expand Up @@ -197,11 +197,11 @@ ProtobufWkt::Struct JsonFormatterImpl::toStruct(const Http::RequestHeaderMap& re
}
return ValueUtil::stringValue(str);
};
const std::function<ProtobufWkt::Value(const JsonFormatterImpl::JsonFormatMapWrapper&)>
json_format_map_callback = [&](const JsonFormatterImpl::JsonFormatMapWrapper& format) {
const std::function<ProtobufWkt::Value(const StructFormatter::StructFormatMapWrapper&)>
struct_format_map_callback = [&](const StructFormatter::StructFormatMapWrapper& format) {
ProtobufWkt::Struct output;
auto* fields = output.mutable_fields();
JsonFormatMapVisitor visitor{json_format_map_callback, providers_callback};
StructFormatMapVisitor visitor{struct_format_map_callback, providers_callback};
for (const auto& pair : *format.value_) {
ProtobufWkt::Value value = absl::visit(visitor, pair.second);
if (omit_empty_values_ && value.kind_case() == ProtobufWkt::Value::kNullValue) {
Expand All @@ -211,7 +211,7 @@ ProtobufWkt::Struct JsonFormatterImpl::toStruct(const Http::RequestHeaderMap& re
}
return ValueUtil::structValue(output);
};
return json_format_map_callback(json_output_format_).struct_value();
return struct_format_map_callback(struct_output_format_).struct_value();
}

void SubstitutionFormatParser::parseCommandHeader(const std::string& token, const size_t start,
Expand Down
60 changes: 37 additions & 23 deletions source/common/formatter/substitution_formatter.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,46 @@ class FormatterImpl : public Formatter {
std::vector<FormatterProviderPtr> providers_;
};

/**
* An formatter for structured log formats, which returns a Struct proto that
* can be converted easily into multiple formats.
*/
class StructFormatter {
public:
StructFormatter(const ProtobufWkt::Struct& format_mapping, bool preserve_types,
bool omit_empty_values)
: omit_empty_values_(omit_empty_values), preserve_types_(preserve_types),
struct_output_format_(toFormatMap(format_mapping)) {}

ProtobufWkt::Struct format(const Http::RequestHeaderMap& request_headers,
const Http::ResponseHeaderMap& response_headers,
const Http::ResponseTrailerMap& response_trailers,
const StreamInfo::StreamInfo& stream_info,
absl::string_view local_reply_body) const;

private:
struct StructFormatMapWrapper;
using StructFormatMapValue =
absl::variant<const std::vector<FormatterProviderPtr>, const StructFormatMapWrapper>;
// Although not required for Struct/JSON, it is nice to have the order of
// properties preserved between the format and the log entry, thus std::map.
using StructFormatMap = std::map<std::string, StructFormatMapValue>;
using StructFormatMapPtr = std::unique_ptr<StructFormatMap>;
struct StructFormatMapWrapper {
StructFormatMapPtr value_;
};

bool omit_empty_values_;
bool preserve_types_;
const StructFormatMapWrapper struct_output_format_;
StructFormatMapWrapper toFormatMap(const ProtobufWkt::Struct& struct_format) const;
};

class JsonFormatterImpl : public Formatter {
public:
JsonFormatterImpl(const ProtobufWkt::Struct& format_mapping, bool preserve_types,
bool omit_empty_values)
: omit_empty_values_(omit_empty_values), preserve_types_(preserve_types),
json_output_format_(toFormatMap(format_mapping)) {}
: struct_formatter_(StructFormatter(format_mapping, preserve_types, omit_empty_values)) {}

// Formatter::format
std::string format(const Http::RequestHeaderMap& request_headers,
Expand All @@ -122,27 +156,7 @@ class JsonFormatterImpl : public Formatter {
absl::string_view local_reply_body) const override;

private:
struct JsonFormatMapWrapper;
using JsonFormatMapValue =
absl::variant<const std::vector<FormatterProviderPtr>, const JsonFormatMapWrapper>;
// Although not required for JSON, it is nice to have the order of properties
// preserved between the format and the log entry, thus std::map.
using JsonFormatMap = std::map<std::string, JsonFormatMapValue>;
using JsonFormatMapPtr = std::unique_ptr<JsonFormatMap>;
struct JsonFormatMapWrapper {
JsonFormatMapPtr value_;
};

bool omit_empty_values_;
bool preserve_types_;
const JsonFormatMapWrapper json_output_format_;

ProtobufWkt::Struct toStruct(const Http::RequestHeaderMap& request_headers,
const Http::ResponseHeaderMap& response_headers,
const Http::ResponseTrailerMap& response_trailers,
const StreamInfo::StreamInfo& stream_info,
absl::string_view local_reply_body) const;
JsonFormatMapWrapper toFormatMap(const ProtobufWkt::Struct& json_format) const;
StructFormatter struct_formatter_;
};

/**
Expand Down

0 comments on commit 6fe09ac

Please sign in to comment.