Skip to content
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

access log: add DYNAMIC_METADATA_UNQUOTED formatter #12991

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/root/configuration/observability/access_log/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,10 @@ The following command operators are supported:
JSON struct or list is rendered. Structs and lists may be nested. In any event, the maximum
length is ignored

%DYNAMIC_METADATA_UNQUOTED(NAMESPACE:KEY*):Z%
Same as DYNAMIC_METADATA, but with quotes removed. Useful when you want to log a plain string or you want to
compose the JSON emitted by DYNAMIC_METADATA.

.. _config_access_log_format_filter_state:

%FILTER_STATE(KEY:F):Z%
Expand Down
1 change: 1 addition & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Removed Config or Runtime
New Features
------------
* access log: added a :ref:`dynamic metadata filter<envoy_v3_api_msg_config.accesslog.v3.MetadataFilter>` for access logs, which filters whether to log based on matching dynamic metadata.
* access log: added DYNAMIC_METADATA_UNQUOTED :ref:`access log formatter <config_access_log_format>`.
* access log: added support for :ref:`%DOWNSTREAM_PEER_FINGERPRINT_1% <config_access_log_format_response_flags>` as a response flag.
* access log: added support for nested objects in :ref:`JSON logging mode <config_access_log_format_dictionaries>`.
* access log: added :ref:`omit_empty_values<envoy_v3_api_field_config.core.v3.SubstitutionFormatString.omit_empty_values>` option to omit unset value from formatted log.
Expand Down
30 changes: 26 additions & 4 deletions source/common/formatter/substitution_formatter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ std::vector<FormatterProviderPtr> SubstitutionFormatParser::parse(const std::str
std::string current_token;
std::vector<FormatterProviderPtr> formatters;
static constexpr absl::string_view DYNAMIC_META_TOKEN{"DYNAMIC_METADATA("};
static constexpr absl::string_view DYNAMIC_META_UNQUOTED_TOKEN{"DYNAMIC_METADATA_UNQUOTED("};
static constexpr absl::string_view FILTER_STATE_TOKEN{"FILTER_STATE("};
const std::regex command_w_args_regex(R"EOF(^%([A-Z]|_)+(\([^\)]*\))?(:[0-9]+)?(%))EOF");

Expand Down Expand Up @@ -348,6 +349,15 @@ std::vector<FormatterProviderPtr> SubstitutionFormatParser::parse(const std::str
parseCommand(token, start, ":", filter_namespace, path, max_length);
formatters.emplace_back(
FormatterProviderPtr{new DynamicMetadataFormatter(filter_namespace, path, max_length)});
} else if (absl::StartsWith(token, DYNAMIC_META_UNQUOTED_TOKEN)) {
std::string filter_namespace;
absl::optional<size_t> max_length;
std::vector<std::string> path;
const size_t start = DYNAMIC_META_UNQUOTED_TOKEN.size();

parseCommand(token, start, ":", filter_namespace, path, max_length);
formatters.emplace_back(FormatterProviderPtr{
new DynamicMetadataFormatter(filter_namespace, path, max_length, true)});
} else if (absl::StartsWith(token, FILTER_STATE_TOKEN)) {
std::string key;
absl::optional<size_t> max_length;
Expand Down Expand Up @@ -980,8 +990,9 @@ GrpcStatusFormatter::formatValue(const Http::RequestHeaderMap&,

MetadataFormatter::MetadataFormatter(const std::string& filter_namespace,
const std::vector<std::string>& path,
absl::optional<size_t> max_length)
: filter_namespace_(filter_namespace), path_(path), max_length_(max_length) {}
absl::optional<size_t> max_length, bool unquoted)
: filter_namespace_(filter_namespace), path_(path), max_length_(max_length),
unquoted_(unquoted) {}

absl::optional<std::string>
MetadataFormatter::formatMetadata(const envoy::config::core::v3::Metadata& metadata) const {
Expand All @@ -992,6 +1003,17 @@ MetadataFormatter::formatMetadata(const envoy::config::core::v3::Metadata& metad

std::string json = MessageUtil::getJsonStringFromMessage(value, false, true);
truncate(json, max_length_);

if (unquoted_) {
if (absl::StartsWith(json, "\"")) {
json.erase(0, 1);
}

if (absl::EndsWith(json, "\"")) {
json.pop_back();
}
}

return json;
}

Expand Down Expand Up @@ -1019,8 +1041,8 @@ MetadataFormatter::formatMetadataValue(const envoy::config::core::v3::Metadata&
// See: https://github.com/envoyproxy/envoy/issues/3006
DynamicMetadataFormatter::DynamicMetadataFormatter(const std::string& filter_namespace,
const std::vector<std::string>& path,
absl::optional<size_t> max_length)
: MetadataFormatter(filter_namespace, path, max_length) {}
absl::optional<size_t> max_length, bool unquoted)
: MetadataFormatter(filter_namespace, path, max_length, unquoted) {}

absl::optional<std::string> DynamicMetadataFormatter::format(
const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&, const Http::ResponseTrailerMap&,
Expand Down
6 changes: 4 additions & 2 deletions source/common/formatter/substitution_formatter.h
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ class StreamInfoFormatter : public FormatterProvider {
class MetadataFormatter {
public:
MetadataFormatter(const std::string& filter_namespace, const std::vector<std::string>& path,
absl::optional<size_t> max_length);
absl::optional<size_t> max_length, bool unquoted = false);

protected:
absl::optional<std::string>
Expand All @@ -318,6 +318,7 @@ class MetadataFormatter {
std::string filter_namespace_;
std::vector<std::string> path_;
absl::optional<size_t> max_length_;
const bool unquoted_;
};

/**
Expand All @@ -326,7 +327,8 @@ class MetadataFormatter {
class DynamicMetadataFormatter : public FormatterProvider, MetadataFormatter {
public:
DynamicMetadataFormatter(const std::string& filter_namespace,
const std::vector<std::string>& path, absl::optional<size_t> max_length);
const std::vector<std::string>& path, absl::optional<size_t> max_length,
bool unquoted = false);

// FormatterProvider
absl::optional<std::string> format(const Http::RequestHeaderMap&, const Http::ResponseHeaderMap&,
Expand Down
5 changes: 5 additions & 0 deletions test/common/formatter/substitution_formatter_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,11 @@ TEST(SubstitutionFormatterTest, DynamicMetadataFormatter) {
stream_info, body),
ProtoEq(ValueUtil::stringValue("test_value")));
}
{
DynamicMetadataFormatter formatter("com.test", {"test_key"}, absl::optional<size_t>(), true);
EXPECT_EQ("test_value", formatter.format(request_headers, response_headers, response_trailers,
stream_info, body));
}
{
DynamicMetadataFormatter formatter("com.test", {"test_obj"}, absl::optional<size_t>());
EXPECT_EQ(
Expand Down