blob: adc9c2b59415a8a5cb0a418618a6c3cf24fd5c2a [file] [log] [blame]
// 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.errorprone.annotations.CanIgnoreReturnValue;
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());
}
@CanIgnoreReturnValue
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).
*/
@CanIgnoreReturnValue
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();
}
}