Rate Limit Status
Status for rate limiting operations, usable by rate limiters and available to subclasses of LogContext to handle rate limiting consistently.
Design Notes
The purpose of this class is to allow rate limiters to behave in a way which is consistent when multiple rate limiters are combined for a single log statement. If you are writing a rate limiter for Flogger which you want to "play well" with other rate limiters, it is essential that you understand how RateLimitStatus is designed to work.
Firstly, LogContext tracks a single status for each log statement reached. This is modified by code in the postProcess() method (which can be overridden by custom logger implementations).
When a rate limiter is used, it returns a RateLimitStatus, which is combined with the existing value held in the context:
rateLimitStatus = RateLimitStatus.combine(rateLimitStatus, MyCustomRateLimiter.check(...));
Content copied to clipboard>/pre>
A rate limiter should switch between two primary states "limiting" and "pending":
- In the "limiting" state, the limiter should return the DISALLOW value
and update any internal state until it reaches its trigger condition. Once the trigger condition
is reached, the limiter enters the "pending" state.
- In the "pending" state, the limiter returns an "allow" status until it is
reset.
This two-step approach means that, when multiple rate limiters are active for a single log
statement, logging occurs after all rate limiters are "pending" (and at this point they are all
reset). This is much more consistent than having each rate limiter operate independently, and
allows a much more intuitive understanding of expected behaviour.
It is recommended that most rate limiters should start in the "pending" state to ensure that
the first log statement they process is emitted (even when multiple rate limiters are used). This
isn't required, but it should be documented either way.
Each rate limiter is expected to follow this basic structure:
final class CustomRateLimiter extends RateLimitStatus {
private static final LogSiteMap<CustomRateLimiter> map =
new LogSiteMap<CustomRateLimiter>() {
protected CustomRateLimiter initialValue() {
return new CustomRateLimiter();
}
};
static RateLimitStatus check(Metadata metadata, LogSiteKey logSiteKey, ...) {
MyRateLimitData rateLimitData = metadata.findValue(MY_CUSTOM_KEY);
if (rateLimitData == null) {
return null;
}
return map.get(logSiteKey, metadata).checkRateLimit(rateLimitData, ...);
}
RateLimitStatus checkRateLimit(MyRateLimitData rateLimitData, ...) {
<update internal state>
return <is-pending> ? this : DISALLOW;
}
public void reset() {
<reset from "pending" to "limiting" state>
}
}
Content copied to clipboard>/pre>
The use of
LogLevelMap
Content copied to clipboard ensures a rate limiter instance is held separately for each log
statement, but it also handles complex garbage collection issues around "specialized" log site
keys. All rate limiter implementations MUST use this approach.
Having the rate limiter class extend
RateLimitStatus
Content copied to clipboard is a convenience for the case
where the reset()
Content copied to clipboard operation requires no additional information. If the reset()
Content copied to clipboard
operation requires extra state (e.g. from previous logging calls) then this approach will not be
possible, and a separate RateLimitStatus
Content copied to clipboard subclass would need to be allocated to hold that
state.
Rate limiter instances MUST be thread safe, and should avoid using locks wherever
possible (since using explicit locking can cause unacceptable thread contention in highly
concurrent systems).
Content copied to clipboardSee also
Original Java code of Google Flogger