LCOV - code coverage report
Current view: top level - server/logging - CallerFinder.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 49 49 100.0 %
Date: 2022-11-19 15:00:39 Functions: 12 12 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2018 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.server.logging;
      16             : 
      17             : import static com.google.common.flogger.LazyArgs.lazy;
      18             : 
      19             : import com.google.auto.value.AutoValue;
      20             : import com.google.common.collect.ImmutableList;
      21             : import com.google.common.flogger.LazyArg;
      22             : import java.util.Optional;
      23             : 
      24             : /**
      25             :  * Utility to compute the caller of a method.
      26             :  *
      27             :  * <p>In the logs we see for each entry from where it was triggered (class/method/line) but in case
      28             :  * the logging is done in a utility method or inside of a module this doesn't tell us from where the
      29             :  * action was actually triggered. To get this information we could included the stacktrace into the
      30             :  * logs (by calling {@link
      31             :  * com.google.common.flogger.LoggingApi#withStackTrace(com.google.common.flogger.StackSize)} but
      32             :  * sometimes there are too many uninteresting stacks so that this would blow up the logs too much.
      33             :  * In this case CallerFinder can be used to find the first interesting caller from the current
      34             :  * stacktrace by specifying the class that interesting callers invoke as target.
      35             :  *
      36             :  * <p>Example:
      37             :  *
      38             :  * <p>Index queries are executed by the {@code query(List<String>, List<Predicate<T>>)} method in
      39             :  * {@link com.google.gerrit.index.query.QueryProcessor}. At this place the index query is logged but
      40             :  * from the log we want to see which code triggered this index query.
      41             :  *
      42             :  * <p>E.g. the stacktrace could look like this:
      43             :  *
      44             :  * <pre>{@code
      45             :  * GroupQueryProcessor(QueryProcessor<T>).query(List<String>, List<Predicate<T>>) line: 216
      46             :  * GroupQueryProcessor(QueryProcessor<T>).query(List<Predicate<T>>) line: 188
      47             :  * GroupQueryProcessor(QueryProcessor<T>).query(Predicate<T>) line: 171
      48             :  * InternalGroupQuery(InternalQuery<T>).query(Predicate<T>) line: 81
      49             :  * InternalGroupQuery.getOnlyGroup(Predicate<InternalGroup>, String) line: 67
      50             :  * InternalGroupQuery.byName(NameKey) line: 50
      51             :  * GroupCacheImpl$ByNameLoader.load(String) line: 166
      52             :  * GroupCacheImpl$ByNameLoader.load(Object) line: 1
      53             :  * LocalCache$LoadingValueReference<K,V>.loadFuture(K, CacheLoader<? super K,V>) line: 3527
      54             :  * ...
      55             :  * }</pre>
      56             :  *
      57             :  * <p>The first interesting caller is {@code GroupCacheImpl$ByNameLoader.load(String) line: 166}. To
      58             :  * find this caller from the stacktrace we could specify {@link
      59             :  * com.google.gerrit.server.query.group.InternalGroupQuery} as a target since we know that all
      60             :  * internal group queries go through this class:
      61             :  *
      62             :  * <pre>
      63             :  * CallerFinder.builder()
      64             :  *   .addTarget(InternalGroupQuery.class)
      65             :  *   .build();
      66             :  * </pre>
      67             :  *
      68             :  * <p>Since in some places {@link com.google.gerrit.server.query.group.GroupQueryProcessor} may also
      69             :  * be used directly we can add it as a secondary target to catch these callers as well:
      70             :  *
      71             :  * <pre>
      72             :  * CallerFinder.builder()
      73             :  *   .addTarget(InternalGroupQuery.class)
      74             :  *   .addTarget(GroupQueryProcessor.class)
      75             :  *   .build();
      76             :  * </pre>
      77             :  *
      78             :  * <p>However since {@link com.google.gerrit.index.query.QueryProcessor} is also responsible to
      79             :  * execute other index queries (for changes, accounts, projects) we would need to add the classes
      80             :  * for them as targets too. Since there are common base classes we can simply specify the base
      81             :  * classes and request matching of subclasses:
      82             :  *
      83             :  * <pre>
      84             :  * CallerFinder.builder()
      85             :  *   .addTarget(InternalQuery.class)
      86             :  *   .addTarget(QueryProcessor.class)
      87             :  *   .matchSubClasses(true)
      88             :  *   .build();
      89             :  * </pre>
      90             :  *
      91             :  * <p>Another special case is if the entry point is always an inner class of a known interface. E.g.
      92             :  * {@link com.google.gerrit.server.permissions.PermissionBackend} is the entry point for all
      93             :  * permission checks but they are done through inner classes, e.g. {@link
      94             :  * com.google.gerrit.server.permissions.PermissionBackend.ForProject}. In this case matching of
      95             :  * inner classes must be enabled as well:
      96             :  *
      97             :  * <pre>
      98             :  * CallerFinder.builder()
      99             :  *   .addTarget(PermissionBackend.class)
     100             :  *   .matchSubClasses(true)
     101             :  *   .matchInnerClasses(true)
     102             :  *   .build();
     103             :  * </pre>
     104             :  *
     105             :  * <p>Finding the interesting caller requires specifying the entry point class as target. This may
     106             :  * easily break when code is refactored and hence should be used only with care. It's recommended to
     107             :  * use this only when the corresponding code is relatively stable and logging the caller information
     108             :  * brings some significant benefit.
     109             :  *
     110             :  * <p>Based on {@link com.google.common.flogger.util.CallerFinder}.
     111             :  */
     112             : @AutoValue
     113         151 : public abstract class CallerFinder {
     114             :   public static Builder builder() {
     115         151 :     return new AutoValue_CallerFinder.Builder()
     116         151 :         .matchSubClasses(false)
     117         151 :         .matchInnerClasses(false)
     118         151 :         .skip(0);
     119             :   }
     120             : 
     121             :   /**
     122             :    * The target classes for which the caller should be found, in the order in which they should be
     123             :    * checked.
     124             :    *
     125             :    * @return the target classes for which the caller should be found
     126             :    */
     127             :   public abstract ImmutableList<Class<?>> targets();
     128             : 
     129             :   /**
     130             :    * Whether inner classes should be matched.
     131             :    *
     132             :    * @return whether inner classes should be matched
     133             :    */
     134             :   public abstract boolean matchSubClasses();
     135             : 
     136             :   /**
     137             :    * Whether sub classes of the target classes should be matched.
     138             :    *
     139             :    * @return whether sub classes of the target classes should be matched
     140             :    */
     141             :   public abstract boolean matchInnerClasses();
     142             : 
     143             :   /**
     144             :    * The minimum number of calls known to have occurred between the first call to the target class
     145             :    * and the call of {@link #findCallerLazy()}. If in doubt, specify zero here to avoid accidentally
     146             :    * skipping past the caller.
     147             :    *
     148             :    * @return the number of stack elements to skip when computing the caller
     149             :    */
     150             :   public abstract int skip();
     151             : 
     152             :   /**
     153             :    * Packages that should be ignored and not be considered as caller once a target has been found.
     154             :    *
     155             :    * @return the ignored packages
     156             :    */
     157             :   public abstract ImmutableList<String> ignoredPackages();
     158             : 
     159             :   /**
     160             :    * Classes that should be ignored and not be considered as caller once a target has been found.
     161             :    *
     162             :    * @return the qualified names of the ignored classes
     163             :    */
     164             :   public abstract ImmutableList<String> ignoredClasses();
     165             : 
     166             :   @AutoValue.Builder
     167         151 :   public abstract static class Builder {
     168             :     abstract ImmutableList.Builder<Class<?>> targetsBuilder();
     169             : 
     170             :     public Builder addTarget(Class<?> target) {
     171         151 :       targetsBuilder().add(target);
     172         151 :       return this;
     173             :     }
     174             : 
     175             :     public abstract Builder matchSubClasses(boolean matchSubClasses);
     176             : 
     177             :     public abstract Builder matchInnerClasses(boolean matchInnerClasses);
     178             : 
     179             :     public abstract Builder skip(int skip);
     180             : 
     181             :     abstract ImmutableList.Builder<String> ignoredPackagesBuilder();
     182             : 
     183             :     public Builder addIgnoredPackage(String ignoredPackage) {
     184         151 :       ignoredPackagesBuilder().add(ignoredPackage);
     185         151 :       return this;
     186             :     }
     187             : 
     188             :     abstract ImmutableList.Builder<String> ignoredClassesBuilder();
     189             : 
     190             :     public Builder addIgnoredClass(Class<?> ignoredClass) {
     191         151 :       ignoredClassesBuilder().add(ignoredClass.getName());
     192         151 :       return this;
     193             :     }
     194             : 
     195             :     public abstract CallerFinder build();
     196             :   }
     197             : 
     198             :   public LazyArg<String> findCallerLazy() {
     199         151 :     return lazy(
     200             :         () ->
     201          27 :             targets().stream()
     202          27 :                 .map(t -> findCallerOf(t, skip() + 1))
     203          27 :                 .filter(Optional::isPresent)
     204          27 :                 .findFirst()
     205          27 :                 .map(Optional::get)
     206          27 :                 .orElse("unknown"));
     207             :   }
     208             : 
     209             :   private Optional<String> findCallerOf(Class<?> target, int skip) {
     210             :     // Skip one additional stack frame because we create the Throwable inside this method, not at
     211             :     // the point that this method was invoked.
     212          27 :     skip++;
     213             : 
     214          27 :     StackTraceElement[] stack = new Throwable().getStackTrace();
     215             : 
     216             :     // Note: To avoid having to reflect the getStackTraceDepth() method as well, we assume that we
     217             :     // will find the caller on the stack and simply catch an exception if we fail (which should
     218             :     // hardly ever happen).
     219          27 :     boolean foundCaller = false;
     220             :     try {
     221          27 :       for (int index = skip; ; index++) {
     222          27 :         StackTraceElement element = stack[index];
     223          27 :         if (isCaller(target, element.getClassName(), matchSubClasses())) {
     224          27 :           foundCaller = true;
     225          27 :         } else if (foundCaller
     226          27 :             && !ignoredPackages().contains(getPackageName(element))
     227          27 :             && !ignoredClasses().contains(element.getClassName())) {
     228          27 :           return Optional.of(element.toString());
     229             :         }
     230             :       }
     231          23 :     } catch (Exception e) {
     232             :       // This should only happen if a) the caller was not found on the stack
     233             :       // (IndexOutOfBoundsException) b) a class that is mentioned in the stack was not found
     234             :       // (ClassNotFoundException), however we don't want anything to be thrown from here.
     235          23 :       return Optional.empty();
     236             :     }
     237             :   }
     238             : 
     239             :   private static String getPackageName(StackTraceElement element) {
     240          27 :     String className = element.getClassName();
     241          27 :     return className.substring(0, className.lastIndexOf("."));
     242             :   }
     243             : 
     244             :   private boolean isCaller(Class<?> target, String className, boolean matchSubClasses)
     245             :       throws ClassNotFoundException {
     246          27 :     if (matchSubClasses) {
     247          27 :       Class<?> clazz = Class.forName(className);
     248          27 :       while (clazz != null) {
     249          27 :         if (Object.class.getName().equals(clazz.getName())) {
     250          27 :           break;
     251             :         }
     252             : 
     253          27 :         if (isCaller(target, clazz.getName(), false)) {
     254          27 :           return true;
     255             :         }
     256          27 :         clazz = clazz.getSuperclass();
     257             :       }
     258             :     }
     259             : 
     260          27 :     if (matchInnerClasses()) {
     261          21 :       int i = className.indexOf('$');
     262          21 :       if (i > 0) {
     263          21 :         className = className.substring(0, i);
     264             :       }
     265             :     }
     266             : 
     267          27 :     if (target.getName().equals(className)) {
     268          27 :       return true;
     269             :     }
     270             : 
     271          27 :     return false;
     272             :   }
     273             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750