diff --git a/gcloud-java-logging/src/main/java/com/google/cloud/logging/Logging.java b/gcloud-java-logging/src/main/java/com/google/cloud/logging/Logging.java index b65e58af43a3..392deaa5d726 100644 --- a/gcloud-java-logging/src/main/java/com/google/cloud/logging/Logging.java +++ b/gcloud-java-logging/src/main/java/com/google/cloud/logging/Logging.java @@ -17,9 +17,11 @@ package com.google.cloud.logging; import com.google.cloud.AsyncPage; +import com.google.cloud.MonitoredResource; import com.google.cloud.MonitoredResourceDescriptor; import com.google.cloud.Page; import com.google.cloud.Service; +import com.google.common.collect.ImmutableMap; import java.util.Map; import java.util.concurrent.Future; @@ -62,6 +64,134 @@ public static ListOption pageToken(String pageToken) { } } + /** + * Class for specifying options for writing log entries. + */ + final class WriteOption extends Option { + + private static final long serialVersionUID = 715900132268584612L; + + enum OptionType implements Option.OptionType { + LOG_NAME, RESOURCE, LABELS; + + @SuppressWarnings("unchecked") + T get(Map options) { + return (T) options.get(this); + } + } + + private WriteOption(OptionType option, Object value) { + super(option, value); + } + + /** + * Returns an option to specify a default log name (see {@link LogEntry#logName()}) for those + * log entries that do not specify their own log name. Example: {@code syslog}. + */ + public static WriteOption logName(String logName) { + return new WriteOption(OptionType.LOG_NAME, logName); + } + + /** + * Returns an option to specify a default monitored resource (see {@link LogEntry#resource()}) + * for those log entries that do not specify their own resource. + */ + public static WriteOption resource(MonitoredResource resource) { + return new WriteOption(OptionType.RESOURCE, resource); + } + + /** + * Sets an option to specify (key, value) pairs that are added to the {@link LogEntry#labels()} + * of each log entry written, except when a log entry already has a value associated to the + * same key. + */ + public static WriteOption labels(Map labels) { + return new WriteOption(OptionType.LABELS, ImmutableMap.copyOf(labels)); + } + } + + /** + * Fields according to which log entries can be sorted. + */ + enum SortingField { + TIMESTAMP; + + String selector() { + return name().toLowerCase(); + } + } + + /** + * Sorting orders available when listing log entries. + */ + enum SortingOrder { + DESCENDING("desc"), + ASCENDING("asc"); + + private final String selector; + + SortingOrder(String selector) { + this.selector = selector; + } + + String selector() { + return selector; + } + } + + /** + * Class for specifying options for listing log entries. + */ + final class EntryListOption extends Option { + + private static final long serialVersionUID = -1561159676386917050L; + + enum OptionType implements Option.OptionType { + PAGE_SIZE, PAGE_TOKEN, ORDER_BY, FILTER; + + @SuppressWarnings("unchecked") + T get(Map options) { + return (T) options.get(this); + } + } + + private EntryListOption(OptionType option, Object value) { + super(option, value); + } + + /** + * Returns an option to specify the maximum number of log entries returned per page. + */ + public static EntryListOption pageSize(int pageSize) { + return new EntryListOption(OptionType.PAGE_SIZE, pageSize); + } + + /** + * Returns an option to specify the page token from which to start listing log entries. + */ + public static EntryListOption pageToken(String pageToken) { + return new EntryListOption(OptionType.PAGE_TOKEN, pageToken); + } + + /** + * Returns an option to sort log entries. If not specified, log entries are sorted in ascending + * (most-recent last) order with respect to the {@link LogEntry#timestamp()} value. + */ + public static EntryListOption sortOrder(SortingField field, SortingOrder order) { + return new EntryListOption(OptionType.ORDER_BY, field.selector() + ' ' + order.selector()); + } + + /** + * Returns an option to specify a filter to the log entries to be listed. + * + * @see Advanced Logs + * Filters + */ + public static EntryListOption filter(String filter) { + return new EntryListOption(OptionType.FILTER, filter); + } + } + /** * Creates a new sink. * @@ -244,4 +374,49 @@ Future> listMonitoredResourceDescriptorsA * if it was not found. */ Future deleteMetricAsync(String metric); + + /** + * Writes log entries to Cloud Logging. Use {@link WriteOption#logName(String)} to provide a log + * name for those entries that do not specify one. Use + * {@link WriteOption#resource(MonitoredResource)} to provide a monitored resource for those + * entries that do not specify one. Use {@link WriteOption#labels(Map)} to provide some labels + * to be added to every entry in {@code logEntries}. + */ + void write(Iterable logEntries, WriteOption... options); + + /** + * Sends a request to log entries to Cloud Logging. Use {@link WriteOption#logName(String)} to + * provide a log name for those entries that do not specify one. Use + * {@link WriteOption#resource(MonitoredResource)} to provide a monitored resource for those + * entries that do not specify one. Use {@link WriteOption#labels(Map)} to provide some labels + * to be added to every entry in {@code logEntries}. The method returns a {@code Future} object + * that can be used to wait for the write operation to be completed. + */ + Future writeAsync(Iterable logEntries, WriteOption... options); + + /** + * Lists log entries. This method returns a {@link Page} object that can be used to consume + * paginated results. Use {@link EntryListOption#pageSize(int)} to specify the page size. Use + * {@link EntryListOption#pageToken(String)} to specify the page token from which to start listing + * entries. Use {@link EntryListOption#sortOrder(SortingField, SortingOrder)} to sort log entries + * according to your preferred order (default is most-recent last). Use + * {@link EntryListOption#filter(String)} to filter listed log entries. + * + * @throws LoggingException upon failure + */ + Page listLogEntries(EntryListOption... options); + + /** + * Sends a request for listing log entries. This method returns a {@code Future} object to consume + * the result. {@link Future#get()} returns an {@link AsyncPage} object that can be used to + * asynchronously handle paginated results. Use {@link EntryListOption#pageSize(int)} to specify + * the page size. Use {@link EntryListOption#pageToken(String)} to specify the page token from + * which to start listing entries. Use + * {@link EntryListOption#sortOrder(SortingField, SortingOrder)} to sort log entries according to + * your preferred order (default is most-recent last). Use {@link EntryListOption#filter(String)} + * to filter listed log entries. + * + * @throws LoggingException upon failure + */ + Future> listLogEntriesAsync(EntryListOption... options); } diff --git a/gcloud-java-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java b/gcloud-java-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java index b44f634126b1..1432b6ef8478 100644 --- a/gcloud-java-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java +++ b/gcloud-java-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java @@ -17,13 +17,19 @@ package com.google.cloud.logging; import static com.google.api.client.util.Preconditions.checkArgument; +import static com.google.cloud.logging.Logging.EntryListOption.OptionType.FILTER; +import static com.google.cloud.logging.Logging.EntryListOption.OptionType.ORDER_BY; import static com.google.cloud.logging.Logging.ListOption.OptionType.PAGE_SIZE; import static com.google.cloud.logging.Logging.ListOption.OptionType.PAGE_TOKEN; +import static com.google.cloud.logging.Logging.WriteOption.OptionType.LABELS; +import static com.google.cloud.logging.Logging.WriteOption.OptionType.LOG_NAME; +import static com.google.cloud.logging.Logging.WriteOption.OptionType.RESOURCE; import static com.google.common.util.concurrent.Futures.lazyTransform; import com.google.cloud.AsyncPage; import com.google.cloud.AsyncPageImpl; import com.google.cloud.BaseService; +import com.google.cloud.MonitoredResource; import com.google.cloud.MonitoredResourceDescriptor; import com.google.cloud.Page; import com.google.cloud.PageImpl; @@ -34,6 +40,7 @@ import com.google.common.base.Function; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.util.concurrent.Uninterruptibles; @@ -44,6 +51,8 @@ import com.google.logging.v2.DeleteSinkRequest; import com.google.logging.v2.GetLogMetricRequest; import com.google.logging.v2.GetSinkRequest; +import com.google.logging.v2.ListLogEntriesRequest; +import com.google.logging.v2.ListLogEntriesResponse; import com.google.logging.v2.ListLogMetricsRequest; import com.google.logging.v2.ListLogMetricsResponse; import com.google.logging.v2.ListMonitoredResourceDescriptorsRequest; @@ -52,6 +61,8 @@ import com.google.logging.v2.ListSinksResponse; import com.google.logging.v2.UpdateLogMetricRequest; import com.google.logging.v2.UpdateSinkRequest; +import com.google.logging.v2.WriteLogEntriesRequest; +import com.google.logging.v2.WriteLogEntriesResponse; import com.google.protobuf.Empty; import java.util.List; @@ -71,6 +82,13 @@ public Boolean apply(Empty input) { return input != null; } }; + private static final Function WRITE_RESPONSE_TO_VOID_FUNCTION = + new Function() { + @Override + public Void apply(WriteLogEntriesResponse input) { + return null; + } + }; LoggingImpl(LoggingOptions options) { super(options); @@ -154,6 +172,21 @@ public Future> nextPage() { } } + private static class LogEntryPageFetcher extends BasePageFetcher { + + private static final long serialVersionUID = 4001239712280747734L; + + LogEntryPageFetcher(LoggingOptions serviceOptions, String cursor, + Map requestOptions) { + super(serviceOptions, cursor, requestOptions); + } + + @Override + public Future> nextPage() { + return listLogEntriesAsync(serviceOptions(), requestOptions()); + } + } + @Override public Sink create(SinkInfo sink) { return get(createAsync(sink)); @@ -408,6 +441,87 @@ public Future deleteMetricAsync(String metric) { return lazyTransform(rpc.delete(request), EMPTY_TO_BOOLEAN_FUNCTION); } + private static WriteLogEntriesRequest writeLogEntriesRequest(LoggingOptions serviceOptions, + Iterable logEntries, Map options) { + String projectId = serviceOptions.projectId(); + WriteLogEntriesRequest.Builder builder = WriteLogEntriesRequest.newBuilder(); + String logName = LOG_NAME.get(options); + if (logName != null) { + builder.setLogName(LoggingServiceV2Api.formatLogName(projectId, logName)); + } + MonitoredResource resource = RESOURCE.get(options); + if (resource != null) { + builder.setResource(resource.toPb()); + } + Map labels = LABELS.get(options); + if (labels != null) { + builder.putAllLabels(labels); + } + builder.addAllEntries(Iterables.transform(logEntries, LogEntry.toPbFunction(projectId))); + return builder.build(); + } + + public void write(Iterable logEntries, WriteOption... options) { + get(writeAsync(logEntries, options)); + } + + public Future writeAsync(Iterable logEntries, WriteOption... options) { + return lazyTransform( + rpc.write(writeLogEntriesRequest(options(), logEntries, optionMap(options))), + WRITE_RESPONSE_TO_VOID_FUNCTION); + } + + private static ListLogEntriesRequest listLogEntriesRequest(LoggingOptions serviceOptions, + Map options) { + ListLogEntriesRequest.Builder builder = ListLogEntriesRequest.newBuilder(); + builder.addProjectIds(serviceOptions.projectId()); + Integer pageSize = PAGE_SIZE.get(options); + if (pageSize != null) { + builder.setPageSize(pageSize); + } + String pageToken = PAGE_TOKEN.get(options); + if (pageToken != null) { + builder.setPageToken(pageToken); + } + String orderBy = ORDER_BY.get(options); + if (orderBy != null) { + builder.setOrderBy(orderBy); + } + String filter = FILTER.get(options); + if (filter != null) { + builder.setFilter(filter); + } + return builder.build(); + } + + private static Future> listLogEntriesAsync( + final LoggingOptions serviceOptions, final Map options) { + final ListLogEntriesRequest request = listLogEntriesRequest(serviceOptions, options); + Future list = serviceOptions.rpc().list(request); + return lazyTransform(list, new Function>() { + @Override + public AsyncPage apply(ListLogEntriesResponse listLogEntrysResponse) { + List entries = listLogEntrysResponse.getEntriesList() == null + ? ImmutableList.of() : Lists.transform(listLogEntrysResponse.getEntriesList(), + LogEntry.FROM_PB_FUNCTION); + String cursor = listLogEntrysResponse.getNextPageToken().equals("") ? null + : listLogEntrysResponse.getNextPageToken(); + return new AsyncPageImpl<>(new LogEntryPageFetcher(serviceOptions, cursor, options), cursor, + entries); + } + }); + } + + @Override + public Page listLogEntries(EntryListOption... options) { + return get(listLogEntriesAsync(options)); + } + + @Override + public Future> listLogEntriesAsync(EntryListOption... options) { + return listLogEntriesAsync(options(), optionMap(options)); + } + @Override public void close() throws Exception { if (closed) { diff --git a/gcloud-java-logging/src/test/java/com/google/cloud/logging/LoggingImplTest.java b/gcloud-java-logging/src/test/java/com/google/cloud/logging/LoggingImplTest.java index b6aed1042e6b..a9b1b3dcb05f 100644 --- a/gcloud-java-logging/src/test/java/com/google/cloud/logging/LoggingImplTest.java +++ b/gcloud-java-logging/src/test/java/com/google/cloud/logging/LoggingImplTest.java @@ -24,15 +24,21 @@ import static org.junit.Assert.assertTrue; import com.google.cloud.AsyncPage; +import com.google.cloud.MonitoredResource; import com.google.cloud.MonitoredResourceDescriptor; import com.google.cloud.Page; import com.google.cloud.RetryParams; +import com.google.cloud.logging.Logging.EntryListOption; import com.google.cloud.logging.Logging.ListOption; +import com.google.cloud.logging.Logging.SortingField; +import com.google.cloud.logging.Logging.WriteOption; +import com.google.cloud.logging.Payload.StringPayload; import com.google.cloud.logging.SinkInfo.Destination; import com.google.cloud.logging.spi.LoggingRpc; import com.google.cloud.logging.spi.LoggingRpcFactory; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; @@ -43,6 +49,8 @@ import com.google.logging.v2.DeleteSinkRequest; import com.google.logging.v2.GetLogMetricRequest; import com.google.logging.v2.GetSinkRequest; +import com.google.logging.v2.ListLogEntriesRequest; +import com.google.logging.v2.ListLogEntriesResponse; import com.google.logging.v2.ListLogMetricsRequest; import com.google.logging.v2.ListLogMetricsResponse; import com.google.logging.v2.ListMonitoredResourceDescriptorsRequest; @@ -53,6 +61,8 @@ import com.google.logging.v2.LogSink; import com.google.logging.v2.UpdateLogMetricRequest; import com.google.logging.v2.UpdateSinkRequest; +import com.google.logging.v2.WriteLogEntriesRequest; +import com.google.logging.v2.WriteLogEntriesResponse; import com.google.protobuf.Empty; import org.easymock.EasyMock; @@ -63,6 +73,7 @@ import org.junit.rules.ExpectedException; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -87,6 +98,16 @@ public class LoggingImplTest { MonitoredResourceDescriptor.fromPb(DESCRIPTOR_PB); private static final String LOG_NAME = "log"; private static final String LOG_NAME_PB = "projects/" + PROJECT + "/logs/" + LOG_NAME; + private static final MonitoredResource MONITORED_RESOURCE = + MonitoredResource.builder("global").addLabel("project-id", PROJECT).build(); + private static final LogEntry LOG_ENTRY1 = LogEntry.builder(StringPayload.of("entry1")) + .logName(LOG_NAME) + .resource(MONITORED_RESOURCE) + .build(); + private static final LogEntry LOG_ENTRY2 = LogEntry.builder(StringPayload.of("entry2")) + .logName(LOG_NAME) + .resource(MONITORED_RESOURCE) + .build(); private static final Function SINK_TO_PB_FUNCTION = new Function() { @Override @@ -1121,4 +1142,270 @@ public void testDeleteLogAsync_Null() throws ExecutionException, InterruptedExce logging = options.service(); assertFalse(logging.deleteLogAsync(LOG_NAME).get()); } + + @Test + public void testWriteLogEntries() { + WriteLogEntriesRequest request = WriteLogEntriesRequest.newBuilder() + .addAllEntries(Iterables.transform(ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2), + LogEntry.toPbFunction(PROJECT))) + .build(); + WriteLogEntriesResponse response = WriteLogEntriesResponse.newBuilder().build(); + EasyMock.expect(loggingRpcMock.write(request)).andReturn(Futures.immediateFuture(response)); + EasyMock.replay(rpcFactoryMock, loggingRpcMock); + logging = options.service(); + logging.write(ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2)); + } + + @Test + public void testWriteLogEntriesWithOptions() { + Map labels = ImmutableMap.of("key", "value"); + WriteLogEntriesRequest request = WriteLogEntriesRequest.newBuilder() + .putAllLabels(labels) + .setLogName(LOG_NAME_PB) + .setResource(MONITORED_RESOURCE.toPb()) + .addAllEntries(Iterables.transform(ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2), + LogEntry.toPbFunction(PROJECT))) + .build(); + WriteLogEntriesResponse response = WriteLogEntriesResponse.newBuilder().build(); + EasyMock.expect(loggingRpcMock.write(request)).andReturn(Futures.immediateFuture(response)); + EasyMock.replay(rpcFactoryMock, loggingRpcMock); + logging = options.service(); + logging.write(ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2), WriteOption.logName(LOG_NAME), + WriteOption.resource(MONITORED_RESOURCE), WriteOption.labels(labels)); + } + + @Test + public void testWriteLogEntriesAsync() throws ExecutionException, InterruptedException { + WriteLogEntriesRequest request = WriteLogEntriesRequest.newBuilder() + .addAllEntries(Iterables.transform(ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2), + LogEntry.toPbFunction(PROJECT))) + .build(); + WriteLogEntriesResponse response = WriteLogEntriesResponse.newBuilder().build(); + EasyMock.expect(loggingRpcMock.write(request)).andReturn(Futures.immediateFuture(response)); + EasyMock.replay(rpcFactoryMock, loggingRpcMock); + logging = options.service(); + logging.writeAsync(ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2)).get(); + } + + @Test + public void testWriteLogEntriesAsyncWithOptions() { + Map labels = ImmutableMap.of("key", "value"); + WriteLogEntriesRequest request = WriteLogEntriesRequest.newBuilder() + .putAllLabels(labels) + .setLogName(LOG_NAME_PB) + .setResource(MONITORED_RESOURCE.toPb()) + .addAllEntries(Iterables.transform(ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2), + LogEntry.toPbFunction(PROJECT))) + .build(); + WriteLogEntriesResponse response = WriteLogEntriesResponse.newBuilder().build(); + EasyMock.expect(loggingRpcMock.write(request)).andReturn(Futures.immediateFuture(response)); + EasyMock.replay(rpcFactoryMock, loggingRpcMock); + logging = options.service(); + logging.writeAsync(ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2), WriteOption.logName(LOG_NAME), + WriteOption.resource(MONITORED_RESOURCE), WriteOption.labels(labels)); + } + + @Test + public void testListLogEntries() { + String cursor = "cursor"; + EasyMock.replay(rpcFactoryMock); + logging = options.service(); + ListLogEntriesRequest request = ListLogEntriesRequest.newBuilder() + .addProjectIds(PROJECT) + .build(); + List entriesList = ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2); + ListLogEntriesResponse response = ListLogEntriesResponse.newBuilder() + .setNextPageToken(cursor) + .addAllEntries(Lists.transform(entriesList, LogEntry.toPbFunction(PROJECT))) + .build(); + Future futureResponse = Futures.immediateFuture(response); + EasyMock.expect(loggingRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(loggingRpcMock); + Page page = logging.listLogEntries(); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(entriesList.toArray(), Iterables.toArray(page.values(), LogEntry.class)); + } + + @Test + public void testListLogEntriesNextPage() throws ExecutionException, InterruptedException { + String cursor1 = "cursor"; + EasyMock.replay(rpcFactoryMock); + logging = options.service(); + ListLogEntriesRequest request1 = ListLogEntriesRequest.newBuilder() + .addProjectIds(PROJECT) + .build(); + ListLogEntriesRequest request2 = ListLogEntriesRequest.newBuilder() + .addProjectIds(PROJECT) + .setPageToken(cursor1) + .build(); + List descriptorList1 = ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2); + List descriptorList2 = ImmutableList.of(LOG_ENTRY1); + ListLogEntriesResponse response1 = ListLogEntriesResponse.newBuilder() + .setNextPageToken(cursor1) + .addAllEntries(Lists.transform(descriptorList1, LogEntry.toPbFunction(PROJECT))) + .build(); + String cursor2 = "nextCursor"; + ListLogEntriesResponse response2 = ListLogEntriesResponse.newBuilder() + .setNextPageToken(cursor2) + .addAllEntries(Lists.transform(descriptorList2, LogEntry.toPbFunction(PROJECT))) + .build(); + Future futureResponse1 = Futures.immediateFuture(response1); + Future futureResponse2 = Futures.immediateFuture(response2); + EasyMock.expect(loggingRpcMock.list(request1)).andReturn(futureResponse1); + EasyMock.expect(loggingRpcMock.list(request2)).andReturn(futureResponse2); + EasyMock.replay(loggingRpcMock); + AsyncPage page = logging.listLogEntriesAsync().get(); + assertEquals(cursor1, page.nextPageCursor()); + assertArrayEquals(descriptorList1.toArray(), Iterables.toArray(page.values(), LogEntry.class)); + page = page.nextPageAsync().get(); + assertEquals(cursor2, page.nextPageCursor()); + assertArrayEquals(descriptorList2.toArray(), Iterables.toArray(page.values(), LogEntry.class)); + } + + @Test + public void testListLogEntriesEmpty() { + String cursor = "cursor"; + EasyMock.replay(rpcFactoryMock); + logging = options.service(); + ListLogEntriesRequest request = ListLogEntriesRequest.newBuilder() + .addProjectIds(PROJECT) + .build(); + List entriesList = ImmutableList.of(); + ListLogEntriesResponse response = ListLogEntriesResponse.newBuilder() + .setNextPageToken(cursor) + .addAllEntries(Lists.transform(entriesList, LogEntry.toPbFunction(PROJECT))) + .build(); + Future futureResponse = Futures.immediateFuture(response); + EasyMock.expect(loggingRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(loggingRpcMock); + Page page = logging.listLogEntries(); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(entriesList.toArray(), Iterables.toArray(page.values(), LogEntry.class)); + } + + @Test + public void testListLogEntriesWithOptions() { + String cursor = "cursor"; + EasyMock.replay(rpcFactoryMock); + logging = options.service(); + ListLogEntriesRequest request = ListLogEntriesRequest.newBuilder() + .addProjectIds(PROJECT) + .setOrderBy("timestamp desc") + .setFilter("logName:syslog") + .build(); + List entriesList = ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2); + ListLogEntriesResponse response = ListLogEntriesResponse.newBuilder() + .setNextPageToken(cursor) + .addAllEntries(Lists.transform(entriesList, LogEntry.toPbFunction(PROJECT))) + .build(); + Future futureResponse = Futures.immediateFuture(response); + EasyMock.expect(loggingRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(loggingRpcMock); + Page page = logging.listLogEntries(EntryListOption.filter("logName:syslog"), + EntryListOption.sortOrder(SortingField.TIMESTAMP, Logging.SortingOrder.DESCENDING)); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(entriesList.toArray(), Iterables.toArray(page.values(), LogEntry.class)); + } + + @Test + public void testListLogEntriesAsync() throws ExecutionException, InterruptedException { + String cursor = "cursor"; + EasyMock.replay(rpcFactoryMock); + logging = options.service(); + ListLogEntriesRequest request = ListLogEntriesRequest.newBuilder() + .addProjectIds(PROJECT) + .build(); + List entriesList = ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2); + ListLogEntriesResponse response = ListLogEntriesResponse.newBuilder() + .setNextPageToken(cursor) + .addAllEntries(Lists.transform(entriesList, LogEntry.toPbFunction(PROJECT))) + .build(); + Future futureResponse = Futures.immediateFuture(response); + EasyMock.expect(loggingRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(loggingRpcMock); + AsyncPage page = logging.listLogEntriesAsync().get(); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(entriesList.toArray(), Iterables.toArray(page.values(), LogEntry.class)); + } + + @Test + public void testListLogEntriesAsyncNextPage() { + String cursor1 = "cursor"; + EasyMock.replay(rpcFactoryMock); + logging = options.service(); + ListLogEntriesRequest request1 = ListLogEntriesRequest.newBuilder() + .addProjectIds(PROJECT) + .build(); + ListLogEntriesRequest request2 = ListLogEntriesRequest.newBuilder() + .addProjectIds(PROJECT) + .setPageToken(cursor1) + .build(); + List descriptorList1 = ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2); + List descriptorList2 = ImmutableList.of(LOG_ENTRY1); + ListLogEntriesResponse response1 = ListLogEntriesResponse.newBuilder() + .setNextPageToken(cursor1) + .addAllEntries(Lists.transform(descriptorList1, LogEntry.toPbFunction(PROJECT))) + .build(); + String cursor2 = "nextCursor"; + ListLogEntriesResponse response2 = ListLogEntriesResponse.newBuilder() + .setNextPageToken(cursor2) + .addAllEntries(Lists.transform(descriptorList2, LogEntry.toPbFunction(PROJECT))) + .build(); + Future futureResponse1 = Futures.immediateFuture(response1); + Future futureResponse2 = Futures.immediateFuture(response2); + EasyMock.expect(loggingRpcMock.list(request1)).andReturn(futureResponse1); + EasyMock.expect(loggingRpcMock.list(request2)).andReturn(futureResponse2); + EasyMock.replay(loggingRpcMock); + Page page = logging.listLogEntries(); + assertEquals(cursor1, page.nextPageCursor()); + assertArrayEquals(descriptorList1.toArray(), Iterables.toArray(page.values(), LogEntry.class)); + page = page.nextPage(); + assertEquals(cursor2, page.nextPageCursor()); + assertArrayEquals(descriptorList2.toArray(), Iterables.toArray(page.values(), LogEntry.class)); + } + + @Test + public void testListLogEntriesAyncEmpty() throws ExecutionException, InterruptedException { + String cursor = "cursor"; + EasyMock.replay(rpcFactoryMock); + logging = options.service(); + ListLogEntriesRequest request = ListLogEntriesRequest.newBuilder() + .addProjectIds(PROJECT) + .build(); + List entriesList = ImmutableList.of(); + ListLogEntriesResponse response = ListLogEntriesResponse.newBuilder() + .setNextPageToken(cursor) + .addAllEntries(Lists.transform(entriesList, LogEntry.toPbFunction(PROJECT))) + .build(); + Future futureResponse = Futures.immediateFuture(response); + EasyMock.expect(loggingRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(loggingRpcMock); + AsyncPage page = logging.listLogEntriesAsync().get(); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(entriesList.toArray(), Iterables.toArray(page.values(), LogEntry.class)); + } + + @Test + public void testListLogEntriesAsyncWithOptions() throws ExecutionException, InterruptedException { + String cursor = "cursor"; + EasyMock.replay(rpcFactoryMock); + logging = options.service(); + ListLogEntriesRequest request = ListLogEntriesRequest.newBuilder() + .addProjectIds(PROJECT) + .setOrderBy("timestamp desc") + .setFilter("logName:syslog") + .build(); + List entriesList = ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2); + ListLogEntriesResponse response = ListLogEntriesResponse.newBuilder() + .setNextPageToken(cursor) + .addAllEntries(Lists.transform(entriesList, LogEntry.toPbFunction(PROJECT))) + .build(); + Future futureResponse = Futures.immediateFuture(response); + EasyMock.expect(loggingRpcMock.list(request)).andReturn(futureResponse); + EasyMock.replay(loggingRpcMock); + AsyncPage page = logging.listLogEntriesAsync(EntryListOption.filter("logName:syslog"), + EntryListOption.sortOrder(SortingField.TIMESTAMP, Logging.SortingOrder.DESCENDING)).get(); + assertEquals(cursor, page.nextPageCursor()); + assertArrayEquals(entriesList.toArray(), Iterables.toArray(page.values(), LogEntry.class)); + } }