Skip to content

Commit b6ace88

Browse files
TAOXUYwbpcode
andauthored
logging: support process level access log rate limiting (#40992)
Commit Message: support process level access log rate limiting Additional Description: this is used to rate limit access log emission on the process level. A typical usage is to rate limit logging to stdout or file when you have multiple access loggers. Fix #40103 Risk Level: NA as this is a new extension Testing: unit tests and integration test are added Docs Changes: updated Release Notes: updated Platform Specific Features:NA --------- Signed-off-by: Xuyang Tao <[email protected]> Co-authored-by: code <[email protected]>
1 parent cea39c2 commit b6ace88

File tree

27 files changed

+1376
-36
lines changed

27 files changed

+1376
-36
lines changed

CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,8 @@ extensions/upstreams/tcp @ggreenway @mattklein123
274274
/*/extensions/compression/zstd @rainingmaster @mattklein123
275275
# cel
276276
/*/extensions/access_loggers/filters/cel @kyessenov @douglas-reid @adisuissa
277+
# process rate limit
278+
/*/extensions/access_loggers/filters/process_ratelimit @taoxuy @kyessenov
277279
# health check
278280
/*/extensions/filters/http/health_check @mattklein123 @adisuissa
279281
# lua

api/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ proto_library(
132132
"//envoy/data/tap/v3:pkg",
133133
"//envoy/extensions/access_loggers/file/v3:pkg",
134134
"//envoy/extensions/access_loggers/filters/cel/v3:pkg",
135+
"//envoy/extensions/access_loggers/filters/process_ratelimit/v3:pkg",
135136
"//envoy/extensions/access_loggers/fluentd/v3:pkg",
136137
"//envoy/extensions/access_loggers/grpc/v3:pkg",
137138
"//envoy/extensions/access_loggers/open_telemetry/v3:pkg",
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py.
2+
3+
load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package")
4+
5+
licenses(["notice"]) # Apache 2
6+
7+
api_proto_package(
8+
deps = [
9+
"//envoy/config/core/v3:pkg",
10+
"@com_github_cncf_xds//udpa/annotations:pkg",
11+
],
12+
)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
syntax = "proto3";
2+
3+
package envoy.extensions.access_loggers.filters.process_ratelimit.v3;
4+
5+
import "envoy/config/core/v3/config_source.proto";
6+
7+
import "udpa/annotations/status.proto";
8+
import "validate/validate.proto";
9+
10+
option java_package = "io.envoyproxy.envoy.extensions.access_loggers.filters.process_ratelimit.v3";
11+
option java_outer_classname = "ProcessRatelimitProto";
12+
option java_multiple_files = true;
13+
option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/filters/process_ratelimit/v3;process_ratelimitv3";
14+
option (udpa.annotations.file_status).package_version_status = ACTIVE;
15+
16+
// [#protodoc-title: ProcessRateLimiter]
17+
// [#extension: envoy.access_loggers.extension_filters.process_ratelimit]
18+
19+
// Filters for rate limiting the access log emission using global token buckets per process and shared across all listeners.
20+
message ProcessRateLimitFilter {
21+
// The dynamic config for the token bucket.
22+
DynamicTokenBucket dynamic_config = 1;
23+
}
24+
25+
message DynamicTokenBucket {
26+
// the key used to find the token bucket in the singleton map.
27+
string resource_name = 1 [(validate.rules).string = {min_len: 1}];
28+
29+
// The configuration source for the :ref:`token_bucket <envoy_v3_api_msg_type.v3.TokenBucket>`.
30+
// It should stay the same through the process lifetime.
31+
config.core.v3.ConfigSource config_source = 2 [(validate.rules).message = {required: true}];
32+
}

api/versioning/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ proto_library(
7171
"//envoy/data/tap/v3:pkg",
7272
"//envoy/extensions/access_loggers/file/v3:pkg",
7373
"//envoy/extensions/access_loggers/filters/cel/v3:pkg",
74+
"//envoy/extensions/access_loggers/filters/process_ratelimit/v3:pkg",
7475
"//envoy/extensions/access_loggers/fluentd/v3:pkg",
7576
"//envoy/extensions/access_loggers/grpc/v3:pkg",
7677
"//envoy/extensions/access_loggers/open_telemetry/v3:pkg",

changelogs/current.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ removed_config_or_runtime:
122122
Removed runtime guard ``envoy.reloadable_features.report_load_with_rq_issued`` and legacy code paths.
123123
124124
new_features:
125+
- area: access_log
126+
change: |
127+
Support process-level rate limiting on access log emission by
128+
:ref:`ProcessRateLimitFilter <envoy_v3_api_msg_extensions.access_loggers.filters.process_ratelimit.v3.ProcessRateLimitFilter>`.
125129
- area: udp_sink
126130
change: |
127131
Enhanced the UDP sink to support tapped messages larger than 64 KB.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
load(
2+
"//bazel:envoy_build_system.bzl",
3+
"envoy_cc_extension",
4+
"envoy_cc_library",
5+
"envoy_extension_package",
6+
)
7+
8+
licenses(["notice"]) # Apache 2
9+
10+
envoy_extension_package()
11+
12+
envoy_cc_library(
13+
name = "provider_singleton_lib",
14+
srcs = ["provider_singleton.cc"],
15+
hdrs = ["provider_singleton.h"],
16+
deps = [
17+
"//envoy/event:dispatcher_interface",
18+
"//envoy/event:timer_interface",
19+
"//envoy/ratelimit:ratelimit_interface",
20+
"//source/common/common:thread_synchronizer_lib",
21+
"//source/common/common:token_bucket_impl_lib",
22+
"//source/common/config:subscription_base_interface",
23+
"//source/common/grpc:common_lib",
24+
"//source/common/init:target_lib",
25+
"//source/extensions/filters/common/local_ratelimit:local_ratelimit_lib",
26+
"@envoy_api//envoy/type/v3:pkg_cc_proto",
27+
],
28+
)
29+
30+
envoy_cc_extension(
31+
name = "filter_lib",
32+
srcs = ["filter.cc"],
33+
hdrs = ["filter.h"],
34+
deps = [
35+
":provider_singleton_lib",
36+
"//envoy/access_log:access_log_interface",
37+
"//source/common/init:target_lib",
38+
"@envoy_api//envoy/extensions/access_loggers/filters/process_ratelimit/v3:pkg_cc_proto",
39+
],
40+
)
41+
42+
envoy_cc_extension(
43+
name = "config",
44+
srcs = ["config.cc"],
45+
hdrs = ["config.h"],
46+
deps = [
47+
":filter_lib",
48+
"//envoy/access_log:access_log_interface",
49+
"//envoy/registry",
50+
"//source/common/access_log:access_log_lib",
51+
"//source/common/protobuf:utility_lib",
52+
"@envoy_api//envoy/extensions/access_loggers/filters/process_ratelimit/v3:pkg_cc_proto",
53+
],
54+
)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#include "source/extensions/access_loggers/filters/process_ratelimit/config.h"
2+
3+
#include "envoy/extensions/access_loggers/filters/process_ratelimit/v3/process_ratelimit.pb.h"
4+
5+
#include "source/common/protobuf/utility.h"
6+
#include "source/extensions/access_loggers/filters/process_ratelimit/filter.h"
7+
#include "source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.h"
8+
9+
namespace Envoy {
10+
namespace Extensions {
11+
namespace AccessLoggers {
12+
namespace Filters {
13+
namespace ProcessRateLimit {
14+
15+
AccessLog::FilterPtr ProcessRateLimitFilterFactory::createFilter(
16+
const envoy::config::accesslog::v3::ExtensionFilter& config,
17+
Server::Configuration::GenericFactoryContext& context) {
18+
auto factory_config =
19+
Config::Utility::translateToFactoryConfig(config, context.messageValidationVisitor(), *this);
20+
const auto& process_ratelimit_config =
21+
dynamic_cast<const envoy::extensions::access_loggers::filters::process_ratelimit::v3::
22+
ProcessRateLimitFilter&>(*factory_config);
23+
auto filter = std::make_unique<ProcessRateLimitFilter>(context.serverFactoryContext(),
24+
process_ratelimit_config);
25+
return filter;
26+
}
27+
28+
ProtobufTypes::MessagePtr ProcessRateLimitFilterFactory::createEmptyConfigProto() {
29+
return std::make_unique<
30+
envoy::extensions::access_loggers::filters::process_ratelimit::v3::ProcessRateLimitFilter>();
31+
}
32+
33+
REGISTER_FACTORY(ProcessRateLimitFilterFactory, AccessLog::ExtensionFilterFactory);
34+
35+
} // namespace ProcessRateLimit
36+
} // namespace Filters
37+
} // namespace AccessLoggers
38+
} // namespace Extensions
39+
} // namespace Envoy
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#pragma once
2+
3+
#include "envoy/access_log/access_log.h"
4+
#include "envoy/registry/registry.h"
5+
6+
#include "source/common/access_log/access_log_impl.h"
7+
8+
namespace Envoy {
9+
namespace Extensions {
10+
namespace AccessLoggers {
11+
namespace Filters {
12+
namespace ProcessRateLimit {
13+
14+
class ProcessRateLimitFilterFactory : public AccessLog::ExtensionFilterFactory {
15+
public:
16+
AccessLog::FilterPtr createFilter(const envoy::config::accesslog::v3::ExtensionFilter& config,
17+
Server::Configuration::GenericFactoryContext& context) override;
18+
ProtobufTypes::MessagePtr createEmptyConfigProto() override;
19+
std::string name() const override {
20+
return "envoy.access_loggers.extension_filters.process_ratelimit";
21+
}
22+
};
23+
24+
} // namespace ProcessRateLimit
25+
} // namespace Filters
26+
} // namespace AccessLoggers
27+
} // namespace Extensions
28+
} // namespace Envoy
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#include "source/extensions/access_loggers/filters/process_ratelimit/filter.h"
2+
3+
#include "envoy/access_log/access_log.h"
4+
5+
#include "source/common/init/target_impl.h"
6+
#include "source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.h"
7+
8+
namespace Envoy {
9+
namespace Extensions {
10+
namespace AccessLoggers {
11+
namespace Filters {
12+
namespace ProcessRateLimit {
13+
14+
ProcessRateLimitFilter::ProcessRateLimitFilter(
15+
Server::Configuration::ServerFactoryContext& context,
16+
const envoy::extensions::access_loggers::filters::process_ratelimit::v3::ProcessRateLimitFilter&
17+
config)
18+
: setter_key_(reinterpret_cast<intptr_t>(this)),
19+
cancel_cb_(std::make_shared<std::atomic<bool>>(false)), context_(context),
20+
stats_({ALL_PROCESS_RATELIMIT_FILTER_STATS(
21+
POOL_COUNTER_PREFIX(context.scope(), "access_log.process_ratelimit."))}) {
22+
auto setter =
23+
[this, cancel_cb = cancel_cb_](
24+
Envoy::Extensions::Filters::Common::LocalRateLimit::LocalRateLimiterSharedPtr limiter)
25+
-> void {
26+
if (!cancel_cb->load()) {
27+
ENVOY_BUG(limiter != nullptr, "limiter shouldn't be null if the `limiter` is set from "
28+
"callback.");
29+
rate_limiter_->setLimiter(limiter);
30+
}
31+
};
32+
33+
if (!config.has_dynamic_config()) {
34+
ExceptionUtil::throwEnvoyException("`dynamic_config` is required.");
35+
}
36+
rate_limiter_ = Envoy::Extensions::Filters::Common::LocalRateLimit::RateLimiterProviderSingleton::
37+
getRateLimiter(context, config.dynamic_config().resource_name(),
38+
config.dynamic_config().config_source(), setter_key_, std::move(setter));
39+
}
40+
41+
ProcessRateLimitFilter::~ProcessRateLimitFilter() {
42+
// The destructor can be called in any thread.
43+
// The `cancel_cb_` is set to true to prevent the `limiter` from being set in
44+
// the `setter` from the main thread.
45+
cancel_cb_->store(true);
46+
context_.mainThreadDispatcher().post(
47+
[limiter = std::move(rate_limiter_), setter_key = setter_key_] {
48+
// remove the setter for this filter.
49+
limiter->getSubscription()->removeSetter(setter_key);
50+
});
51+
}
52+
53+
bool ProcessRateLimitFilter::evaluate(const Formatter::Context&,
54+
const StreamInfo::StreamInfo&) const {
55+
ENVOY_BUG(rate_limiter_->getLimiter() != nullptr,
56+
"rate_limiter_.limiter_ should be already set in init callback.");
57+
Extensions::Filters::Common::LocalRateLimit::LocalRateLimiterSharedPtr limiter =
58+
rate_limiter_->getLimiter();
59+
auto result = limiter->requestAllowed({});
60+
if (!result.allowed) {
61+
stats_.denied_.inc();
62+
} else {
63+
stats_.allowed_.inc();
64+
}
65+
return result.allowed;
66+
}
67+
68+
} // namespace ProcessRateLimit
69+
} // namespace Filters
70+
} // namespace AccessLoggers
71+
} // namespace Extensions
72+
} // namespace Envoy

0 commit comments

Comments
 (0)