blob: 1a4a33542444a9d51b1aa5a6b7d6cf872900161e [file] [log] [blame]
// Copyright (C) 2019 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 com.google.auto.value.AutoValue;
import com.google.common.base.MoreObjects;
import com.google.common.base.MoreObjects.ToStringHelper;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.LazyArg;
import com.google.common.flogger.LazyArgs;
import com.google.gerrit.common.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Optional;
/** Metadata that is provided to {@link PerformanceLogger}s as context for performance records. */
@AutoValue
public abstract class Metadata {
/** The numeric ID of an account. */
public abstract Optional<Integer> accountId();
/**
* The type of an action (ACCOUNT_UPDATE, CHANGE_UPDATE, GROUP_UPDATE, INDEX_QUERY,
* PLUGIN_UPDATE).
*/
public abstract Optional<String> actionType();
/** An authentication domain name. */
public abstract Optional<String> authDomainName();
/** The name of a branch. */
public abstract Optional<String> branchName();
/** Key of an entity in a cache. */
public abstract Optional<String> cacheKey();
/** The name of a cache. */
public abstract Optional<String> cacheName();
/** The name of the implementation class. */
public abstract Optional<String> className();
/** The numeric ID of a change. */
public abstract Optional<Integer> changeId();
/**
* The type of change ID which the user used to identify a change (e.g. numeric ID, triplet etc.).
*/
public abstract Optional<String> changeIdType();
/** The cause of an error. */
public abstract Optional<String> cause();
/** The SHA1 of a commit. */
public abstract Optional<String> commit();
/** The type of an event. */
public abstract Optional<String> eventType();
/** The value of the @Export annotation which was used to register a plugin extension. */
public abstract Optional<String> exportValue();
/** Path of a file in a repository. */
public abstract Optional<String> filePath();
/** Garbage collector name. */
public abstract Optional<String> garbageCollectorName();
/** Git operation (CLONE, FETCH). */
public abstract Optional<String> gitOperation();
/** The numeric ID of an internal group. */
public abstract Optional<Integer> groupId();
/** The name of a group. */
public abstract Optional<String> groupName();
/** The UUID of a group. */
public abstract Optional<String> groupUuid();
/** HTTP status response code. */
public abstract Optional<Integer> httpStatus();
/** The name of a secondary index. */
public abstract Optional<String> indexName();
/** The version of a secondary index. */
public abstract Optional<Integer> indexVersion();
/** The name of the implementation method. */
public abstract Optional<String> methodName();
/** One or more resources */
public abstract Optional<Boolean> multiple();
/** The name of an operation that is performed. */
public abstract Optional<String> operationName();
/** Partial or full computation */
public abstract Optional<Boolean> partial();
/** If a value is still current or not */
public abstract Optional<Boolean> outdated();
/** Path of a metadata file in NoteDb. */
public abstract Optional<String> noteDbFilePath();
/** Name of a metadata ref in NoteDb. */
public abstract Optional<String> noteDbRefName();
/** Type of a sequence in NoteDb (ACCOUNTS, CHANGES, GROUPS). */
public abstract Optional<String> noteDbSequenceType();
/** The ID of a patch set. */
public abstract Optional<Integer> patchSetId();
/** Plugin metadata that doesn't fit into any other category. */
public abstract ImmutableList<PluginMetadata> pluginMetadata();
/** The name of a plugin. */
public abstract Optional<String> pluginName();
/** The name of a Gerrit project (aka Git repository). */
public abstract Optional<String> projectName();
/** The type of a Git push to Gerrit (CREATE_REPLACE, NORMAL, AUTOCLOSE). */
public abstract Optional<String> pushType();
/** The number of resources that is processed. */
public abstract Optional<Integer> resourceCount();
/** The name of a REST view. */
public abstract Optional<String> restViewName();
/** The SHA1 of Git commit. */
public abstract Optional<String> revision();
/** The username of an account. */
public abstract Optional<String> username();
/**
* Returns a string representation of this instance that is suitable for logging. This is wrapped
* in a {@link LazyArg} because it is expensive to evaluate.
*
* <p>{@link #toString()} formats the {@link Optional} fields as {@code key=Optional[value]} or
* {@code key=Optional.empty}. Since this class has many optional fields from which usually only a
* few are populated this leads to long string representations such as
*
* <pre>
* Metadata{accountId=Optional.empty, actionType=Optional.empty, authDomainName=Optional.empty,
* branchName=Optional.empty, cacheKey=Optional.empty, cacheName=Optional.empty,
* className=Optional.empty, changeId=Optional[9212550], changeIdType=Optional.empty,
* cause=Optional.empty, eventType=Optional.empty, exportValue=Optional.empty,
* filePath=Optional.empty, garbageCollectorName=Optional.empty, gitOperation=Optional.empty,
* groupId=Optional.empty, groupName=Optional.empty, groupUuid=Optional.empty,
* httpStatus=Optional.empty, indexName=Optional.empty, indexVersion=Optional[0],
* methodName=Optional.empty, multiple=Optional.empty, operationName=Optional.empty,
* partial=Optional.empty, noteDbFilePath=Optional.empty, noteDbRefName=Optional.empty,
* noteDbSequenceType=Optional.empty, patchSetId=Optional.empty, pluginMetadata=[],
* pluginName=Optional.empty, projectName=Optional.empty, pushType=Optional.empty,
* resourceCount=Optional.empty, restViewName=Optional.empty, revision=Optional.empty,
* username=Optional.empty}
* </pre>
*
* <p>That's hard to read in logs. This is why this method
*
* <ul>
* <li>drops fields which have {@code Optional.empty} as value and
* <li>reformats values that are {@code Optional[value]} to {@code value}.
* </ul>
*
* <p>For the example given above the formatted string would look like this:
*
* <pre>
* Metadata{changeId=9212550, indexVersion=0, pluginMetadata=[]}
* </pre>
*
* @return string representation of this instance that is suitable for logging
*/
LazyArg<String> toStringForLoggingLazy() {
// Don't use a lambda because different compilers generate different method names for lambdas,
// e.g. "lambda$myFunction$0" vs. just "lambda$0" in Eclipse. We need to identify the method
// by name to skip it and avoid infinite recursion.
return LazyArgs.lazy(this::toStringForLoggingImpl);
}
private String toStringForLoggingImpl() {
// Append class name.
String className = getClass().getSimpleName();
if (className.startsWith("AutoValue_")) {
className = className.substring(10);
}
ToStringHelper stringHelper = MoreObjects.toStringHelper(className);
// Append key-value pairs for field which are set.
Method[] methods = Metadata.class.getDeclaredMethods();
Arrays.sort(methods, Comparator.comparing(Method::getName));
for (Method method : methods) {
if (Modifier.isStatic(method.getModifiers())) {
// skip static method
continue;
}
if (method.getName().equals("toStringForLoggingLazy")
|| method.getName().equals("toStringForLoggingImpl")) {
// Don't call myself in infinite recursion.
continue;
}
if (method.getReturnType().equals(Void.TYPE) || method.getParameterCount() > 0) {
// skip method since it's not a getter
continue;
}
method.setAccessible(true);
Object returnValue;
try {
returnValue = method.invoke(this);
} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
// should never happen
throw new IllegalStateException(e);
}
if (returnValue instanceof Optional) {
Optional<?> fieldValueOptional = (Optional<?>) returnValue;
if (!fieldValueOptional.isPresent()) {
// drop this key-value pair
continue;
}
// format as 'key=value' instead of 'key=Optional[value]'
stringHelper.add(method.getName(), fieldValueOptional.get());
} else {
// not an Optional value, keep as is
stringHelper.add(method.getName(), returnValue);
}
}
return stringHelper.toString();
}
public static Metadata.Builder builder() {
return new AutoValue_Metadata.Builder();
}
public static Metadata empty() {
return builder().build();
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder accountId(int accountId);
public abstract Builder actionType(@Nullable String actionType);
public abstract Builder authDomainName(@Nullable String authDomainName);
public abstract Builder branchName(@Nullable String branchName);
public abstract Builder cacheKey(@Nullable String cacheKey);
public abstract Builder cacheName(@Nullable String cacheName);
public abstract Builder className(@Nullable String className);
public abstract Builder changeId(int changeId);
public abstract Builder changeIdType(@Nullable String changeIdType);
public abstract Builder cause(@Nullable String cause);
public abstract Builder commit(@Nullable String commit);
public abstract Builder eventType(@Nullable String eventType);
public abstract Builder exportValue(@Nullable String exportValue);
public abstract Builder filePath(@Nullable String filePath);
public abstract Builder garbageCollectorName(@Nullable String garbageCollectorName);
public abstract Builder gitOperation(@Nullable String gitOperation);
public abstract Builder groupId(int groupId);
public abstract Builder groupName(@Nullable String groupName);
public abstract Builder groupUuid(@Nullable String groupUuid);
public abstract Builder httpStatus(int httpStatus);
public abstract Builder indexName(@Nullable String indexName);
public abstract Builder indexVersion(int indexVersion);
public abstract Builder methodName(@Nullable String methodName);
public abstract Builder multiple(boolean multiple);
public abstract Builder operationName(String operationName);
public abstract Builder partial(boolean partial);
public abstract Builder outdated(boolean outdated);
public abstract Builder noteDbFilePath(@Nullable String noteDbFilePath);
public abstract Builder noteDbRefName(@Nullable String noteDbRefName);
public abstract Builder noteDbSequenceType(@Nullable String noteDbSequenceType);
public abstract Builder patchSetId(int patchSetId);
abstract ImmutableList.Builder<PluginMetadata> pluginMetadataBuilder();
public Builder addPluginMetadata(PluginMetadata pluginMetadata) {
pluginMetadataBuilder().add(pluginMetadata);
return this;
}
public abstract Builder pluginName(@Nullable String pluginName);
public abstract Builder projectName(@Nullable String projectName);
public abstract Builder pushType(@Nullable String pushType);
public abstract Builder resourceCount(int resourceCount);
public abstract Builder restViewName(@Nullable String restViewName);
public abstract Builder revision(@Nullable String revision);
public abstract Builder username(@Nullable String username);
public abstract Metadata build();
}
}