-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Add support for nested JSON format in json logging mode #12602
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
2586857
b15125c
f36fe96
9b0df3f
aed8788
8e3ad25
cabef01
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,6 +49,9 @@ 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...>; | ||
|
|
||
| } // namespace | ||
|
|
||
| const std::string SubstitutionFormatUtils::DEFAULT_FORMAT = | ||
|
|
@@ -109,14 +112,6 @@ std::string FormatterImpl::format(const Http::RequestHeaderMap& request_headers, | |
| return log_line; | ||
| } | ||
|
|
||
| JsonFormatterImpl::JsonFormatterImpl( | ||
| const absl::flat_hash_map<std::string, std::string>& format_mapping, bool preserve_types) | ||
| : preserve_types_(preserve_types) { | ||
| for (const auto& pair : format_mapping) { | ||
| json_output_format_.emplace(pair.first, SubstitutionFormatParser::parse(pair.second)); | ||
| } | ||
| } | ||
|
|
||
| std::string JsonFormatterImpl::format(const Http::RequestHeaderMap& request_headers, | ||
| const Http::ResponseHeaderMap& response_headers, | ||
| const Http::ResponseTrailerMap& response_trailers, | ||
|
|
@@ -129,37 +124,61 @@ std::string JsonFormatterImpl::format(const Http::RequestHeaderMap& request_head | |
| return absl::StrCat(log_line, "\n"); | ||
| } | ||
|
|
||
| JsonFormatterImpl::JsonFormatMap | ||
| JsonFormatterImpl::toFormatMap(const ProtobufWkt::Struct& json_format) const { | ||
| auto output = std::make_unique<std::map<std::string, JsonFormatterImpl::JsonFormatMapValue>>(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume we want to preserve order here? Do you mind adding comments here so we understand why we choose
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. Given that I've made aliases per the other comment, the explanation comment is now in the header file where the type's defined. |
||
| for (const auto& pair : json_format.fields()) { | ||
| switch (pair.second.kind_case()) { | ||
| case ProtobufWkt::Value::kStringValue: | ||
| output->emplace(pair.first, SubstitutionFormatParser::parse(pair.second.string_value())); | ||
| break; | ||
| case ProtobufWkt::Value::kStructValue: | ||
| output->emplace(pair.first, toFormatMap(pair.second.struct_value())); | ||
| break; | ||
| default: | ||
| throw EnvoyException( | ||
| "Only string values or nested structs are supported in the JSON access log format."); | ||
| } | ||
| } | ||
| return {std::move(output)}; | ||
| }; | ||
|
|
||
| ProtobufWkt::Struct JsonFormatterImpl::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 { | ||
| ProtobufWkt::Struct output; | ||
| auto* fields = output.mutable_fields(); | ||
| for (const auto& pair : json_output_format_) { | ||
| const auto& providers = pair.second; | ||
| ASSERT(!providers.empty()); | ||
|
|
||
| if (providers.size() == 1) { | ||
| const auto& provider = providers.front(); | ||
| const auto val = | ||
| preserve_types_ ? provider->formatValue(request_headers, response_headers, | ||
| response_trailers, stream_info, local_reply_body) | ||
| : ValueUtil::stringValue( | ||
| provider->format(request_headers, response_headers, | ||
| response_trailers, stream_info, local_reply_body)); | ||
| (*fields)[pair.first] = val; | ||
| } else { | ||
| // Multiple providers forces string output. | ||
| std::string str; | ||
| for (const auto& provider : providers) { | ||
| str += provider->format(request_headers, response_headers, response_trailers, stream_info, | ||
| local_reply_body); | ||
| } | ||
| (*fields)[pair.first] = ValueUtil::stringValue(str); | ||
| } | ||
| } | ||
| return output; | ||
| const std::function<ProtobufWkt::Value(const std::vector<FormatterProviderPtr>&)> | ||
| providers_callback = [&](const std::vector<FormatterProviderPtr>& providers) { | ||
| ASSERT(!providers.empty()); | ||
| if (providers.size() == 1) { | ||
| const auto& provider = providers.front(); | ||
| if (preserve_types_) { | ||
| return provider->formatValue(request_headers, response_headers, response_trailers, | ||
| stream_info, local_reply_body); | ||
| } | ||
| return ValueUtil::stringValue(provider->format( | ||
| request_headers, response_headers, response_trailers, stream_info, local_reply_body)); | ||
| } | ||
| // Multiple providers forces string output. | ||
| std::string str; | ||
| for (const auto& provider : providers) { | ||
| str += provider->format(request_headers, response_headers, response_trailers, stream_info, | ||
| local_reply_body); | ||
| } | ||
| return ValueUtil::stringValue(str); | ||
| }; | ||
| const std::function<ProtobufWkt::Value(const JsonFormatterImpl::JsonFormatMap&)> | ||
| json_format_map_callback = [&](const JsonFormatterImpl::JsonFormatMap& format) { | ||
| ProtobufWkt::Struct output; | ||
| auto* fields = output.mutable_fields(); | ||
| JsonFormatMapVisitor visitor{json_format_map_callback, providers_callback}; | ||
| for (const auto& pair : *format.value_) { | ||
| (*fields)[pair.first] = std::visit(visitor, pair.second); | ||
| } | ||
| return ValueUtil::structValue(output); | ||
| }; | ||
| return json_format_map_callback(json_output_format_).struct_value(); | ||
| } | ||
|
|
||
| void SubstitutionFormatParser::parseCommandHeader(const std::string& token, const size_t start, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -102,8 +102,8 @@ class FormatterImpl : public Formatter { | |
|
|
||
| class JsonFormatterImpl : public Formatter { | ||
| public: | ||
| JsonFormatterImpl(const absl::flat_hash_map<std::string, std::string>& format_mapping, | ||
| bool preserve_types); | ||
| JsonFormatterImpl(const ProtobufWkt::Struct& format_mapping, bool preserve_types) | ||
| : preserve_types_(preserve_types), json_output_format_(toFormatMap(format_mapping)) {} | ||
|
|
||
| // Formatter::format | ||
| std::string format(const Http::RequestHeaderMap& request_headers, | ||
|
|
@@ -113,14 +113,22 @@ class JsonFormatterImpl : public Formatter { | |
| absl::string_view local_reply_body) const override; | ||
|
|
||
| private: | ||
| struct JsonFormatMap; | ||
| using JsonFormatMapValue = | ||
| absl::variant<const std::vector<FormatterProviderPtr>, const JsonFormatMap>; | ||
| struct JsonFormatMap { | ||
| std::unique_ptr<std::map<std::string, JsonFormatMapValue>> value_; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We usually have a typedef for this. But I don't feel strongly about it.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added some aliases. Looks more readable indeed now. |
||
| }; | ||
|
|
||
| const bool preserve_types_; | ||
| std::map<const std::string, const std::vector<FormatterProviderPtr>> json_output_format_; | ||
| const JsonFormatMap 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; | ||
| JsonFormatMap toFormatMap(const ProtobufWkt::Struct& json_format) const; | ||
| }; | ||
|
|
||
| /** | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.