From 53481bde1e455037cd8102bc1b404ea2c491c235 Mon Sep 17 00:00:00 2001 From: minherz Date: Thu, 23 Sep 2021 11:10:16 +0300 Subject: [PATCH] fix: fix samples region tags and test flakiness (#680) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: fix list logs snippet define timestamp filter in UTC time. change filter to last minute entries. enforce closing logging client at the end of the snippet. apply formatting. * chore: enforce closing logging client in samples force closing logging client at the end of the snippet * fix: fix region tag for LogEntryWriteHttpRequest change logging_write_request_entry to logging_write_log_entry_advanced * fix: add snippet for missing logging_write_log_entry * chore: move testListLogNamesSample to separate test * fix: fix samples testing add closing logging client at the end of the test unit add multiple attempts to clean log after each test print debug info in a case cleaning fails * fix: add test for WriteLogEntry remove unused textPayload from the snippet * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot --- README.md | 22 ++++++++ .../com/example/logging/ListLogEntries.java | 50 ++++++++--------- .../java/com/example/logging/ListLogs.java | 16 +++--- .../logging/LogEntryWriteHttpRequest.java | 4 +- .../com/example/logging/QuickstartSample.java | 33 ++++++------ .../com/example/logging/WriteLogEntry.java | 53 ++++++++++++++++++ .../java/com/example/logging/LoggingIT.java | 52 ++++++++++++------ .../test/java/com/example/logging/LogsIT.java | 54 +++++++++++++++++++ 8 files changed, 216 insertions(+), 68 deletions(-) create mode 100644 samples/snippets/src/main/java/com/example/logging/WriteLogEntry.java create mode 100644 samples/snippets/src/test/java/com/example/logging/LogsIT.java diff --git a/README.md b/README.md index 8a2501a85..f38167542 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,27 @@ LogEntry firstEntry = LogEntry.newBuilder(StringPayload.of("message")) logging.write(Collections.singleton(firstEntry)); ``` +The library supports writing log entries synchronously and asynchronously. +In the synchronous mode each call to `write()` method results in a consequent call to Logging API to write a log entry. +In the asynchronous mode the call(s) to Logging API takes place asynchronously and few calls to `write()` method may be batched together to compose a single call to Logging API. +The default mode of writing is asynchronous. +It can be configured in the `java.util.logging` handler [configuration file](https://cloud.google.com/logging/docs/setup/java#javautillogging_configuration): + +``` +com.google.cloud.logging.LoggingHandler.synchronicity=SYNC +``` + +or in the code after initiating an instance of `Logging` by calling: + +```java +logging.setWriteSynchronicity(Synchronicity.SYNC); +``` + +NOTE: +> Writing log entries asynchronously in some Google Cloud managed environments (e.g. Cloud Functions) +> may lead to unexpected results such as absense of expected log entries or abnormal program execution. +> To avoid these unexpected results, it is recommended to use synchronous mode. + #### Listing log entries With Logging you can also list log entries that have been previously written. Add the following @@ -236,6 +257,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-logging/tree/ | Log Entry Write Http Request | [source code](https://github.com/googleapis/java-logging/blob/master/samples/snippets/src/main/java/com/example/logging/LogEntryWriteHttpRequest.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-logging&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/logging/LogEntryWriteHttpRequest.java) | | Quickstart Sample | [source code](https://github.com/googleapis/java-logging/blob/master/samples/snippets/src/main/java/com/example/logging/QuickstartSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-logging&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/logging/QuickstartSample.java) | | Tail Log Entries | [source code](https://github.com/googleapis/java-logging/blob/master/samples/snippets/src/main/java/com/example/logging/TailLogEntries.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-logging&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/logging/TailLogEntries.java) | +| Write Log Entry | [source code](https://github.com/googleapis/java-logging/blob/master/samples/snippets/src/main/java/com/example/logging/WriteLogEntry.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-logging&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/logging/WriteLogEntry.java) | | Quickstart | [source code](https://github.com/googleapis/java-logging/blob/master/samples/snippets/src/main/java/com/example/logging/jul/Quickstart.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-logging&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/logging/jul/Quickstart.java) | | Example Enhancer | [source code](https://github.com/googleapis/java-logging/blob/master/samples/snippets/src/main/java/com/example/logging/jul/enhancers/ExampleEnhancer.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-logging&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/logging/jul/enhancers/ExampleEnhancer.java) | diff --git a/samples/snippets/src/main/java/com/example/logging/ListLogEntries.java b/samples/snippets/src/main/java/com/example/logging/ListLogEntries.java index dfad54872..824b7fac6 100644 --- a/samples/snippets/src/main/java/com/example/logging/ListLogEntries.java +++ b/samples/snippets/src/main/java/com/example/logging/ListLogEntries.java @@ -25,6 +25,7 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; +import java.util.TimeZone; public class ListLogEntries { @@ -33,33 +34,34 @@ public static void main(String[] args) throws Exception { // or provide it as an argument. String logName = args.length > 0 ? args[0] : "test-log"; - LoggingOptions options = LoggingOptions.getDefaultInstance(); - Logging logging = options.getService(); + try (Logging logging = LoggingOptions.getDefaultInstance().getService()) { - // When composing a filter, using indexed fields, such as timestamp, resource.type, logName and - // others can help accelerate the results - // Full list of indexed fields here: - // https://cloud.google.com/logging/docs/view/advanced-queries#finding-quickly - // This sample restrict the results to only last hour to minimize number of API calls - Calendar calendar = Calendar.getInstance(); - calendar.add(Calendar.HOUR, -1); - DateFormat rfc3339 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - String logFilter = - "logName=projects/" - + options.getProjectId() - + "/logs/" - + logName - + " AND timestamp>=\"" - + rfc3339.format(calendar.getTime()) - + "\""; + // When composing a filter, using indexed fields, such as timestamp, resource.type, logName + // and + // others can help accelerate the results + // Full list of indexed fields here: + // https://cloud.google.com/logging/docs/view/advanced-queries#finding-quickly + // This sample restrict the results to only last minute to minimize number of API calls + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + calendar.add(Calendar.MINUTE, -1); + DateFormat rfc3339 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + String logFilter = + "logName=projects/" + + logging.getOptions().getProjectId() + + "/logs/" + + logName + + " AND timestamp>=\"" + + rfc3339.format(calendar.getTime()) + + "\""; - // List all log entries - Page entries = logging.listLogEntries(EntryListOption.filter(logFilter)); - while (entries != null) { - for (LogEntry logEntry : entries.iterateAll()) { - System.out.println(logEntry); + // List all log entries + Page entries = logging.listLogEntries(EntryListOption.filter(logFilter)); + while (entries != null) { + for (LogEntry logEntry : entries.iterateAll()) { + System.out.println(logEntry); + } + entries = entries.getNextPage(); } - entries = entries.getNextPage(); } } } diff --git a/samples/snippets/src/main/java/com/example/logging/ListLogs.java b/samples/snippets/src/main/java/com/example/logging/ListLogs.java index 5573c0717..775caa530 100644 --- a/samples/snippets/src/main/java/com/example/logging/ListLogs.java +++ b/samples/snippets/src/main/java/com/example/logging/ListLogs.java @@ -25,16 +25,16 @@ public class ListLogs { public static void main(String... args) throws Exception { - LoggingOptions options = LoggingOptions.getDefaultInstance(); - Logging logging = options.getService(); + try (Logging logging = LoggingOptions.getDefaultInstance().getService()) { - // List all log names - Page logNames = logging.listLogs(); - while (logNames != null) { - for (String logName : logNames.iterateAll()) { - System.out.println(logName); + // List all log names + Page logNames = logging.listLogs(); + while (logNames != null) { + for (String logName : logNames.iterateAll()) { + System.out.println(logName); + } + logNames = logNames.getNextPage(); } - logNames = logNames.getNextPage(); } } } diff --git a/samples/snippets/src/main/java/com/example/logging/LogEntryWriteHttpRequest.java b/samples/snippets/src/main/java/com/example/logging/LogEntryWriteHttpRequest.java index 05e29a517..68e5af892 100644 --- a/samples/snippets/src/main/java/com/example/logging/LogEntryWriteHttpRequest.java +++ b/samples/snippets/src/main/java/com/example/logging/LogEntryWriteHttpRequest.java @@ -16,7 +16,7 @@ package com.example.logging; -// [START logging_write_request_entry] +// [START logging_write_log_entry_advanced] import com.google.cloud.MonitoredResource; import com.google.cloud.logging.HttpRequest; import com.google.cloud.logging.LogEntry; @@ -61,4 +61,4 @@ public static void createLogEntryRequest(String logName, String payLoad, HttpReq } } } -// [END logging_write_request_entry] +// [END logging_write_log_entry_advanced] diff --git a/samples/snippets/src/main/java/com/example/logging/QuickstartSample.java b/samples/snippets/src/main/java/com/example/logging/QuickstartSample.java index 754c0bf41..062d41d9a 100644 --- a/samples/snippets/src/main/java/com/example/logging/QuickstartSample.java +++ b/samples/snippets/src/main/java/com/example/logging/QuickstartSample.java @@ -37,27 +37,24 @@ public class QuickstartSample { /** Expects a new or existing Cloud log name as the first argument. */ public static void main(String... args) throws Exception { - - // Instantiates a client - Logging logging = LoggingOptions.getDefaultInstance().getService(); - // The name of the log to write to String logName = args[0]; // "my-log"; + String textPayload = "Hello, world!"; - // The data to write to the log - String text = "Hello, world!"; - - LogEntry entry = - LogEntry.newBuilder(StringPayload.of(text)) - .setSeverity(Severity.ERROR) - .setLogName(logName) - .setResource(MonitoredResource.newBuilder("global").build()) - .build(); - - // Writes the log entry asynchronously - logging.write(Collections.singleton(entry)); - - System.out.printf("Logged: %s%n", text); + // Instantiates a client + try (Logging logging = LoggingOptions.getDefaultInstance().getService()) { + + LogEntry entry = + LogEntry.newBuilder(StringPayload.of(textPayload)) + .setSeverity(Severity.ERROR) + .setLogName(logName) + .setResource(MonitoredResource.newBuilder("global").build()) + .build(); + + // Writes the log entry asynchronously + logging.write(Collections.singleton(entry)); + } + System.out.printf("Logged: %s%n", textPayload); } } // [END logging_quickstart] diff --git a/samples/snippets/src/main/java/com/example/logging/WriteLogEntry.java b/samples/snippets/src/main/java/com/example/logging/WriteLogEntry.java new file mode 100644 index 000000000..8c5c00108 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/logging/WriteLogEntry.java @@ -0,0 +1,53 @@ +/* + * Copyright 2021 Google LLC + * + * 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.example.logging; + +// [START logging_write_log_entry] +import com.google.cloud.MonitoredResource; +import com.google.cloud.logging.LogEntry; +import com.google.cloud.logging.Logging; +import com.google.cloud.logging.LoggingOptions; +import com.google.cloud.logging.Payload.JsonPayload; +import com.google.cloud.logging.Severity; +import com.google.common.collect.ImmutableMap; +import java.util.Collections; +import java.util.Map; + +public class WriteLogEntry { + + public static void main(String[] args) throws Exception { + // TODO(developer): Optionally provide the logname as an argument + String logName = args.length > 0 ? args[0] : "test-log"; + + // Instantiates a client + try (Logging logging = LoggingOptions.getDefaultInstance().getService()) { + Map payload = + ImmutableMap.of( + "name", "King Arthur", "quest", "Find the Holy Grail", "favorite_color", "Blue"); + LogEntry entry = + LogEntry.newBuilder(JsonPayload.of(payload)) + .setSeverity(Severity.INFO) + .setLogName(logName) + .setResource(MonitoredResource.newBuilder("global").build()) + .build(); + + logging.write(Collections.singleton(entry)); + } + System.out.printf("Wrote to %s\n", logName); + } +} +// [END logging_write_log_entry] diff --git a/samples/snippets/src/test/java/com/example/logging/LoggingIT.java b/samples/snippets/src/test/java/com/example/logging/LoggingIT.java index aa9ba6942..1bbb0a4d7 100644 --- a/samples/snippets/src/test/java/com/example/logging/LoggingIT.java +++ b/samples/snippets/src/test/java/com/example/logging/LoggingIT.java @@ -30,7 +30,9 @@ import java.io.PrintStream; import java.util.Collections; import org.junit.After; +import org.junit.AfterClass; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -41,16 +43,38 @@ public class LoggingIT { private static final String TEST_LOG = formatForTest("test-log"); - private static final String GOOGLEAPIS_AUDIT_LOGNAME = "cloudaudit.googleapis.com%2Factivity"; private static final String STRING_PAYLOAD = "Hello, world!"; private static final String STRING_PAYLOAD2 = "Hello world again"; private ByteArrayOutputStream bout; private PrintStream out; - private Logging logging = LoggingOptions.getDefaultInstance().getService(); + private static Logging logging; + + private void deleteLog(String logName) throws InterruptedException { + int deleteAttempts = 0; + int allowedDeleteAttempts = 5; + boolean deleted = false; + while (!deleted && deleteAttempts < allowedDeleteAttempts) { + deleted = logging.deleteLog(logName); + deleteAttempts++; + if (!deleted) { + Thread.sleep(1000); + } + } + if (!deleted) { + System.err.println( + "Failed to clean up log entries after 5 attempts. Following tests may be flaky..."); + } + } - private void deleteLog(String logName) { - logging.deleteLog(logName); + @BeforeClass + public static void startup() { + logging = LoggingOptions.getDefaultInstance().getService(); + } + + @AfterClass + public static void shutDown() throws Exception { + logging.close(); } @Before @@ -61,13 +85,13 @@ public void setUp() { } @After - public void tearDown() { + public void tearDown() throws Exception { // Clean up created logs deleteLog(TEST_LOG); System.setOut(null); } - @Test + @Test(timeout = 60000) public void testQuickstartSample() throws Exception { QuickstartSample.main(TEST_LOG); String got = bout.toString(); @@ -116,7 +140,7 @@ public void testWriteLogHttpRequestSample() throws Exception { String[] args = new String[] {TEST_LOG}; while (bout.toString().isEmpty()) { ListLogEntries.main(args); - Thread.sleep(5000); + Thread.sleep(1000); } // check log entry contain request data @@ -125,14 +149,10 @@ public void testWriteLogHttpRequestSample() throws Exception { } @Test(timeout = 60000) - public void testListLogNamesSample() throws Exception { - ListLogs.main(); - // Check for mocked STDOUT having data - while (bout.toString().isEmpty()) { - Thread.sleep(5000); - } - - assertThat(bout.toString().contains(GOOGLEAPIS_AUDIT_LOGNAME)).isTrue(); + public void testWriteLogEntrySample() throws Exception { + WriteLogEntry.main(new String[] {TEST_LOG}); + String got = bout.toString(); + assertThat(got).contains(String.format("Wrote to %s", TEST_LOG)); } @Test(timeout = 60000) @@ -153,7 +173,7 @@ public void testTailLogEntriesSample() throws Exception { logging.write(Collections.singleton(logEntry)); } } catch (Exception t) { - System.out.println("Failed to write log entry:\n" + t); + System.err.println("Failed to write log entry:\n" + t); } }; Thread thread = new Thread(task); diff --git a/samples/snippets/src/test/java/com/example/logging/LogsIT.java b/samples/snippets/src/test/java/com/example/logging/LogsIT.java new file mode 100644 index 000000000..181ba2f3c --- /dev/null +++ b/samples/snippets/src/test/java/com/example/logging/LogsIT.java @@ -0,0 +1,54 @@ +/* + * Copyright 2021 Google LLC + * + * 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.example.logging; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class LogsIT { + private static final String GOOGLEAPIS_AUDIT_LOGNAME = "cloudaudit.googleapis.com%2Factivity"; + + private ByteArrayOutputStream bout; + private PrintStream out; + + @Before + public void setUp() { + bout = new ByteArrayOutputStream(); + out = new PrintStream(bout); + System.setOut(out); + } + + @After + public void tearDown() { + System.setOut(null); + } + + @Test(timeout = 60000) + public void testListLogNamesSample() throws Exception { + ListLogs.main(); + // Check for mocked STDOUT having data + while (bout.toString().isEmpty()) { + Thread.sleep(1000); + } + assertThat(bout.toString().contains(GOOGLEAPIS_AUDIT_LOGNAME)).isTrue(); + } +}