Class LogContext<LOGGER extends AbstractLogger<API>,​API extends LoggingApi<API>>

  • All Implemented Interfaces:
    LogData, LoggingApi<API>

    public abstract class LogContext<LOGGER extends AbstractLogger<API>,​API extends LoggingApi<API>>
    extends Object
    implements LoggingApi<API>, LogData
    The base context for a logging statement, which implements the base logging API.

    This class is an implementation of the base LoggingApi interface and acts as a holder for any state applied to the log statement during the fluent call sequence. The lifecycle of a logging context is very short; it is created by a logger, usually in response to a call to the AbstractLogger.at(Level) method, and normally lasts only as long as the log statement.

    This class should not be visible to normal users of the logging API and it is only needed when extending the API to add more functionality. In order to extend the logging API and add methods to the fluent call chain, the LoggingApi interface should be extended to add any new methods, and this class should be extended to implement them. A new logger class will then be needed to return the extended context.

    Logging contexts are not thread safe.

    • Constructor Detail

      • LogContext

        protected LogContext​(Level level,
                             boolean isForced)
        Creates a logging context with the specified level, and with a timestamp obtained from the configured logging Platform.
        Parameters:
        level - the log level for this log statement.
        isForced - whether to force this log statement (see wasForced() for details).
      • LogContext

        protected LogContext​(Level level,
                             boolean isForced,
                             long timestampNanos)
        Creates a logging context with the specified level and timestamp. This constructor is provided only for testing when timestamps need to be injected. In general, subclasses would only need to call this constructor when testing additional API methods which require timestamps (e.g. additional rate limiting functionality). Most unit tests for logger subclasses should not test the value of the timestamp at all, since this is already well tested elsewhere.
        Parameters:
        level - the log level for this log statement.
        isForced - whether to force this log statement (see wasForced() for details).
        timestampNanos - the nanosecond timestamp for this log statement.
    • Method Detail

      • api

        protected abstract API api()
        Returns the current API (which is just the concrete sub-type of this instance). This is returned by fluent methods to continue the fluent call chain.
      • getLogger

        protected abstract LOGGER getLogger()
        Returns the logger which created this context. This is implemented as an abstract method to save a field in every context.
      • noOp

        protected abstract API noOp()
        Returns the constant no-op logging API, which can be returned by fluent methods in extended logging contexts to efficiently disable logging. This is implemented as an abstract method to save a field in every context.
      • getMessageParser

        protected abstract MessageParser getMessageParser()
        Returns the message parser used for all log statements made through this logger.
      • getLevel

        public final Level getLevel()
        Description copied from interface: LogData
        Returns the log level for the current log statement.
        Specified by:
        getLevel in interface LogData
      • getTimestampNanos

        public final long getTimestampNanos()
        Description copied from interface: LogData
        Returns a nanosecond timestamp for the current log statement.
        Specified by:
        getTimestampNanos in interface LogData
      • getLoggerName

        public final String getLoggerName()
        Description copied from interface: LogData
        Returns the logger name (which is usually a canonicalized class name) or null if not given.
        Specified by:
        getLoggerName in interface LogData
      • getLogSite

        public final LogSite getLogSite()
        Description copied from interface: LogData
        Returns the log site data for the current log statement.
        Specified by:
        getLogSite in interface LogData
      • getArguments

        public final Object[] getArguments()
        Description copied from interface: LogData
        Returns the arguments to be formatted with the message. Arguments exist when a log() method with a format message and separate arguments was invoked.
        Specified by:
        getArguments in interface LogData
      • getLiteralArgument

        public final Object getLiteralArgument()
        Description copied from interface: LogData
        Returns the single argument to be logged directly when no arguments were provided.
        Specified by:
        getLiteralArgument in interface LogData
      • wasForced

        public final boolean wasForced()
        Description copied from interface: LogData
        Returns whether this log statement should be emitted regardless of its log level or any other properties.

        This allows extensions of LogContext or LoggingBackend which implement additional filtering or rate-limiting fluent methods to easily check whether a log statement was forced. Forced log statements should behave exactly as if none of the filtering or rate-limiting occurred, including argument validity checks.

        Thus the idiomatic use of wasForced() is:

        
         public API someFilteringMethod(int value) {
           if (wasForced()) {
             return api();
           }
           if (value < 0) {
             throw new IllegalArgumentException("Bad things ...");
           }
           // rest of method...
         }
         

        Checking for forced log statements before checking the validity of arguments provides a last-resort means to mitigate cases in which syntactically incorrect log statements are only discovered when they are enabled.

        Specified by:
        wasForced in interface LogData
      • getMetadata

        public final Metadata getMetadata()
        Returns any additional metadata for this log statement.

        When called outside of the logging backend, this method may return different values at different times (ie, it may initially return a shared static "empty" metadata object and later return a different implementation). As such it is not safe to cache the instance returned by this method or to attempt to cast it to any particular implementation.

        Specified by:
        getMetadata in interface LogData
      • addMetadata

        protected final <T> void addMetadata​(MetadataKey<T> key,
                                             T value)
        Adds the given key/value pair to this logging context. If the key cannot be repeated, and there is already a value for the key in the metadata, then the existing value is replaced, otherwise the value is added at the end of the metadata.
        Parameters:
        key - the metadata key (see LogData).
        value - the metadata value.
      • removeMetadata

        protected final void removeMetadata​(MetadataKey<?> key)
        Removes all key/value pairs with the specified key. Note that this method does not resize any underlying backing arrays or other storage as logging contexts are expected to be short lived.
        Parameters:
        key - the metadata key (see LogData).
      • postProcess

        protected boolean postProcess​(@Nullable LogSiteKey logSiteKey)
        A callback which can be overridden to implement post processing of logging contexts prior to passing them to the backend.

        Basic Responsibilities

        This method is responsible for:

        1. Performing any rate limiting operations specific to the extended API.
        2. Updating per log-site information (e.g. for debug metrics).
        3. Adding any additional metadata to this context.
        4. Returning whether logging should be attempted.

        Implementations of this method must always call super.postProcess() first with the given log site key:

        protected boolean postProcess(@Nullable LogSiteKey logSiteKey) {
           boolean shouldLog = super.postProcess(logSiteKey);
           // Handle rate limiting if present.
           // Add additional metadata etc.
           return shouldLog;
         }

        Log Site Keys

        If per log-site information is needed during post-processing, it should be stored using a LogSiteMap. This will correctly handle "specialized" log-site keys and remove the risk of memory leaks due to retaining unused log site data indefinitely.

        Note that the given logSiteKey can be more specific than the LogSite of a log statement (i.e. a single log statement can have multiple distinct versions of its state). See per(Enum) for more information.

        If a log statement cannot be identified uniquely, then logSiteKey will be null, and this method must behave exactly as if the corresponding fluent method had not been invoked. On a system in which log site information is unavailable:

        logger.atInfo().every(100).withCause(e).log("Some message"); 
        should behave exactly the same as:
        logger.atInfo().withCause(e).log("Some message"); 

        Rate Limiting and Skipped Logs

        When handling rate limiting, updateRateLimiterStatus(RateLimitStatus) should be called for each active rate limiter. This ensures that even if logging does not occur, the number of "skipped" log statements is recorded correctly and emitted for the next allowed log.

        If postProcess() returns false without updating the rate limit status, the log statement may not be counted as skipped. In some situations this is desired, but either way the extended logging API should make it clear to the user (via documentation) what will happen. However in most cases postProcess() is only expected to return false due to rate limiting.

        If rate limiters are used there are still situations in which postProcess() can return true, but logging will not occur. This is due to race conditions around the resetting of rate limiter state. A postProcess() method can "early exit" as soon as shouldLog is false, but should assume logging will occur while it remains true.

        If a method in the logging chain determines that logging should definitely not occur, it may choose to return the NoOp logging API at that point. However this will bypass any post-processing, and no rate limiter state will be updated. This is sometimes desirable, but the API documentation should make it clear to the user as to which behaviour occurs.

        For example, level selector methods (such as atInfo()) return the NoOp API for "disabled" log statements, and these have no effect on rate limiter state, and will not update the "skipped" count. This is fine because controlling logging via log level selection is not conceptually a form of "rate limiting".

        The default implementation of this method enforces the rate limits as set by every(int) and atMostEvery(int, TimeUnit).

        Parameters:
        logSiteKey - used to lookup persistent, per log statement, state.
        Returns:
        true if logging should be attempted (usually based on rate limiter state).
      • updateRateLimiterStatus

        protected final boolean updateRateLimiterStatus​(@Nullable RateLimitStatus status)
        Callback to allow custom log contexts to apply additional rate limiting behaviour. This should be called from within an overriden postProcess() method. Typically this is invoked after calling super.postProcess(logSiteKey), such as:
        protected boolean postProcess(@Nullable LogSiteKey logSiteKey) {
           boolean shouldLog = super.postProcess(logSiteKey);
           // Even if `shouldLog` is false, we still call the rate limiter to update its state.
           shouldLog &= updateRateLimiterStatus(CustomRateLimiter.check(...));
           if (shouldLog) {
             // Maybe add additional metadata here...
           }
           return shouldLog;
         }

        See RateLimitStatus for more information on how to implement custom rate limiting in Flogger.

        Parameters:
        status - a rate limiting status, or null if the rate limiter was not active.
        Returns:
        whether logging will occur based on the current combined state of active rate limiters.
      • withInjectedLogSite

        public final API withInjectedLogSite​(LogSite logSite)
        Description copied from interface: LoggingApi
        Sets the log site for the current log statement. Explicit log site injection is very rarely necessary, since either the log site is injected automatically, or it is determined at runtime via stack analysis. The one use case where calling this method explicitly may be useful is when making logging helper methods, where some common project specific logging behavior is enshrined. For example, you can write:
        
         public void logStandardWarningAt(LogSite logSite, String message, Object... args) {
           logger.atWarning()
               .withInjectedLogSite(logSite)
               .atMostEvery(5, MINUTES)
               .logVarargs(message, args);
         }
         
        and then code can do:
        
         import static com.google.common.flogger.LogSites.logSite;
         
        and elsewhere:
        
         logStandardWarningAt(logSite(), "Badness");
         ...
         logStandardWarningAt(logSite(), "More badness: %s", getData());
         

        Now each of the call sites for the helper method is treated as if it were in the logging API, and things like rate limiting work separately for each, and the location in the log statement will be the point at which the helper method was called.

        It is very important to note that the logSite() call can be very slow, since determining the log site can involve stack trace analysis. It is only recommended in cases where logging is expected to occur (e.g. WARNING level or above). Luckily, there is typically no need to implement helper methods for FINE logging, since it's usually less structured and doesn't normally need to follow any specific "best practice" behavior.

        Note however that any stack traces generated by LoggingApi.withStackTrace(StackSize) will still contain the complete stack, including the call to the logger itself inside the helper method.

        This method must only be explicitly called once for any log statement, and if this method is called multiple times the first invocation will take precedence. This is because log site injection (if present) is expected to occur just before the final log() call and must be overrideable by earlier (explicit) calls. A null argument has no effect.

        Specified by:
        withInjectedLogSite in interface LoggingApi<LOGGER extends AbstractLogger<API>>
        Parameters:
        logSite - Log site which uniquely identifies any per-log statement resources.
      • withInjectedLogSite

        public final API withInjectedLogSite​(String internalClassName,
                                             String methodName,
                                             int encodedLineNumber,
                                             @Nullable String sourceFileName)
        Description copied from interface: LoggingApi
        Internal method not for public use. This method is only intended for use by the logger agent and related classes and should never be invoked manually.
        Specified by:
        withInjectedLogSite in interface LoggingApi<LOGGER extends AbstractLogger<API>>
        Parameters:
        internalClassName - Slash separated class name obtained from the class constant pool.
        methodName - Method name obtained from the class constant pool.
        encodedLineNumber - line number and per-line log statement index encoded as a single 32-bit value. The low 16-bits is the line number (0 to 0xFFFF inclusive) and the high 16 bits is a log statement index to distinguish multiple statements on the same line (this becomes important if line numbers are stripped from the class file and everything appears to be on the same line).
        sourceFileName - Optional base name of the source file (this value is strictly for debugging and does not contribute to either equals() or hashCode() behavior).
      • isEnabled

        public final boolean isEnabled()
        Description copied from interface: LoggingApi
        Returns true if logging is enabled at the level implied for this API, according to the current logger backend. For example:
        
           if (logger.atFine().isEnabled()) {
             // Do non-trivial argument processing
             logger.atFine().log("Message: %s", value);
           }
         

        Note that if logging is enabled for a log level, it does not always follow that the log statement will definitely be written to the backend (due to the effects of other methods in the fluent chain), but if this method returns false then it can safely be assumed that no logging will occur.

        This method is unaffected by additional methods in the fluent chain and should only ever be invoked immediately after the level selector method. In other words, the expression:

        logger.atFine().every(100).isEnabled()
        is incorrect because it will always behave identically to:
        logger.atFine().isEnabled()

        Implementation Note

        By avoiding passing a separate Level at runtime to determine "loggability", this API makes it easier to coerce bytecode optimizers into doing "dead code" removal on sections guarded by this method.

        If a proxy logger class is supplied for which:

        logger.atFine()
        unconditionally returns the "NoOp" implementation of the API (in which isEnabled() always returns false), it becomes simple for bytecode analysis to determine that:
        logger.atFine().isEnabled()
        always evaluates to false .
        Specified by:
        isEnabled in interface LoggingApi<LOGGER extends AbstractLogger<API>>
      • with

        public final <T> API with​(MetadataKey<T> key,
                                  @Nullable T value)
        Description copied from interface: LoggingApi
        Associates a metadata key constant with a runtime value for this log statement in a structured way that is accessible to logger backends.

        This method is not a replacement for general parameter passing in the LoggingApi.log() method and should be reserved for keys/values with specific semantics. Examples include:

        • Keys that are recognised by specific logger backends (typically to control logging behaviour in some way).
        • Key value pairs which are explicitly extracted from logs by tools.

        Metadata keys can support repeated values (see MetadataKey.canRepeat()), and if a repeatable key is used multiple times in the same log statement, the effect is to collect all the given values in order. If a non-repeatable key is passed multiple times, only the last value is retained (though callers should not rely on this behavior and should simply avoid repeating non-repeatable keys).

        If value is null, this method is a no-op. This is useful for specifying conditional values (e.g. via logger.atInfo().with(MY_KEY, getValueOrNull()).log(...)).

        Specified by:
        with in interface LoggingApi<LOGGER extends AbstractLogger<API>>
        Parameters:
        key - the metadata key (expected to be a static constant)
        value - a value to be associated with the key in this log statement. Null values are allowed, but the effect is always a no-op
        See Also:
        MetadataKey
      • with

        public final API with​(MetadataKey<Boolean> key)
        Description copied from interface: LoggingApi
        Sets a boolean metadata key constant to true for this log statement in a structured way that is accessible to logger backends.

        This method is not a replacement for general parameter passing in the LoggingApi.log() method and should be reserved for keys/values with specific semantics. Examples include:

        • Keys that are recognised by specific logger backends (typically to control logging behaviour in some way).
        • Key value pairs which are explicitly extracted from logs by tools.

        This method is just an alias for with(key, true) to improve readability.

        Specified by:
        with in interface LoggingApi<LOGGER extends AbstractLogger<API>>
        Parameters:
        key - the boolean metadata key (expected to be a static constant)
        See Also:
        MetadataKey
      • per

        public <T> API per​(@Nullable T key,
                           LogPerBucketingStrategy<? super T> strategy)
        Description copied from interface: LoggingApi
        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 LogPerBucketingStrategy.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.

        Specified by:
        per in interface LoggingApi<LOGGER extends AbstractLogger<API>>
      • per

        public final API per​(Enum<?> key)
        Description copied from interface: LoggingApi
        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.

        Specified by:
        per in interface LoggingApi<LOGGER extends AbstractLogger<API>>
      • per

        public API per​(LoggingScopeProvider scopeProvider)
        Description copied from interface: LoggingApi
        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.

        Specified by:
        per in interface LoggingApi<LOGGER extends AbstractLogger<API>>
        Parameters:
        scopeProvider - a constant used to defined the type of the scope in which logging is aggregated.
      • withCause

        public final API withCause​(Throwable cause)
        Description copied from interface: LoggingApi
        Associates a Throwable instance with the current log statement, to be interpreted as the cause of this statement. Typically this method will be used from within catch blocks to log the caught exception or error. If the cause is null then this method has no effect.

        If this method is called multiple times for a single log statement, the last invocation will take precedence.

        Specified by:
        withCause in interface LoggingApi<LOGGER extends AbstractLogger<API>>
      • withStackTrace

        public API withStackTrace​(StackSize size)
        Description copied from interface: LoggingApi
        Creates a synthetic exception and attaches it as the "cause" of the log statement as a way to provide additional context for the logging call itself. The exception created by this method is always of the type LogSiteStackTrace, and its message indicates the stack size.

        If the withCause(e) method is also called for the log statement (either before or after) withStackTrace(), the given exception becomes the cause of the synthetic exception.

        Specified by:
        withStackTrace in interface LoggingApi<LOGGER extends AbstractLogger<API>>
        Parameters:
        size - the maximum size of the stack trace to be generated.
      • every

        public final API every​(int n)
        Description copied from interface: LoggingApi
        Modifies the current log statement to be emitted at most one-in-N times. The specified count must be greater than zero and it is expected, but not required, that it is constant. In the absence of any other rate limiting, this method always allows the first invocation of any log statement to be emitted.

        Notes

        If multiple rate limiters are used for a single log statement, that log statement will only be emitted once all rate limiters have reached their threshold, and when a log statement is emitted all the rate limiters are reset. In particular for every(N) this means that logs need not always be emitted at multiples of N if other rate limiters are active, though it will always be at least N.

        When rate limiting is active, a "skipped" count is added to log statements to indicate how many logs were disallowed since the last log statement was emitted.

        If this method is called multiple times for a single log statement, the last invocation will take precedence.

        Specified by:
        every in interface LoggingApi<LOGGER extends AbstractLogger<API>>
        Parameters:
        n - the factor by which to reduce logging frequency.
      • onAverageEvery

        public final API onAverageEvery​(int n)
        Description copied from interface: LoggingApi
        Modifies the current log statement to be emitted with likelihood 1 in n. For example, inserting onAverageEvery(20) into a call chain results in approximately 5% as many messages being emitted as before. Unlike the other rate-limiting options, there is no guarantee about when the first such message will be emitted, though it becomes highly likely as the number of calls reaches several times n.

        Notes

        If multiple rate limiters are used for a single log statement, that log statement will only be emitted once all rate limiters have reached their threshold, and when a log statement is emitted all the rate limiters are reset. In particular for onAverageEvery(N) this means that logs may occurs less frequently than one-in-N if other rate limiters are active.

        When rate limiting is active, a "skipped" count is added to log statements to indicate how many logs were disallowed since the last log statement was emitted.

        If this method is called multiple times for a single log statement, the last invocation will take precedence.

        Specified by:
        onAverageEvery in interface LoggingApi<LOGGER extends AbstractLogger<API>>
        Parameters:
        n - the factor by which to reduce logging frequency; a value of 1 has no effect.
      • atMostEvery

        public final API atMostEvery​(int n,
                                     TimeUnit unit)
        Description copied from interface: LoggingApi
        Modifies the current log statement to be emitted at most once per specified time period. The specified duration must not be negative, and it is expected, but not required, that it is constant. In the absence of any other rate limiting, this method always allows the first invocation of any log statement to be emitted.

        Note that for performance reasons atMostEvery() is explicitly not intended to perform "proper" rate limiting to produce a limited average rate over many samples.

        Behaviour

        A call to atMostEvery() will emit the current log statement if:
        
           currentTimestampNanos >= lastTimestampNanos + unit.toNanos(n)
         
        where currentTimestampNanos is the timestamp of the current log statement and lastTimestampNanos is a time stamp of the last log statement that was emitted.

        The effect of this is that when logging invocation is relatively infrequent, the period between emitted log statements can be higher than the specified duration. For example if the following log statement were called every 600ms:

        
           logger.atFine().atMostEvery(2, SECONDS).log(...);
         
        logging would occur after 0s, 2.4s and 4.8s (not 4.2s), giving an effective duration of 2.4s between log statements over time.

        Providing a zero length duration (ie, n == 0) disables rate limiting and makes this method an effective no-op.

        Granularity

        Because the implementation of this feature relies on a nanosecond timestamp provided by the backend, the actual granularity of the underlying clock used may vary, and it is possible to specify a time period smaller than the smallest visible time increment. If this occurs, then the effective rate limit applied to the log statement will be the smallest available time increment. For example, if the system clock granularity is 1 millisecond, and a log statement is called with atMostEvery(700, MICROSECONDS), the effective rate of logging (even averaged over long periods) could never be more than once every millisecond.

        Notes

        If multiple rate limiters are used for a single log statement, that log statement will only be emitted once all rate limiters have reached their threshold, and when a log statement is emitted all the rate limiters are reset. So even if the rate limit duration has expired, it does not mean that logging will occur.

        When rate limiting is active, a "skipped" count is added to log statements to indicate how many logs were disallowed since the last log statement was emitted.

        If this method is called multiple times for a single log statement, the last invocation will take precedence.

        Specified by:
        atMostEvery in interface LoggingApi<LOGGER extends AbstractLogger<API>>
        Parameters:
        n - the minimum number of time units between emitted log statements
        unit - the time unit for the duration
      • log

        public final void log()
        Description copied from interface: LoggingApi
        Terminal log statement when a message is not required. A log method must terminate all fluent logging chains and the no-argument method can be used if there is no need for a log message. For example:
        
         logger.at(INFO).withCause(error).log();
         

        However as it is good practice to give all log statements a meaningful log message, use of this method should be rare.

        Specified by:
        log in interface LoggingApi<LOGGER extends AbstractLogger<API>>
      • log

        public final void log​(String msg)
        Description copied from interface: LoggingApi
        Logs the given literal string without interpreting any argument placeholders.

        Important: This is intended only for use with hard-coded, literal strings which cannot contain user data. If you wish to log user generated data, you should do something like:

        
         log("user data=%s", value);
         
        This serves to give the user data context in the log file but, more importantly, makes it clear which arguments may contain PII and other sensitive data (which might need to be scrubbed during logging). This recommendation also applies to all the overloaded log() methods below.
        Specified by:
        log in interface LoggingApi<LOGGER extends AbstractLogger<API>>
      • log

        public final void log​(String message,
                              @Nullable Object p1)
        Description copied from interface: LoggingApi
        Logs a formatted representation of the given parameter, using the specified message template. The message string is expected to contain argument placeholder terms appropriate to the logger's choice of parser.

        Note that printf-style loggers are always expected to accept the standard Java printf formatting characters (e.g. "%s", "%d" etc...) and all flags unless otherwise stated. Null arguments are formatted as the literal string "null" regardless of formatting flags.

        Specified by:
        log in interface LoggingApi<LOGGER extends AbstractLogger<API>>
        Parameters:
        message - the message template string containing a single argument placeholder.
      • logVarargs

        public final void logVarargs​(String message,
                                     @Nullable Object[] params)
        Description copied from interface: LoggingApi
        Logs a formatted representation of values in the given array, using the specified message template.

        This method is only expected to be invoked with an existing varargs array passed in from another method. Unlike LoggingApi.log(String, Object), which would treat an array as a single parameter, this method will unwrap the given array.

        Specified by:
        logVarargs in interface LoggingApi<LOGGER extends AbstractLogger<API>>
        Parameters:
        message - the message template string containing an argument placeholder for each element of varargs.
        params - the non-null array of arguments to be formatted.