| // Copyright 2018 The Bazel Authors. All rights reserved. | |
| // | |
| // 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.devtools.build.lib.util; | |
| import static com.google.common.base.Preconditions.checkNotNull; | |
| import com.google.common.annotations.VisibleForTesting; | |
| import com.google.common.base.Suppliers; | |
| import com.google.errorprone.annotations.ForOverride; | |
| import java.io.IOException; | |
| import java.nio.file.Path; | |
| import java.util.Optional; | |
| import java.util.function.Supplier; | |
| import java.util.logging.Handler; | |
| import java.util.logging.Logger; | |
| /** | |
| * A retriever for logging handler properties, e.g. the log file path. | |
| * | |
| * <p>A querier is intended for situations where a logging handler is configured on the JVM command | |
| * line, and where the code which needs to query the handler does not know the handler's class or | |
| * cannot import it. The command line then should in addition specify an appropriate child class of | |
| * {@link LogHandlerQuerier} via the {@code | |
| * -Dcom.google.devtools.build.lib.util.LogHandlerQuerier.class} flag, and an instance of that | |
| * appropriate child class can be obtained from {@code LogHandlerQuerier.getInstance()}. | |
| */ | |
| public abstract class LogHandlerQuerier { | |
| private static final Supplier<LogHandlerQuerier> configuredInstanceSupplier = | |
| Suppliers.memoize(LogHandlerQuerier::makeConfiguredInstance); | |
| // Morally visible only for testing. | |
| protected static final String PROPERTY_NAME = LogHandlerQuerier.class.getName() + ".class"; | |
| private static LogHandlerQuerier makeConfiguredInstance() { | |
| String subclassName = System.getProperty(PROPERTY_NAME); | |
| checkNotNull(subclassName, "System property %s is not defined", PROPERTY_NAME); | |
| try { | |
| return (LogHandlerQuerier) | |
| ClassLoader.getSystemClassLoader() | |
| .loadClass(subclassName) | |
| .getDeclaredConstructor() | |
| .newInstance(); | |
| } catch (ReflectiveOperationException e) { | |
| throw new ReflectiveOperationRuntimeException( | |
| "System property " + PROPERTY_NAME + " value is invalid", e); | |
| } | |
| } | |
| /** | |
| * Returns the singleton instance of the LogHandlerQuerier child class which was configured as a | |
| * system property on the JVM command line via the {@code | |
| * -Dcom.google.devtools.build.lib.util.LogHandlerQuerier.class} flag. | |
| * | |
| * <p>This method is thread-safe. | |
| * | |
| * @throws IOException if the JVM property was not defined or if an instance of the class named by | |
| * the property could not be constructed | |
| */ | |
| static LogHandlerQuerier getConfiguredInstance() throws IOException { | |
| try { | |
| return configuredInstanceSupplier.get(); | |
| } catch (ReflectiveOperationRuntimeException e) { | |
| throw new IOException("Could not find a querier for server log location", e.getCause()); | |
| } | |
| } | |
| /** | |
| * Returns a logger's handler's log file path, iterating through all handlers and the logger's | |
| * ancestors' handlers as necessary. | |
| * | |
| * <p>The method will stop iterating at the first log handler that it can query, returning the log | |
| * path if it is available for that log handler, or an empty {@link Optional} if the log file for | |
| * that handler is currently unavailable. | |
| * | |
| * @param logger a logger whose handlers, and ancestors' handlers if necessary, will be queried | |
| * @throws IOException if the {@link LogHandlerQuerier} cannot query any {@link Handler} for this | |
| * logger or its ancestors | |
| */ | |
| @VisibleForTesting | |
| public Optional<Path> getLoggerFilePath(Logger logger) throws IOException { | |
| for (; logger != null; logger = logger.getParent()) { | |
| for (Handler handler : logger.getHandlers()) { | |
| if (canQuery(handler)) { | |
| return getLogHandlerFilePath(handler); | |
| } | |
| } | |
| } | |
| throw new IOException("Failed to find a queryable logging handler"); | |
| } | |
| /** Checks if this {@link LogHandlerQuerier} can query the given handler. */ | |
| @ForOverride | |
| protected abstract boolean canQuery(Handler handler); | |
| /** | |
| * Returns a logging handler's log file path. | |
| * | |
| * @param handler logging handler to query | |
| * @return the log handler's log file path if the log file is currently available | |
| */ | |
| @ForOverride | |
| protected abstract Optional<Path> getLogHandlerFilePath(Handler handler); | |
| private static class ReflectiveOperationRuntimeException extends RuntimeException { | |
| private ReflectiveOperationRuntimeException( | |
| String message, ReflectiveOperationException exception) { | |
| super(message, exception); | |
| } | |
| } | |
| } |