| // Copyright (C) 2018 The Android Open Source Project | |
| // | |
| // 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.gerrit.server.logging; | |
| import static java.util.Objects.requireNonNull; | |
| import com.google.common.base.MoreObjects; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableSetMultimap; | |
| import com.google.common.flogger.context.Tags; | |
| import com.google.inject.Provider; | |
| import java.util.List; | |
| import java.util.concurrent.Callable; | |
| import java.util.logging.Level; | |
| /** | |
| * Logging context for Flogger. | |
| * | |
| * <p>To configure this logging context for Flogger set the following system property (also see | |
| * {@link com.google.common.flogger.backend.system.DefaultPlatform}): | |
| * | |
| * <ul> | |
| * <li>{@code | |
| * flogger.logging_context=com.google.gerrit.server.logging.LoggingContext#getInstance}. | |
| * </ul> | |
| */ | |
| public class LoggingContext extends com.google.common.flogger.backend.system.LoggingContext { | |
| private static final LoggingContext INSTANCE = new LoggingContext(); | |
| private static final ThreadLocal<MutableTags> tags = new ThreadLocal<>(); | |
| private static final ThreadLocal<Boolean> forceLogging = new ThreadLocal<>(); | |
| private static final ThreadLocal<Boolean> performanceLogging = new ThreadLocal<>(); | |
| private static final ThreadLocal<Boolean> aclLogging = new ThreadLocal<>(); | |
| /** | |
| * When copying the logging context to a new thread we need to ensure that the mutable log records | |
| * (performance logs and ACL logs) that are added in the new thread are added to the same multable | |
| * log records instance (see {@link LoggingContextAwareRunnable} and {@link | |
| * LoggingContextAwareCallable}). This is important since performance log records are processed | |
| * only at the end of the request and performance log records that are created in another thread | |
| * should not get lost. | |
| */ | |
| private static final ThreadLocal<MutablePerformanceLogRecords> performanceLogRecords = | |
| new ThreadLocal<>(); | |
| private static final ThreadLocal<MutableAclLogRecords> aclLogRecords = new ThreadLocal<>(); | |
| private LoggingContext() {} | |
| /** This method is expected to be called via reflection (and might otherwise be unused). */ | |
| public static LoggingContext getInstance() { | |
| return INSTANCE; | |
| } | |
| public static Runnable copy(Runnable runnable) { | |
| if (runnable instanceof LoggingContextAwareRunnable) { | |
| return runnable; | |
| } | |
| return new LoggingContextAwareRunnable( | |
| runnable, | |
| getInstance().getMutablePerformanceLogRecords(), | |
| getInstance().getMutableAclRecords()); | |
| } | |
| public static <T> Callable<T> copy(Callable<T> callable) { | |
| if (callable instanceof LoggingContextAwareCallable) { | |
| return callable; | |
| } | |
| return new LoggingContextAwareCallable<>( | |
| callable, | |
| getInstance().getMutablePerformanceLogRecords(), | |
| getInstance().getMutableAclRecords()); | |
| } | |
| public boolean isEmpty() { | |
| return tags.get() == null | |
| && forceLogging.get() == null | |
| && performanceLogging.get() == null | |
| && (performanceLogRecords.get() == null || performanceLogRecords.get().isEmtpy()) | |
| && aclLogging.get() == null | |
| && (aclLogRecords.get() == null || aclLogRecords.get().isEmpty()); | |
| } | |
| public void clear() { | |
| tags.remove(); | |
| forceLogging.remove(); | |
| performanceLogging.remove(); | |
| performanceLogRecords.remove(); | |
| aclLogging.remove(); | |
| aclLogRecords.remove(); | |
| } | |
| @Override | |
| public boolean shouldForceLogging(String loggerName, Level level, boolean isEnabled) { | |
| return isLoggingForced(); | |
| } | |
| @Override | |
| public Tags getTags() { | |
| MutableTags mutableTags = tags.get(); | |
| return mutableTags != null ? mutableTags.getTags() : Tags.empty(); | |
| } | |
| public ImmutableSetMultimap<String, String> getTagsAsMap() { | |
| MutableTags mutableTags = tags.get(); | |
| return mutableTags != null ? mutableTags.asMap() : ImmutableSetMultimap.of(); | |
| } | |
| boolean addTag(String name, String value) { | |
| return getMutableTags().add(name, value); | |
| } | |
| void removeTag(String name, String value) { | |
| MutableTags mutableTags = getMutableTags(); | |
| mutableTags.remove(name, value); | |
| if (mutableTags.isEmpty()) { | |
| tags.remove(); | |
| } | |
| } | |
| void setTags(ImmutableSetMultimap<String, String> newTags) { | |
| if (newTags.isEmpty()) { | |
| tags.remove(); | |
| return; | |
| } | |
| getMutableTags().set(newTags); | |
| } | |
| void clearTags() { | |
| tags.remove(); | |
| } | |
| private MutableTags getMutableTags() { | |
| MutableTags mutableTags = tags.get(); | |
| if (mutableTags == null) { | |
| mutableTags = new MutableTags(); | |
| tags.set(mutableTags); | |
| } | |
| return mutableTags; | |
| } | |
| boolean isLoggingForced() { | |
| return Boolean.TRUE.equals(forceLogging.get()); | |
| } | |
| boolean forceLogging(boolean force) { | |
| Boolean oldValue = forceLogging.get(); | |
| if (force) { | |
| forceLogging.set(true); | |
| } else { | |
| forceLogging.remove(); | |
| } | |
| return Boolean.TRUE.equals(oldValue); | |
| } | |
| boolean isPerformanceLogging() { | |
| Boolean isPerformanceLogging = performanceLogging.get(); | |
| return isPerformanceLogging != null ? isPerformanceLogging : false; | |
| } | |
| /** | |
| * Enables performance logging. | |
| * | |
| * <p>It's important to enable performance logging only in a context that ensures to consume the | |
| * captured performance log records. Otherwise captured performance log records might leak into | |
| * other requests that are executed by the same thread (if a thread pool is used to process | |
| * requests). | |
| * | |
| * @param enable whether performance logging should be enabled. | |
| */ | |
| void performanceLogging(boolean enable) { | |
| if (enable) { | |
| performanceLogging.set(true); | |
| } else { | |
| performanceLogging.remove(); | |
| } | |
| } | |
| /** | |
| * Adds a performance log record, if performance logging is enabled. | |
| * | |
| * @param recordProvider Provider for the performance log record. This provider is only invoked if | |
| * performance logging is enabled. This means if performance logging is disabled, we avoid the | |
| * creation of a {@link PerformanceLogRecord}. | |
| */ | |
| public void addPerformanceLogRecord(Provider<PerformanceLogRecord> recordProvider) { | |
| if (!isPerformanceLogging()) { | |
| // return early and avoid the creation of a PerformanceLogRecord | |
| return; | |
| } | |
| getMutablePerformanceLogRecords().add(recordProvider.get()); | |
| } | |
| ImmutableList<PerformanceLogRecord> getPerformanceLogRecords() { | |
| MutablePerformanceLogRecords records = performanceLogRecords.get(); | |
| if (records != null) { | |
| return records.list(); | |
| } | |
| return ImmutableList.of(); | |
| } | |
| void clearPerformanceLogEntries() { | |
| performanceLogRecords.remove(); | |
| } | |
| /** | |
| * Set the performance log records in this logging context. Existing log records are overwritten. | |
| * | |
| * <p>This method makes a defensive copy of the passed in list. | |
| * | |
| * @param newPerformanceLogRecords performance log records that should be set | |
| */ | |
| void setPerformanceLogRecords(List<PerformanceLogRecord> newPerformanceLogRecords) { | |
| if (newPerformanceLogRecords.isEmpty()) { | |
| performanceLogRecords.remove(); | |
| return; | |
| } | |
| getMutablePerformanceLogRecords().set(newPerformanceLogRecords); | |
| } | |
| /** | |
| * Sets a {@link MutablePerformanceLogRecords} instance for storing performance log records. | |
| * | |
| * <p><strong>Attention:</strong> The passed in {@link MutablePerformanceLogRecords} instance is | |
| * directly stored in the logging context. | |
| * | |
| * <p>This method is intended to be only used when the logging context is copied to a new thread. | |
| * | |
| * @param mutablePerformanceLogRecords the {@link MutablePerformanceLogRecords} instance in which | |
| * performance log records should be stored | |
| */ | |
| void setMutablePerformanceLogRecords(MutablePerformanceLogRecords mutablePerformanceLogRecords) { | |
| performanceLogRecords.set(requireNonNull(mutablePerformanceLogRecords)); | |
| } | |
| private MutablePerformanceLogRecords getMutablePerformanceLogRecords() { | |
| MutablePerformanceLogRecords records = performanceLogRecords.get(); | |
| if (records == null) { | |
| records = new MutablePerformanceLogRecords(); | |
| performanceLogRecords.set(records); | |
| } | |
| return records; | |
| } | |
| public boolean isAclLogging() { | |
| Boolean isAclLogging = aclLogging.get(); | |
| return isAclLogging != null ? isAclLogging : false; | |
| } | |
| /** | |
| * Enables ACL logging. | |
| * | |
| * <p>It's important to enable ACL logging only in a context that ensures to consume the captured | |
| * ACL log records. Otherwise captured ACL log records might leak into other requests that are | |
| * executed by the same thread (if a thread pool is used to process requests). | |
| * | |
| * @param enable whether ACL logging should be enabled. | |
| * @return whether ACL logging was be enabled before invoking this method (old value). | |
| */ | |
| boolean aclLogging(boolean enable) { | |
| Boolean oldValue = aclLogging.get(); | |
| if (enable) { | |
| aclLogging.set(true); | |
| } else { | |
| aclLogging.remove(); | |
| } | |
| return oldValue != null ? oldValue : false; | |
| } | |
| /** | |
| * Adds an ACL log record. | |
| * | |
| * @param aclLogRecord ACL log record | |
| */ | |
| public void addAclLogRecord(String aclLogRecord) { | |
| if (!isAclLogging()) { | |
| return; | |
| } | |
| getMutableAclRecords().add(aclLogRecord); | |
| } | |
| ImmutableList<String> getAclLogRecords() { | |
| MutableAclLogRecords records = aclLogRecords.get(); | |
| if (records != null) { | |
| return records.list(); | |
| } | |
| return ImmutableList.of(); | |
| } | |
| /** | |
| * Set the ACL log records in this logging context. Existing log records are overwritten. | |
| * | |
| * <p>This method makes a defensive copy of the passed in list. | |
| * | |
| * @param newAclLogRecords ACL log records that should be set | |
| */ | |
| void setAclLogRecords(List<String> newAclLogRecords) { | |
| if (newAclLogRecords.isEmpty()) { | |
| aclLogRecords.remove(); | |
| return; | |
| } | |
| getMutableAclRecords().set(newAclLogRecords); | |
| } | |
| /** | |
| * Sets a {@link MutableAclLogRecords} instance for storing ACL log records. | |
| * | |
| * <p><strong>Attention:</strong> The passed in {@link MutableAclLogRecords} instance is directly | |
| * stored in the logging context. | |
| * | |
| * <p>This method is intended to be only used when the logging context is copied to a new thread | |
| * to ensure that the ACL log records that are added in the new thread are added to the same | |
| * {@link MutableAclLogRecords} instance (see {@link LoggingContextAwareRunnable} and {@link | |
| * LoggingContextAwareCallable}). This is important since ACL log records are processed only at | |
| * the end of the request and ACL log records that are created in another thread should not get | |
| * lost. | |
| * | |
| * @param mutableAclLogRecords the {@link MutableAclLogRecords} instance in which ACL log records | |
| * should be stored | |
| */ | |
| void setMutableAclLogRecords(MutableAclLogRecords mutableAclLogRecords) { | |
| aclLogRecords.set(requireNonNull(mutableAclLogRecords)); | |
| } | |
| private MutableAclLogRecords getMutableAclRecords() { | |
| MutableAclLogRecords records = aclLogRecords.get(); | |
| if (records == null) { | |
| records = new MutableAclLogRecords(); | |
| aclLogRecords.set(records); | |
| } | |
| return records; | |
| } | |
| @Override | |
| public String toString() { | |
| return MoreObjects.toStringHelper(this) | |
| .add("tags", tags.get()) | |
| .add("forceLogging", forceLogging.get()) | |
| .add("performanceLogging", performanceLogging.get()) | |
| .add("performanceLogRecords", performanceLogRecords.get()) | |
| .add("aclLogging", aclLogging.get()) | |
| .add("aclLogRecords", aclLogRecords.get()) | |
| .toString(); | |
| } | |
| } |