Skip to content

Commit 51087aa

Browse files
committed
formatter: add query_params to log all params
Fixes: #40925 Signed-off-by: Christian Rohmann <christian.rohmann@inovex.de>
1 parent 4af3832 commit 51087aa

File tree

5 files changed

+145
-0
lines changed

5 files changed

+145
-0
lines changed

changelogs/current.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,9 @@ removed_config_or_runtime:
1919
# *Normally occurs at the end of the* :ref:`deprecation period <deprecated>`
2020

2121
new_features:
22+
- area: formatter
23+
change: |
24+
Added ``QUERY_PARAMS`` support for substitution formatter to log all query params.
25+
See :ref:`access log formatter <config_access_log_format>` for more details.
2226
2327
deprecated:

docs/root/configuration/advanced/substitution_formatter.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1425,6 +1425,21 @@ Current supported substitution commands include:
14251425
TCP/UDP
14261426
Not implemented. It will appear as ``"-"`` in the access logs.
14271427

1428+
``%QUERY_PARAMS(X):Z%``
1429+
HTTP
1430+
All of the query parameters. The parameter ``X`` is used to specify how the query parameters are presented.
1431+
1432+
The ``X`` parameter can be:
1433+
1434+
* ``ORIG``: The output will be original query params string part of the path with no treatment. If the ``X`` is
1435+
not present, ``WQ`` will be used.
1436+
* ``DECODED``: The query params will be URL decoded.
1437+
1438+
``Z`` is an optional parameter denoting string truncation up to ``Z`` characters long.
1439+
1440+
TCP/UDP
1441+
Not implemented. It will appear as ``"-"`` in the access logs.
1442+
14281443
``%PATH(X:Y):Z%``
14291444
HTTP
14301445
The value of the request path. The parameter ``X`` is used to specify whether the output contains

source/common/formatter/http_specific_formatter.cc

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,55 @@ QueryParameterFormatter::formatValue(const Context& context,
304304
return ValueUtil::optionalStringValue(format(context, stream_info));
305305
}
306306

307+
QueryParametersFormatter::QueryParametersFormatter(absl::string_view decoding,
308+
absl::optional<size_t> max_length)
309+
: decoding_(decoding), max_length_(max_length) {}
310+
311+
// FormatterProvider
312+
absl::optional<std::string> QueryParametersFormatter::format(const Context& context,
313+
const StreamInfo::StreamInfo&) const {
314+
const auto request_headers = context.requestHeaders();
315+
if (!request_headers.has_value()) {
316+
return absl::nullopt;
317+
}
318+
319+
bool decode_bool = false;
320+
if (decoding.empty()) {
321+
decode_bool = false;
322+
} else if (decoding == "ORIG") {
323+
decode_bool = false;
324+
} if (decoding == "DECODED") {
325+
decode_bool = true;
326+
} else {
327+
return absl::InvalidArgumentError(
328+
fmt::format("Invalid QUERY_PARAMS option: '{}', only 'ORIG'/'DECODED' are allowed", decoding));
329+
}
330+
331+
// Gather query parameters substring from path
332+
absl::string_view path_view = request_headers->getPathValue();
333+
auto query_offset = path_view.find('?');
334+
absl::optional<string_view> query_params;
335+
if (query_offset != absl::string_view::npos) {
336+
query_params = path_view.substr(query_offset+1);
337+
}
338+
339+
// Apply query param percent decoding on the query params if requested
340+
if (query_params.has_value() && decode_bool) {
341+
query_params = Http::Utility::PercentEncoding::urlDecodeQueryParameter(query_params);
342+
}
343+
344+
if (query_params.has_value() && max_length_.has_value()) {
345+
SubstitutionFormatUtils::truncate(query_params.value(), max_length_.value());
346+
}
347+
return query_params;
348+
}
349+
350+
Protobuf::Value
351+
QueryParametersFormatter::formatValue(const Context& context,
352+
const StreamInfo::StreamInfo& stream_info) const {
353+
return ValueUtil::optionalStringValue(format(context, stream_info));
354+
}
355+
307356
absl::optional<std::string> PathFormatter::format(const Context& context,
308357
const StreamInfo::StreamInfo&) const {
309358

@@ -495,6 +544,11 @@ BuiltInHttpCommandParser::getKnownFormatters() {
495544
[](absl::string_view format, absl::optional<size_t> max_length) {
496545
return std::make_unique<QueryParameterFormatter>(std::string(format), max_length);
497546
}}},
547+
{"QUERY_PARAMS",
548+
{CommandSyntaxChecker::PARAMS_OPTIONAL | CommandSyntaxChecker::LENGTH_ALLOWED,
549+
[](absl::string_view decoding, absl::optional<size_t> max_length) {
550+
return std::make_unique<QueryParametersFormatter>(std::string(decoding), max_length);
551+
}}},
498552
{"PATH",
499553
{CommandSyntaxChecker::PARAMS_OPTIONAL | CommandSyntaxChecker::LENGTH_ALLOWED,
500554
[](absl::string_view format, absl::optional<size_t> max_length) {

source/common/formatter/http_specific_formatter.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,21 @@ class QueryParameterFormatter : public FormatterProvider {
180180
absl::optional<size_t> max_length_;
181181
};
182182

183+
class QueryParametersFormatter : public FormatterProvider {
184+
public:
185+
QueryParametersFormatter(absl::string_view decoding, absl::optional<size_t> max_length);
186+
187+
// FormatterProvider
188+
absl::optional<std::string> decoding(const Context& context,
189+
const StreamInfo::StreamInfo& stream_info) const override;
190+
Protobuf::Value formatValue(const Context& context,
191+
const StreamInfo::StreamInfo& stream_info) const override;
192+
193+
private:
194+
const std::string decoding_;
195+
absl::optional<size_t> max_length_;
196+
};
197+
183198
class PathFormatter : public FormatterProvider {
184199
public:
185200
enum PathFormatterOption {

test/common/formatter/substitution_formatter_test.cc

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2766,6 +2766,63 @@ TEST(SubstitutionFormatterTest, QueryParameterFormatter) {
27662766
}
27672767
}
27682768

2769+
TEST(SubstitutionFormatterTest, QueryParametersFormatter) {
2770+
StreamInfo::MockStreamInfo stream_info;
2771+
Http::TestRequestHeaderMapImpl request_header{{":method", "GET"}, {":path",
2772+
"/path?x=xxxxxx&y=yyyyy&z=zzz&encoded=%26%"}};
2773+
2774+
Context formatter_context;
2775+
formatter_context.setRequestHeaders(request_header);
2776+
2777+
{
2778+
EXPECT_THROW_WITH_MESSAGE(SubstitutionFormatParser::parse("%QUERY_PARAMS(A)%").IgnoreError(),
2779+
EnvoyException,
2780+
"Invalid PATH option: 'A', only 'ORIG'/'DECODED' are allowed");
2781+
}
2782+
2783+
{
2784+
QueryParametersFormatter formatter("", absl::optional<size_t>());
2785+
EXPECT_EQ("x=xxxxxx&y=yyyyy&z=zzz&encoded=%23%", formatter.format(formatter_context, stream_info));
2786+
EXPECT_THAT(formatter.formatValue(formatter_context, stream_info),
2787+
ProtoEq(ValueUtil::stringValue("x=xxxxxx&y=yyyyy&z=zzz&encoded=%23%")));
2788+
}
2789+
2790+
{
2791+
QueryParametersFormatter formatter("ORIG", absl::optional<size_t>());
2792+
EXPECT_EQ("x=xxxxxx&y=yyyyy&z=zzz&encoded=%23%", formatter.format(formatter_context, stream_info));
2793+
EXPECT_THAT(formatter.formatValue(formatter_context, stream_info),
2794+
ProtoEq(ValueUtil::stringValue("x=xxxxxx&y=yyyyy&z=zzz&encoded=%23%")));
2795+
}
2796+
2797+
{
2798+
QueryParametersFormatter formatter("DECODED",absl::optional<size_t>());
2799+
EXPECT_EQ("x=xxxxxx&y=yyyyy&z=zzz&encoded=#", formatter.format(formatter_context, stream_info));
2800+
EXPECT_THAT(formatter.formatValue(formatter_context, stream_info),
2801+
ProtoEq(ValueUtil::stringValue("x=xxxxxx&y=yyyyy&z=zzz&encoded=#")));
2802+
}
2803+
2804+
{
2805+
QueryParametersFormatter formatter("", absl::optional<size_t>(4));
2806+
EXPECT_EQ("x=xx", formatter.format(formatter_context, stream_info));
2807+
EXPECT_THAT(formatter.formatValue(formatter_context, stream_info),
2808+
ProtoEq(ValueUtil::stringValue("x=xx")));
2809+
}
2810+
2811+
{
2812+
QueryParametersFormatter formatter("ORIG", absl::optional<size_t>(4));
2813+
EXPECT_EQ("x=xx", formatter.format(formatter_context, stream_info));
2814+
EXPECT_THAT(formatter.formatValue(formatter_context, stream_info),
2815+
ProtoEq(ValueUtil::stringValue("x=xx")));
2816+
}
2817+
2818+
{
2819+
QueryParametersFormatter formatter("DECODED", absl::optional<size_t>(4));
2820+
EXPECT_EQ("x=x", formatter.format(formatter_context, stream_info));
2821+
EXPECT_THAT(formatter.formatValue(formatter_context, stream_info),
2822+
ProtoEq(ValueUtil::stringValue("x=xx")));
2823+
}
2824+
}
2825+
27692826
TEST(SubstitutionFormatterTest, headersByteSizeFormatter) {
27702827
StreamInfo::MockStreamInfo stream_info;
27712828
Http::TestRequestHeaderMapImpl request_header{{":method", "GET"}, {":path", "/"}};

0 commit comments

Comments
 (0)