All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.common.flogger.LoggingScope Maven / Gradle / Ivy

There is a newer version: 2.0.30
Show newest version
/*
 * Copyright (C) 2020 The Flogger Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.common.flogger;

import static com.google.common.flogger.util.Checks.checkNotNull;

import com.google.common.flogger.backend.Metadata;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * An opaque scope marker which can be attached to log sites to provide "per scope" behaviour for
 * stateful logging operations (e.g. rate limiting).
 *
 * 

Scopes are provided via the {@link Provider} interface and found by looking for * the current {@link com.google.common.flogger.context.ScopedLoggingContext ScopedLoggingContexts}. * *

Stateful fluent logging APIs which need to look up per log site information (e.g. rate limit * state) should do so via a {@link LogSiteMap} using the {@link LogSiteKey} passed into the {@link * LogContext#postProcess(LogSiteKey)} method. If scopes are present in the log site {@link * Metadata} then the log site key provided to the {@code postProcess()} method will already be * specialized to take account of any scopes present. * *

Note that scopes have no effect when applied to stateless log statements (e.g. log statements * without rate limiting) since the log site key for that log statement will not be used in any * maps. */ public abstract class LoggingScope { /** * Creates a scope which automatically removes any associated keys from {@link LogSiteMap}s when * it's garbage collected. The given label is used only for debugging purposes and may appear in * log statements, it should not contain any user data or other runtime information. */ // TODO: Strongly consider making the label a compile time constant. public static LoggingScope create(String label) { return new WeakScope(checkNotNull(label, "label")); } private final String label; /** * Creates a basic scope with the specified label. Custom subclasses of {@code LoggingScope} must * manage their own lifecycles to avoid leaking memory and polluting {@link LogSiteMap}s with * unused keys. */ protected LoggingScope(String label) { this.label = label; } /** * Returns a specialization of the given key which accounts for this scope instance. Two * specialized keys should compare as {@link Object#equals(Object)} if and only if they are * specializations from the same log site, with the same sequence of scopes applied. * *

The returned instance: * *

    *
  • Must be an immutable "value type". *
  • Must not compare as {@link Object#equals(Object)} to the given key. *
  • Should have a different {@link Object#hashCode()} to the given key. *
  • Should be efficient and lightweight. *
* * As such it is recommended that the {@link SpecializedLogSiteKey#of(LogSiteKey, Object)} method * is used in implementations, passing in a suitable qualifier (which need not be the scope * itself, but must be unique per scope). */ protected abstract LogSiteKey specialize(LogSiteKey key); /** * Registers "hooks" which should be called when this scope is "closed". The hooks are intended to * remove the keys associated with this scope from any data structures they may be held in, to * avoid leaking allocations. * *

Note that a key may be specialized with several scopes and the first scope to be closed will * remove it from any associated data structures (conceptually the scope that a log site is called * from is the intersection of all the currently active scopes which apply to it). */ protected abstract void onClose(Runnable removalHook); @Override public final String toString() { return label; } // VisibleForTesting static final class WeakScope extends LoggingScope { // Do NOT reference the Scope directly from a specialized key, use the "key part" // to avoid the key from keeping the Scope instance alive. When the scope becomes // unreachable, the key part weak reference is enqueued which triggers tidyup at // the next call to specializeForScopesIn() where scopes are used. // // This must be unique per scope since it acts as a qualifier within specialized // log site keys. Using a different weak reference per specialized key would not // work (which is part of the reason we also need the "on close" queue as well as // the reference queue). private final KeyPart keyPart; public WeakScope(String label) { super(label); this.keyPart = new KeyPart(this); } @Override protected LogSiteKey specialize(LogSiteKey key) { return SpecializedLogSiteKey.of(key, keyPart); } @Override protected void onClose(Runnable remove) { // Clear the reference queue about as often as we would add a new key to a map. // This should still mean that the queue is almost always empty when we check // it (since we expect more than one specialized log site key per scope) and it // avoids spamming the queue clearance loop for every log statement and avoids // class loading the reference queue until we know scopes have been used. KeyPart.removeUnusedKeys(); keyPart.onCloseHooks.offer(remove); } void closeForTesting() { keyPart.close(); } // Class is only loaded once we've seen scopes in action (Android doesn't like // eager class loading and many Android apps won't use scopes). // This forms part of each log site key, some must have singleton semantics. private static class KeyPart extends WeakReference { private static final ReferenceQueue queue = new ReferenceQueue(); private final Queue onCloseHooks = new ConcurrentLinkedQueue(); KeyPart(LoggingScope scope) { super(scope, queue); } // If this were ever too "bursty" due to removal of many keys for the same scope, // we could modify this code to process only a maximum number of removals each // time and keep a single "in progress" KeyPart around until next time. static void removeUnusedKeys() { // There are always more specialized keys than entries in the reference queue, // so the queue should be empty most of the time we get here. for (KeyPart p = (KeyPart) queue.poll(); p != null; p = (KeyPart) queue.poll()) { p.close(); } } private void close() { // This executes once for each map entry created in the enclosing scope. It is // very dependent on logging usage in the scope and theoretically unbounded. for (Runnable r = onCloseHooks.poll(); r != null; r = onCloseHooks.poll()) { r.run(); } } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy