From f11a0f70394746b11ea12bffd820d1983321b52d Mon Sep 17 00:00:00 2001 From: arostovtsev Date: Fri, 10 Aug 2018 00:26:27 -0700 Subject: Introduce a retriever for logging handler properties. We want a way for Bazel to find a logging handler's current log file without direct dependencies on the exact handler class. We do this with an abstract parent class whose concrete child class (to be used as a singleton) will be given in startup_options, i.e. in the same place as the server logging configuration. RELNOTES: None. PiperOrigin-RevId: 208171084 --- src/main/java/com/google/devtools/build/lib/BUILD | 1 + .../devtools/build/lib/util/LogHandlerQuerier.java | 105 +++++++++++++++++++++ .../devtools/build/lib/util/SimpleLogHandler.java | 23 +++++ src/test/java/com/google/devtools/build/lib/BUILD | 1 + .../build/lib/util/SimpleLogHandlerTest.java | 53 +++++++++++ 5 files changed, 183 insertions(+) create mode 100644 src/main/java/com/google/devtools/build/lib/util/LogHandlerQuerier.java (limited to 'src') diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD index 454c3ec42d..07d8281e03 100644 --- a/src/main/java/com/google/devtools/build/lib/BUILD +++ b/src/main/java/com/google/devtools/build/lib/BUILD @@ -225,6 +225,7 @@ java_library( "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec", "//src/main/java/com/google/devtools/build/lib/vfs", "//src/main/java/com/google/devtools/common/options", + "//third_party:error_prone_annotations", "//third_party:guava", "//third_party:jsr305", "//third_party/protobuf:protobuf_java", diff --git a/src/main/java/com/google/devtools/build/lib/util/LogHandlerQuerier.java b/src/main/java/com/google/devtools/build/lib/util/LogHandlerQuerier.java new file mode 100644 index 0000000000..1af8b2aec5 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/util/LogHandlerQuerier.java @@ -0,0 +1,105 @@ +// 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.checkState; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.errorprone.annotations.ForOverride; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Handler; +import java.util.logging.Logger; + +/** + * A retriever for logging handler properties, e.g. the log file path. + * + *

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 configuredInstanceSupplier = + Suppliers.memoize(LogHandlerQuerier::makeConfiguredInstance); + + private static LogHandlerQuerier makeConfiguredInstance() { + String propertyName = LogHandlerQuerier.class.getName() + ".class"; + String subclassName = System.getProperty(propertyName); + checkState(subclassName != null, "System property %s is not defined", propertyName); + try { + return (LogHandlerQuerier) + ClassLoader.getSystemClassLoader() + .loadClass(subclassName) + .getDeclaredConstructor() + .newInstance(); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("System property " + propertyName + " 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. + * + *

This method is thread-safe. + * + * @throws IllegalStateException if the JVM property was not defined or if an instance of the + * class named by the property could not be constructed + */ + public static LogHandlerQuerier getConfiguredInstance() { + return configuredInstanceSupplier.get(); + } + + /** + * Returns a logger's handler's log file path, iterating through all handlers and the logger's + * ancestors' handlers as necessary. + * + *

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 IllegalArgumentException if the {@link LogHandlerQuerier} cannot query any {@link + * Handler} for this logger or its ancestors + */ + public Optional getLoggerFilePath(Logger logger) { + for (; logger != null; logger = logger.getParent()) { + for (Handler handler : logger.getHandlers()) { + if (canQuery(handler)) { + return getLogHandlerFilePath(handler); + } + } + } + throw new IllegalArgumentException("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 getLogHandlerFilePath(Handler handler); +} diff --git a/src/main/java/com/google/devtools/build/lib/util/SimpleLogHandler.java b/src/main/java/com/google/devtools/build/lib/util/SimpleLogHandler.java index 5b46123dbd..d26a1bf257 100644 --- a/src/main/java/com/google/devtools/build/lib/util/SimpleLogHandler.java +++ b/src/main/java/com/google/devtools/build/lib/util/SimpleLogHandler.java @@ -103,6 +103,29 @@ public final class SimpleLogHandler extends Handler { @GuardedBy("this") private final SimpleDateFormat timestampFormat = new SimpleDateFormat(DEFAULT_TIMESTAMP_FORMAT); + /** + * A {@link} LogHandlerQuerier for working with {@code SimpleLogHandler} instances. + * + *

This querier is intended for situations where the logging handler is configured on the JVM + * command line to be {@link SimpleLogHandler}, but 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 {@code + * -Dcom.google.devtools.build.lib.util.LogHandlerQuerier.class=com.google.devtools.build.lib.util.SimpleLogHandler$HandlerQuerier} + * and an instance of {@link SimpleLogHandler.HandlerQuerier} class can then be obtained from + * {@code LogHandlerQuerier.getInstance()}. + */ + public static final class HandlerQuerier extends LogHandlerQuerier { + @Override + protected boolean canQuery(Handler handler) { + return handler instanceof SimpleLogHandler; + } + + @Override + protected Optional getLogHandlerFilePath(Handler handler) { + return ((SimpleLogHandler) handler).getCurrentLogFilePath(); + } + } + /** Creates a new {@link Builder}. */ public static Builder builder() { return new Builder(); diff --git a/src/test/java/com/google/devtools/build/lib/BUILD b/src/test/java/com/google/devtools/build/lib/BUILD index f030b0abe0..50d954e62e 100644 --- a/src/test/java/com/google/devtools/build/lib/BUILD +++ b/src/test/java/com/google/devtools/build/lib/BUILD @@ -324,6 +324,7 @@ java_test( "//src/main/java/com/google/devtools/build/lib/vfs", "//src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs", "//src/main/java/com/google/devtools/common/options", + "//third_party:truth8", ], ) diff --git a/src/test/java/com/google/devtools/build/lib/util/SimpleLogHandlerTest.java b/src/test/java/com/google/devtools/build/lib/util/SimpleLogHandlerTest.java index a8f95727f7..4b889508af 100644 --- a/src/test/java/com/google/devtools/build/lib/util/SimpleLogHandlerTest.java +++ b/src/test/java/com/google/devtools/build/lib/util/SimpleLogHandlerTest.java @@ -15,10 +15,12 @@ package com.google.devtools.build.lib.util; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.base.Strings; +import com.google.devtools.build.lib.util.SimpleLogHandler.HandlerQuerier; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -39,9 +41,11 @@ import java.util.Arrays; import java.util.Date; import java.util.Optional; import java.util.TimeZone; +import java.util.logging.FileHandler; import java.util.logging.Formatter; import java.util.logging.Level; import java.util.logging.LogRecord; +import java.util.logging.Logger; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -386,4 +390,53 @@ public final class SimpleLogHandlerTest { assertThat(Files.exists(kept)).isTrue(); assertThat(Files.exists(currentLogPath)).isTrue(); } + + @Test + public void getLoggerFilePath_onSimpleLogHandler_withFile_returnsPath() throws Exception { + HandlerQuerier handlerQuerier = new HandlerQuerier(); + SimpleLogHandler handler = + SimpleLogHandler.builder().setPrefix(tmp.getRoot() + File.separator + "hello").build(); + Logger logger = Logger.getAnonymousLogger(); + logger.addHandler(handler); + handler.publish(new LogRecord(Level.SEVERE, "Hello world")); // Ensure log file is opened. + + Optional retrievedLogPath = handlerQuerier.getLoggerFilePath(logger); + + assertThat(retrievedLogPath).isPresent(); + assertThat(retrievedLogPath.get().toString()) + .startsWith(tmp.getRoot() + File.separator + "hello"); + + handler.close(); + } + + @Test + public void getLoggerFilePath_onSimpleLogHandler_withoutFile_returnsEmpty() throws Exception { + HandlerQuerier handlerQuerier = new HandlerQuerier(); + SimpleLogHandler handler = + SimpleLogHandler.builder().setPrefix(tmp.getRoot() + File.separator + "hello").build(); + Logger logger = Logger.getAnonymousLogger(); + logger.addHandler(handler); + + assertThat(handlerQuerier.getLoggerFilePath(logger)).isEmpty(); + } + + @Test + public void getLoggerFilePath_onUnsupportedLogHandler_fails() throws Exception { + HandlerQuerier handlerQuerier = new HandlerQuerier(); + FileHandler unsupportedHandler = new FileHandler(tmp.getRoot() + File.separator + "hello"); + Logger logger = Logger.getAnonymousLogger(); + logger.addHandler(unsupportedHandler); + + assertThrows(IllegalArgumentException.class, () -> handlerQuerier.getLoggerFilePath(logger)); + + unsupportedHandler.close(); + } + + @Test + public void getLoggerFilePath_onMissingLogHandler_fails() throws Exception { + HandlerQuerier handlerQuerier = new HandlerQuerier(); + Logger logger = Logger.getAnonymousLogger(); + + assertThrows(IllegalArgumentException.class, () -> handlerQuerier.getLoggerFilePath(logger)); + } } -- cgit v1.2.3