| // 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 reason of a request cancellation (CLIENT_CLOSED_REQUEST, CLIENT_PROVIDED_DEADLINE_EXCEEDED, | |
| * SERVER_DEADLINE_EXCEEDED). | |
| */ | |
| public abstract Optional<String> cancellationReason(); | |
| /** 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(); | |
| /** Side where the comment is written: <= 0 for parent, 1 for revision. */ | |
| public abstract Optional<Integer> commentSide(); | |
| /** The SHA1 of a commit. */ | |
| public abstract Optional<String> commit(); | |
| /** Diff algorithm used in diff computation. */ | |
| public abstract Optional<String> diffAlgorithm(); | |
| /** 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 group system being queried. */ | |
| public abstract Optional<String> groupSystem(); | |
| /** 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> memoryPoolName(); | |
| /** 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 type of a Git push to Gerrit (GIT_RECEIVE, GIT_UPLOAD, REST, SSH). */ | |
| public abstract Optional<String> requestType(); | |
| /** The number of resources that is processed. */ | |
| public abstract Optional<Integer> resourceCount(); | |
| /** The name of a REST view. */ | |
| public abstract Optional<String> restViewName(); | |
| public abstract Optional<String> submitRequirementName(); | |
| /** 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, cancellationReason=Optional.empty changeId=Optional[9212550], | |
| * changeIdType=Optional.empty, cause=Optional.empty, diffAlgorithm=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, | |
| * requestType=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 cancellationReason(@Nullable String cancellationReason); | |
| public abstract Builder changeId(int changeId); | |
| public abstract Builder changeIdType(@Nullable String changeIdType); | |
| public abstract Builder cause(@Nullable String cause); | |
| public abstract Builder commentSide(int side); | |
| public abstract Builder commit(@Nullable String commit); | |
| public abstract Builder diffAlgorithm(@Nullable String diffAlgorithm); | |
| 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 groupSystem(@Nullable String groupSystem); | |
| 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 memoryPoolName(@Nullable String memoryPoolName); | |
| 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 requestType(@Nullable String requestType); | |
| public abstract Builder resourceCount(int resourceCount); | |
| public abstract Builder restViewName(@Nullable String restViewName); | |
| public abstract Builder revision(@Nullable String revision); | |
| public abstract Builder submitRequirementName(@Nullable String srName); | |
| public abstract Builder username(@Nullable String username); | |
| public abstract Metadata build(); | |
| } | |
| } |