diff --git a/api/envoy/config/accesslog/v2/als.proto b/api/envoy/config/accesslog/v2/als.proto index 1f7a0e61d42c6..4f77fcaa4cbaf 100644 --- a/api/envoy/config/accesslog/v2/als.proto +++ b/api/envoy/config/accesslog/v2/als.proto @@ -42,6 +42,7 @@ message TcpGrpcAccessLogConfig { } // Common configuration for gRPC access logs. +// [#next-free-field: 6] message CommonGrpcAccessLogConfig { // The friendly name of the access log to be returned in :ref:`StreamAccessLogsMessage.Identifier // `. This allows the @@ -60,4 +61,9 @@ message CommonGrpcAccessLogConfig { // this limit it hit, or every time flush interval is elapsed, whichever comes first. Setting it // to zero effectively disables the batching. Defaults to 16384. google.protobuf.UInt32Value buffer_size_bytes = 4; + + // Additional filter state objects to log in :ref:`filter_state_objects + // `. + // Logger will call `FilterState::Object::serializeAsProto` to serialize the filter state object. + repeated string filter_state_objects_to_log = 5; } diff --git a/api/envoy/config/accesslog/v3alpha/als.proto b/api/envoy/config/accesslog/v3alpha/als.proto index c7fa8da334f9b..cdbb81741e1f6 100644 --- a/api/envoy/config/accesslog/v3alpha/als.proto +++ b/api/envoy/config/accesslog/v3alpha/als.proto @@ -42,6 +42,7 @@ message TcpGrpcAccessLogConfig { } // Common configuration for gRPC access logs. +// [#next-free-field: 6] message CommonGrpcAccessLogConfig { // The friendly name of the access log to be returned in :ref:`StreamAccessLogsMessage.Identifier // `. This allows the @@ -60,4 +61,9 @@ message CommonGrpcAccessLogConfig { // this limit it hit, or every time flush interval is elapsed, whichever comes first. Setting it // to zero effectively disables the batching. Defaults to 16384. google.protobuf.UInt32Value buffer_size_bytes = 4; + + // Additional filter state objects to log in :ref:`filter_state_objects + // `. + // Logger will call `FilterState::Object::serializeAsProto` to serialize the filter state object. + repeated string filter_state_objects_to_log = 5; } diff --git a/api/envoy/data/accesslog/v2/accesslog.proto b/api/envoy/data/accesslog/v2/accesslog.proto index 84d32fb2c56b9..00a2e9ccab188 100644 --- a/api/envoy/data/accesslog/v2/accesslog.proto +++ b/api/envoy/data/accesslog/v2/accesslog.proto @@ -9,6 +9,7 @@ option java_package = "io.envoyproxy.envoy.data.accesslog.v2"; import "envoy/api/v2/core/address.proto"; import "envoy/api/v2/core/base.proto"; +import "google/protobuf/any.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; import "google/protobuf/wrappers.proto"; @@ -66,7 +67,7 @@ message ConnectionProperties { } // Defines fields that are shared by all Envoy access logs. -// [#next-free-field: 21] +// [#next-free-field: 22] message AccessLogCommon { // [#not-implemented-hide:] // This field indicates the rate at which this log entry was sampled. @@ -162,6 +163,11 @@ message AccessLogCommon { // received. Note: This is always the physical peer, even if the remote address is inferred from // for example the x-forwarder-for header, proxy protocol, etc. api.v2.core.Address downstream_direct_remote_address = 20; + + // Map of filter state in stream info that have been configured to be logged. If the filter + // state serialized to any message other than `google.protobuf.Any` it will be packed into + // `google.protobuf.Any`. + map filter_state_objects = 21; } // Flags indicating occurrences during request/response processing. diff --git a/api/envoy/data/accesslog/v3alpha/accesslog.proto b/api/envoy/data/accesslog/v3alpha/accesslog.proto index 3325f249f0c40..f32307ddaf426 100644 --- a/api/envoy/data/accesslog/v3alpha/accesslog.proto +++ b/api/envoy/data/accesslog/v3alpha/accesslog.proto @@ -9,6 +9,7 @@ option java_package = "io.envoyproxy.envoy.data.accesslog.v3alpha"; import "envoy/api/v3alpha/core/address.proto"; import "envoy/api/v3alpha/core/base.proto"; +import "google/protobuf/any.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; import "google/protobuf/wrappers.proto"; @@ -66,7 +67,7 @@ message ConnectionProperties { } // Defines fields that are shared by all Envoy access logs. -// [#next-free-field: 21] +// [#next-free-field: 22] message AccessLogCommon { // [#not-implemented-hide:] // This field indicates the rate at which this log entry was sampled. @@ -162,6 +163,11 @@ message AccessLogCommon { // received. Note: This is always the physical peer, even if the remote address is inferred from // for example the x-forwarder-for header, proxy protocol, etc. api.v3alpha.core.Address downstream_direct_remote_address = 20; + + // Map of filter state in stream info that have been configured to be logged. If the filter + // state serialized to any message other than `google.protobuf.Any` it will be packed into + // `google.protobuf.Any`. + map filter_state_objects = 21; } // Flags indicating occurrences during request/response processing. diff --git a/docs/root/configuration/observability/access_log.rst b/docs/root/configuration/observability/access_log.rst index 9639ce3b6af11..337ceacd600b9 100644 --- a/docs/root/configuration/observability/access_log.rst +++ b/docs/root/configuration/observability/access_log.rst @@ -353,6 +353,16 @@ The following command operators are supported: TCP Not implemented ("-"). +%FILTER_STATE(KEY):Z% + HTTP + :ref:`Filter State ` info, where the KEY is required to + look up the filter state object. The serialized proto will be logged as JSON string if possible. + If the serialized proto is unknown to Envoy it will be logged as protobuf debug string. + Z is an optional parameter denoting string truncation up to Z characters long. + + TCP + Same as HTTP, the filter state is from connection instead of a L7 request. + %REQUESTED_SERVER_NAME% HTTP String value set on ssl connection socket for Server Name Indication (SNI) diff --git a/docs/root/intro/arch_overview/advanced/data_sharing_between_filters.rst b/docs/root/intro/arch_overview/advanced/data_sharing_between_filters.rst index a614068b6fabb..3ef220bb89a52 100644 --- a/docs/root/intro/arch_overview/advanced/data_sharing_between_filters.rst +++ b/docs/root/intro/arch_overview/advanced/data_sharing_between_filters.rst @@ -51,7 +51,8 @@ into an instance of `ServicePolicy` class (inherited from `FilterState::Object`). When a `Cluster` is created, the associated `ServicePolicy` instance will be created and cached. Note that typed metadata is not a new source of metadata. It is obtained from metadata that -is specified as part of the configuration. +is specified as part of the configuration. A `FilterState::Object` implements +`serializeAsProto` method can be configured in access loggers to log it. HTTP Per-Route Filter Configuration ----------------------------------- diff --git a/include/envoy/stream_info/BUILD b/include/envoy/stream_info/BUILD index 68a28a8ac6c61..4b3abbf1e9891 100644 --- a/include/envoy/stream_info/BUILD +++ b/include/envoy/stream_info/BUILD @@ -28,4 +28,5 @@ envoy_cc_library( name = "filter_state_interface", hdrs = ["filter_state.h"], external_deps = ["abseil_optional"], + deps = ["//source/common/protobuf"], ) diff --git a/include/envoy/stream_info/filter_state.h b/include/envoy/stream_info/filter_state.h index 28cec7f01d783..db993b59a4d03 100644 --- a/include/envoy/stream_info/filter_state.h +++ b/include/envoy/stream_info/filter_state.h @@ -7,6 +7,7 @@ #include "envoy/common/pure.h" #include "common/common/fmt.h" +#include "common/protobuf/protobuf.h" #include "absl/strings/string_view.h" @@ -25,6 +26,13 @@ class FilterState { class Object { public: virtual ~Object() = default; + + /** + * @return Protobuf::MessagePtr an unique pointer to the proto serialization of the filter + * state. If returned message type is ProtobufWkt::Any it will be directly used in protobuf + * logging. nullptr if the filter state cannot be serialized or serialization is not supported. + */ + virtual ProtobufTypes::MessagePtr serializeAsProto() const { return nullptr; } }; virtual ~FilterState() = default; diff --git a/source/common/access_log/access_log_formatter.cc b/source/common/access_log/access_log_formatter.cc index 7a20c3a36b673..1b73d4e8a4b76 100644 --- a/source/common/access_log/access_log_formatter.cc +++ b/source/common/access_log/access_log_formatter.cc @@ -209,14 +209,18 @@ void AccessLogFormatParser::parseCommand(const std::string& token, const size_t } const std::string name_data = token.substr(start, end_request - start); - const std::vector keys = absl::StrSplit(name_data, separator); - if (!keys.empty()) { - // The main value is the first key - main = keys.at(0); - if (keys.size() > 1) { - // Sub items contain additional keys - sub_items.insert(sub_items.end(), keys.begin() + 1, keys.end()); + if (!separator.empty()) { + const std::vector keys = absl::StrSplit(name_data, separator); + if (!keys.empty()) { + // The main value is the first key + main = keys.at(0); + if (keys.size() > 1) { + // Sub items contain additional keys + sub_items.insert(sub_items.end(), keys.begin() + 1, keys.end()); + } } + } else { + main = name_data; } } @@ -224,7 +228,8 @@ void AccessLogFormatParser::parseCommand(const std::string& token, const size_t std::vector AccessLogFormatParser::parse(const std::string& format) { std::string current_token; std::vector formatters; - const std::string DYNAMIC_META_TOKEN = "DYNAMIC_METADATA("; + static constexpr absl::string_view DYNAMIC_META_TOKEN{"DYNAMIC_METADATA("}; + static constexpr absl::string_view FILTER_STATE_TOKEN{"FILTER_STATE("}; const std::regex command_w_args_regex(R"EOF(%([A-Z]|_)+(\([^\)]*\))?(:[0-9]+)?(%))EOF"); for (size_t pos = 0; pos < format.length(); ++pos) { @@ -280,6 +285,18 @@ std::vector AccessLogFormatParser::parse(const std::string parseCommand(token, start, ":", filter_namespace, path, max_length); formatters.emplace_back( FormatterProviderPtr{new DynamicMetadataFormatter(filter_namespace, path, max_length)}); + } else if (absl::StartsWith(token, FILTER_STATE_TOKEN)) { + std::string key; + absl::optional max_length; + std::vector path; + const size_t start = FILTER_STATE_TOKEN.size(); + + parseCommand(token, start, "", key, path, max_length); + if (key.empty()) { + throw EnvoyException("Invalid filter state configuration, key cannot be empty."); + } + + formatters.push_back(std::make_unique(key, max_length)); } else if (absl::StartsWith(token, "START_TIME")) { const size_t parameters_length = pos + StartTimeParamStart + 1; const size_t parameters_end = command_end_position - parameters_length; @@ -619,6 +636,37 @@ std::string DynamicMetadataFormatter::format(const Http::HeaderMap&, const Http: return MetadataFormatter::format(stream_info.dynamicMetadata()); } +FilterStateFormatter::FilterStateFormatter(const std::string& key, + absl::optional max_length) + : key_(key), max_length_(max_length) {} + +std::string FilterStateFormatter::format(const Http::HeaderMap&, const Http::HeaderMap&, + const Http::HeaderMap&, + const StreamInfo::StreamInfo& stream_info) const { + const StreamInfo::FilterState& filter_state = stream_info.filterState(); + if (!filter_state.hasDataWithName(key_)) { + return UnspecifiedValueString; + } + + const auto& object = filter_state.getDataReadOnly(key_); + ProtobufTypes::MessagePtr proto = object.serializeAsProto(); + if (proto == nullptr) { + return UnspecifiedValueString; + } + + std::string value; + const auto status = Protobuf::util::MessageToJsonString(*proto, &value); + if (!status.ok()) { + // If the message contains an unknown Any (from WASM or Lua), MessageToJsonString will fail. + // TODO(lizan): add support of unknown Any. + return UnspecifiedValueString; + } + if (max_length_.has_value() && value.length() > max_length_.value()) { + return value.substr(0, max_length_.value()); + } + return value; +} + StartTimeFormatter::StartTimeFormatter(const std::string& format) : date_formatter_(format) {} std::string StartTimeFormatter::format(const Http::HeaderMap&, const Http::HeaderMap&, diff --git a/source/common/access_log/access_log_formatter.h b/source/common/access_log/access_log_formatter.h index 103657dcb40db..1094ae732f93b 100644 --- a/source/common/access_log/access_log_formatter.h +++ b/source/common/access_log/access_log_formatter.h @@ -233,6 +233,22 @@ class DynamicMetadataFormatter : public FormatterProvider, MetadataFormatter { const StreamInfo::StreamInfo& stream_info) const override; }; +/** + * Formatter based on the FilterState from StreamInfo. + */ +class FilterStateFormatter : public FormatterProvider { +public: + FilterStateFormatter(const std::string& key, absl::optional max_length); + + // FormatterProvider::format + std::string format(const Http::HeaderMap&, const Http::HeaderMap&, const Http::HeaderMap&, + const StreamInfo::StreamInfo& stream_info) const override; + +private: + std::string key_; + absl::optional max_length_; +}; + /** * Formatter */ diff --git a/source/common/router/string_accessor_impl.h b/source/common/router/string_accessor_impl.h index e579b41f7ddac..251a714f4b142 100644 --- a/source/common/router/string_accessor_impl.h +++ b/source/common/router/string_accessor_impl.h @@ -12,6 +12,13 @@ class StringAccessorImpl : public StringAccessor { // StringAccessor absl::string_view asString() const override { return value_; } + // FilterState::Object + ProtobufTypes::MessagePtr serializeAsProto() const override { + auto message = std::make_unique(); + message->set_value(value_); + return message; + } + private: std::string value_; }; diff --git a/source/extensions/access_loggers/grpc/BUILD b/source/extensions/access_loggers/grpc/BUILD index f663d1c469136..4452820391170 100644 --- a/source/extensions/access_loggers/grpc/BUILD +++ b/source/extensions/access_loggers/grpc/BUILD @@ -51,6 +51,7 @@ envoy_cc_library( "//source/common/network:utility_lib", "//source/common/stream_info:stream_info_lib", "//source/common/stream_info:utility_lib", + "@envoy_api//envoy/config/accesslog/v2:pkg_cc_proto", "@envoy_api//envoy/data/accesslog/v2:pkg_cc_proto", ], ) diff --git a/source/extensions/access_loggers/grpc/grpc_access_log_utils.cc b/source/extensions/access_loggers/grpc/grpc_access_log_utils.cc index cdefbf08e997a..a42354ccd7a1a 100644 --- a/source/extensions/access_loggers/grpc/grpc_access_log_utils.cc +++ b/source/extensions/access_loggers/grpc/grpc_access_log_utils.cc @@ -119,7 +119,8 @@ void Utility::responseFlagsToAccessLogResponseFlags( void Utility::extractCommonAccessLogProperties( envoy::data::accesslog::v2::AccessLogCommon& common_access_log, - const StreamInfo::StreamInfo& stream_info) { + const StreamInfo::StreamInfo& stream_info, + const envoy::config::accesslog::v2::CommonGrpcAccessLogConfig& config) { // TODO(mattklein123): Populate sample_rate field. if (stream_info.downstreamRemoteAddress() != nullptr) { Network::Utility::addressToProtobufAddress( @@ -235,6 +236,23 @@ void Utility::extractCommonAccessLogProperties( if (stream_info.dynamicMetadata().filter_metadata_size() > 0) { common_access_log.mutable_metadata()->MergeFrom(stream_info.dynamicMetadata()); } + + for (const auto& key : config.filter_state_objects_to_log()) { + if (stream_info.filterState().hasDataWithName(key)) { + const auto& obj = + stream_info.filterState().getDataReadOnly(key); + ProtobufTypes::MessagePtr serialized_proto = obj.serializeAsProto(); + if (serialized_proto != nullptr) { + auto& filter_state_objects = *common_access_log.mutable_filter_state_objects(); + ProtobufWkt::Any& any = filter_state_objects[key]; + if (dynamic_cast(serialized_proto.get()) != nullptr) { + any.Swap(dynamic_cast(serialized_proto.get())); + } else { + any.PackFrom(*serialized_proto); + } + } + } + } } } // namespace GrpcCommon diff --git a/source/extensions/access_loggers/grpc/grpc_access_log_utils.h b/source/extensions/access_loggers/grpc/grpc_access_log_utils.h index 1ba23cd6d169a..555481a2efc88 100644 --- a/source/extensions/access_loggers/grpc/grpc_access_log_utils.h +++ b/source/extensions/access_loggers/grpc/grpc_access_log_utils.h @@ -1,5 +1,6 @@ #pragma once +#include "envoy/config/accesslog/v2/als.pb.h" #include "envoy/data/accesslog/v2/accesslog.pb.h" #include "envoy/stream_info/stream_info.h" @@ -10,9 +11,10 @@ namespace GrpcCommon { class Utility { public: - static void - extractCommonAccessLogProperties(envoy::data::accesslog::v2::AccessLogCommon& common_access_log, - const StreamInfo::StreamInfo& stream_info); + static void extractCommonAccessLogProperties( + envoy::data::accesslog::v2::AccessLogCommon& common_access_log, + const StreamInfo::StreamInfo& stream_info, + const envoy::config::accesslog::v2::CommonGrpcAccessLogConfig& filter_states_to_log); static void responseFlagsToAccessLogResponseFlags( envoy::data::accesslog::v2::AccessLogCommon& common_access_log, diff --git a/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc b/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc index f2991d1d53aa3..8c3bb139d069c 100644 --- a/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc +++ b/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc @@ -47,7 +47,7 @@ void HttpGrpcAccessLog::emitLog(const Http::HeaderMap& request_headers, // TODO(mattklein123): Populate sample_rate field. envoy::data::accesslog::v2::HTTPAccessLogEntry log_entry; GrpcCommon::Utility::extractCommonAccessLogProperties(*log_entry.mutable_common_properties(), - stream_info); + stream_info, config_.common_config()); if (stream_info.protocol()) { switch (stream_info.protocol().value()) { diff --git a/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.h b/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.h index 6fa2b505eac3a..be8d1e1d7d3ff 100644 --- a/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.h +++ b/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.h @@ -55,6 +55,7 @@ class HttpGrpcAccessLog : public Common::ImplBase { std::vector request_headers_to_log_; std::vector response_headers_to_log_; std::vector response_trailers_to_log_; + std::vector filter_states_to_log_; }; } // namespace HttpGrpc diff --git a/source/extensions/access_loggers/grpc/tcp_grpc_access_log_impl.cc b/source/extensions/access_loggers/grpc/tcp_grpc_access_log_impl.cc index c6c8a1bb5a580..99eeec94f45fa 100644 --- a/source/extensions/access_loggers/grpc/tcp_grpc_access_log_impl.cc +++ b/source/extensions/access_loggers/grpc/tcp_grpc_access_log_impl.cc @@ -31,7 +31,7 @@ void TcpGrpcAccessLog::emitLog(const Http::HeaderMap&, const Http::HeaderMap&, // Common log properties. envoy::data::accesslog::v2::TCPAccessLogEntry log_entry; GrpcCommon::Utility::extractCommonAccessLogProperties(*log_entry.mutable_common_properties(), - stream_info); + stream_info, config_.common_config()); envoy::data::accesslog::v2::ConnectionProperties& connection_properties = *log_entry.mutable_connection_properties(); diff --git a/source/extensions/common/wasm/BUILD b/source/extensions/common/wasm/BUILD index f33cfe1a32159..274014c23b5d1 100644 --- a/source/extensions/common/wasm/BUILD +++ b/source/extensions/common/wasm/BUILD @@ -62,6 +62,9 @@ envoy_cc_library( envoy_cc_library( name = "wasm_interoperation_lib", + srcs = [ + "wasm_state.cc", + ], hdrs = [ "wasm_state.h", ], diff --git a/source/extensions/common/wasm/wasm_state.cc b/source/extensions/common/wasm/wasm_state.cc new file mode 100644 index 0000000000000..0ebb859beb9bb --- /dev/null +++ b/source/extensions/common/wasm/wasm_state.cc @@ -0,0 +1,30 @@ +#include "extensions/common/wasm/wasm_state.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +WasmState::WasmState(absl::string_view type, absl::string_view value) + : type_(type), value_(value) {} + +ProtobufTypes::MessagePtr WasmState::serializeAsProto() const { + auto any = std::make_unique(); + + if (type_.empty()) { + ProtobufWkt::BytesValue value; + value.set_value(value_); + any->PackFrom(value); + } else { + // The WASM extension serialized in its own type. + any->set_type_url(type_); + any->set_value(value_); + } + + return any; +} + +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy \ No newline at end of file diff --git a/source/extensions/common/wasm/wasm_state.h b/source/extensions/common/wasm/wasm_state.h index 071b0f85c5723..872f44b166350 100644 --- a/source/extensions/common/wasm/wasm_state.h +++ b/source/extensions/common/wasm/wasm_state.h @@ -18,10 +18,16 @@ namespace Wasm { // A simple wrapper around generic values class WasmState : public StreamInfo::FilterState::Object { public: - WasmState(absl::string_view value) : value_(value) {} + WasmState(absl::string_view type, absl::string_view value); + + explicit WasmState(absl::string_view value) : WasmState("", value) {} + const std::string& value() const { return value_; } + ProtobufTypes::MessagePtr serializeAsProto() const override; + private: + const std::string type_; const std::string value_; }; diff --git a/test/common/access_log/BUILD b/test/common/access_log/BUILD index 264a7afa2a33b..48a286109fc90 100644 --- a/test/common/access_log/BUILD +++ b/test/common/access_log/BUILD @@ -36,6 +36,7 @@ envoy_cc_test( "//source/common/access_log:access_log_formatter_lib", "//source/common/common:utility_lib", "//source/common/http:header_map_lib", + "//source/common/router:string_accessor_lib", "//test/mocks/http:http_mocks", "//test/mocks/ssl:ssl_mocks", "//test/mocks/stream_info:stream_info_mocks", diff --git a/test/common/access_log/access_log_formatter_test.cc b/test/common/access_log/access_log_formatter_test.cc index 73f2e50db962e..c5ff7b41c30a6 100644 --- a/test/common/access_log/access_log_formatter_test.cc +++ b/test/common/access_log/access_log_formatter_test.cc @@ -6,6 +6,7 @@ #include "common/access_log/access_log_formatter.h" #include "common/common/utility.h" #include "common/http/header_map_impl.h" +#include "common/router/string_accessor_impl.h" #include "test/mocks/http/mocks.h" #include "test/mocks/ssl/mocks.h" @@ -26,6 +27,16 @@ namespace Envoy { namespace AccessLog { namespace { +class TestSerializedUnknownFilterState : public StreamInfo::FilterState::Object { +public: + ProtobufTypes::MessagePtr serializeAsProto() const override { + auto any = std::make_unique(); + any->set_type_url("UnknownType"); + any->set_value("\xde\xad\xbe\xef"); + return any; + } +}; + TEST(AccessLogFormatUtilsTest, protocolToString) { EXPECT_EQ("HTTP/1.0", AccessLogFormatUtils::protocolToString(Http::Protocol::Http10)); EXPECT_EQ("HTTP/1.1", AccessLogFormatUtils::protocolToString(Http::Protocol::Http11)); @@ -989,6 +1000,22 @@ TEST(AccessLogFormatterTest, CompositeFormatterSuccess) { formatter.format(request_header, response_header, response_trailer, stream_info)); } + { + EXPECT_CALL(Const(stream_info), filterState()).Times(testing::AtLeast(1)); + stream_info.filter_state_.setData("testing", + std::make_unique("test_value"), + StreamInfo::FilterState::StateType::ReadOnly); + stream_info.filter_state_.setData("serialized", + std::make_unique(), + StreamInfo::FilterState::StateType::ReadOnly); + const std::string format = "%FILTER_STATE(testing)%|%FILTER_STATE(serialized)%|" + "%FILTER_STATE(testing):8%|%FILTER_STATE(nonexisting)%"; + FormatterImpl formatter(format); + + EXPECT_EQ("\"test_value\"|-|\"test_va|-", + formatter.format(request_header, response_header, response_trailer, stream_info)); + } + { const std::string format = "%START_TIME(%Y/%m/%d)%|%START_TIME(%s)%|%START_TIME(bad_format)%|" "%START_TIME%|%START_TIME(%f.%1f.%2f.%3f)%"; @@ -1079,6 +1106,8 @@ TEST(AccessLogFormatterTest, ParserFailures) { "%TRAILER(X?Y?Z)%", "%TRAILER(:TEST):10", "%DYNAMIC_METADATA(TEST", + "%FILTER_STATE(TEST", + "%FILTER_STATE()%", "%START_TIME(%85n)%", "%START_TIME(%#__88n)%"}; diff --git a/test/extensions/access_loggers/grpc/BUILD b/test/extensions/access_loggers/grpc/BUILD index 652fc010d5780..adc3d3f37af2e 100644 --- a/test/extensions/access_loggers/grpc/BUILD +++ b/test/extensions/access_loggers/grpc/BUILD @@ -43,6 +43,7 @@ envoy_extension_cc_test( srcs = ["http_grpc_access_log_impl_test.cc"], extension_name = "envoy.access_loggers.http_grpc", deps = [ + "//source/common/router:string_accessor_lib", "//source/extensions/access_loggers/grpc:http_grpc_access_log_lib", "//test/mocks/access_log:access_log_mocks", "//test/mocks/grpc:grpc_mocks", diff --git a/test/extensions/access_loggers/grpc/http_grpc_access_log_impl_test.cc b/test/extensions/access_loggers/grpc/http_grpc_access_log_impl_test.cc index 4ae7918b8e636..11ea5848e7d01 100644 --- a/test/extensions/access_loggers/grpc/http_grpc_access_log_impl_test.cc +++ b/test/extensions/access_loggers/grpc/http_grpc_access_log_impl_test.cc @@ -2,6 +2,7 @@ #include "common/buffer/zero_copy_input_stream_impl.h" #include "common/network/address_impl.h" +#include "common/router/string_accessor_impl.h" #include "extensions/access_loggers/grpc/http_grpc_access_log_impl.h" @@ -50,6 +51,8 @@ class HttpGrpcAccessLogTest : public testing::Test { void init() { ON_CALL(*filter_, evaluate(_, _, _, _)).WillByDefault(Return(true)); config_.mutable_common_config()->set_log_name("hello_log"); + config_.mutable_common_config()->add_filter_state_objects_to_log("string_accessor"); + config_.mutable_common_config()->add_filter_state_objects_to_log("serialized"); EXPECT_CALL(*logger_cache_, getOrCreateLogger(_, _)) .WillOnce([this](const ::envoy::config::accesslog::v2::CommonGrpcAccessLogConfig& config, GrpcCommon::GrpcAccessLoggerType logger_type) { @@ -115,6 +118,17 @@ response: {{}} std::unique_ptr access_log_; }; +class TestSerializedFilterState : public StreamInfo::FilterState::Object { +public: + ProtobufTypes::MessagePtr serializeAsProto() const override { + auto any = std::make_unique(); + ProtobufWkt::Duration value; + value.set_seconds(10); + any->PackFrom(value); + return any; + } +}; + // Test HTTP log marshaling. TEST_F(HttpGrpcAccessLogTest, Marshalling) { InSequence s; @@ -127,7 +141,11 @@ TEST_F(HttpGrpcAccessLogTest, Marshalling) { stream_info.last_downstream_tx_byte_sent_ = 2ms; stream_info.setDownstreamLocalAddress(std::make_shared("/foo")); (*stream_info.metadata_.mutable_filter_metadata())["foo"] = ProtobufWkt::Struct(); - + stream_info.filter_state_.setData("string_accessor", + std::make_unique("test_value"), + StreamInfo::FilterState::StateType::ReadOnly); + stream_info.filter_state_.setData("serialized", std::make_unique(), + StreamInfo::FilterState::StateType::ReadOnly); expectLog(R"EOF( common_properties: downstream_remote_address: @@ -148,6 +166,13 @@ TEST_F(HttpGrpcAccessLogTest, Marshalling) { metadata: filter_metadata: foo: {} + filter_state_objects: + string_accessor: + "@type": type.googleapis.com/google.protobuf.StringValue + value: test_value + serialized: + "@type": type.googleapis.com/google.protobuf.Duration + value: 10s request: {} response: {} )EOF");