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-logger: support request/response headers size #14692

Merged
merged 8 commits into from
Jan 18, 2021
Merged
Show file tree
Hide file tree
Changes from 5 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
21 changes: 21 additions & 0 deletions docs/root/configuration/observability/access_log/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ The following command operators are supported:

In typed JSON logs, START_TIME is always rendered as a string.

%REQUEST_HEADERS_BYTES%
HTTP
Request headers bytes.

TCP
TAOXUY marked this conversation as resolved.
Show resolved Hide resolved
Not implemented (0).

%BYTES_RECEIVED%
HTTP
Body bytes received.
Expand Down Expand Up @@ -213,6 +220,20 @@ The following command operators are supported:
Connection termination details may provide additional information about why the connection was
terminated by Envoy for L4 reasons.

%RESPONSE_HEADERS_BYTES%
HTTP
Response headers bytes.
TAOXUY marked this conversation as resolved.
Show resolved Hide resolved

TCP
Not implemented (0).
TAOXUY marked this conversation as resolved.
Show resolved Hide resolved

%RESPONSE_TRAILERS_BYTES%
HTTP
Response trailers bytes.

TCP
Not implemented (0).

%BYTES_SENT%
HTTP
Body bytes sent. For WebSocket connection it will also include response header bytes.
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 @@ -32,6 +32,7 @@ Removed Config or Runtime
New Features
------------
* access log: added the :ref:`formatters <envoy_v3_api_field_config.core.v3.SubstitutionFormatString.formatters>` extension point for custom formatters (command operators).
* access log: support command operator: %REQUEST_HEADERS_BYTES%, %RESPONSE_HEADERS_BYTES% and %RESPONSE_TRAILERS_BYTES%.
* http: added support for :ref:`:ref:`preconnecting <envoy_v3_api_msg_config.cluster.v3.Cluster.PreconnectPolicy>`. Preconnecting is off by default, but recommended for clusters serving latency-sensitive traffic, especially if using HTTP/1.1.
* tcp_proxy: add support for converting raw TCP streams into HTTP/1.1 CONNECT requests. See :ref:`upgrade documentation <tunneling-tcp-over-http>` for details.

Expand Down
44 changes: 44 additions & 0 deletions source/common/formatter/substitution_formatter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,15 @@ FormatterProviderPtr SubstitutionFormatParser::parseBuiltinCommand(const std::st
return std::make_unique<DownstreamPeerCertVEndFormatter>(token);
} else if (absl::StartsWith(token, "GRPC_STATUS")) {
return std::make_unique<GrpcStatusFormatter>("grpc-status", "", absl::optional<size_t>());
} else if (absl::StartsWith(token, "REQUEST_HEADERS_BYTES")) {
return std::make_unique<HeadersByteSizeFormatter>(
HeadersByteSizeFormatter::HeaderType::RequestHeaders);
} else if (absl::StartsWith(token, "RESPONSE_HEADERS_BYTES")) {
return std::make_unique<HeadersByteSizeFormatter>(
HeadersByteSizeFormatter::HeaderType::ResponseHeaders);
} else if (absl::StartsWith(token, "RESPONSE_TRAILERS_BYTES")) {
return std::make_unique<HeadersByteSizeFormatter>(
HeadersByteSizeFormatter::HeaderType::ResponseTrailers);
}

return nullptr;
Expand Down Expand Up @@ -955,6 +964,41 @@ ResponseTrailerFormatter::formatValue(const Http::RequestHeaderMap&, const Http:
return HeaderFormatter::formatValue(response_trailers);
}

HeadersByteSizeFormatter::HeadersByteSizeFormatter(const HeaderType header_type)
: header_type_(header_type) {}

uint64_t HeadersByteSizeFormatter::extractHeadersByteSize(
const Http::RequestHeaderMap& request_headers, const Http::ResponseHeaderMap& response_headers,
const Http::ResponseTrailerMap& response_trailers) const {
switch (header_type_) {
case HeaderType::RequestHeaders:
return request_headers.byteSize();
case HeaderType::ResponseHeaders:
return response_headers.byteSize();
case HeaderType::ResponseTrailers:
return response_trailers.byteSize();
default:
NOT_REACHED_GCOVR_EXCL_LINE;
}
}

absl::optional<std::string>
HeadersByteSizeFormatter::format(const Http::RequestHeaderMap& request_headers,
const Http::ResponseHeaderMap& response_headers,
const Http::ResponseTrailerMap& response_trailers,
const StreamInfo::StreamInfo&, absl::string_view) const {
return absl::StrCat(extractHeadersByteSize(request_headers, response_headers, response_trailers));
}

ProtobufWkt::Value
HeadersByteSizeFormatter::formatValue(const Http::RequestHeaderMap& request_headers,
const Http::ResponseHeaderMap& response_headers,
const Http::ResponseTrailerMap& response_trailers,
const StreamInfo::StreamInfo&, absl::string_view) const {
return ValueUtil::numberValue(
extractHeadersByteSize(request_headers, response_headers, response_trailers));
}

GrpcStatusFormatter::GrpcStatusFormatter(const std::string& main_header,
const std::string& alternative_header,
absl::optional<size_t> max_length)
Expand Down
26 changes: 26 additions & 0 deletions source/common/formatter/substitution_formatter.h
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,32 @@ class HeaderFormatter {
absl::optional<size_t> max_length_;
};

/**
* FormatterProvider for headers byte size.
*/
class HeadersByteSizeFormatter : public FormatterProvider {
public:
enum class HeaderType { RequestHeaders, ResponseHeaders, ResponseTrailers };

HeadersByteSizeFormatter(const HeaderType header_type);

absl::optional<std::string> format(const Http::RequestHeaderMap& request_headers,
const Http::ResponseHeaderMap& response_headers,
const Http::ResponseTrailerMap& response_trailers,
const StreamInfo::StreamInfo&,
absl::string_view) const override;
ProtobufWkt::Value formatValue(const Http::RequestHeaderMap& request_headers,
const Http::ResponseHeaderMap& response_headers,
const Http::ResponseTrailerMap& response_trailers,
const StreamInfo::StreamInfo&, absl::string_view) const override;

private:
uint64_t extractHeadersByteSize(const Http::RequestHeaderMap& request_headers,
const Http::ResponseHeaderMap& response_headers,
const Http::ResponseTrailerMap& response_trailers) const;
HeaderType header_type_;
};

/**
* FormatterProvider for request headers.
*/
Expand Down
36 changes: 36 additions & 0 deletions test/common/formatter/substitution_formatter_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,42 @@ TEST(SubstitutionFormatterTest, requestHeaderFormatter) {
}
}

TEST(SubstitutionFormatterTest, headersByteSizeFormatter) {
StreamInfo::MockStreamInfo stream_info;
Http::TestRequestHeaderMapImpl request_header{{":method", "GET"}, {":path", "/"}};
Http::TestResponseHeaderMapImpl response_header{{":method", "PUT"}};
Http::TestResponseTrailerMapImpl response_trailer{{":method", "POST"}, {"test-2", "test-2"}};
std::string body;

{
HeadersByteSizeFormatter formatter(HeadersByteSizeFormatter::HeaderType::RequestHeaders);
EXPECT_EQ(
formatter.format(request_header, response_header, response_trailer, stream_info, body),
"16");
EXPECT_THAT(
formatter.formatValue(request_header, response_header, response_trailer, stream_info, body),
ProtoEq(ValueUtil::numberValue(16)));
}
{
HeadersByteSizeFormatter formatter(HeadersByteSizeFormatter::HeaderType::ResponseHeaders);
EXPECT_EQ(
formatter.format(request_header, response_header, response_trailer, stream_info, body),
"10");
EXPECT_THAT(
formatter.formatValue(request_header, response_header, response_trailer, stream_info, body),
ProtoEq(ValueUtil::numberValue(10)));
}
{
HeadersByteSizeFormatter formatter(HeadersByteSizeFormatter::HeaderType::ResponseTrailers);
EXPECT_EQ(
formatter.format(request_header, response_header, response_trailer, stream_info, body),
"23");
EXPECT_THAT(
formatter.formatValue(request_header, response_header, response_trailer, stream_info, body),
ProtoEq(ValueUtil::numberValue(23)));
}
}

TEST(SubstitutionFormatterTest, responseHeaderFormatter) {
StreamInfo::MockStreamInfo stream_info;
Http::TestRequestHeaderMapImpl request_header{{":method", "GET"}, {":path", "/"}};
Expand Down
38 changes: 38 additions & 0 deletions test/common/router/router_upstream_log_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -326,5 +326,43 @@ name: accesslog
EXPECT_LE(std::abs(std::difftime(log_time, now)), 300);
}

// Test request headers/response headers/response trailers byte size.
TEST_F(RouterUpstreamLogTest, HeaderByteSize) {
const std::string yaml = R"EOF(
name: accesslog
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
log_format:
text_format_source:
inline_string: "%REQUEST_HEADERS_BYTES% %RESPONSE_HEADERS_BYTES% %RESPONSE_TRAILERS_BYTES%"
path: "/dev/null"
)EOF";

envoy::config::accesslog::v3::AccessLog upstream_log;
TestUtility::loadFromYaml(yaml, upstream_log);

init(absl::optional<envoy::config::accesslog::v3::AccessLog>(upstream_log));
run(200, {{"request-header-name", "request-header-val"}},
{{"response-header-name", "response-header-val"}},
{{"response-trailer-name", "response-trailer-val"}});

EXPECT_EQ(output_.size(), 1U);
// Request headers:
// scheme: http
// :method: GET
// :authority: host
// :path: /
// x-envoy-expected-rq-timeout-ms: 10
// request-header-name: request-header-val

// Response headers:
// :status: 200
// response-header-name: response-header-val

// Response trailers:
// response-trailer-name: response-trailer-val
EXPECT_EQ(output_.front(), "110 49 41");
}

} // namespace Router
} // namespace Envoy