From 43ea0b465b29c22e7eb439811f19ada90b9d23dd Mon Sep 17 00:00:00 2001 From: losalex <90795544+losalex@users.noreply.github.com> Date: Sun, 24 Oct 2021 13:43:20 -0700 Subject: [PATCH] feat: Add destination property into LogEntry (#720) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add ResourceName class which will provide destination log resource name customization and integrate it with LogEntry * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Add testToAndFromPbWithExpectedFailure and fix copyright header * Adress PR comments * Fix build break which caused by missing isBlank() symbol * Address PR comments and fix tests accordingly * Add extra test in testToAndFromPb() * Fix description for fromLogName() * Address latest PR comments * Fix fromPb() to call fromLogName() only once Co-authored-by: Owl Bot --- .../cloud/logging/LogDestinationName.java | 129 ++++++++++++++++++ .../com/google/cloud/logging/LogEntry.java | 41 +++++- .../google/cloud/logging/LogEntryTest.java | 56 ++++++-- .../google/cloud/logging/LoggingImplTest.java | 2 + 4 files changed, 214 insertions(+), 14 deletions(-) create mode 100644 google-cloud-logging/src/main/java/com/google/cloud/logging/LogDestinationName.java diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/LogDestinationName.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/LogDestinationName.java new file mode 100644 index 000000000..e23446b7a --- /dev/null +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/LogDestinationName.java @@ -0,0 +1,129 @@ +/* + * 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 + * + * https://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 static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.logging.v2.LogName; +import java.util.Map; + +/** + * Class for specifying resource name of the log to which this log entry belongs (see 'logName' + * parameter in https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry) + */ +public final class LogDestinationName extends Option { + + enum DestinationType implements Option.OptionType { + PROJECT, + FOLDER, + ORGANIZATION, + BILLINGACCOUNT; + + @SuppressWarnings("unchecked") + T get(Map options) { + return (T) options.get(this); + } + } + + private LogDestinationName(Option.OptionType option, Object value) { + super(option, value); + checkArgument(!checkNotNull(value).toString().trim().isEmpty()); + } + + /** + * Returns an option which sets and validates project ID resource name for log entries. + * + * @param id corresponds to PROJECT_ID token in 'logName' field described in + * https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry + */ + public static LogDestinationName project(String id) { + return new LogDestinationName(DestinationType.PROJECT, id); + } + + /** + * Returns an option which sets and validates project ID resource name for log entries. + * + * @param id corresponds to FOLDER_ID token in 'logName' field described in + * https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry + */ + public static LogDestinationName folder(String id) { + return new LogDestinationName(DestinationType.FOLDER, id); + } + + /** + * Returns an option which sets and validates project ID resource name for log entries. + * + * @param id corresponds to ORGANIZATION_ID token in 'logName' field described in + * https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry + */ + public static LogDestinationName organization(String id) { + return new LogDestinationName(DestinationType.ORGANIZATION, id); + } + + /** + * Returns an option which sets and validates project ID resource name for log entries. + * + * @param id corresponds to BILLING_ACCOUNT_ID token in 'logName' field described in + * https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry + */ + public static LogDestinationName billingAccount(String id) { + return new LogDestinationName(DestinationType.BILLINGACCOUNT, id); + } + + /** Creates a {@code LogEntry} object for given log ID. */ + public LogName toLogName(String logId) { + if (logId == null) { + return null; + } + + switch ((DestinationType) getOptionType()) { + case PROJECT: + return LogName.ofProjectLogName(getValue().toString(), logId); + + case FOLDER: + return LogName.ofFolderLogName(getValue().toString(), logId); + + case ORGANIZATION: + return LogName.ofOrganizationLogName(getValue().toString(), logId); + + case BILLINGACCOUNT: + return LogName.ofBillingAccountLogName(getValue().toString(), logId); + } + + return null; + } + + /** Creates a {@code LogDestinationName} object from given {@code LogName}. */ + public static LogDestinationName fromLogName(LogName logName) { + if (logName == null) { + return null; + } + + if (logName.getProject() != null) { + return project(logName.getProject()); + } else if (logName.getBillingAccount() != null) { + return billingAccount(logName.getBillingAccount()); + } else if (logName.getFolder() != null) { + return folder(logName.getFolder()); + } else if (logName.getOrganization() != null) { + return organization(logName.getOrganization()); + } + + return null; + } +} diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/LogEntry.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/LogEntry.java index 84779edd2..16b469cd8 100644 --- a/google-cloud-logging/src/main/java/com/google/cloud/logging/LogEntry.java +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/LogEntry.java @@ -66,6 +66,7 @@ public LogEntry apply(com.google.logging.v2.LogEntry pb) { private final boolean traceSampled; private final SourceLocation sourceLocation; private final Payload payload; + private final LogDestinationName destination; /** A builder for {@code LogEntry} objects. */ public static class Builder { @@ -84,6 +85,7 @@ public static class Builder { private boolean traceSampled; private SourceLocation sourceLocation; private Payload payload; + private LogDestinationName destination; Builder(Payload payload) { this.payload = payload; @@ -104,6 +106,7 @@ public static class Builder { this.traceSampled = entry.traceSampled; this.sourceLocation = entry.sourceLocation; this.payload = entry.payload; + this.destination = entry.destination; } /** @@ -282,6 +285,12 @@ public Builder setPayload(Payload payload) { return this; } + /** Sets the log path destination name type associated with the log entry. */ + public Builder setDestination(LogDestinationName destination) { + this.destination = destination; + return this; + } + /** Creates a {@code LogEntry} object for this builder. */ public LogEntry build() { return new LogEntry(this); @@ -303,6 +312,7 @@ public LogEntry build() { this.traceSampled = builder.traceSampled; this.sourceLocation = builder.sourceLocation; this.payload = builder.payload; + this.destination = builder.destination; } /** @@ -438,6 +448,16 @@ public > T getPayload() { return (T) payload; } + /** + * Returns the log path destination name type associated with log entry. By default, project name + * based destination is used. + * + * @see logName + */ + public LogDestinationName getDestination() { + return destination; + } + @Override public int hashCode() { return Objects.hash( @@ -454,7 +474,8 @@ public int hashCode() { getSpanId(), traceSampled, sourceLocation, - payload); + payload, + destination); } @Override @@ -479,7 +500,8 @@ public boolean equals(Object obj) { && Objects.equals(getSpanId(), other.getSpanId()) && Objects.equals(traceSampled, other.traceSampled) && Objects.equals(sourceLocation, other.sourceLocation) - && Objects.equals(payload, other.payload); + && Objects.equals(payload, other.payload) + && Objects.equals(destination, other.destination); } @Override @@ -499,6 +521,7 @@ public String toString() { .add("traceSampled", traceSampled) .add("sourceLocation", sourceLocation) .add("payload", payload) + .add("destination", destination) .toString(); } @@ -510,8 +533,13 @@ public Builder toBuilder() { com.google.logging.v2.LogEntry toPb(String projectId) { com.google.logging.v2.LogEntry.Builder builder = payload.toPb(); builder.putAllLabels(labels); + if (logName != null) { - builder.setLogName(LogName.ofProjectLogName(projectId, logName).toString()); + if (destination == null) { + builder.setLogName(LogName.ofProjectLogName(projectId, logName).toString()); + } else { + builder.setLogName(destination.toLogName(logName).toString()); + } } if (resource != null) { builder.setResource(resource.toPb()); @@ -570,7 +598,12 @@ static LogEntry fromPb(com.google.logging.v2.LogEntry entryPb) { builder.setLabels(entryPb.getLabelsMap()); builder.setSeverity(Severity.fromPb(entryPb.getSeverity())); if (!entryPb.getLogName().equals("")) { - builder.setLogName(LogName.parse(entryPb.getLogName()).getLog()); + LogName name = LogName.parse(entryPb.getLogName()); + builder.setLogName(name.getLog()); + LogDestinationName resource = LogDestinationName.fromLogName(name); + if (resource != null) { + builder.setDestination(resource); + } } if (!entryPb.getResource().equals(com.google.api.MonitoredResource.getDefaultInstance())) { builder.setResource(MonitoredResource.fromPb(entryPb.getResource())); diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/LogEntryTest.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/LogEntryTest.java index 8adc1765f..fe10893f1 100644 --- a/google-cloud-logging/src/test/java/com/google/cloud/logging/LogEntryTest.java +++ b/google-cloud-logging/src/test/java/com/google/cloud/logging/LogEntryTest.java @@ -34,6 +34,7 @@ public class LogEntryTest { private static final String LOG_NAME = "syslog"; + private static final String PROJECT = "project"; private static final MonitoredResource RESOURCE = MonitoredResource.newBuilder("cloudsql_database") .setLabels(ImmutableMap.of("datasetId", "myDataset", "zone", "myZone")) @@ -74,6 +75,11 @@ public String toString() { JsonPayload.of(ImmutableMap.of("key", "val")); private static final ProtoPayload PROTO_PAYLOAD = ProtoPayload.of(Any.pack(Empty.getDefaultInstance())); + private static final LogDestinationName BILLING_NAME = + LogDestinationName.billingAccount("000000-111111-222222"); + private static final LogDestinationName PROJECT_NAME = LogDestinationName.project(PROJECT); + private static final LogDestinationName FOLDER_NAME = LogDestinationName.folder("123456789"); + private static final LogDestinationName ORG_NAME = LogDestinationName.organization("1122334455"); private static final LogEntry STRING_ENTRY = LogEntry.newBuilder(STRING_PAYLOAD) .setLogName(LOG_NAME) @@ -122,6 +128,14 @@ public String toString() { .setTraceSampled(TRACE_SAMPLED) .setSourceLocation(SOURCE_LOCATION) .build(); + private static final LogEntry STRING_ENTRY_BILLING = + STRING_ENTRY.toBuilder().setDestination(BILLING_NAME).build(); + private static final LogEntry STRING_ENTRY_PROJECT = + STRING_ENTRY.toBuilder().setDestination(PROJECT_NAME).build(); + private static final LogEntry STRING_ENTRY_FOLDER = + STRING_ENTRY.toBuilder().setDestination(FOLDER_NAME).build(); + private static final LogEntry STRING_ENTRY_ORG = + STRING_ENTRY.toBuilder().setDestination(ORG_NAME).build(); @Test public void testOf() { @@ -251,7 +265,7 @@ public void testBuilder() { @Test public void testToBuilder() { - compareLogEntry(STRING_ENTRY, STRING_ENTRY.toBuilder().build()); + compareLogEntry(STRING_ENTRY, STRING_ENTRY.toBuilder().build(), true); HttpRequest request = HttpRequest.newBuilder() .setRequestMethod(HttpRequest.RequestMethod.POST) @@ -309,22 +323,45 @@ public void testToBuilder() { .setTraceSampled(TRACE_SAMPLED) .setSourceLocation(SOURCE_LOCATION) .build(); - compareLogEntry(STRING_ENTRY, logEntry); + compareLogEntry(STRING_ENTRY, logEntry, true); } @Test public void testToAndFromPb() { - compareLogEntry(STRING_ENTRY, LogEntry.fromPb(STRING_ENTRY.toPb("project"))); - compareLogEntry(JSON_ENTRY, LogEntry.fromPb(JSON_ENTRY.toPb("project"))); - compareLogEntry(PROTO_ENTRY, LogEntry.fromPb(PROTO_ENTRY.toPb("project"))); + compareLogEntry(STRING_ENTRY, LogEntry.fromPb(STRING_ENTRY.toPb(PROJECT)), false); + compareLogEntry(JSON_ENTRY, LogEntry.fromPb(JSON_ENTRY.toPb(PROJECT)), false); + compareLogEntry(PROTO_ENTRY, LogEntry.fromPb(PROTO_ENTRY.toPb(PROJECT)), false); + compareLogEntry( + STRING_ENTRY_BILLING, LogEntry.fromPb(STRING_ENTRY_BILLING.toPb(PROJECT)), true); + compareLogEntry(STRING_ENTRY_FOLDER, LogEntry.fromPb(STRING_ENTRY_FOLDER.toPb(PROJECT)), true); + compareLogEntry(STRING_ENTRY_ORG, LogEntry.fromPb(STRING_ENTRY_ORG.toPb(PROJECT)), true); + compareLogEntry( + STRING_ENTRY_PROJECT, LogEntry.fromPb(STRING_ENTRY_PROJECT.toPb(PROJECT)), true); LogEntry logEntry = LogEntry.of(STRING_PAYLOAD); - compareLogEntry(logEntry, LogEntry.fromPb(logEntry.toPb("project"))); + compareLogEntry(logEntry, LogEntry.fromPb(logEntry.toPb(PROJECT)), true); logEntry = LogEntry.of(LOG_NAME, RESOURCE, STRING_PAYLOAD); - compareLogEntry(logEntry, LogEntry.fromPb(logEntry.toPb("project"))); + compareLogEntry(logEntry, LogEntry.fromPb(logEntry.toPb(PROJECT)), false); + logEntry = + LogEntry.newBuilder(STRING_PAYLOAD) + .setLogName(LOG_NAME) + .setResource(RESOURCE) + .setDestination(FOLDER_NAME) + .build(); + compareLogEntry(logEntry, LogEntry.fromPb(logEntry.toPb(PROJECT)), true); + } + + @Test(expected = AssertionError.class) + public void testToAndFromPbWithExpectedFailure() { + LogEntry logEntry = + LogEntry.newBuilder(STRING_PAYLOAD).setLogName(LOG_NAME).setResource(RESOURCE).build(); + compareLogEntry(logEntry, LogEntry.fromPb(logEntry.toPb(PROJECT)), true); } - private void compareLogEntry(LogEntry expected, LogEntry value) { - assertEquals(expected, value); + private void compareLogEntry(LogEntry expected, LogEntry value, Boolean extraValidations) { + if (extraValidations) { + assertEquals(expected.hashCode(), value.hashCode()); + assertEquals(expected, value); + } assertEquals(expected.getLogName(), value.getLogName()); assertEquals(expected.getResource(), value.getResource()); assertEquals(expected.getTimestamp(), value.getTimestamp()); @@ -341,6 +378,5 @@ private void compareLogEntry(LogEntry expected, LogEntry value) { assertEquals(expected.getTraceSampled(), value.getTraceSampled()); assertEquals(expected.getSourceLocation(), value.getSourceLocation()); assertEquals(expected.getPayload(), value.getPayload()); - assertEquals(expected.hashCode(), value.hashCode()); } } 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 d1cd5946e..5e286f747 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 @@ -118,11 +118,13 @@ public class LoggingImplTest { private static final LogEntry LOG_ENTRY1 = LogEntry.newBuilder(StringPayload.of("entry1")) .setLogName(LOG_NAME) + .setDestination(LogDestinationName.project(PROJECT)) .setResource(MONITORED_RESOURCE) .build(); private static final LogEntry LOG_ENTRY2 = LogEntry.newBuilder(StringPayload.of("entry2")) .setLogName(LOG_NAME) + .setDestination(LogDestinationName.project(PROJECT)) .setResource(MONITORED_RESOURCE) .build(); private static final Function SINK_TO_PB_FUNCTION =