Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Extend a set of options in WriteOption to allow defining the log location as custom project, folder, organization or billing account #727

Merged
merged 12 commits into from Nov 2, 2021
Merged
Expand Up @@ -69,7 +69,8 @@ final class WriteOption extends Option {
enum OptionType implements Option.OptionType {
LOG_NAME,
RESOURCE,
LABELS;
LABELS,
LOG_DESTINATION;

@SuppressWarnings("unchecked")
<T> T get(Map<Option.OptionType, ?> options) {
Expand Down Expand Up @@ -105,6 +106,14 @@ public static WriteOption resource(MonitoredResource resource) {
public static WriteOption labels(Map<String, String> labels) {
return new WriteOption(OptionType.LABELS, ImmutableMap.copyOf(labels));
}

/**
* Returns an option to specify a log destination resource path (see {@link LogDestinationName}
* for details)
*/
public static WriteOption destination(LogDestinationName destination) {
return new WriteOption(OptionType.LOG_DESTINATION, destination);
}
}

/** Fields according to which log entries can be sorted. */
Expand Down
Expand Up @@ -22,7 +22,9 @@
import com.google.cloud.logging.Logging.WriteOption;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
Expand Down Expand Up @@ -172,7 +174,7 @@ public LoggingHandler(String log, LoggingOptions options) {
* a default resource is created based on the project ID and deployment environment.
*/
public LoggingHandler(String log, LoggingOptions options, MonitoredResource monitoredResource) {
this(log, options, monitoredResource, null);
this(log, options, monitoredResource, null, null);
}

/**
Expand All @@ -190,6 +192,27 @@ public LoggingHandler(
LoggingOptions options,
MonitoredResource monitoredResource,
List<LoggingEnhancer> enhancers) {
this(log, options, monitoredResource, enhancers, null);
}

/**
* Creates a handler that publishes messages to Cloud Logging.
*
* @param log the name of the log to which log entries are written
* @param options options for the Cloud Logging service
* @param monitoredResource the monitored resource to which log entries refer. If it is null then
* a default resource is created based on the project ID and deployment environment.
* @param enhancers List of {@link LoggingEnhancer} instances used to enhance any{@link LogEntry}
* instances built by this handler.
* @param destination the log destination {@link LogDestinationName} (see 'logName' parameter in
* https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry)
*/
public LoggingHandler(
String log,
LoggingOptions options,
MonitoredResource monitoredResource,
List<LoggingEnhancer> enhancers,
LogDestinationName destination) {
try {
loggingOptions = options != null ? options : LoggingOptions.getDefaultInstance();
LoggingConfig config = new LoggingConfig(getClass().getName());
Expand All @@ -204,17 +227,20 @@ public LoggingHandler(
MonitoredResource resource =
firstNonNull(
monitoredResource, config.getMonitoredResource(loggingOptions.getProjectId()));
defaultWriteOptions =
new WriteOption[] {
WriteOption.logName(logName),
WriteOption.resource(resource),
WriteOption.labels(
ImmutableMap.of(
LEVEL_NAME_KEY,
baseLevel.getName(),
LEVEL_VALUE_KEY,
String.valueOf(baseLevel.intValue())))
};
List<WriteOption> writeOptions = new ArrayList<WriteOption>();
writeOptions.add(WriteOption.logName(logName));
writeOptions.add(WriteOption.resource(resource));
writeOptions.add(
WriteOption.labels(
ImmutableMap.of(
LEVEL_NAME_KEY,
baseLevel.getName(),
LEVEL_VALUE_KEY,
String.valueOf(baseLevel.intValue()))));
if (destination != null) {
writeOptions.add(WriteOption.destination(destination));
}
defaultWriteOptions = Iterables.toArray(writeOptions, WriteOption.class);

getLogging().setFlushSeverity(severityFor(flushLevel));
getLogging().setWriteSynchronicity(config.getSynchronicity());
Expand Down
Expand Up @@ -21,6 +21,7 @@
import static com.google.cloud.logging.Logging.ListOption.OptionType.PAGE_SIZE;
import static com.google.cloud.logging.Logging.ListOption.OptionType.PAGE_TOKEN;
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.RESOURCE;

Expand Down Expand Up @@ -738,11 +739,13 @@ private static WriteLogEntriesRequest writeLogEntriesRequest(
LoggingOptions serviceOptions,
Iterable<LogEntry> logEntries,
Map<Option.OptionType, ?> options) {
String projectId = serviceOptions.getProjectId();
WriteLogEntriesRequest.Builder builder = WriteLogEntriesRequest.newBuilder();
String logName = LOG_NAME.get(options);
String projectId = serviceOptions.getProjectId();

LogName logName = getLogName(projectId, LOG_NAME.get(options), LOG_DESTINATION.get(options));

if (logName != null) {
builder.setLogName(LogName.ofProjectLogName(projectId, logName).toString());
builder.setLogName(logName.toString());
}
MonitoredResource resource = RESOURCE.get(options);
if (resource != null) {
Expand All @@ -752,10 +755,25 @@ private static WriteLogEntriesRequest writeLogEntriesRequest(
if (labels != null) {
builder.putAllLabels(labels);
}

builder.addAllEntries(Iterables.transform(logEntries, LogEntry.toPbFunction(projectId)));
return builder.build();
}

private static LogName getLogName(
String projectId, String logName, LogDestinationName destination) {
if (logName == null) {
return null;
}

// If no destination specified, fallback to project based log name
if (destination == null) {
return LogName.ofProjectLogName(projectId, logName);
}

return destination.toLogName(logName);
}

public void write(Iterable<LogEntry> logEntries, WriteOption... options) {
if (inWriteCall.get() != null) {
return;
Expand Down
Expand Up @@ -310,6 +310,28 @@ public void testPublishCustomResource() {
handler.publish(newLogRecord(Level.FINEST, MESSAGE));
}

@Test
public void testPublishCustomResourceWithFolder() {
testPublishCustomResourceWithDestination(FINEST_ENTRY, LogDestinationName.folder("folder"));
}

@Test
public void testPublishCustomResourceWithBilling() {
testPublishCustomResourceWithDestination(
FINEST_ENTRY, LogDestinationName.billingAccount("billing"));
}

@Test
public void testPublishCustomResourceWithOrganization() {
testPublishCustomResourceWithDestination(
FINEST_ENTRY, LogDestinationName.organization("organization"));
}

@Test
public void testPublishCustomResourceWithProject() {
testPublishCustomResourceWithDestination(FINEST_ENTRY, LogDestinationName.project(PROJECT));
}

@Test
public void testPublishKubernetesContainerResource() {
expect(options.getProjectId()).andReturn(PROJECT).anyTimes();
Expand Down Expand Up @@ -582,4 +604,27 @@ public void testClose() throws Exception {
handler.close();
handler.close();
}

private void testPublishCustomResourceWithDestination(
LogEntry entry, LogDestinationName destination) {
expect(options.getProjectId()).andReturn(PROJECT).anyTimes();
expect(options.getService()).andReturn(logging);
logging.setFlushSeverity(Severity.ERROR);
expectLastCall().once();
logging.setWriteSynchronicity(Synchronicity.ASYNC);
expectLastCall().once();
MonitoredResource resource = MonitoredResource.of("custom", ImmutableMap.<String, String>of());
logging.write(
ImmutableList.of(entry),
WriteOption.logName(LOG_NAME),
WriteOption.resource(resource),
WriteOption.labels(BASE_SEVERITY_MAP),
WriteOption.destination(destination));
expectLastCall().once();
replay(options, logging);
Handler handler = new LoggingHandler(LOG_NAME, options, resource, null, destination);
handler.setLevel(Level.ALL);
handler.setFormatter(new TestFormatter());
handler.publish(newLogRecord(Level.FINEST, MESSAGE));
}
}