summaryrefslogtreecommitdiffstats
path: root/java/com/google/gerrit/server/logging/Metadata.java
blob: 89b5b46da448f8d17c1dd68923dffa7fc5f9bb92 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
// 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 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();

  /** 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 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 username(@Nullable String username);

    public abstract Metadata build();
  }
}