Skip to content

Commit 9359569

Browse files
authored
feat: implement listLogs API and provide sample snippet (#602)
* feat: add listLogs and listLogsAsync to Logging Add listLogs API support to hand-written layer of google-cloud-logging. Add unit testing for the new listLogs API. Fixes #593 * feat: add listLogs snippet example Add a sample snippet to demonstrate use of listLogs API. Refactor ListLogs to include snippets for listLogs and listLogEntries. Format all snippets. Fixes #358. * chore: set generated serialVersionUID for LogNamePageFetcher * chore(code): fixing errors * chore(deps): fix clirr plugin 7012 error Because of JDK 1.7 it is impossible to provide default implementation for new interface methods. File with exclusions is added instead. The file should be removed once JDK version is upgraded. * chore(tests): fix samples' tests Fix printed string in LogEntryWriteHttpRequest.createLogEntryRequest(). Fix loops to wait for any data in STDOUT. Add test for listLogs snippet. * chore(test): forward exception throwing Update testListLogNames() signature to throw exceptions * chore(tests): refactoring tests Test ListLogs.printLogNames vs audit logs to save time. Restore retrieval of log entries in the wait loop to ensure printing to STDOUT * chore(tests): fine tune clirr exceptions Provide method level exception configuration in clirr-ignored-differences. Implement default methods for new methods in Logging and LoggingRpc interfaces. Following guidelines, remove serialVersionUID from LogNamePageFetcher. * chore: refactoring method naming and sample snippets Make more verbose naming for methods. Refactor testing after renaming interface method(s). Split ListLogs sample into two: ListLogEntries and ListLogs. * chore(fix): fix formatting * chore(comment): fix copyright year of the new file * chore(fix): restore sample filter to list log entries update the list log entries filter to bring results only for the last hour. * chore(fix): fix snippet-bot errors adding empty region tag logging_list_log_entries to ListLogs.java
1 parent 63a79ab commit 9359569

File tree

11 files changed

+398
-54
lines changed

11 files changed

+398
-54
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!-- see http://www.mojohaus.org/clirr-maven-plugin/examples/ignored-differences.html -->
3+
<!-- added to resolve breaking changes until JDK will be upgraded to ≥1.8 -->
4+
<differences>
5+
<!-- Added methods to com.google.cloud.logging.Logging interface with implementation in LoggingImpl is always okay -->
6+
<difference>
7+
<differenceType>7012</differenceType>
8+
<className>com/google/cloud/logging/Logging</className>
9+
<method>* listLogs*(com.google.cloud.logging.Logging$ListOption[])</method>
10+
</difference>
11+
<!-- Added methods to com.google.cloud.logging.spi.v2.LoggingRpc interface with implementation in GrpcLoggingRpc is always okay -->
12+
<difference>
13+
<differenceType>7012</differenceType>
14+
<className>com/google/cloud/logging/spi/v2/LoggingRpc</className>
15+
<method>* listLogs(com.google.logging.v2.ListLogsRequest)</method>
16+
</difference>
17+
</differences>

google-cloud-logging/src/main/java/com/google/cloud/logging/Logging.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,53 @@ public static EntryListOption folder(String folder) {
414414
*/
415415
ApiFuture<Boolean> deleteSinkAsync(String sink);
416416

417+
/**
418+
* Lists the log names. This method returns a {@link Page} object that can be used to consume
419+
* paginated results. Use {@link ListOption} to specify the page size or the page token from which
420+
* to start listing logs.
421+
*
422+
* <p>Example of listing log names, specifying the page size.
423+
*
424+
* <pre>{@code
425+
* Page<Log> logNames = logging.listLogs(ListOption.pageSize(100));
426+
* Iterator<Log> logIterator = logNames.iterateAll().iterator();
427+
* while (logIterator.hasNext()) {
428+
* String logName = logIterator.next();
429+
* // do something with the log name
430+
* }
431+
* }</pre>
432+
*
433+
* @throws LoggingException upon failure
434+
*/
435+
default Page<String> listLogs(ListOption... options) {
436+
throw new UnsupportedOperationException(
437+
"method listLogs() does not have default implementation");
438+
}
439+
440+
/**
441+
* Sends a request for listing log names. This method returns a {@code ApiFuture} object to
442+
* consume the result. {@link ApiFuture#get()} returns an {@link AsyncPage} object that can be
443+
* used to asynchronously handle paginated results. Use {@link ListOption} to specify the page
444+
* size or the page token from which to start listing log names.
445+
*
446+
* <p>Example of asynchronously listing log names, specifying the page size.
447+
*
448+
* <pre>{@code
449+
* ApiFuture<AsyncPage<Log>> future = logging.listLogsAsync(ListOption.pageSize(100));
450+
* // ...
451+
* AsyncPage<Sink> logNames = future.get();
452+
* Iterator<Sink> logIterator = logNames.iterateAll().iterator();
453+
* while (logIterator.hasNext()) {
454+
* String logName = logIterator.next();
455+
* // do something with the log name
456+
* }
457+
* }</pre>
458+
*/
459+
default ApiFuture<AsyncPage<String>> listLogsAsync(ListOption... options) {
460+
throw new UnsupportedOperationException(
461+
"method listLogsAsync() does not have default implementation");
462+
}
463+
417464
/**
418465
* Deletes a log and all its log entries. The log will reappear if new entries are written to it.
419466
*

google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@
6565
import com.google.logging.v2.ListLogEntriesResponse;
6666
import com.google.logging.v2.ListLogMetricsRequest;
6767
import com.google.logging.v2.ListLogMetricsResponse;
68+
import com.google.logging.v2.ListLogsRequest;
69+
import com.google.logging.v2.ListLogsResponse;
6870
import com.google.logging.v2.ListMonitoredResourceDescriptorsRequest;
6971
import com.google.logging.v2.ListMonitoredResourceDescriptorsResponse;
7072
import com.google.logging.v2.ListSinksRequest;
@@ -195,6 +197,19 @@ public ApiFuture<AsyncPage<Sink>> getNextPage() {
195197
}
196198
}
197199

200+
private static class LogNamePageFetcher extends BasePageFetcher<String> {
201+
202+
LogNamePageFetcher(
203+
LoggingOptions serviceOptions, String cursor, Map<Option.OptionType, ?> requestOptions) {
204+
super(serviceOptions, cursor, requestOptions);
205+
}
206+
207+
@Override
208+
public ApiFuture<AsyncPage<String>> getNextPage() {
209+
return listLogsAsync(serviceOptions(), requestOptions());
210+
}
211+
}
212+
198213
private static class MonitoredResourceDescriptorPageFetcher
199214
extends BasePageFetcher<MonitoredResourceDescriptor> {
200215

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

384+
/**
385+
* Creates a new {@code ListLogsRequest} object.
386+
*
387+
* <p>Builds an instance of {@code ListLogsRequest} using page size, page token and project id
388+
* from the {@code LoggingOptions}. The project id is used as the request's parent parameter.
389+
*
390+
* @see com.google.logging.v2.ListLogEntriesRequest
391+
* @return the created {@code ListLogsRequest} object
392+
*/
393+
private static ListLogsRequest listLogsRequest(
394+
LoggingOptions serviceOptions, Map<Option.OptionType, ?> options) {
395+
ListLogsRequest.Builder builder = ListLogsRequest.newBuilder();
396+
builder.setParent(ProjectName.of(serviceOptions.getProjectId()).toString());
397+
Integer pageSize = PAGE_SIZE.get(options);
398+
String pageToken = PAGE_TOKEN.get(options);
399+
if (pageSize != null) {
400+
builder.setPageSize(pageSize);
401+
}
402+
if (pageToken != null) {
403+
builder.setPageToken(pageToken);
404+
}
405+
return builder.build();
406+
}
407+
408+
private static ApiFuture<AsyncPage<String>> listLogsAsync(
409+
final LoggingOptions serviceOptions, final Map<Option.OptionType, ?> options) {
410+
final ListLogsRequest request = listLogsRequest(serviceOptions, options);
411+
ApiFuture<ListLogsResponse> list = serviceOptions.getLoggingRpcV2().listLogs(request);
412+
return transform(
413+
list,
414+
new Function<ListLogsResponse, AsyncPage<String>>() {
415+
@Override
416+
public AsyncPage<String> apply(ListLogsResponse listLogsResponse) {
417+
List<String> logNames =
418+
listLogsResponse.getLogNamesList() == null
419+
? ImmutableList.<String>of()
420+
: listLogsResponse.getLogNamesList();
421+
String cursor =
422+
listLogsResponse.getNextPageToken().equals("")
423+
? null
424+
: listLogsResponse.getNextPageToken();
425+
return new AsyncPageImpl<>(
426+
new LogNamePageFetcher(serviceOptions, cursor, options), cursor, logNames);
427+
}
428+
});
429+
}
430+
431+
@Override
432+
public Page<String> listLogs(ListOption... options) {
433+
return get(listLogsAsync(options));
434+
}
435+
436+
@Override
437+
public ApiFuture<AsyncPage<String>> listLogsAsync(ListOption... options) {
438+
return listLogsAsync(getOptions(), optionMap(options));
439+
}
440+
369441
public boolean deleteLog(String log) {
370442
return get(deleteLogAsync(log));
371443
}

google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/GrpcLoggingRpc.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@
6161
import com.google.logging.v2.ListLogEntriesResponse;
6262
import com.google.logging.v2.ListLogMetricsRequest;
6363
import com.google.logging.v2.ListLogMetricsResponse;
64+
import com.google.logging.v2.ListLogsRequest;
65+
import com.google.logging.v2.ListLogsResponse;
6466
import com.google.logging.v2.ListMonitoredResourceDescriptorsRequest;
6567
import com.google.logging.v2.ListMonitoredResourceDescriptorsResponse;
6668
import com.google.logging.v2.ListSinksRequest;
@@ -260,6 +262,11 @@ public ApiFuture<Empty> delete(DeleteExclusionRequest request) {
260262
StatusCode.Code.NOT_FOUND);
261263
}
262264

265+
@Override
266+
public ApiFuture<ListLogsResponse> listLogs(ListLogsRequest request) {
267+
return translate(loggingClient.listLogsCallable().futureCall(request), true);
268+
}
269+
263270
@Override
264271
public ApiFuture<Empty> delete(DeleteLogRequest request) {
265272
return translate(

google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/LoggingRpc.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
import com.google.logging.v2.ListLogEntriesResponse;
3535
import com.google.logging.v2.ListLogMetricsRequest;
3636
import com.google.logging.v2.ListLogMetricsResponse;
37+
import com.google.logging.v2.ListLogsRequest;
38+
import com.google.logging.v2.ListLogsResponse;
3739
import com.google.logging.v2.ListMonitoredResourceDescriptorsRequest;
3840
import com.google.logging.v2.ListMonitoredResourceDescriptorsResponse;
3941
import com.google.logging.v2.ListSinksRequest;
@@ -93,6 +95,18 @@ public interface LoggingRpc extends AutoCloseable, ServiceRpc {
9395
*/
9496
ApiFuture<Empty> delete(DeleteSinkRequest request);
9597

98+
/**
99+
* Sends a request to list the log names in a project. This method returns a {@code ApiFuture}
100+
* object to consume the result. {@link ApiFuture#get()} returns a response object containing the
101+
* listing result.
102+
*
103+
* @param request the request object containing all of the parameters for the API call
104+
*/
105+
default ApiFuture<ListLogsResponse> listLogs(ListLogsRequest request) {
106+
throw new UnsupportedOperationException(
107+
"method list(ListLogsRequest request) does not have default implementation");
108+
}
109+
96110
/**
97111
* Sends a request to deletes a log. This method returns a {@code ApiFuture} object to consume the
98112
* result. {@link ApiFuture#get()} returns {@link Empty#getDefaultInstance()} or {@code null} if

google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingImplTest.java

Lines changed: 128 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@
6363
import com.google.logging.v2.ListLogEntriesResponse;
6464
import com.google.logging.v2.ListLogMetricsRequest;
6565
import com.google.logging.v2.ListLogMetricsResponse;
66+
import com.google.logging.v2.ListLogsRequest;
67+
import com.google.logging.v2.ListLogsResponse;
6668
import com.google.logging.v2.ListMonitoredResourceDescriptorsRequest;
6769
import com.google.logging.v2.ListMonitoredResourceDescriptorsResponse;
6870
import com.google.logging.v2.ListSinksRequest;
@@ -106,6 +108,9 @@ public class LoggingImplTest {
106108
com.google.api.MonitoredResourceDescriptor.getDefaultInstance();
107109
private static final MonitoredResourceDescriptor DESCRIPTOR =
108110
MonitoredResourceDescriptor.fromPb(DESCRIPTOR_PB);
111+
private static final String LOG_NAME1 = "test-list-log-name-1";
112+
private static final String LOG_NAME2 = "test-list-log-name-2";
113+
private static final String LOG_NAMES_CURSOR = "cursor";
109114
private static final String LOG_NAME = "log";
110115
private static final String LOG_NAME_PB = "projects/" + PROJECT + "/logs/" + LOG_NAME;
111116
private static final MonitoredResource MONITORED_RESOURCE =
@@ -173,6 +178,40 @@ public com.google.api.MonitoredResourceDescriptor apply(
173178
private LoggingRpc loggingRpcMock;
174179
private Logging logging;
175180

181+
private void configureListLogsTests(List<String> returnedList, String cursor) {
182+
ListLogsRequest request = ListLogsRequest.newBuilder().setParent(PROJECT_PB).build();
183+
ListLogsResponse response =
184+
ListLogsResponse.newBuilder().setNextPageToken(cursor).addAllLogNames(returnedList).build();
185+
ApiFuture<ListLogsResponse> futureResponse = ApiFutures.immediateFuture(response);
186+
EasyMock.expect(loggingRpcMock.listLogs(request)).andReturn(futureResponse);
187+
EasyMock.replay(loggingRpcMock);
188+
}
189+
190+
private void configureListLogsTests(
191+
List<String> page1ReturnedList,
192+
List<String> page2ReturnedList,
193+
String page1Cursor,
194+
String page2Cursor) {
195+
ListLogsRequest request1 = ListLogsRequest.newBuilder().setParent(PROJECT_PB).build();
196+
ListLogsRequest request2 =
197+
ListLogsRequest.newBuilder().setParent(PROJECT_PB).setPageToken(page1Cursor).build();
198+
ListLogsResponse response1 =
199+
ListLogsResponse.newBuilder()
200+
.setNextPageToken(page1Cursor)
201+
.addAllLogNames(page1ReturnedList)
202+
.build();
203+
ListLogsResponse response2 =
204+
ListLogsResponse.newBuilder()
205+
.setNextPageToken(page2Cursor)
206+
.addAllLogNames(page2ReturnedList)
207+
.build();
208+
ApiFuture<ListLogsResponse> futureResponse1 = ApiFutures.immediateFuture(response1);
209+
ApiFuture<ListLogsResponse> futureResponse2 = ApiFutures.immediateFuture(response2);
210+
EasyMock.expect(loggingRpcMock.listLogs(request1)).andReturn(futureResponse1);
211+
EasyMock.expect(loggingRpcMock.listLogs(request2)).andReturn(futureResponse2);
212+
EasyMock.replay(loggingRpcMock);
213+
}
214+
176215
@Before
177216
public void setUp() {
178217
rpcFactoryMock = EasyMock.createStrictMock(LoggingRpcFactory.class);
@@ -187,8 +226,10 @@ public void setUp() {
187226
.build();
188227

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

1620+
@Test
1621+
public void testListLogsWithLogNames() {
1622+
EasyMock.replay(rpcFactoryMock);
1623+
logging = options.getService();
1624+
List<String> logNames = ImmutableList.of(LOG_NAME1, LOG_NAME2);
1625+
configureListLogsTests(logNames, LOG_NAMES_CURSOR);
1626+
1627+
Page<String> page = logging.listLogs();
1628+
assertEquals(LOG_NAMES_CURSOR, page.getNextPageToken());
1629+
assertArrayEquals(logNames.toArray(), Iterables.toArray(page.getValues(), String.class));
1630+
}
1631+
1632+
@Test
1633+
public void testListLogsWithEmptySet() {
1634+
EasyMock.replay(rpcFactoryMock);
1635+
logging = options.getService();
1636+
List<String> emptyList = ImmutableList.of();
1637+
configureListLogsTests(emptyList, LOG_NAMES_CURSOR);
1638+
1639+
Page<String> page = logging.listLogs();
1640+
assertEquals(LOG_NAMES_CURSOR, page.getNextPageToken());
1641+
assertArrayEquals(emptyList.toArray(), Iterables.toArray(page.getValues(), String.class));
1642+
}
1643+
1644+
@Test
1645+
public void testListLogsNextPageWithLogNames() throws ExecutionException, InterruptedException {
1646+
EasyMock.replay(rpcFactoryMock);
1647+
logging = options.getService();
1648+
List<String> logNames1 = ImmutableList.of(LOG_NAME1, LOG_NAME2);
1649+
List<String> logNames2 = ImmutableList.of(LOG_NAME1);
1650+
String nextPageCursor = "nextCursor";
1651+
configureListLogsTests(logNames1, logNames2, LOG_NAMES_CURSOR, nextPageCursor);
1652+
1653+
Page<String> page = logging.listLogs();
1654+
assertEquals(LOG_NAMES_CURSOR, page.getNextPageToken());
1655+
assertArrayEquals(logNames1.toArray(), Iterables.toArray(page.getValues(), String.class));
1656+
page = page.getNextPage();
1657+
assertEquals(nextPageCursor, page.getNextPageToken());
1658+
assertArrayEquals(logNames2.toArray(), Iterables.toArray(page.getValues(), String.class));
1659+
}
1660+
1661+
@Test
1662+
public void testListLogsAsyncWithLogNames() throws ExecutionException, InterruptedException {
1663+
EasyMock.replay(rpcFactoryMock);
1664+
logging = options.getService();
1665+
List<String> logNames = ImmutableList.of(LOG_NAME1, LOG_NAME2);
1666+
configureListLogsTests(logNames, LOG_NAMES_CURSOR);
1667+
1668+
AsyncPage<String> page = logging.listLogsAsync().get();
1669+
assertEquals(LOG_NAMES_CURSOR, page.getNextPageToken());
1670+
assertArrayEquals(logNames.toArray(), Iterables.toArray(page.getValues(), String.class));
1671+
}
1672+
1673+
@Test
1674+
public void testListLogsAsyncWithEmptySet() throws ExecutionException, InterruptedException {
1675+
EasyMock.replay(rpcFactoryMock);
1676+
logging = options.getService();
1677+
List<String> emptyList = ImmutableList.of();
1678+
configureListLogsTests(emptyList, LOG_NAMES_CURSOR);
1679+
1680+
AsyncPage<String> page = logging.listLogsAsync().get();
1681+
assertEquals(LOG_NAMES_CURSOR, page.getNextPageToken());
1682+
assertArrayEquals(emptyList.toArray(), Iterables.toArray(page.getValues(), String.class));
1683+
}
1684+
1685+
@Test
1686+
public void testListLogsAsyncNextPageWithLogNames()
1687+
throws ExecutionException, InterruptedException {
1688+
EasyMock.replay(rpcFactoryMock);
1689+
logging = options.getService();
1690+
List<String> logNames1 = ImmutableList.of(LOG_NAME1, LOG_NAME2);
1691+
List<String> logNames2 = ImmutableList.of(LOG_NAME1);
1692+
String nextPageCursor = "nextCursor";
1693+
configureListLogsTests(logNames1, logNames2, LOG_NAMES_CURSOR, nextPageCursor);
1694+
1695+
AsyncPage<String> page = logging.listLogsAsync().get();
1696+
assertEquals(LOG_NAMES_CURSOR, page.getNextPageToken());
1697+
assertArrayEquals(logNames1.toArray(), Iterables.toArray(page.getValues(), String.class));
1698+
page = page.getNextPageAsync().get();
1699+
assertEquals(nextPageCursor, page.getNextPageToken());
1700+
assertArrayEquals(logNames2.toArray(), Iterables.toArray(page.getValues(), String.class));
1701+
}
1702+
15791703
@Test
15801704
public void testDeleteLog() {
15811705
DeleteLogRequest request = DeleteLogRequest.newBuilder().setLogName(LOG_NAME_PB).build();
@@ -2034,7 +2158,8 @@ public void testFlushStress() throws InterruptedException {
20342158
EasyMock.expect(loggingRpcMock.write(request)).andReturn(mockRpcResponse).times(threads.length);
20352159
EasyMock.replay(loggingRpcMock);
20362160

2037-
// log and flush concurrently in many threads to trigger a ConcurrentModificationException
2161+
// log and flush concurrently in many threads to trigger a
2162+
// ConcurrentModificationException
20382163
final AtomicInteger exceptions = new AtomicInteger(0);
20392164
for (int i = 0; i < threads.length; i++) {
20402165
threads[i] =

0 commit comments

Comments
 (0)