per

public API per<T>(@Nullable() @Nullable() T key, LogPerBucketingStrategy<? extends Object> strategy)

Aggregates stateful logging with respect to a given key.

Normally log statements with conditional behaviour (e.g. rate limiting) use the same state for each invocation (e.g. counters or timestamps). This method allows an additional qualifier to be given which allows for different conditional state for each unique qualifier.

This only makes a difference for log statements which use persistent state to control conditional behaviour (e.g. atMostEvery() or every()).

This is the most general form of log aggregation and allows any keys to be used, but it requires the caller to have chosen a bucketing strategy. Where it is possible to refactor code to avoid passing keys from an unbounded space into the per(...) method (e.g. by mapping cases to an Enum), this is usually preferable. When using this method, a bucketing strategy is needed to reduce the risk of leaking memory. Consider the alternate API:


// Rate limit per unique error message ("No such file", "File corrupted" etc.).
logger.atWarning().per(error.getMessage()).atMostEvery(30, SECONDS).log(...);

A method such as the one above would need to store some record of all the unique messages it has seen in order to perform aggregation. This means that the API would suffer a potentially unbounded memory leak if a timestamp were included in the message (since all values would now be unique and need to be retained).

To fix (or at least mitigate) this issue, a LogPerBucketingStrategy is passed to provide a mapping from "unbounded key space" (e.g. arbitrary strings) to a bounded set of "bucketed" values. In the case of error messages, you might implement a bucketing strategy to classify error messages based on the type of error.

This method is most useful in helping to avoid cases where a rare event might never be logged due to rate limiting. For example, the following code will cause log statements with different types of errorMessages to be rate-limited independently of each other.


// Rate limit for each type of error (FileNotFoundException, CorruptedFileException etc.).
logger.atInfo().per(error, byClass()).atMostEvery(30, SECONDS).log(...);

If a user knows that the given key values really do form a strictly bounded set, the knownBounded strategy can be used, but it should always be documented as to why this is safe.

The key passed to this method should always be a variable (passing a constant value has no effect). If a null key is passed, this call has no effect (e.g. rate limiting will apply normally, without respect to any specific scope).

If multiple aggregation keys are added to a single log statement, then they all take effect and logging is aggregated by the unique combination of keys passed to all "per" methods.


public API per(@Nullable() @Nullable() Enum<? extends Object> key)

Aggregates stateful logging with respect to the given enum value.

Normally log statements with conditional behaviour (e.g. rate limiting) use the same state for each invocation (e.g. counters or timestamps). This method allows an additional qualifier to be given which allows for different conditional state for each unique qualifier.

This only makes a difference for log statements which use persistent state to control conditional behaviour (e.g. atMostEvery() or every()).

This method is most useful in helping to avoid cases where a rare event might never be logged due to rate limiting. For example, the following code will cause log statements with different taskTypes to be rate-limited independently of each other.


// We want to rate limit logging separately for all task types.
logger.at(INFO).per(taskType).atMostEvery(30, SECONDS).log("Start task: %s", taskSpec);

The key passed to this method should always be a variable (passing a constant value has no effect). If null is passed, this call has no effect (e.g. rate limiting will apply normally, without respect to any specific scope).

If multiple aggregation keys are added to a single log statement, then they all take effect and logging is aggregated by the unique combination of keys passed to all "per" methods.


public API per(LoggingScopeProvider scopeProvider)

Aggregates stateful logging with respect to a scoped context determined by the given scope provider.

When ScopedLoggingContext is used to create a context, it can be bound to a ScopeType. For example:


ScopedLoggingContexts.newContext(REQUEST).run(() -> scopedMethod(x, y, z));
where REQUEST defines the scope type for the context in which scopedMethod() is called. Within this context, the scope associated with the REQUEST type can then be used to aggregate logging behavior:

logger.atInfo().atMostEvery(5, SECONDS).per(REQUEST).log("Some message...");

New scope types can be created for specific subtasks using ScopeType.create("") but it is recommended to use shared constants (such as ScopeType.REQUEST) wherever feasible to avoid confusion.

Note that in order for the request scope to be applied to a log statement, the per(REQUEST) method must still be called; just being inside the request scope isn't enough.

Unlike other per() methods, this method is expected to be given a constant value. This is because the given value provides the current scope, rather than being the current scope.

If a log statement using this method is invoked outside a context of the given type, this call has no effect (e.g. rate limiting will apply normally, without respect to any specific scope).

If multiple aggregation keys are added to a single log statement, then they all take effect and logging is aggregated by the unique combination of keys passed to all "per" methods.

Parameters

scopeProvider

a constant used to defined the type of the scope in which logging is aggregated.