Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement listLogs API and provide sample snippet #602

Merged
merged 14 commits into from Aug 12, 2021
Merged
17 changes: 17 additions & 0 deletions google-cloud-logging/clirr-ignored-differences.xml
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- see http://www.mojohaus.org/clirr-maven-plugin/examples/ignored-differences.html -->
<!-- added to resolve breaking changes until JDK will be upgraded to ≥1.8 -->
minherz marked this conversation as resolved.
Show resolved Hide resolved
<differences>
<!-- Added methods to com.google.cloud.logging.Logging interface with implementation in LoggingImpl is always okay -->
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/logging/Logging</className>
<method>* listLogs*(com.google.cloud.logging.Logging$ListOption[])</method>
</difference>
<!-- Added methods to com.google.cloud.logging.spi.v2.LoggingRpc interface with implementation in GrpcLoggingRpc is always okay -->
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/logging/spi/v2/LoggingRpc</className>
<method>* listLogs(com.google.logging.v2.ListLogsRequest)</method>
</difference>
</differences>
Expand Up @@ -414,6 +414,53 @@ public static EntryListOption folder(String folder) {
*/
ApiFuture<Boolean> deleteSinkAsync(String sink);

/**
* Lists the log names. This method returns a {@link Page} object that can be used to consume
* paginated results. Use {@link ListOption} to specify the page size or the page token from which
* to start listing logs.
*
* <p>Example of listing log names, specifying the page size.
*
* <pre>{@code
* Page<Log> logNames = logging.listLogs(ListOption.pageSize(100));
* Iterator<Log> logIterator = logNames.iterateAll().iterator();
* while (logIterator.hasNext()) {
* String logName = logIterator.next();
* // do something with the log name
* }
* }</pre>
*
* @throws LoggingException upon failure
*/
default Page<String> listLogs(ListOption... options) {
throw new UnsupportedOperationException(
"method listLogs() does not have default implementation");
}
lesv marked this conversation as resolved.
Show resolved Hide resolved

/**
* Sends a request for listing log names. This method returns a {@code ApiFuture} object to
* consume the result. {@link ApiFuture#get()} returns an {@link AsyncPage} object that can be
* used to asynchronously handle paginated results. Use {@link ListOption} to specify the page
* size or the page token from which to start listing log names.
*
* <p>Example of asynchronously listing log names, specifying the page size.
*
* <pre>{@code
* ApiFuture<AsyncPage<Log>> future = logging.listLogsAsync(ListOption.pageSize(100));
* // ...
* AsyncPage<Sink> logNames = future.get();
* Iterator<Sink> logIterator = logNames.iterateAll().iterator();
* while (logIterator.hasNext()) {
* String logName = logIterator.next();
* // do something with the log name
* }
* }</pre>
*/
default ApiFuture<AsyncPage<String>> listLogsAsync(ListOption... options) {
throw new UnsupportedOperationException(
"method listLogsAsync() does not have default implementation");
}

/**
* Deletes a log and all its log entries. The log will reappear if new entries are written to it.
*
Expand Down
Expand Up @@ -65,6 +65,8 @@
import com.google.logging.v2.ListLogEntriesResponse;
import com.google.logging.v2.ListLogMetricsRequest;
import com.google.logging.v2.ListLogMetricsResponse;
import com.google.logging.v2.ListLogsRequest;
import com.google.logging.v2.ListLogsResponse;
import com.google.logging.v2.ListMonitoredResourceDescriptorsRequest;
import com.google.logging.v2.ListMonitoredResourceDescriptorsResponse;
import com.google.logging.v2.ListSinksRequest;
Expand Down Expand Up @@ -195,6 +197,19 @@ public ApiFuture<AsyncPage<Sink>> getNextPage() {
}
}

private static class LogNamePageFetcher extends BasePageFetcher<String> {

LogNamePageFetcher(
LoggingOptions serviceOptions, String cursor, Map<Option.OptionType, ?> requestOptions) {
super(serviceOptions, cursor, requestOptions);
}

@Override
public ApiFuture<AsyncPage<String>> getNextPage() {
return listLogsAsync(serviceOptions(), requestOptions());
}
}

private static class MonitoredResourceDescriptorPageFetcher
extends BasePageFetcher<MonitoredResourceDescriptor> {

Expand Down Expand Up @@ -366,6 +381,63 @@ public ApiFuture<Boolean> deleteSinkAsync(String sink) {
return transform(rpc.delete(request), EMPTY_TO_BOOLEAN_FUNCTION);
}

/**
* Creates a new {@code ListLogsRequest} object.
*
* <p>Builds an instance of {@code ListLogsRequest} using page size, page token and project id
* from the {@code LoggingOptions}. The project id is used as the request's parent parameter.
*
* @see com.google.logging.v2.ListLogEntriesRequest
* @return the created {@code ListLogsRequest} object
*/
private static ListLogsRequest listLogsRequest(
minherz marked this conversation as resolved.
Show resolved Hide resolved
LoggingOptions serviceOptions, Map<Option.OptionType, ?> options) {
ListLogsRequest.Builder builder = ListLogsRequest.newBuilder();
builder.setParent(ProjectName.of(serviceOptions.getProjectId()).toString());
Integer pageSize = PAGE_SIZE.get(options);
String pageToken = PAGE_TOKEN.get(options);
if (pageSize != null) {
builder.setPageSize(pageSize);
}
if (pageToken != null) {
builder.setPageToken(pageToken);
}
return builder.build();
}

private static ApiFuture<AsyncPage<String>> listLogsAsync(
minherz marked this conversation as resolved.
Show resolved Hide resolved
final LoggingOptions serviceOptions, final Map<Option.OptionType, ?> options) {
final ListLogsRequest request = listLogsRequest(serviceOptions, options);
ApiFuture<ListLogsResponse> list = serviceOptions.getLoggingRpcV2().listLogs(request);
return transform(
list,
new Function<ListLogsResponse, AsyncPage<String>>() {
@Override
public AsyncPage<String> apply(ListLogsResponse listLogsResponse) {
List<String> logNames =
listLogsResponse.getLogNamesList() == null
? ImmutableList.<String>of()
: listLogsResponse.getLogNamesList();
String cursor =
listLogsResponse.getNextPageToken().equals("")
minherz marked this conversation as resolved.
Show resolved Hide resolved
? null
: listLogsResponse.getNextPageToken();
return new AsyncPageImpl<>(
new LogNamePageFetcher(serviceOptions, cursor, options), cursor, logNames);
}
});
}

@Override
public Page<String> listLogs(ListOption... options) {
return get(listLogsAsync(options));
}

@Override
public ApiFuture<AsyncPage<String>> listLogsAsync(ListOption... options) {
return listLogsAsync(getOptions(), optionMap(options));
}

public boolean deleteLog(String log) {
return get(deleteLogAsync(log));
}
Expand Down
Expand Up @@ -61,6 +61,8 @@
import com.google.logging.v2.ListLogEntriesResponse;
import com.google.logging.v2.ListLogMetricsRequest;
import com.google.logging.v2.ListLogMetricsResponse;
import com.google.logging.v2.ListLogsRequest;
import com.google.logging.v2.ListLogsResponse;
import com.google.logging.v2.ListMonitoredResourceDescriptorsRequest;
import com.google.logging.v2.ListMonitoredResourceDescriptorsResponse;
import com.google.logging.v2.ListSinksRequest;
Expand Down Expand Up @@ -260,6 +262,11 @@ public ApiFuture<Empty> delete(DeleteExclusionRequest request) {
StatusCode.Code.NOT_FOUND);
}

@Override
public ApiFuture<ListLogsResponse> listLogs(ListLogsRequest request) {
return translate(loggingClient.listLogsCallable().futureCall(request), true);
}

@Override
public ApiFuture<Empty> delete(DeleteLogRequest request) {
return translate(
Expand Down
Expand Up @@ -34,6 +34,8 @@
import com.google.logging.v2.ListLogEntriesResponse;
import com.google.logging.v2.ListLogMetricsRequest;
import com.google.logging.v2.ListLogMetricsResponse;
import com.google.logging.v2.ListLogsRequest;
import com.google.logging.v2.ListLogsResponse;
import com.google.logging.v2.ListMonitoredResourceDescriptorsRequest;
import com.google.logging.v2.ListMonitoredResourceDescriptorsResponse;
import com.google.logging.v2.ListSinksRequest;
Expand Down Expand Up @@ -93,6 +95,18 @@ public interface LoggingRpc extends AutoCloseable, ServiceRpc {
*/
ApiFuture<Empty> delete(DeleteSinkRequest request);

/**
* Sends a request to list the log names in a project. This method returns a {@code ApiFuture}
* object to consume the result. {@link ApiFuture#get()} returns a response object containing the
* listing result.
*
* @param request the request object containing all of the parameters for the API call
*/
default ApiFuture<ListLogsResponse> listLogs(ListLogsRequest request) {
throw new UnsupportedOperationException(
"method list(ListLogsRequest request) does not have default implementation");
}

/**
* Sends a request to deletes a log. This method returns a {@code ApiFuture} object to consume the
* result. {@link ApiFuture#get()} returns {@link Empty#getDefaultInstance()} or {@code null} if
Expand Down
Expand Up @@ -63,6 +63,8 @@
import com.google.logging.v2.ListLogEntriesResponse;
import com.google.logging.v2.ListLogMetricsRequest;
import com.google.logging.v2.ListLogMetricsResponse;
import com.google.logging.v2.ListLogsRequest;
import com.google.logging.v2.ListLogsResponse;
import com.google.logging.v2.ListMonitoredResourceDescriptorsRequest;
import com.google.logging.v2.ListMonitoredResourceDescriptorsResponse;
import com.google.logging.v2.ListSinksRequest;
Expand Down Expand Up @@ -106,6 +108,9 @@ public class LoggingImplTest {
com.google.api.MonitoredResourceDescriptor.getDefaultInstance();
private static final MonitoredResourceDescriptor DESCRIPTOR =
MonitoredResourceDescriptor.fromPb(DESCRIPTOR_PB);
private static final String LOG_NAME1 = "test-list-log-name-1";
private static final String LOG_NAME2 = "test-list-log-name-2";
private static final String LOG_NAMES_CURSOR = "cursor";
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 =
Expand Down Expand Up @@ -173,6 +178,40 @@ public com.google.api.MonitoredResourceDescriptor apply(
private LoggingRpc loggingRpcMock;
private Logging logging;

private void configureListLogsTests(List<String> returnedList, String cursor) {
minherz marked this conversation as resolved.
Show resolved Hide resolved
minherz marked this conversation as resolved.
Show resolved Hide resolved
ListLogsRequest request = ListLogsRequest.newBuilder().setParent(PROJECT_PB).build();
ListLogsResponse response =
ListLogsResponse.newBuilder().setNextPageToken(cursor).addAllLogNames(returnedList).build();
ApiFuture<ListLogsResponse> futureResponse = ApiFutures.immediateFuture(response);
EasyMock.expect(loggingRpcMock.listLogs(request)).andReturn(futureResponse);
EasyMock.replay(loggingRpcMock);
}

private void configureListLogsTests(
List<String> page1ReturnedList,
List<String> page2ReturnedList,
String page1Cursor,
String page2Cursor) {
ListLogsRequest request1 = ListLogsRequest.newBuilder().setParent(PROJECT_PB).build();
ListLogsRequest request2 =
ListLogsRequest.newBuilder().setParent(PROJECT_PB).setPageToken(page1Cursor).build();
ListLogsResponse response1 =
ListLogsResponse.newBuilder()
.setNextPageToken(page1Cursor)
.addAllLogNames(page1ReturnedList)
.build();
ListLogsResponse response2 =
ListLogsResponse.newBuilder()
.setNextPageToken(page2Cursor)
.addAllLogNames(page2ReturnedList)
.build();
ApiFuture<ListLogsResponse> futureResponse1 = ApiFutures.immediateFuture(response1);
ApiFuture<ListLogsResponse> futureResponse2 = ApiFutures.immediateFuture(response2);
EasyMock.expect(loggingRpcMock.listLogs(request1)).andReturn(futureResponse1);
EasyMock.expect(loggingRpcMock.listLogs(request2)).andReturn(futureResponse2);
EasyMock.replay(loggingRpcMock);
}

@Before
public void setUp() {
rpcFactoryMock = EasyMock.createStrictMock(LoggingRpcFactory.class);
Expand All @@ -187,8 +226,10 @@ public void setUp() {
.build();

// By default when calling ListLogEntries, we append a filter of last 24 hours.
// However when testing, the time when it was called by the test and by the method
// implementation might differ by microseconds so we use the same time filter implementation
// However when testing, the time when it was called by the test and by the
// method
// implementation might differ by microseconds so we use the same time filter
// implementation
// for test and in "real" method
LoggingImpl.defaultTimestampFilterCreator =
new ITimestampDefaultFilter() {
Expand Down Expand Up @@ -1576,6 +1617,89 @@ public void testListResourceDescriptorAsyncWithOptions()
Iterables.toArray(page.getValues(), MonitoredResourceDescriptor.class));
}

@Test
public void testListLogsWithLogNames() {
EasyMock.replay(rpcFactoryMock);
logging = options.getService();
List<String> logNames = ImmutableList.of(LOG_NAME1, LOG_NAME2);
configureListLogsTests(logNames, LOG_NAMES_CURSOR);

Page<String> page = logging.listLogs();
assertEquals(LOG_NAMES_CURSOR, page.getNextPageToken());
assertArrayEquals(logNames.toArray(), Iterables.toArray(page.getValues(), String.class));
}

@Test
public void testListLogsWithEmptySet() {
EasyMock.replay(rpcFactoryMock);
logging = options.getService();
List<String> emptyList = ImmutableList.of();
configureListLogsTests(emptyList, LOG_NAMES_CURSOR);

Page<String> page = logging.listLogs();
assertEquals(LOG_NAMES_CURSOR, page.getNextPageToken());
assertArrayEquals(emptyList.toArray(), Iterables.toArray(page.getValues(), String.class));
}

@Test
public void testListLogsNextPageWithLogNames() throws ExecutionException, InterruptedException {
EasyMock.replay(rpcFactoryMock);
logging = options.getService();
List<String> logNames1 = ImmutableList.of(LOG_NAME1, LOG_NAME2);
List<String> logNames2 = ImmutableList.of(LOG_NAME1);
String nextPageCursor = "nextCursor";
configureListLogsTests(logNames1, logNames2, LOG_NAMES_CURSOR, nextPageCursor);
minherz marked this conversation as resolved.
Show resolved Hide resolved

Page<String> page = logging.listLogs();
assertEquals(LOG_NAMES_CURSOR, page.getNextPageToken());
assertArrayEquals(logNames1.toArray(), Iterables.toArray(page.getValues(), String.class));
page = page.getNextPage();
assertEquals(nextPageCursor, page.getNextPageToken());
assertArrayEquals(logNames2.toArray(), Iterables.toArray(page.getValues(), String.class));
}

@Test
public void testListLogsAsyncWithLogNames() throws ExecutionException, InterruptedException {
EasyMock.replay(rpcFactoryMock);
logging = options.getService();
List<String> logNames = ImmutableList.of(LOG_NAME1, LOG_NAME2);
configureListLogsTests(logNames, LOG_NAMES_CURSOR);

AsyncPage<String> page = logging.listLogsAsync().get();
assertEquals(LOG_NAMES_CURSOR, page.getNextPageToken());
assertArrayEquals(logNames.toArray(), Iterables.toArray(page.getValues(), String.class));
}

@Test
public void testListLogsAsyncWithEmptySet() throws ExecutionException, InterruptedException {
EasyMock.replay(rpcFactoryMock);
logging = options.getService();
List<String> emptyList = ImmutableList.of();
configureListLogsTests(emptyList, LOG_NAMES_CURSOR);

AsyncPage<String> page = logging.listLogsAsync().get();
assertEquals(LOG_NAMES_CURSOR, page.getNextPageToken());
assertArrayEquals(emptyList.toArray(), Iterables.toArray(page.getValues(), String.class));
}

@Test
public void testListLogsAsyncNextPageWithLogNames()
throws ExecutionException, InterruptedException {
EasyMock.replay(rpcFactoryMock);
logging = options.getService();
List<String> logNames1 = ImmutableList.of(LOG_NAME1, LOG_NAME2);
List<String> logNames2 = ImmutableList.of(LOG_NAME1);
String nextPageCursor = "nextCursor";
configureListLogsTests(logNames1, logNames2, LOG_NAMES_CURSOR, nextPageCursor);

AsyncPage<String> page = logging.listLogsAsync().get();
assertEquals(LOG_NAMES_CURSOR, page.getNextPageToken());
assertArrayEquals(logNames1.toArray(), Iterables.toArray(page.getValues(), String.class));
page = page.getNextPageAsync().get();
assertEquals(nextPageCursor, page.getNextPageToken());
assertArrayEquals(logNames2.toArray(), Iterables.toArray(page.getValues(), String.class));
}

@Test
public void testDeleteLog() {
DeleteLogRequest request = DeleteLogRequest.newBuilder().setLogName(LOG_NAME_PB).build();
Expand Down Expand Up @@ -2034,7 +2158,8 @@ public void testFlushStress() throws InterruptedException {
EasyMock.expect(loggingRpcMock.write(request)).andReturn(mockRpcResponse).times(threads.length);
EasyMock.replay(loggingRpcMock);

// log and flush concurrently in many threads to trigger a ConcurrentModificationException
// log and flush concurrently in many threads to trigger a
// ConcurrentModificationException
final AtomicInteger exceptions = new AtomicInteger(0);
for (int i = 0; i < threads.length; i++) {
threads[i] =
Expand Down