From 935956944200d978738d86ae4adff6107532531c Mon Sep 17 00:00:00 2001 From: minherz Date: Thu, 12 Aug 2021 20:35:04 +0300 Subject: [PATCH] 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 --- .../clirr-ignored-differences.xml | 17 +++ .../com/google/cloud/logging/Logging.java | 47 +++++++ .../com/google/cloud/logging/LoggingImpl.java | 72 ++++++++++ .../cloud/logging/spi/v2/GrpcLoggingRpc.java | 7 + .../cloud/logging/spi/v2/LoggingRpc.java | 14 ++ .../google/cloud/logging/LoggingImplTest.java | 131 +++++++++++++++++- .../com/example/logging/ListLogEntries.java | 60 ++++++++ .../java/com/example/logging/ListLogs.java | 53 +++---- .../logging/LogEntryWriteHttpRequest.java | 2 +- .../com/example/logging/QuickstartSample.java | 4 +- .../java/com/example/logging/LoggingIT.java | 45 ++++-- 11 files changed, 398 insertions(+), 54 deletions(-) create mode 100644 google-cloud-logging/clirr-ignored-differences.xml create mode 100644 samples/snippets/src/main/java/com/example/logging/ListLogEntries.java diff --git a/google-cloud-logging/clirr-ignored-differences.xml b/google-cloud-logging/clirr-ignored-differences.xml new file mode 100644 index 000000000..cdd6886af --- /dev/null +++ b/google-cloud-logging/clirr-ignored-differences.xml @@ -0,0 +1,17 @@ + + + + + + + 7012 + com/google/cloud/logging/Logging + * listLogs*(com.google.cloud.logging.Logging$ListOption[]) + + + + 7012 + com/google/cloud/logging/spi/v2/LoggingRpc + * listLogs(com.google.logging.v2.ListLogsRequest) + + \ No newline at end of file diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/Logging.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/Logging.java index b4b89001f..ce7334255 100644 --- a/google-cloud-logging/src/main/java/com/google/cloud/logging/Logging.java +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/Logging.java @@ -414,6 +414,53 @@ public static EntryListOption folder(String folder) { */ ApiFuture 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. + * + *

Example of listing log names, specifying the page size. + * + *

{@code
+   * Page logNames = logging.listLogs(ListOption.pageSize(100));
+   * Iterator logIterator = logNames.iterateAll().iterator();
+   * while (logIterator.hasNext()) {
+   *   String logName = logIterator.next();
+   *   // do something with the log name
+   * }
+   * }
+ * + * @throws LoggingException upon failure + */ + default Page listLogs(ListOption... options) { + throw new UnsupportedOperationException( + "method listLogs() does not have default implementation"); + } + + /** + * 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. + * + *

Example of asynchronously listing log names, specifying the page size. + * + *

{@code
+   * ApiFuture> future = logging.listLogsAsync(ListOption.pageSize(100));
+   * // ...
+   * AsyncPage logNames = future.get();
+   * Iterator logIterator = logNames.iterateAll().iterator();
+   * while (logIterator.hasNext()) {
+   *   String logName = logIterator.next();
+   *   // do something with the log name
+   * }
+   * }
+ */ + default ApiFuture> 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. * 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 bffc794b4..76c2195ce 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 @@ -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; @@ -195,6 +197,19 @@ public ApiFuture> getNextPage() { } } + private static class LogNamePageFetcher extends BasePageFetcher { + + LogNamePageFetcher( + LoggingOptions serviceOptions, String cursor, Map requestOptions) { + super(serviceOptions, cursor, requestOptions); + } + + @Override + public ApiFuture> getNextPage() { + return listLogsAsync(serviceOptions(), requestOptions()); + } + } + private static class MonitoredResourceDescriptorPageFetcher extends BasePageFetcher { @@ -366,6 +381,63 @@ public ApiFuture deleteSinkAsync(String sink) { return transform(rpc.delete(request), EMPTY_TO_BOOLEAN_FUNCTION); } + /** + * Creates a new {@code ListLogsRequest} object. + * + *

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( + LoggingOptions serviceOptions, Map 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> listLogsAsync( + final LoggingOptions serviceOptions, final Map options) { + final ListLogsRequest request = listLogsRequest(serviceOptions, options); + ApiFuture list = serviceOptions.getLoggingRpcV2().listLogs(request); + return transform( + list, + new Function>() { + @Override + public AsyncPage apply(ListLogsResponse listLogsResponse) { + List logNames = + listLogsResponse.getLogNamesList() == null + ? ImmutableList.of() + : listLogsResponse.getLogNamesList(); + String cursor = + listLogsResponse.getNextPageToken().equals("") + ? null + : listLogsResponse.getNextPageToken(); + return new AsyncPageImpl<>( + new LogNamePageFetcher(serviceOptions, cursor, options), cursor, logNames); + } + }); + } + + @Override + public Page listLogs(ListOption... options) { + return get(listLogsAsync(options)); + } + + @Override + public ApiFuture> listLogsAsync(ListOption... options) { + return listLogsAsync(getOptions(), optionMap(options)); + } + public boolean deleteLog(String log) { return get(deleteLogAsync(log)); } diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/GrpcLoggingRpc.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/GrpcLoggingRpc.java index 6b9730c66..9217bd4ea 100644 --- a/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/GrpcLoggingRpc.java +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/GrpcLoggingRpc.java @@ -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; @@ -260,6 +262,11 @@ public ApiFuture delete(DeleteExclusionRequest request) { StatusCode.Code.NOT_FOUND); } + @Override + public ApiFuture listLogs(ListLogsRequest request) { + return translate(loggingClient.listLogsCallable().futureCall(request), true); + } + @Override public ApiFuture delete(DeleteLogRequest request) { return translate( diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/LoggingRpc.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/LoggingRpc.java index 06e4c068b..a41b35d24 100644 --- a/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/LoggingRpc.java +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/spi/v2/LoggingRpc.java @@ -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; @@ -93,6 +95,18 @@ public interface LoggingRpc extends AutoCloseable, ServiceRpc { */ ApiFuture 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 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 diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingImplTest.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingImplTest.java index fe34719d8..d1cd5946e 100644 --- a/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingImplTest.java +++ b/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingImplTest.java @@ -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; @@ -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 = @@ -173,6 +178,40 @@ public com.google.api.MonitoredResourceDescriptor apply( private LoggingRpc loggingRpcMock; private Logging logging; + private void configureListLogsTests(List returnedList, String cursor) { + ListLogsRequest request = ListLogsRequest.newBuilder().setParent(PROJECT_PB).build(); + ListLogsResponse response = + ListLogsResponse.newBuilder().setNextPageToken(cursor).addAllLogNames(returnedList).build(); + ApiFuture futureResponse = ApiFutures.immediateFuture(response); + EasyMock.expect(loggingRpcMock.listLogs(request)).andReturn(futureResponse); + EasyMock.replay(loggingRpcMock); + } + + private void configureListLogsTests( + List page1ReturnedList, + List 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 futureResponse1 = ApiFutures.immediateFuture(response1); + ApiFuture 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); @@ -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() { @@ -1576,6 +1617,89 @@ public void testListResourceDescriptorAsyncWithOptions() Iterables.toArray(page.getValues(), MonitoredResourceDescriptor.class)); } + @Test + public void testListLogsWithLogNames() { + EasyMock.replay(rpcFactoryMock); + logging = options.getService(); + List logNames = ImmutableList.of(LOG_NAME1, LOG_NAME2); + configureListLogsTests(logNames, LOG_NAMES_CURSOR); + + Page 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 emptyList = ImmutableList.of(); + configureListLogsTests(emptyList, LOG_NAMES_CURSOR); + + Page 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 logNames1 = ImmutableList.of(LOG_NAME1, LOG_NAME2); + List logNames2 = ImmutableList.of(LOG_NAME1); + String nextPageCursor = "nextCursor"; + configureListLogsTests(logNames1, logNames2, LOG_NAMES_CURSOR, nextPageCursor); + + Page 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 logNames = ImmutableList.of(LOG_NAME1, LOG_NAME2); + configureListLogsTests(logNames, LOG_NAMES_CURSOR); + + AsyncPage 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 emptyList = ImmutableList.of(); + configureListLogsTests(emptyList, LOG_NAMES_CURSOR); + + AsyncPage 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 logNames1 = ImmutableList.of(LOG_NAME1, LOG_NAME2); + List logNames2 = ImmutableList.of(LOG_NAME1); + String nextPageCursor = "nextCursor"; + configureListLogsTests(logNames1, logNames2, LOG_NAMES_CURSOR, nextPageCursor); + + AsyncPage 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(); @@ -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] = diff --git a/samples/snippets/src/main/java/com/example/logging/ListLogEntries.java b/samples/snippets/src/main/java/com/example/logging/ListLogEntries.java new file mode 100644 index 000000000..8c5131f41 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/logging/ListLogEntries.java @@ -0,0 +1,60 @@ +/* + * 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_list_log_entries] +import com.google.api.gax.paging.Page; +import com.google.cloud.logging.LogEntry; +import com.google.cloud.logging.Logging; +import com.google.cloud.logging.Logging.EntryListOption; +import com.google.cloud.logging.LoggingOptions; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; + + +public class ListLogEntries { + + public static void main(String[] args) throws Exception { + // TODO(developer): Replace the variable value with valid log name before running the sample + // or provide it as an argument. + String logName = args.length > 0 ? args[0] : "test-log"; + + LoggingOptions options = LoggingOptions.getDefaultInstance(); + Logging logging = options.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()) + "\""; + + // 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(); + } + } +} +// [END logging_list_log_entries] 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 349c2788d..666077ebd 100644 --- a/samples/snippets/src/main/java/com/example/logging/ListLogs.java +++ b/samples/snippets/src/main/java/com/example/logging/ListLogs.java @@ -16,47 +16,34 @@ package com.example.logging; +// [START logging_list_logs] import com.google.api.gax.paging.Page; -import com.google.cloud.logging.LogEntry; import com.google.cloud.logging.Logging; -import com.google.cloud.logging.Logging.EntryListOption; import com.google.cloud.logging.LoggingOptions; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Calendar; -/** List logs programmatically using the Cloud Logging API. */ public class ListLogs { - /** Expects an existing Cloud Logging log name as an argument. */ public static void main(String... args) throws Exception { - // [START logging_list_log_entries] - // Instantiates a client - LoggingOptions options = LoggingOptions.getDefaultInstance(); - String logName = args[0]; - - try (Logging logging = options.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 - // Below we are restricting the results to only last hour to speedup getting the results back - 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()) + "\""; - - // List all log entries - Page entries = logging.listLogEntries(EntryListOption.filter(logFilter)); - do { - for (LogEntry logEntry : entries.iterateAll()) { - System.out.println(logEntry); - } - entries = entries.getNextPage(); - } while (entries != null); + LoggingOptions options = LoggingOptions.getDefaultInstance(); + Logging logging = options.getService(); + + // List all log names + Page logNames = logging.listLogs(); + while (logNames != null) { + for (String logName : logNames.iterateAll()) { + System.out.println(logName); + } + logNames = logNames.getNextPage(); } - // [END logging_list_log_entries] } } +// [END logging_list_logs] + +// the following is a temporary location due to exclusive requirement +// from snippet layout vs. snippet-bot + +// [START logging_list_log_entries] + + +// [END logging_list_log_entries] 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 d17907d45..05e29a517 100644 --- a/samples/snippets/src/main/java/com/example/logging/LogEntryWriteHttpRequest.java +++ b/samples/snippets/src/main/java/com/example/logging/LogEntryWriteHttpRequest.java @@ -57,7 +57,7 @@ public static void createLogEntryRequest(String logName, String payLoad, HttpReq // Writes the log entry asynchronously logging.write(Collections.singleton(logEntry)); - System.out.printf("Logged: %s%n", payLoad); + System.out.printf("Logged: %s", payLoad); } } } 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 7ed408ed7..754c0bf41 100644 --- a/samples/snippets/src/main/java/com/example/logging/QuickstartSample.java +++ b/samples/snippets/src/main/java/com/example/logging/QuickstartSample.java @@ -26,8 +26,8 @@ import java.util.Collections; /** - * This sample demonstrates writing logs using the Cloud Logging API. The library also offers - * a java.util.logging Handler `com.google.cloud.logging.LoggingHandler` Logback integration is also + * This sample demonstrates writing logs using the Cloud Logging API. The library also offers a + * java.util.logging Handler `com.google.cloud.logging.LoggingHandler` Logback integration is also * available : * https://github.com/googleapis/google-cloud-java/tree/master/google-cloud-clients/google-cloud-contrib/google-cloud-logging-logback * Using the java.util.logging handler / Logback appender should be preferred to using the API 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 791f8c3b0..5bfc1e9b1 100644 --- a/samples/snippets/src/test/java/com/example/logging/LoggingIT.java +++ b/samples/snippets/src/test/java/com/example/logging/LoggingIT.java @@ -38,9 +38,10 @@ @SuppressWarnings("checkstyle:abbreviationaswordinname") public class LoggingIT { - private static final String QUICKSTART_LOG = "my-log"; - private static final String TEST_WRITE_LOG = "test-log"; - private static final String STRING_PAYLOAD = "Hello world!"; + private static final String TEST_LOG = "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; @@ -60,37 +61,38 @@ public void setUp() { @After public void tearDown() { // Clean up created logs - deleteLog(QUICKSTART_LOG); - deleteLog(TEST_WRITE_LOG); + deleteLog(TEST_LOG); System.setOut(null); } @Test public void testQuickstart() throws Exception { - QuickstartSample.main(QUICKSTART_LOG); + QuickstartSample.main(TEST_LOG); String got = bout.toString(); - assertThat(got).contains("Logged: Hello, world!"); + assertThat(got).contains(String.format("Logged: %s", STRING_PAYLOAD)); } @Test(timeout = 60000) public void testWriteAndListLogs() throws Exception { // write a log entry LogEntry entry = - LogEntry.newBuilder(StringPayload.of("Hello world again")) - .setLogName(TEST_WRITE_LOG) + LogEntry.newBuilder(StringPayload.of(STRING_PAYLOAD2)) + .setLogName(TEST_LOG) .setResource(MonitoredResource.newBuilder("global").build()) .build(); logging.write(Collections.singleton(entry)); // flush out log immediately logging.flush(); bout.reset(); - // Check if the log is listed yet + + // Check for mocked STDOUT having data + String[] args = new String[] {TEST_LOG}; while (bout.toString().isEmpty()) { - ListLogs.main(TEST_WRITE_LOG); + ListLogEntries.main(args); Thread.sleep(5000); } - assertThat(bout.toString().contains("Hello world again")).isTrue(); + assertThat(bout.toString().contains(STRING_PAYLOAD2)).isTrue(); } @Test(timeout = 60000) @@ -101,15 +103,17 @@ public void testWriteLogHttpRequest() throws Exception { .setRequestMethod(HttpRequest.RequestMethod.GET) .setStatus(200) .build(); - LogEntryWriteHttpRequest.createLogEntryRequest(TEST_WRITE_LOG, STRING_PAYLOAD, request); + LogEntryWriteHttpRequest.createLogEntryRequest(TEST_LOG, STRING_PAYLOAD, request); String got = bout.toString(); // Check weather log entry is logged or not assertThat(got).contains(String.format("Logged: %s", STRING_PAYLOAD)); bout.reset(); - // Check if the log is listed yet + + // Check for mocked STDOUT having data + String[] args = new String[] {TEST_LOG}; while (bout.toString().isEmpty()) { - ListLogs.main(TEST_WRITE_LOG); + ListLogEntries.main(args); Thread.sleep(5000); } @@ -117,4 +121,15 @@ public void testWriteLogHttpRequest() throws Exception { assertThat(bout.toString().contains(STRING_PAYLOAD)).isTrue(); assertThat(bout.toString().contains(request.toString())).isTrue(); } + + @Test(timeout = 60000) + public void testListLogNames_shouldPass() 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(); + } }