diff options
-rw-r--r-- | src/main/cpp/startup_options.cc | 3 | ||||
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/BUILD | 11 | ||||
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/util/SingleLineFormatter.java | 87 | ||||
-rw-r--r-- | src/test/cpp/startup_options_test.cc | 2 | ||||
-rw-r--r-- | src/test/java/com/google/devtools/build/lib/BUILD | 2 | ||||
-rw-r--r-- | src/test/java/com/google/devtools/build/lib/util/SingleLineFormatterTest.java | 82 | ||||
-rw-r--r-- | src/test/shell/integration/BUILD | 7 | ||||
-rwxr-xr-x | src/test/shell/integration/execution_phase_tests.sh | 4 | ||||
-rw-r--r-- | src/test/shell/integration/logging_test.sh | 43 |
9 files changed, 237 insertions, 4 deletions
diff --git a/src/main/cpp/startup_options.cc b/src/main/cpp/startup_options.cc index c44da56a8c..d41d74790a 100644 --- a/src/main/cpp/startup_options.cc +++ b/src/main/cpp/startup_options.cc @@ -55,7 +55,8 @@ StartupOptions::StartupOptions(const string &product_name, connect_timeout_secs(10), invocation_policy(NULL), client_debug(false), - java_logging_formatter("java.util.logging.SimpleFormatter") { + java_logging_formatter( + "com.google.devtools.build.lib.util.SingleLineFormatter") { bool testing = !blaze::GetEnv("TEST_TMPDIR").empty(); if (testing) { output_root = MakeAbsolute(blaze::GetEnv("TEST_TMPDIR")); diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD index cd90d661d8..51b444d586 100644 --- a/src/main/java/com/google/devtools/build/lib/BUILD +++ b/src/main/java/com/google/devtools/build/lib/BUILD @@ -275,6 +275,15 @@ java_library( ) java_library( + name = "single-line-formatter", + srcs = ["util/SingleLineFormatter.java"], + deps = [ + "//third_party:guava", + "//third_party:joda_time", + ], +) + +java_library( name = "util", srcs = glob( ["util/*.java"], @@ -286,6 +295,7 @@ java_library( "util/OS.java", "util/Preconditions.java", "util/ProcessUtils.java", + "util/SingleLineFormatter.java", "util/StringCanonicalizer.java", "util/StringTrie.java", "util/VarInt.java", @@ -1192,6 +1202,7 @@ java_binary( main_class = "com.google.devtools.build.lib.bazel.BazelMain", runtime_deps = [ ":bazel-main", + ":single-line-formatter", # See startup_options.cc ], ) diff --git a/src/main/java/com/google/devtools/build/lib/util/SingleLineFormatter.java b/src/main/java/com/google/devtools/build/lib/util/SingleLineFormatter.java new file mode 100644 index 0000000000..6748917bdc --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/util/SingleLineFormatter.java @@ -0,0 +1,87 @@ +// Copyright 2017 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 com.google.common.collect.ImmutableRangeMap; +import com.google.common.collect.Range; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.logging.Formatter; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +/** + * Formatter to write java.util.logging messages out in single-line format. + * + * <p>Log entries contain the date and time (in UTC), log level (as letter and numerical value), + * source location, thread ID, message and, if applicable, a stack trace. + */ +public class SingleLineFormatter extends Formatter { + + /** Single-character codes based on {@link Level}s. */ + private static final ImmutableRangeMap<Integer, Character> CODES_BY_LEVEL = + ImmutableRangeMap.<Integer, Character>builder() + .put(Range.atMost(Level.FINE.intValue()), 'D') + .put(Range.open(Level.FINE.intValue(), Level.WARNING.intValue()), 'I') + .put(Range.closedOpen(Level.WARNING.intValue(), Level.SEVERE.intValue()), 'W') + .put(Range.atLeast(Level.SEVERE.intValue()), 'X') + .build(); + + /** A thread safe, immutable formatter that can be used by all without contention. */ + private static final DateTimeFormatter DATE_TIME_FORMAT = + DateTimeFormat.forPattern("yyMMdd HH:mm:ss.SSS").withZoneUTC(); + + @Override + public String format(LogRecord rec) { + StringBuilder buf = new StringBuilder(); + + // Timestamp + buf.append(DATE_TIME_FORMAT.print(rec.getMillis())) + .append(':'); + + // One character code for level + buf.append(CODES_BY_LEVEL.get(rec.getLevel().intValue())); + + // The stack trace, if any + Throwable thrown = rec.getThrown(); + if (thrown != null) { + buf.append('T'); + } + + buf.append(' '); + + // Information about the source of the exception + buf.append(rec.getThreadID()) + .append(" [") + .append(rec.getSourceClassName()) + .append('.') + .append(rec.getSourceMethodName()) + .append("] "); + + // The actual message + buf.append(formatMessage(rec)).append('\n'); + + if (thrown != null) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + thrown.printStackTrace(pw); + pw.flush(); + buf.append(sw.toString()); + } + + return buf.toString(); + } +} diff --git a/src/test/cpp/startup_options_test.cc b/src/test/cpp/startup_options_test.cc index 0815c52b9e..72cdf1bbfa 100644 --- a/src/test/cpp/startup_options_test.cc +++ b/src/test/cpp/startup_options_test.cc @@ -61,7 +61,7 @@ TEST_F(StartupOptionsTest, ProductName) { } TEST_F(StartupOptionsTest, JavaLoggingOptions) { - ASSERT_EQ("java.util.logging.SimpleFormatter", + ASSERT_EQ("com.google.devtools.build.lib.util.SingleLineFormatter", startup_options_->java_logging_formatter); } diff --git a/src/test/java/com/google/devtools/build/lib/BUILD b/src/test/java/com/google/devtools/build/lib/BUILD index c2b5b8f2b5..759b3148ce 100644 --- a/src/test/java/com/google/devtools/build/lib/BUILD +++ b/src/test/java/com/google/devtools/build/lib/BUILD @@ -167,12 +167,14 @@ java_test( "//src/main/java/com/google/devtools/build/lib:os_util", "//src/main/java/com/google/devtools/build/lib:packages", "//src/main/java/com/google/devtools/build/lib:shell", + "//src/main/java/com/google/devtools/build/lib:single-line-formatter", "//src/main/java/com/google/devtools/build/lib:unix", "//src/main/java/com/google/devtools/build/lib:util", "//src/main/java/com/google/devtools/build/lib:vfs", "//src/main/java/com/google/devtools/common/options", "//third_party:guava", "//third_party:guava-testlib", + "//third_party:joda_time", "//third_party:junit4", "//third_party:truth", ], diff --git a/src/test/java/com/google/devtools/build/lib/util/SingleLineFormatterTest.java b/src/test/java/com/google/devtools/build/lib/util/SingleLineFormatterTest.java new file mode 100644 index 0000000000..3c9f38aca9 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/util/SingleLineFormatterTest.java @@ -0,0 +1,82 @@ +// Copyright 2017 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.truth.Truth.assertThat; + +import java.util.logging.Level; +import java.util.logging.LogRecord; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class SingleLineFormatterTest { + + private static final DateTime TIMESTAMP = + new DateTime(2017, 04, 01, 17, 03, 43, 142, DateTimeZone.UTC); + + @Test + public void testFormat() { + LogRecord logRecord = createLogRecord(Level.SEVERE, TIMESTAMP); + assertThat(new SingleLineFormatter().format(logRecord)) + .isEqualTo("170401 17:03:43.142:X 543 [SomeSourceClass.aSourceMethod] some message\n"); + } + + @Test + public void testLevel() { + LogRecord logRecord = createLogRecord(Level.WARNING, TIMESTAMP); + String formatted = new SingleLineFormatter().format(logRecord); + assertThat(formatted).contains("W"); + assertThat(formatted).doesNotContain("X"); + } + + @Test + public void testTime() { + LogRecord logRecord = + createLogRecord( + Level.SEVERE, new DateTime(1999, 11, 30, 03, 04, 05, 722, DateTimeZone.UTC)); + assertThat(new SingleLineFormatter().format(logRecord)).contains("991130 03:04:05.722"); + } + + @Test + public void testStackTrace() { + LogRecord logRecord = createLogRecord( + Level.SEVERE, TIMESTAMP, new RuntimeException("something wrong")); + assertThat(new SingleLineFormatter().format(logRecord)) + .startsWith( + "170401 17:03:43.142:XT 543 [SomeSourceClass.aSourceMethod] some message\n" + + "java.lang.RuntimeException: something wrong\n" + + "\tat com.google.devtools.build.lib.util.SingleLineFormatterTest.testStackTrace"); + } + + private static LogRecord createLogRecord(Level level, DateTime dateTime) { + return createLogRecord(level, dateTime, null); + } + + private static LogRecord createLogRecord( + Level level, DateTime dateTime, RuntimeException thrown) { + LogRecord record = new LogRecord(level, "some message"); + record.setMillis(dateTime.getMillis()); + record.setSourceClassName("SomeSourceClass"); + record.setSourceMethodName("aSourceMethod"); + record.setThreadID(543); + if (thrown != null) { + record.setThrown(thrown); + } + return record; + } +} diff --git a/src/test/shell/integration/BUILD b/src/test/shell/integration/BUILD index c33be609aa..7d9ecb12e9 100644 --- a/src/test/shell/integration/BUILD +++ b/src/test/shell/integration/BUILD @@ -258,6 +258,13 @@ sh_test( tags = ["jdk8"], ) +sh_test( + name = "logging_test", + size = "medium", + srcs = ["logging_test.sh"], + data = [":test-deps"], +) + test_suite( name = "all_tests", visibility = ["//visibility:public"], diff --git a/src/test/shell/integration/execution_phase_tests.sh b/src/test/shell/integration/execution_phase_tests.sh index 3d4f0dd445..b202fe6029 100755 --- a/src/test/shell/integration/execution_phase_tests.sh +++ b/src/test/shell/integration/execution_phase_tests.sh @@ -47,7 +47,7 @@ function assert_last_log() { local log="${1}"; shift local fail_message="${1}"; shift - if ! grep -A1 "${context}" "${log}" | tail -n1 | grep -q "${message}" ; then + if ! grep "${context}" "${log}" | grep -q "${message}" ; then cat "${log}" >>"${TEST_log}" # Help debugging when we fail. fail "${fail_message}" fi @@ -60,7 +60,7 @@ function assert_cache_stats() { local exp_value="${1}"; shift local java_log="$(bazel info output_base 2>/dev/null)/java.log" - local last="$(grep -A1 "CacheFileDigestsModule" "${java_log}" | tail -n1)" + local last="$(grep "CacheFileDigestsModule" "${java_log}")" [ -n "${last}" ] || fail "Could not find cache stats in log" if ! echo "${last}" | grep -q "${metric}=${exp_value}"; then echo "Last cache stats: ${last}" >>"${TEST_log}" diff --git a/src/test/shell/integration/logging_test.sh b/src/test/shell/integration/logging_test.sh new file mode 100644 index 0000000000..4e3e801860 --- /dev/null +++ b/src/test/shell/integration/logging_test.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# +# Copyright 2017 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. +# +# Test of Bazel's java logging. + +set -e + +# Load the test setup defined in the parent directory +CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${CURRENT_DIR}/../integration_test_setup.sh" \ + || { echo "integration_test_setup.sh not found!" >&2; exit 1; } + +function test_log_file_uses_single_line_formatter() { + local client_log="$(bazel info output_base)/java.log" + + # Construct a regular expression to match a sample message in the log using + # the single-line format. More specifically, we expect the log entry's + # context (timestamp, severity and class) to appear in the same line as the + # actual log message. + local timestamp_re='[0-9]{6} [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3}' + local sample_re="^${timestamp_re}:I .*BlazeVersionInfo" + + if ! grep -E "${sample_re}" "${client_log}"; then + # Dump some client log lines on stdout for debugging of this test failure. + head -n 10 "${client_log}" + fail "invalid format in java.log; see output for sample lines" + fi +} + +run_suite "${PRODUCT_NAME} logging test" |