Skip to content

access_log: support for dynamic metadata set as part of the request info#2895

Merged
htuch merged 7 commits intoenvoyproxy:masterfrom
glicht:dynamic-metadata
Apr 9, 2018
Merged

access_log: support for dynamic metadata set as part of the request info#2895
htuch merged 7 commits intoenvoyproxy:masterfrom
glicht:dynamic-metadata

Conversation

@glicht
Copy link
Copy Markdown
Contributor

@glicht glicht commented Mar 24, 2018

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:

com.test.my_filter: {"test_key": "foo", "test_object": {"inner_key": "bar"}}

%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

@mattklein123
Copy link
Copy Markdown
Member

@rodaine can you please review this also? Thank you.

Copy link
Copy Markdown
Member

@rodaine rodaine left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we'll need to support an unset?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@mattklein123
Copy link
Copy Markdown
Member

@glicht quick note: Do you mind doing the parallel doc/release note PR in data-plane-api so we can review it together?

@glicht
Copy link
Copy Markdown
Contributor Author

glicht commented Mar 29, 2018

Sure. Will add the documentation soon.

glicht added a commit to glicht/data-plane-api that referenced this pull request Mar 29, 2018
glicht added a commit to glicht/data-plane-api that referenced this pull request Mar 29, 2018
documentation for PR: envoyproxy/envoy#2895

Signed-off-by: Guy Lichtman <[email protected]>
@glicht
Copy link
Copy Markdown
Contributor Author

glicht commented Mar 29, 2018

Documentation PR is available at: envoyproxy/data-plane-api#590

glicht added a commit to glicht/data-plane-api that referenced this pull request Mar 31, 2018
return json;
}

DynamicMetadataFormatter::DynamicMetadataFormatter(const std::string& filter_namespace,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can probably skip the explicit FormatterPtr here, since this is emplace_back.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add comments/examples of what is going on here, it's a bit hard to process without context.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do

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);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: s/dataStruct/data_struct/

const ProtobufWkt::Struct* dataStruct = &(filter_it->second);
const Protobuf::Message* data = dataStruct;
// go through path to select sub entries
for (const auto p : path_) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public:
DynamicMetadataFormatter(const std::string& filter_namespace,
const std::vector<std::string>& path,
const absl::optional<size_t>& max_length);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably not worth passing this one by ref, it will be a tiny object that can fit in a register.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd skip this in the loop, it's a superfluous alias.

glicht added 2 commits April 3, 2018 19:36
Added additional comments, small text naming fixes, refactored out utility methods, added more tests

Signed-off-by: Guy Lichtman <[email protected]>
if (val->has_struct_value()) {
dataStruct = &(val->struct_value());
} else {
dataStruct = nullptr;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a TODO and link to #2967, this code is all getting quite hairy.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use complete sentences for comments.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: sub_items

}

// TODO(glicht): Consider adding support for route/listener/cluster metadata as suggested by @htuch.
// See: https://github.com/envoyproxy/envoy/pull/2895#pullrequestreview-108668292
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the link doesn't work anymore. Can an issue be created instead to track this ask?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use 'absl::StrSplit' instead to simplify the next set of lines

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea. will do.

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);
/**
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: namespace might be a better name to match naming pattern across the code base.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: sub_items

@glicht
Copy link
Copy Markdown
Contributor Author

glicht commented Apr 5, 2018

circleci seems to be failing on network issues:

ERROR: /source/bazel/repositories.bzl:203:5: no such package '@boringssl//': Traceback (most recent call last):
File "/build/tmp/_bazel_bazel/436badd4919a15958fa3800a4e21074a/external/bazel_tools/tools/build_defs/repo/git.bzl", line 69
_clone_or_update(ctx)
File "/build/tmp/_bazel_bazel/436badd4919a15958fa3800a4e21074a/external/bazel_tools/tools/build_defs/repo/git.bzl", line 44, in _clone_or_update
fail(("error cloning %s:\n%s" % (ctx....)))
error cloning boringssl:

@htuch
Copy link
Copy Markdown
Member

htuch commented Apr 5, 2018

@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).

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) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const std::vector<std::string>& path

ProtobufWkt::Value val;
val.set_string_value(value);
(*structObj.mutable_fields())[key] = val;
return structObj;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.....!

Copy link
Copy Markdown
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@htuch htuch merged commit 2bda629 into envoyproxy:master Apr 9, 2018
htuch pushed a commit to envoyproxy/data-plane-api that referenced this pull request Apr 9, 2018
Elite1015 pushed a commit to Elite1015/data-plane-api that referenced this pull request Feb 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants