access_log: support for dynamic metadata set as part of the request info#2895
access_log: support for dynamic metadata set as part of the request info#2895htuch merged 7 commits intoenvoyproxy:masterfrom
Conversation
Signed-off-by: Guy Lichtman <[email protected]>
|
@rodaine can you please review this also? Thank you. |
rodaine
left a comment
There was a problem hiding this comment.
LGTM, just one minor comment on supporting unsetting values
| * values for the same key overriding existing. | ||
| */ | ||
| virtual void setDynamicMetadata(const std::string& name, const std::string& key, | ||
| const std::string& value) PURE; |
There was a problem hiding this comment.
Do you think we'll need to support an unset?
There was a problem hiding this comment.
I didn't really think about a use case which needs the clear option. I kind of envisioned this as append only with possible over write of existing values.
|
@glicht quick note: Do you mind doing the parallel doc/release note PR in data-plane-api so we can review it together? |
|
Sure. Will add the documentation soon. |
documentation for PR: envoyproxy/envoy#2895 Signed-off-by: Guy Lichtman <[email protected]>
documentation for PR: envoyproxy/envoy#2895 Signed-off-by: Guy Lichtman <[email protected]>
|
Documentation PR is available at: envoyproxy/data-plane-api#590 |
documentation for PR: envoyproxy/envoy#2895 Signed-off-by: Guy Lichtman <[email protected]>
| return json; | ||
| } | ||
|
|
||
| DynamicMetadataFormatter::DynamicMetadataFormatter(const std::string& filter_namespace, |
There was a problem hiding this comment.
Since we're going to the effort to add support for ASCII logging of dynamic metadata, how about adding in support for logging the other forms of metadata? Or at least a TODO, I think most of this is highly reusable for logging route, listener, cluster etc. metadata.
There was a problem hiding this comment.
I am not sure I have a good understanding of the various use cases that one would want to log route/listener/cluster metadata. I can add a TODO note, and if someone needs this info in the log they can add the functionality.
There was a problem hiding this comment.
That's fine. There are definitely useful situations for this, e.g. if you have a multitenant environment and want to add per-tenant identifiers to routes or listeners.
| virtual const envoy::api::v2::core::Metadata& dynamicMetadata() const PURE; | ||
|
|
||
| /** | ||
| * @param name the namespace used in the metadata in reverse dns format for example: |
There was a problem hiding this comment.
Nit: s/in reverse dns format for example/in reverse DNS format, for example/
| * @param value the string value to associate with the key. A merge will be performed with new | ||
| * values for the same key overriding existing. | ||
| */ | ||
| virtual void setDynamicMetadata(const std::string& name, const std::string& key, |
There was a problem hiding this comment.
I don't think this needs to be part of the abstract interface, this can just be a static utility method that operates on the above setDynamicMetadata.
There was a problem hiding this comment.
OK. Will remove and add a static keyValueStruct method to common/protobuf/utility.h
|
|
||
| parseCommand(token, start, ":", filter_namespace, path, max_length); | ||
| formatters.emplace_back( | ||
| FormatterPtr(new DynamicMetadataFormatter(filter_namespace, path, max_length))); |
There was a problem hiding this comment.
You can probably skip the explicit FormatterPtr here, since this is emplace_back.
There was a problem hiding this comment.
Will take a look. Note that the existing formatters also use this pattern.
| } else { | ||
| main_header = header_name.substr(0, separator); | ||
| alternative_header = header_name.substr(separator + 1, end_request - separator - 1); | ||
| main = name_data.substr(0, separator_pos); |
There was a problem hiding this comment.
Can you add comments/examples of what is going on here, it's a bit hard to process without context.
| absl::optional<size_t>& max_length); | ||
| static void parseCommand(const std::string& token, const size_t start, | ||
| const std::string& separator, std::string& main, | ||
| std::vector<std::string>& subs, absl::optional<size_t>& max_length); |
There was a problem hiding this comment.
I think some comments on what subs are and the expected pre/postconditions would be helpful.
| if (filter_it == metadata.filter_metadata().end()) { | ||
| return UnspecifiedValueString; | ||
| } | ||
| const ProtobufWkt::Struct* dataStruct = &(filter_it->second); |
| const ProtobufWkt::Struct* dataStruct = &(filter_it->second); | ||
| const Protobuf::Message* data = dataStruct; | ||
| // go through path to select sub entries | ||
| for (const auto p : path_) { |
There was a problem hiding this comment.
Can you refactor this lookup part of the method to be a general lookup helper in https://github.com/envoyproxy/envoy/blob/master/source/common/config/metadata.h?
| public: | ||
| DynamicMetadataFormatter(const std::string& filter_namespace, | ||
| const std::vector<std::string>& path, | ||
| const absl::optional<size_t>& max_length); |
There was a problem hiding this comment.
It's probably not worth passing this one by ref, it will be a tiny object that can fit in a register.
There was a problem hiding this comment.
It's the same pattern as used in the other formatters: ResponseHeaderFormatter/RequestHeaderFormatter.
| return UnspecifiedValueString; | ||
| } | ||
| const ProtobufWkt::Struct* dataStruct = &(filter_it->second); | ||
| const Protobuf::Message* data = dataStruct; |
There was a problem hiding this comment.
I'd skip this in the loop, it's a superfluous alias.
Added additional comments, small text naming fixes, refactored out utility methods, added more tests Signed-off-by: Guy Lichtman <[email protected]>
Signed-off-by: Guy Lichtman <[email protected]>
source/common/config/metadata.cc
Outdated
| if (val->has_struct_value()) { | ||
| dataStruct = &(val->struct_value()); | ||
| } else { | ||
| dataStruct = nullptr; |
There was a problem hiding this comment.
Use data_struct rather than dataStruct.
| ProtobufWkt::Value val; | ||
| *val.mutable_struct_value() = obj; | ||
| (*filter_struct.mutable_fields())["test_obj"] = val; | ||
| EXPECT_EQ(Metadata::metadataValue(metadata, filter, path).string_value(), "inner_value"); |
There was a problem hiding this comment.
Are you covering all the failure branches here? https://37371-65214191-gh.circle-artifacts.com/0/build/envoy/generated/coverage/coverage.source_common_config_metadata.cc.html
There was a problem hiding this comment.
| Config::Metadata::metadataValue(request_info.dynamicMetadata(), "com.test", "test_key") | ||
| .string_value()); | ||
| std::string json; | ||
| const auto testStruct = request_info.dynamicMetadata().filter_metadata().at("com.test"); |
There was a problem hiding this comment.
Please switch away from this snake case for local everywhere.
| * "main" and any additional sub values will be set in the vector "subs". For example token of: | ||
| * "com.test.my_filter:test_object:inner_key):100" with separator of ":" will set the following: | ||
| * - main: com.test.my_filter | ||
| * - subs: {test_object, inner_key} |
There was a problem hiding this comment.
I would rename this something like subitems, I kept expecting this to be a "substitution" list.
| std::string& main_header, std::string& alternative_header, | ||
| absl::optional<size_t>& max_length); | ||
| /** | ||
| * General parse command utility. Will parse token from start position. Token is expected to end |
There was a problem hiding this comment.
Please add a TODO and link to #2967, this code is all getting quite hairy.
There was a problem hiding this comment.
added in latest commit
| parseCommand(token, start, "?", main_header, subs, max_length); | ||
| if (subs.size() > 1) { | ||
| throw EnvoyException( | ||
| fmt::format("More than 1 alternative header specified in token: {}", token)); |
There was a problem hiding this comment.
At this point I've lost track of what an alternative header is. Should the exception message or a comment be added to explain?
| fmt::format("More than 1 alternative header specified in token: {}", token)); | ||
| } | ||
| if (subs.size() == 1) { | ||
| alternative_header = subs.front(); |
There was a problem hiding this comment.
This method has no comment, so I don't know what alternative_header assignment means.
…ments and syntax corrections Signed-off-by: Guy Lichtman <[email protected]>
| main_header = header_name; | ||
| alternative_header = ""; | ||
| if (separator_pos == std::string::npos) { | ||
| // no separator -> main value is simply the whole data |
There was a problem hiding this comment.
Please use complete sentences for comments.
There was a problem hiding this comment.
And can the comment be expanded that the first entry is meant to be the namespace and the sub_items is the key/path to the values that are desired to be printed?
| void AccessLogFormatParser::parseCommand(const std::string& token, const size_t start, | ||
| std::string& main_header, std::string& alternative_header, | ||
| const std::string& separator, std::string& main, | ||
| std::vector<std::string>& subitems, |
| } | ||
|
|
||
| // TODO(glicht): Consider adding support for route/listener/cluster metadata as suggested by @htuch. | ||
| // See: https://github.com/envoyproxy/envoy/pull/2895#pullrequestreview-108668292 |
There was a problem hiding this comment.
the link doesn't work anymore. Can an issue be created instead to track this ask?
There was a problem hiding this comment.
I've opened issue #3006 and will change the link in the comment
| std::string header_name = token.substr(start, end_request - start); | ||
| size_t separator = header_name.find('?'); | ||
| std::string name_data = token.substr(start, end_request - start); | ||
| size_t separator_pos = name_data.find(separator); |
There was a problem hiding this comment.
You can use 'absl::StrSplit' instead to simplify the next set of lines
| static void parseCommandHeader(const std::string& token, const size_t start, | ||
| std::string& main_header, std::string& alternative_header, | ||
| absl::optional<size_t>& max_length); | ||
| /** |
There was a problem hiding this comment.
nit: add a new line before this line
| * @param token the token to parse | ||
| * @param start the index to start parsing from | ||
| * @param seperator seperator between values | ||
| * @param main the first value |
There was a problem hiding this comment.
nit: namespace might be a better name to match naming pattern across the code base.
There was a problem hiding this comment.
For metadata, namespace fits, but for the header commands it doesn't. I would leave it as is.
| * @param start the index to start parsing from | ||
| * @param seperator seperator between values | ||
| * @param main the first value | ||
| * @param subitems any additional values |
… use of StrSplit Signed-off-by: Guy Lichtman <[email protected]>
|
circleci seems to be failing on network issues: ERROR: /source/bazel/repositories.bzl:203:5: no such package '@boringssl//': Traceback (most recent call last):
|
|
@glicht you need to merge master. We've switched the BorginSSL repo to GitHub to avoid this known CI issue (also, there are a bunch of conflicts to merge with now that the time format PR has merged). |
source/common/config/metadata.cc
Outdated
| const ProtobufWkt::Value& Metadata::metadataValue(const envoy::api::v2::core::Metadata& metadata, | ||
| const std::string& filter, | ||
| const std::string& key) { | ||
| const std::vector<std::string> path) { |
There was a problem hiding this comment.
const std::vector<std::string>& path
source/common/protobuf/utility.cc
Outdated
| ProtobufWkt::Value val; | ||
| val.set_string_value(value); | ||
| (*structObj.mutable_fields())[key] = val; | ||
| return structObj; |
There was a problem hiding this comment.
Still need to replace some fooBar for locals with foo_bar. Would really appreciate if you could clean this up globally. Thanks!
| * "com.test": {"test_key":"test_value","test_obj":{"inner_key":"inner_value"}} | ||
| */ | ||
| void populateMetadataTestData(envoy::api::v2::core::Metadata& metadata) { | ||
| ProtobufWkt::Struct structObj; |
Signed-off-by: Guy Lichtman <[email protected]>
…data.cc, syntax corrections Signed-off-by: Guy Lichtman <[email protected]>
…nfo (#590) documentation for PR: envoyproxy/envoy#2895 Signed-off-by: Guy Lichtman <[email protected]>
…nfo (#590) documentation for PR: envoyproxy/envoy#2895 Signed-off-by: Guy Lichtman <[email protected]>
Description: Relates to issue #1278. Added new dynamic Metadata object to request_info (accessed via
request_info.dynamicMetadata()). Dynamic Metadata can be set by filters and consumed by either other filters or the access log. Added to the access_log an option to specify logging of dynamic metadata through the following access log format token: %DYNAMIC_METADATA(filter_namespace:key*):Z%. "Z" is an optional parameter denoting max length. It is possible to have a nested key path. Each key is separated by ':'. For example for the following dynamic metadata:%DYNAMIC_METADATA(com.test.my_filter)% will log:
{"test_key": "foo", "test_object": {"inner_key": "bar"}}%DYNAMIC_METADATA(com.test.my_filter:test_key)% will log:
"foo"%DYNAMIC_METADATA(com.test.my_filter:test_object)% will log:
{"inner_key": "bar"}%DYNAMIC_METADATA(com.test.my_filter:test_object:inner_key)% will log:
"bar"Risk Level: Low
Testing: unit tests added to access_log_formatter_test and request_info_impl_test. I've done manual testing with a custom "body parser" filter which parses the request body and stores the parsed result in the dynamic metadata of request_info. If this is of interest, I am ok with contributing this filter too.
Docs Changes: envoyproxy/data-plane-api#590