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 : }