aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar arostovtsev <arostovtsev@google.com>2018-08-10 00:26:27 -0700
committerGravatar Copybara-Service <copybara-piper@google.com>2018-08-10 00:28:36 -0700
commitf11a0f70394746b11ea12bffd820d1983321b52d (patch)
tree376581ba52b3d812eb66bde5875bb74968887e55
parentdf16c77a501ba634b30a1e3d6f76023ac9ea93a9 (diff)
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
-rw-r--r--src/main/java/com/google/devtools/build/lib/BUILD1
-rw-r--r--src/main/java/com/google/devtools/build/lib/util/LogHandlerQuerier.java105
-rw-r--r--src/main/java/com/google/devtools/build/lib/util/SimpleLogHandler.java23
-rw-r--r--src/test/java/com/google/devtools/build/lib/BUILD1
-rw-r--r--src/test/java/com/google/devtools/build/lib/util/SimpleLogHandlerTest.java53
5 files changed, 183 insertions, 0 deletions
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.
+ *
+ * <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);
+
+ 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.
+ *
+ * <p>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.
+ *
+ * <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 IllegalArgumentException if the {@link LogHandlerQuerier} cannot query any {@link
+ * Handler} for this logger or its ancestors
+ */
+ public Optional<Path> 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<Path> 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.
+ *
+ * <p>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<Path> 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<Path> 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));
+ }
}