buffer = new LinkedList<>();
diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/LogEntryServerStream.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/LogEntryServerStream.java
index 2d4b5ec12..1bbf038f3 100644
--- a/google-cloud-logging/src/main/java/com/google/cloud/logging/LogEntryServerStream.java
+++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/LogEntryServerStream.java
@@ -22,6 +22,26 @@
import com.google.logging.v2.TailLogEntriesResponse;
import java.util.Iterator;
+/**
+ * The class implements {@Iterable} interface over {@see LogEntry}. It wraps around {@BidiStream}
+ * bi-directional gRPC stream to support iterating through ingested responses. The class uses {@see
+ * LogEntryIterator} to iterate through the processed responses. The stream should be explicitly
+ * canceled by calling {@see LogEntryServerStream#cancel()} method. The class does not provide
+ * recovery or resuming functionality over the stream.
+ *
+ * To iterate run:
+ *
+ *
{@code
+ * LogEntryServerStream stream;
+ * // code to initialize stream
+ * for (LogEntry log : stream) {
+ * // do something with logs
+ * }
+ * stream.cancel();
+ * }
+ *
+ * The iteration can be blocked on waiting for another response sent in the stream.
+ */
public class LogEntryServerStream implements Iterable {
private final BidiStream serverStream;
diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java
index 41e08d41f..ce8e60ff7 100644
--- a/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java
+++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java
@@ -924,7 +924,7 @@ public ApiFuture> listLogEntriesAsync(EntryListOption... opt
return listLogEntriesAsync(getOptions(), optionMap(options));
}
- static TailLogEntriesRequest tailLogEntriesRequest(
+ static TailLogEntriesRequest buildTailLogEntriesRequest(
Map options, String defaultProjectId) {
TailLogEntriesRequest.Builder builder = TailLogEntriesRequest.newBuilder();
@@ -967,7 +967,7 @@ public LogEntryServerStream tailLogEntries(TailOption... options) {
BidiStream bidiStream =
serviceOptions.getLoggingRpcV2().getTailLogEntriesStream();
final TailLogEntriesRequest request =
- tailLogEntriesRequest(optionMap(options), serviceOptions.getProjectId());
+ buildTailLogEntriesRequest(optionMap(options), serviceOptions.getProjectId());
bidiStream.send(request);
return new LogEntryServerStream(bidiStream);
}
diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/TailLogEntriesTest.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/TailLogEntriesTest.java
index d79a4bc5e..ee3971d6e 100644
--- a/google-cloud-logging/src/test/java/com/google/cloud/logging/TailLogEntriesTest.java
+++ b/google-cloud-logging/src/test/java/com/google/cloud/logging/TailLogEntriesTest.java
@@ -43,7 +43,7 @@ public class TailLogEntriesTest {
@Test
public void testTailOptions() {
TailLogEntriesRequest request =
- LoggingImpl.tailLogEntriesRequest(
+ LoggingImpl.buildTailLogEntriesRequest(
LoggingImpl.optionMap(
TailOption.filter(FILTER),
TailOption.bufferWindow(WINDOW),
@@ -66,7 +66,7 @@ public void testTailOptions() {
@Test
public void testEmptyTailOptions() {
TailLogEntriesRequest request =
- LoggingImpl.tailLogEntriesRequest(LoggingImpl.optionMap(), DEFAULT_PROJECT_ID);
+ LoggingImpl.buildTailLogEntriesRequest(LoggingImpl.optionMap(), DEFAULT_PROJECT_ID);
assertThat(request.getFilter()).isEqualTo("");
assertThat(request.getBufferWindow()).isEqualTo(Duration.newBuilder().build());
assertThat(request.getResourceNamesList()).containsExactly("projects/" + DEFAULT_PROJECT_ID);
diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/it/ITTailLogsTest.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/it/ITTailLogsTest.java
index 2882595c0..48e86fa44 100644
--- a/google-cloud-logging/src/test/java/com/google/cloud/logging/it/ITTailLogsTest.java
+++ b/google-cloud-logging/src/test/java/com/google/cloud/logging/it/ITTailLogsTest.java
@@ -34,10 +34,6 @@ public class ITTailLogsTest extends BaseSystemTest {
private static final MonitoredResource GLOBAL_RESOURCE =
MonitoredResource.newBuilder("global").build();
- // @BeforeClass
- // public static void configureWriteLogs() {
- // }
-
@AfterClass
public static void cleanUpLogs() throws InterruptedException {
assertTrue(cleanupLog(LOG_ID));
@@ -83,7 +79,9 @@ public void testTailLogEntries() throws InterruptedException {
assertEquals(testLogEntry.getLogName(), resultEntry.getLogName());
assertEquals(
testLogEntry.getHttpRequest().getStatus(), resultEntry.getHttpRequest().getStatus());
+ // assert equals for Payload objects
assertTrue(testLogEntry.getPayload().equals(resultEntry.getPayload()));
+ // assert equals for Map objects
assertTrue(testLogEntry.getLabels().equals(resultEntry.getLabels()));
}
}
diff --git a/samples/snippets/src/main/java/com/example/logging/TailLogEntries.java b/samples/snippets/src/main/java/com/example/logging/TailLogEntries.java
new file mode 100644
index 000000000..2580802a1
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/logging/TailLogEntries.java
@@ -0,0 +1,55 @@
+/*
+ * 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.logging.LogEntry;
+import com.google.cloud.logging.LogEntryServerStream;
+import com.google.cloud.logging.Logging;
+import com.google.cloud.logging.Logging.TailOption;
+import com.google.cloud.logging.LoggingOptions;
+
+public class TailLogEntries {
+
+ public static void main(String[] args) throws Exception {
+ // TODO(developer): Optionally provide the logname as an argument.
+ String logName = args.length > 0 ? args[0] : "";
+
+ LoggingOptions options = LoggingOptions.getDefaultInstance();
+ try (Logging logging = options.getService()) {
+
+ // Optionally compose a filter to tail log entries only from specific log
+ LogEntryServerStream stream;
+
+ if (logName != "") {
+ stream =
+ logging.tailLogEntries(
+ TailOption.filter(
+ "logName=projects/" + options.getProjectId() + "/logs/" + logName));
+ } else {
+ stream = logging.tailLogEntries();
+ }
+ System.out.println("start streaming..");
+ for (LogEntry log : stream) {
+ System.out.println(log);
+ // cancel infinite streaming after receiving first entry
+ stream.cancel();
+ }
+ }
+ }
+}
+// [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 619595962..3087f97a7 100644
--- a/samples/snippets/src/test/java/com/example/logging/LoggingIT.java
+++ b/samples/snippets/src/test/java/com/example/logging/LoggingIT.java
@@ -64,19 +64,18 @@ public void setUp() {
public void tearDown() {
// Clean up created logs
deleteLog(TEST_LOG);
-
System.setOut(null);
}
@Test
- public void testQuickstart() throws Exception {
+ public void testQuickstartSample() throws Exception {
QuickstartSample.main(TEST_LOG);
String got = bout.toString();
assertThat(got).contains(String.format("Logged: %s", STRING_PAYLOAD));
}
@Test(timeout = 60000)
- public void testListLogEntries() throws Exception {
+ public void testListLogEntriesSample() throws Exception {
// write a log entry
LogEntry entry =
LogEntry.newBuilder(StringPayload.of(STRING_PAYLOAD2))
@@ -99,7 +98,7 @@ public void testListLogEntries() throws Exception {
}
@Test(timeout = 60000)
- public void testWriteLogHttpRequest() throws Exception {
+ public void testWriteLogHttpRequestSample() throws Exception {
HttpRequest request =
HttpRequest.newBuilder()
.setRequestUrl("www.example.com")
@@ -126,7 +125,7 @@ public void testWriteLogHttpRequest() throws Exception {
}
@Test(timeout = 60000)
- public void testListLogNames_shouldPass() throws Exception {
+ public void testListLogNamesSample() throws Exception {
ListLogs.main();
// Check for mocked STDOUT having data
while (bout.toString().isEmpty()) {
@@ -135,4 +134,37 @@ public void testListLogNames_shouldPass() throws Exception {
assertThat(bout.toString().contains(GOOGLEAPIS_AUDIT_LOGNAME)).isTrue();
}
+
+ @Test(timeout = 60000)
+ public void testTailLogEntriesSample() throws Exception {
+ Runnable task =
+ () -> {
+ // wait 10 seconds to allow establishing tail stream in the sample
+ try {
+ Thread.sleep(10_000);
+ try (Logging logging = LoggingOptions.getDefaultInstance().getService()) {
+ // create an instance of LogEntry with HTTP request
+ LogEntry logEntry =
+ LogEntry.newBuilder(StringPayload.of(STRING_PAYLOAD))
+ .setLogName(TEST_LOG)
+ .setResource(MonitoredResource.newBuilder("global").build())
+ .build();
+ // Writes the log entry asynchronously
+ logging.write(Collections.singleton(logEntry));
+ }
+ } catch (Exception t) {
+ System.out.println("Failed to write log entry:\n" + t)
+ }
+ };
+ Thread thread = new Thread(task);
+ thread.start();
+
+ TailLogEntries.main(new String[] {TEST_LOG});
+
+ // Check for mocked STDOUT having data
+ while (bout.toString().isEmpty()) {
+ Thread.sleep(1000);
+ }
+ assertThat(bout.toString().contains(STRING_PAYLOAD)).isTrue();
+ }
}