From 27499744f37a5fddcc2d6825c69481374e78829c Mon Sep 17 00:00:00 2001 From: losalex <90795544+losalex@users.noreply.github.com> Date: Sat, 25 Jun 2022 00:36:22 -0700 Subject: [PATCH] feat: Add support for library instrumentation (#979) * feat: Add support for library instrumentation * Print instrumentation record only once * Fix test * Fix tests * Fix integration test * Address PR comments, fix tests * Address comments * Fix tests and methods * Fix test failures and add default logName for instrumentation entry --- .../google/cloud/logging/Instrumentation.java | 224 ++++++++++++++++++ .../com/google/cloud/logging/Logging.java | 12 +- .../google/cloud/logging/LoggingHandler.java | 5 +- .../com/google/cloud/logging/LoggingImpl.java | 12 +- .../cloud/logging/InstrumentationTest.java | 145 ++++++++++++ .../cloud/logging/LoggingHandlerTest.java | 1 + .../google/cloud/logging/LoggingImplTest.java | 37 +++ .../com/google/cloud/logging/LoggingTest.java | 5 + .../cloud/logging/it/ITJulLoggerTest.java | 1 - 9 files changed, 436 insertions(+), 6 deletions(-) create mode 100644 google-cloud-logging/src/main/java/com/google/cloud/logging/Instrumentation.java create mode 100644 google-cloud-logging/src/test/java/com/google/cloud/logging/InstrumentationTest.java diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/Instrumentation.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/Instrumentation.java new file mode 100644 index 000000000..8471d882e --- /dev/null +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/Instrumentation.java @@ -0,0 +1,224 @@ +/* + * Copyright 2022 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.google.cloud.logging; + +import com.google.api.client.util.Strings; +import com.google.api.gax.core.GaxProperties; +import com.google.cloud.Tuple; +import com.google.cloud.logging.Logging.WriteOption; +import com.google.cloud.logging.Payload.JsonPayload; +import com.google.cloud.logging.Payload.Type; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.protobuf.ListValue; +import com.google.protobuf.Struct; +import com.google.protobuf.Value; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class Instrumentation { + public static final String DIAGNOSTIC_INFO_KEY = "logging.googleapis.com/diagnostic"; + public static final String INSTRUMENTATION_SOURCE_KEY = "instrumentation_source"; + public static final String INSTRUMENTATION_NAME_KEY = "name"; + public static final String INSTRUMENTATION_VERSION_KEY = "version"; + public static final String JAVA_LIBRARY_NAME_PREFIX = "java"; + public static final String DEFAULT_INSTRUMENTATION_VERSION = "UNKNOWN"; + public static final String INSTRUMENTATION_LOG_NAME = "diagnostic-log"; + public static final int MAX_DIAGNOSTIC_VALUE_LENGTH = 14; + public static final int MAX_DIAGNOSTIC_ENTIES = 3; + private static boolean instrumentationAdded = false; + private static Object instrumentationLock = new Object(); + + /** + * Populates entries with instrumentation info which is added in separate log entry + * + * @param logEntries {Iterable} The list of entries to be populated + * @return {Tuple>} containg a flag if instrumentation info was added + * or not and a modified list of log entries + */ + public static Tuple> populateInstrumentationInfo( + Iterable logEntries) { + boolean isWritten = setInstrumentationStatus(true); + if (isWritten) return Tuple.of(false, logEntries); + List entries = new ArrayList<>(); + + for (LogEntry logEntry : logEntries) { + // Check if LogEntry has a proper payload and also contains a diagnostic entry + if (!isWritten + && logEntry.getPayload().getType() == Type.JSON + && logEntry + .getPayload() + .getData() + .containsFields(DIAGNOSTIC_INFO_KEY)) { + try { + ListValue infoList = + logEntry + .getPayload() + .getData() + .getFieldsOrThrow(DIAGNOSTIC_INFO_KEY) + .getStructValue() + .getFieldsOrThrow(INSTRUMENTATION_SOURCE_KEY) + .getListValue(); + entries.add(createDiagnosticEntry(null, null, infoList)); + isWritten = true; + } catch (Exception ex) { + System.err.println("ERROR: unexpected exception in populateInstrumentationInfo: " + ex); + } + } else { + entries.add(logEntry); + } + } + if (!isWritten) { + entries.add(createDiagnosticEntry(null, null, null)); + } + return Tuple.of(true, entries); + } + + /** + * Adds a partialSuccess flag option to array of WriteOption + * + * @param options {WriteOption[]} The options array to be extended + * @return The new array of oprions containing WriteOption.OptionType.PARTIAL_SUCCESS flag set to + * true + */ + public static WriteOption[] addPartialSuccessOption(WriteOption[] options) { + if (options == null) return options; + List writeOptions = new ArrayList(); + writeOptions.addAll(Arrays.asList(options)); + // Make sure we remove all partial success flags if any exist + writeOptions.removeIf( + option -> option.getOptionType() == WriteOption.OptionType.PARTIAL_SUCCESS); + writeOptions.add(WriteOption.partialSuccess(true)); + return Iterables.toArray(writeOptions, WriteOption.class); + } + + /** + * The helper method to generate a log entry with diagnostic instrumentation data. + * + * @param libraryName {string} The name of the logging library to be reported. Should be prefixed + * with 'java'. Will be truncated if longer than 14 characters. + * @param libraryVersion {string} The version of the logging library to be reported. Will be + * truncated if longer than 14 characters. + * @returns {LogEntry} The entry with diagnostic instrumentation data. + */ + public static LogEntry createDiagnosticEntry(String libraryName, String libraryVersion) { + return createDiagnosticEntry(libraryName, libraryVersion, null); + } + + private static LogEntry createDiagnosticEntry( + String libraryName, String libraryVersion, ListValue existingLibraryList) { + Struct instrumentation = + Struct.newBuilder() + .putAllFields( + ImmutableMap.of( + INSTRUMENTATION_SOURCE_KEY, + Value.newBuilder() + .setListValue( + generateLibrariesList(libraryName, libraryVersion, existingLibraryList)) + .build())) + .build(); + LogEntry entry = + LogEntry.newBuilder( + JsonPayload.of( + Struct.newBuilder() + .putAllFields( + ImmutableMap.of( + DIAGNOSTIC_INFO_KEY, + Value.newBuilder().setStructValue(instrumentation).build())) + .build())) + .setLogName(INSTRUMENTATION_LOG_NAME) + .build(); + return entry; + } + + private static ListValue generateLibrariesList( + String libraryName, String libraryVersion, ListValue existingLibraryList) { + if (Strings.isNullOrEmpty(libraryName) || !libraryName.startsWith(JAVA_LIBRARY_NAME_PREFIX)) + libraryName = JAVA_LIBRARY_NAME_PREFIX; + if (Strings.isNullOrEmpty(libraryVersion)) { + libraryVersion = getLibraryVersion(Instrumentation.class.getClass()); + } + Struct libraryInfo = createInfoStruct(libraryName, libraryVersion); + ListValue.Builder libraryList = ListValue.newBuilder(); + // Append first the library info for this library + libraryList.addValues(Value.newBuilder().setStructValue(libraryInfo).build()); + if (existingLibraryList != null) { + for (Value val : existingLibraryList.getValuesList()) { + if (val.hasStructValue()) { + try { + String name = + val.getStructValue().getFieldsOrThrow(INSTRUMENTATION_NAME_KEY).getStringValue(); + if (Strings.isNullOrEmpty(name) || !name.startsWith(JAVA_LIBRARY_NAME_PREFIX)) continue; + String version = + val.getStructValue().getFieldsOrThrow(INSTRUMENTATION_VERSION_KEY).getStringValue(); + if (Strings.isNullOrEmpty(version)) continue; + libraryList.addValues( + Value.newBuilder().setStructValue(createInfoStruct(name, version)).build()); + if (libraryList.getValuesCount() == MAX_DIAGNOSTIC_ENTIES) break; + } catch (Exception ex) { + } + } + } + } + return libraryList.build(); + } + + private static Struct createInfoStruct(String libraryName, String libraryVersion) { + return Struct.newBuilder() + .putAllFields( + ImmutableMap.of( + INSTRUMENTATION_NAME_KEY, + Value.newBuilder().setStringValue(truncateValue(libraryName)).build(), + INSTRUMENTATION_VERSION_KEY, + Value.newBuilder().setStringValue(truncateValue(libraryVersion)).build())) + .build(); + } + + /** + * The package-private helper method used to set the flag which indicates if instrumentation info + * already written or not. + * + * @returns The value of the flag before it was set. + */ + static boolean setInstrumentationStatus(boolean value) { + if (instrumentationAdded == value) return instrumentationAdded; + synchronized (instrumentationLock) { + boolean current = instrumentationAdded; + instrumentationAdded = value; + return current; + } + } + + /** + * Returns a library version associated with given class + * + * @param libraryClass {Class} The class to be used to determine a library version + * @return The version number string for given class or "UNKNOWN" if class library version cannot + * be detected + */ + public static String getLibraryVersion(Class libraryClass) { + String libraryVersion = GaxProperties.getLibraryVersion(libraryClass); + if (Strings.isNullOrEmpty(libraryVersion)) libraryVersion = DEFAULT_INSTRUMENTATION_VERSION; + return libraryVersion; + } + + private static String truncateValue(String value) { + if (Strings.isNullOrEmpty(value) || value.length() < MAX_DIAGNOSTIC_VALUE_LENGTH) return value; + return value.substring(0, MAX_DIAGNOSTIC_VALUE_LENGTH) + "*"; + } +} 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 a765c73e0..832c61137 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 @@ -71,7 +71,8 @@ enum OptionType implements Option.OptionType { RESOURCE, LABELS, LOG_DESTINATION, - AUTO_POPULATE_METADATA; + AUTO_POPULATE_METADATA, + PARTIAL_SUCCESS; @SuppressWarnings("unchecked") T get(Map options) { @@ -123,6 +124,15 @@ public static WriteOption destination(LogDestinationName destination) { public static WriteOption autoPopulateMetadata(boolean autoPopulateMetadata) { return new WriteOption(OptionType.AUTO_POPULATE_METADATA, autoPopulateMetadata); } + + /** + * Returns an option to set partialSuccess flag. See {@link + * https://cloud.google.com/logging/docs/reference/v2/rest/v2/entries/write#body.request_body.FIELDS.partial_success} + * for more details. + */ + public static WriteOption partialSuccess(boolean partialSuccess) { + return new WriteOption(OptionType.PARTIAL_SUCCESS, partialSuccess); + } } /** Fields according to which log entries can be sorted. */ diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingHandler.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingHandler.java index 92b9f8794..ffa4c6273 100644 --- a/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingHandler.java +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingHandler.java @@ -312,7 +312,10 @@ public void publish(LogRecord record) { } if (logEntry != null) { try { - Iterable logEntries = ImmutableList.of(logEntry); + Iterable logEntries = + redirectToStdout + ? Instrumentation.populateInstrumentationInfo(ImmutableList.of(logEntry)).y() + : ImmutableList.of(logEntry); if (autoPopulateMetadata) { logEntries = logging.populateMetadata( 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 c75658c11..a2078e079 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 @@ -23,6 +23,7 @@ import static com.google.cloud.logging.Logging.WriteOption.OptionType.LABELS; import static com.google.cloud.logging.Logging.WriteOption.OptionType.LOG_DESTINATION; import static com.google.cloud.logging.Logging.WriteOption.OptionType.LOG_NAME; +import static com.google.cloud.logging.Logging.WriteOption.OptionType.PARTIAL_SUCCESS; import static com.google.cloud.logging.Logging.WriteOption.OptionType.RESOURCE; import static com.google.common.base.Preconditions.checkNotNull; @@ -39,6 +40,7 @@ import com.google.cloud.MonitoredResource; import com.google.cloud.MonitoredResourceDescriptor; import com.google.cloud.PageImpl; +import com.google.cloud.Tuple; import com.google.cloud.logging.spi.v2.LoggingRpc; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; @@ -92,7 +94,6 @@ import java.util.concurrent.TimeoutException; class LoggingImpl extends BaseService implements Logging { - protected static final String RESOURCE_NAME_FORMAT = "projects/%s/traces/%s"; private static final int FLUSH_WAIT_TIMEOUT_SECONDS = 6; private final LoggingRpc rpc; @@ -774,6 +775,7 @@ private static WriteLogEntriesRequest writeLogEntriesRequest( builder.putAllLabels(labels); } + builder.setPartialSuccess(Boolean.TRUE.equals(PARTIAL_SUCCESS.get(options))); builder.addAllEntries(Iterables.transform(logEntries, LogEntry.toPbFunction(projectId))); return builder.build(); } @@ -851,6 +853,9 @@ public void write(Iterable logEntries, WriteOption... options) { final Boolean logingOptionsPopulateFlag = getOptions().getAutoPopulateMetadata(); final Boolean writeOptionPopulateFlga = WriteOption.OptionType.AUTO_POPULATE_METADATA.get(writeOptions); + Tuple> pair = + Instrumentation.populateInstrumentationInfo(logEntries); + logEntries = pair.y(); if (writeOptionPopulateFlga == Boolean.TRUE || (writeOptionPopulateFlga == null && logingOptionsPopulateFlag == Boolean.TRUE)) { @@ -858,8 +863,9 @@ public void write(Iterable logEntries, WriteOption... options) { logEntries = populateMetadata(logEntries, sharedResourceMetadata, this.getClass().getName()); } - - writeLogEntries(logEntries, options); + // Add partialSuccess option always for request containing instrumentation data + writeLogEntries( + logEntries, pair.x() ? Instrumentation.addPartialSuccessOption(options) : options); if (flushSeverity != null) { for (LogEntry logEntry : logEntries) { // flush pending writes if log severity at or above flush severity diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/InstrumentationTest.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/InstrumentationTest.java new file mode 100644 index 000000000..5838fa80f --- /dev/null +++ b/google-cloud-logging/src/test/java/com/google/cloud/logging/InstrumentationTest.java @@ -0,0 +1,145 @@ +/* + * Copyright 2022 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.google.cloud.logging; + +import com.google.api.client.util.Lists; +import com.google.cloud.Tuple; +import com.google.cloud.logging.Payload.JsonPayload; +import com.google.cloud.logging.Payload.StringPayload; +import com.google.cloud.logging.Payload.Type; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.ListValue; +import com.google.protobuf.Value; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import org.junit.Assert; +import org.junit.Test; + +public class InstrumentationTest { + private static final StringPayload STRING_PAYLOAD = StringPayload.of("payload"); + private static final LogEntry STRING_ENTRY = LogEntry.newBuilder(STRING_PAYLOAD).build(); + private static final String JAVA_OTHER_NAME = "java-other"; + private static final String JAVA_INVALID_NAME = "no-java-name"; + private static final String JAVA_OTHER_VERSION = "1.0.0"; + + @Test + public void testInstrumentationGenerated() { + Instrumentation.setInstrumentationStatus(false); + verifyEntries( + Instrumentation.populateInstrumentationInfo(ImmutableList.of(STRING_ENTRY)), + 1, + 2, + new HashSet<>(Arrays.asList(Instrumentation.JAVA_LIBRARY_NAME_PREFIX)), + new HashSet<>( + Arrays.asList(Instrumentation.getLibraryVersion(Instrumentation.class.getClass())))); + } + + @Test + public void testNoInstrumentationGenerated() { + Instrumentation.setInstrumentationStatus(true); + Tuple> pair = + Instrumentation.populateInstrumentationInfo(ImmutableList.of(STRING_ENTRY)); + ArrayList entries = Lists.newArrayList(pair.y()); + Assert.assertFalse(pair.x()); + Assert.assertEquals(entries.size(), 1); + Assert.assertTrue(entries.get(0).getPayload().getType() == Type.STRING); + } + + @Test + public void testInstrumentationUpdated() { + Instrumentation.setInstrumentationStatus(false); + LogEntry json_entry = + LogEntry.newBuilder(generateInstrumentationPayload(JAVA_OTHER_NAME, JAVA_OTHER_VERSION)) + .build(); + verifyEntries( + Instrumentation.populateInstrumentationInfo(ImmutableList.of(json_entry)), + 0, + 1, + new HashSet<>(Arrays.asList(Instrumentation.JAVA_LIBRARY_NAME_PREFIX, JAVA_OTHER_NAME)), + new HashSet<>( + Arrays.asList( + Instrumentation.getLibraryVersion(Instrumentation.class.getClass()), + JAVA_OTHER_VERSION))); + } + + @Test + public void testInvalidInstrumentationRemoved() { + Instrumentation.setInstrumentationStatus(false); + LogEntry json_entry = + LogEntry.newBuilder(generateInstrumentationPayload(JAVA_INVALID_NAME, JAVA_OTHER_VERSION)) + .build(); + verifyEntries( + Instrumentation.populateInstrumentationInfo(ImmutableList.of(json_entry)), + 0, + 1, + new HashSet<>(Arrays.asList(Instrumentation.JAVA_LIBRARY_NAME_PREFIX)), + new HashSet<>( + Arrays.asList(Instrumentation.getLibraryVersion(Instrumentation.class.getClass())))); + } + + public static JsonPayload generateInstrumentationPayload( + String libraryName, String libraryVersion) { + Map json_data = new HashMap<>(); + Map instrumentation_data = new HashMap<>(); + Map info = new HashMap<>(); + info.put(Instrumentation.INSTRUMENTATION_NAME_KEY, libraryName); + info.put(Instrumentation.INSTRUMENTATION_VERSION_KEY, libraryVersion); + List list = ImmutableList.of(info); + instrumentation_data.put(Instrumentation.INSTRUMENTATION_SOURCE_KEY, list); + json_data.put(Instrumentation.DIAGNOSTIC_INFO_KEY, instrumentation_data); + return JsonPayload.of(json_data); + } + + private static void verifyEntries( + Tuple> pair, + int index, + int expected, + HashSet names, + HashSet versions) { + ArrayList entries = Lists.newArrayList(pair.y()); + Assert.assertTrue(pair.x()); + Assert.assertEquals(entries.size(), expected); + Assert.assertTrue(entries.get(index).getPayload().getType() == Type.JSON); + ListValue infoList = + entries + .get(index) + .getPayload() + .getData() + .getFieldsOrThrow(Instrumentation.DIAGNOSTIC_INFO_KEY) + .getStructValue() + .getFieldsOrThrow(Instrumentation.INSTRUMENTATION_SOURCE_KEY) + .getListValue(); + for (Value val : infoList.getValuesList()) { + Assert.assertTrue( + names.remove( + val.getStructValue() + .getFieldsOrThrow(Instrumentation.INSTRUMENTATION_NAME_KEY) + .getStringValue())); + Assert.assertTrue( + versions.remove( + val.getStructValue() + .getFieldsOrThrow(Instrumentation.INSTRUMENTATION_VERSION_KEY) + .getStringValue())); + } + Assert.assertEquals(names.size(), 0); + Assert.assertEquals(versions.size(), 0); + } +} diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingHandlerTest.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingHandlerTest.java index dbe3f7f5f..d55a3e786 100644 --- a/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingHandlerTest.java +++ b/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingHandlerTest.java @@ -201,6 +201,7 @@ public void enhanceLogEntry(LogEntry.Builder builder) { @Before public void setUp() { + Instrumentation.setInstrumentationStatus(true); logging = EasyMock.createMock(Logging.class); options = EasyMock.createMock(LoggingOptions.class); expect(options.getProjectId()).andStubReturn(PROJECT); 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 68774e720..421f057e7 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 @@ -252,6 +252,7 @@ private void configureListLogsTests( @Before public void setUp() { + Instrumentation.setInstrumentationStatus(true); rpcFactoryMock = EasyMock.createStrictMock(LoggingRpcFactory.class); loggingRpcMock = EasyMock.createStrictMock(LoggingRpc.class); EasyMock.expect(rpcFactoryMock.create(EasyMock.anyObject(LoggingOptions.class))) @@ -2288,6 +2289,42 @@ public void run() { assertSame(0, exceptions.get()); } + @Test + public void testDiagnosticInfoWithNoPartialSuccess() { + testDiagnosticInfoGeneration(false); + } + + @Test + public void testDiagnosticInfoWithPartialSuccess() { + testDiagnosticInfoGeneration(true); + } + + private void testDiagnosticInfoGeneration(boolean addPartialSuccessOption) { + Instrumentation.setInstrumentationStatus(false); + LogEntry json_entry = + LogEntry.newBuilder( + InstrumentationTest.generateInstrumentationPayload( + Instrumentation.JAVA_LIBRARY_NAME_PREFIX, + Instrumentation.getLibraryVersion(Instrumentation.class.getClass()))) + .setLogName(Instrumentation.INSTRUMENTATION_LOG_NAME) + .build(); + WriteLogEntriesRequest request = + WriteLogEntriesRequest.newBuilder() + .addAllEntries( + Iterables.transform( + ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2, json_entry), + LogEntry.toPbFunction(PROJECT))) + .setPartialSuccess(true) + .build(); + WriteLogEntriesResponse response = WriteLogEntriesResponse.newBuilder().build(); + EasyMock.expect(loggingRpcMock.write(request)).andReturn(ApiFutures.immediateFuture(response)); + EasyMock.replay(rpcFactoryMock, loggingRpcMock); + logging = options.getService(); + logging.write( + ImmutableList.of(LOG_ENTRY1, LOG_ENTRY2), + WriteOption.partialSuccess(addPartialSuccessOption)); + } + private void testDeleteByDestination( String logId, String logName, LogDestinationName destination, boolean useAsyncDelete) throws ExecutionException, InterruptedException { diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingTest.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingTest.java index fab23f972..ef87bb13d 100644 --- a/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingTest.java +++ b/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingTest.java @@ -44,6 +44,7 @@ public class LoggingTest { private static final String ORGANIZATION_NAME = "organization"; private static final String BILLING_NAME = "billing"; private static final Boolean DONT_AUTO_POPULATE_METADATA = false; + private static final Boolean DO_PARTIAL_SUCCESS = false; @Test public void testListOption() { @@ -114,6 +115,10 @@ public void testWriteOption() { writeOption = WriteOption.autoPopulateMetadata(DONT_AUTO_POPULATE_METADATA); assertEquals(DONT_AUTO_POPULATE_METADATA, writeOption.getValue()); assertEquals(WriteOption.OptionType.AUTO_POPULATE_METADATA, writeOption.getOptionType()); + + writeOption = WriteOption.partialSuccess(DO_PARTIAL_SUCCESS); + assertEquals(DO_PARTIAL_SUCCESS, writeOption.getValue()); + assertEquals(WriteOption.OptionType.PARTIAL_SUCCESS, writeOption.getOptionType()); } @Test diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/it/ITJulLoggerTest.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/it/ITJulLoggerTest.java index 9f4853628..80f0e8a50 100644 --- a/google-cloud-logging/src/test/java/com/google/cloud/logging/it/ITJulLoggerTest.java +++ b/google-cloud-logging/src/test/java/com/google/cloud/logging/it/ITJulLoggerTest.java @@ -82,7 +82,6 @@ public void testLoggingHandler() throws InterruptedException { assertThat(entry.getOperation()).isNull(); assertThat(entry.getInsertId()).isNotNull(); assertThat(entry.getTimestamp()).isNotNull(); - assertThat(iterator.hasNext()).isFalse(); logger.removeHandler(handler); }