diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/AsyncLoggingHandler.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/AsyncLoggingHandler.java deleted file mode 100644 index 1773ef07aa55..000000000000 --- a/google-cloud-logging/src/main/java/com/google/cloud/logging/AsyncLoggingHandler.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2016 Google Inc. All Rights Reserved. - * - * 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.cloud.MonitoredResource; -import com.google.cloud.logging.Logging.WriteOption; - -import java.util.List; -import java.util.logging.Filter; -import java.util.logging.Formatter; -import java.util.logging.Logger; -import java.util.logging.SimpleFormatter; - -/** - * A logging handler that asynchronously outputs logs generated with - * {@link java.util.logging.Logger} to Stackdriver Logging. - * - *

Java logging levels (see {@link java.util.logging.Level}) are mapped to the following Google - * Stackdriver Logging severities: - * - * - * - * - * - * - * - * - * - * - *
Java LevelStackdriver Logging Severity
SEVEREERROR
WARNINGWARNING
INFOINFO
CONFIGINFO
FINEDEBUG
FINERDEBUG
FINESTDEBUG
- * - *

Original Java logging levels are added as labels (with {@code levelName} and - * {@code levelValue} keys, respectively) to the corresponding Stackdriver Logging {@link LogEntry}. - * You can read entry labels using {@link LogEntry#getLabels()}. To use logging levels that correspond - * to Stackdriver Logging severities you can use {@link LoggingLevel}. - * - *

Configuration: By default each {@code AsyncLoggingHandler} is initialized using the - * following {@code LogManager} configuration properties (that you can set in the - * {@code logging.properties} file. If properties are not defined (or have invalid values) then the - * specified default values are used. - *

- * - *

To add a {@code LoggingHandler} to an existing {@link Logger} and be sure to avoid infinite - * recursion when logging, use the {@link #addHandler(Logger, LoggingHandler)} method. Alternatively - * you can add the handler via {@code logging.properties}. For example using the following line: - *

- * {@code com.example.mypackage.handlers=com.google.cloud.logging.AsyncLoggingHandler}
- * 
- */ -public class AsyncLoggingHandler extends LoggingHandler { - - public AsyncLoggingHandler() { - super(); - } - - public AsyncLoggingHandler(String logName) { - super(logName); - } - - public AsyncLoggingHandler(String logName, LoggingOptions options) { - super(logName, options); - } - - public AsyncLoggingHandler(String logName, LoggingOptions options, MonitoredResource resource) { - super(logName, options, resource); - } - - @Override - void write(List entries, WriteOption... options) { - getLogging().writeAsync(entries, options); - } -} 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 9af8e231917b..cb182a742a6d 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 @@ -21,7 +21,6 @@ import com.google.cloud.MonitoredResource; import com.google.cloud.logging.Logging.WriteOption; import com.google.common.collect.ImmutableList; - import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; @@ -83,6 +82,8 @@ * a {@link MonitoredResource} or {@link LogEntry} instance (defaults to empty list). *
  • {@code com.google.cloud.logging.LoggingHandler.resourceType} the type name to use when * creating the default {@link MonitoredResource} (defaults to "global"). + *
  • {@code com.google.cloud.logging.Synchronicity} the synchronicity of the write method to use + * to write logs to the Stackdriver Logging service (defaults to {@link Synchronicity#ASYNC}). * * *

    To add a {@code LoggingHandler} to an existing {@link Logger} and be sure to avoid infinite @@ -106,6 +107,7 @@ public class LoggingHandler extends Handler { private volatile Logging logging; private Level flushLevel; private long flushSize; + private Synchronicity synchronicity; private final List enhancers; /** @@ -169,6 +171,8 @@ public LoggingHandler(String log, LoggingOptions options, MonitoredResource moni this.options = options != null ? options : LoggingOptions.getDefaultInstance(); this.flushLevel = helper.getLevelProperty(className + ".flushLevel", LoggingLevel.ERROR); this.flushSize = helper.getLongProperty(className + ".flushSize", 1L); + this.synchronicity = + helper.getSynchronicityProperty(className + ".synchronicity", Synchronicity.ASYNC); setLevel(helper.getLevelProperty(className + ".level", Level.INFO)); setFilter(helper.getFilterProperty(className + ".filter", null)); setFormatter(helper.getFormatterProperty(className + ".formatter", new SimpleFormatter())); @@ -296,6 +300,16 @@ List getEnhancerProperty(String name) { } return Collections.emptyList(); } + + Synchronicity getSynchronicityProperty(String name, Synchronicity defaultValue) { + String synchronicity = manager.getProperty(name); + try { + return Synchronicity.valueOf(synchronicity); + } catch (Exception ex) { + // If we cannot create the Synchronicity we fall back to default value + } + return defaultValue; + } } /** @@ -419,7 +433,15 @@ private static Severity severityFor(Level level) { * how entries should be written. */ void write(List entries, WriteOption... options) { - getLogging().writeAsync(entries, options); + switch (this.synchronicity) { + case SYNC: + getLogging().write(entries, options); + break; + case ASYNC: + default: + getLogging().writeAsync(entries, options); + break; + } } @Override @@ -476,6 +498,11 @@ public synchronized Level setFlushLevel(Level flushLevel) { return flushLevel; } + /** Get the flush log level. */ + public Level getFlushLevel() { + return this.flushLevel; + } + /** * Sets the maximum size of the log buffer. Once the maximum size of the buffer is reached, logs * are transmitted to the Stackdriver Logging service. If not set, a log is sent to the service as @@ -486,6 +513,28 @@ public synchronized long setFlushSize(long flushSize) { return flushSize; } + /** Get the maximum size of the log buffer. */ + public long getFlushSize() { + return this.flushSize; + } + + /** + * Sets the synchronicity of the write method used to write logs to the Stackdriver Logging + * service. Defaults to {@link Synchronicity#ASYNC}. + */ + public synchronized Synchronicity setSynchronicity(Synchronicity synchronicity) { + this.synchronicity = synchronicity; + return synchronicity; + } + + /** + * Get the synchronicity of the write method used to write logs to the Stackdriver Logging + * service. + */ + public Synchronicity getSynchronicity() { + return this.synchronicity; + } + /** * Adds the provided {@code LoggingHandler} to {@code logger}. Use this method to register Cloud * Logging handlers instead of {@link Logger#addHandler(Handler)} to avoid infinite recursion diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/Synchronicity.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/Synchronicity.java new file mode 100644 index 000000000000..c0b5afb82f03 --- /dev/null +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/Synchronicity.java @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * 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; + +/** + * Used to specify the behavior of write calls to the Stackdriver Logging service. Specifying SYNC + * will make synchronous calls; specifying ASYNC will make asynchronous calls. The default behavior + * is ASYNC. + */ +public enum Synchronicity { + SYNC, + ASYNC, +} diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/AsyncLoggingHandlerTest.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/AsyncLoggingHandlerTest.java deleted file mode 100644 index 4f34dc4ded1c..000000000000 --- a/google-cloud-logging/src/test/java/com/google/cloud/logging/AsyncLoggingHandlerTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2016 Google Inc. All Rights Reserved. - * - * 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 static com.google.cloud.logging.LoggingHandlerTest.TestFormatter; - -import com.google.api.gax.core.ApiFuture; -import com.google.api.gax.core.ApiFutures; -import com.google.cloud.MonitoredResource; -import com.google.cloud.logging.Logging.WriteOption; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import org.easymock.EasyMock; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -public class AsyncLoggingHandlerTest { - - private static final String LOG_NAME = "java.log"; - private static final String MESSAGE = "message"; - private static final String PROJECT = "project"; - private static final MonitoredResource DEFAULT_RESOURCE = - MonitoredResource.of("global", ImmutableMap.of("project_id", PROJECT)); - private static final ApiFuture FUTURE = ApiFutures.immediateFuture(null); - - private Logging logging; - private LoggingOptions options; - - @Before - public void setUp() { - logging = EasyMock.createStrictMock(Logging.class); - options = EasyMock.createStrictMock(LoggingOptions.class); - } - - @After - public void afterClass() { - EasyMock.verify(logging, options); - } - - @Test - public void testPublish() { - EasyMock.expect(options.getProjectId()).andReturn(PROJECT).anyTimes(); - EasyMock.expect(options.getService()).andReturn(logging); - LogEntry entry = LogEntry.newBuilder(Payload.StringPayload.of(MESSAGE)) - .setSeverity(Severity.DEBUG) - .addLabel("levelName", "FINEST") - .addLabel("levelValue", String.valueOf(Level.FINEST.intValue())) - .setTimestamp(123456789L) - .build(); - EasyMock.expect(logging.writeAsync(ImmutableList.of(entry), WriteOption.logName(LOG_NAME), - WriteOption.resource(DEFAULT_RESOURCE))).andReturn(FUTURE); - EasyMock.replay(options, logging); - Handler handler = new AsyncLoggingHandler(LOG_NAME, options); - handler.setLevel(Level.ALL); - handler.setFormatter(new TestFormatter()); - LogRecord record = new LogRecord(Level.FINEST, MESSAGE); - record.setMillis(123456789L); - handler.publish(record); - } -} diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/BaseSystemTest.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/BaseSystemTest.java index 3ff4e2d93457..9ec649200988 100644 --- a/google-cloud-logging/src/test/java/com/google/cloud/logging/BaseSystemTest.java +++ b/google-cloud-logging/src/test/java/com/google/cloud/logging/BaseSystemTest.java @@ -40,17 +40,15 @@ import com.google.common.collect.Sets; import com.google.protobuf.Any; import com.google.protobuf.StringValue; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.rules.Timeout; - import java.util.Iterator; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.Timeout; /** * A base class for system tests. This class can be extended to run system tests in different @@ -497,15 +495,16 @@ public void testLoggingHandler() throws InterruptedException { } @Test - public void testAsyncLoggingHandler() throws InterruptedException { - String logName = formatForTest("test-async-logging-handler"); + public void testSyncLoggingHandler() throws InterruptedException { + String logName = formatForTest("test-sync-logging-handler"); LoggingOptions options = logging().getOptions(); MonitoredResource resource = MonitoredResource.of("gce_instance", ImmutableMap.of("project_id", options.getProjectId(), "instance_id", "instance", "zone", "us-central1-a")); - LoggingHandler handler = new AsyncLoggingHandler(logName, options, resource); + LoggingHandler handler = new LoggingHandler(logName, options, resource); handler.setLevel(Level.WARNING); + handler.setSynchronicity(Synchronicity.SYNC); Logger logger = Logger.getLogger(getClass().getName()); logger.addHandler(handler); logger.setLevel(Level.WARNING); 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 afe60d7d44f7..60acff8f638b 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 @@ -16,19 +16,26 @@ package com.google.cloud.logging; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + import com.google.api.gax.core.ApiFutures; import com.google.cloud.MonitoredResource; import com.google.cloud.logging.LogEntry.Builder; import com.google.cloud.logging.Logging.WriteOption; +import com.google.cloud.logging.LoggingHandler.Enhancer; import com.google.cloud.logging.Payload.StringPayload; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; - +import java.io.ByteArrayInputStream; +import java.io.IOException; import java.util.Collections; import java.util.logging.ErrorManager; +import java.util.logging.Filter; import java.util.logging.Formatter; import java.util.logging.Handler; import java.util.logging.Level; +import java.util.logging.LogManager; import java.util.logging.LogRecord; import java.util.logging.Logger; import org.easymock.EasyMock; @@ -128,6 +135,30 @@ public class LoggingHandlerTest { .addLabel("levelValue", String.valueOf(LoggingLevel.EMERGENCY.intValue())) .setTimestamp(123456789L) .build(); + private static final String CONFIG_FILE_STRING = + (new StringBuilder()) + .append("com.google.cloud.logging.LoggingHandler.log=testLogName") + .append(System.lineSeparator()) + .append("com.google.cloud.logging.LoggingHandler.level=ALL") + .append(System.lineSeparator()) + .append( + "com.google.cloud.logging.LoggingHandler.filter=com.google.cloud.logging.LoggingHandlerTest$TestFilter") + .append(System.lineSeparator()) + .append( + "com.google.cloud.logging.LoggingHandler.formatter=com.google.cloud.logging.LoggingHandlerTest$TestFormatter") + .append(System.lineSeparator()) + .append("com.google.cloud.logging.LoggingHandler.flushSize=2") + .append(System.lineSeparator()) + .append("com.google.cloud.logging.LoggingHandler.flushLevel=CRITICAL") + .append(System.lineSeparator()) + .append( + "com.google.cloud.logging.LoggingHandler.enhancers=com.google.cloud.logging.LoggingHandlerTest$TestEnhancer") + .append(System.lineSeparator()) + .append("com.google.cloud.logging.LoggingHandler.resourceType=testResourceType") + .append(System.lineSeparator()) + .append("com.google.cloud.logging.LoggingHandler.synchronicity=SYNC") + .append(System.lineSeparator()) + .toString(); private Logging logging; private LoggingOptions options; @@ -140,6 +171,25 @@ public String format(LogRecord record) { } } + static final class TestFilter implements Filter { + @Override + public boolean isLoggable(LogRecord record) { + return true; + } + } + + static final class TestEnhancer implements Enhancer { + @Override + public void enhanceMonitoredResource(MonitoredResource.Builder builder) { + builder.addLabel("enhanced", "true"); + } + + @Override + public void enhanceLogEntry(LogEntry.Builder builder, LogRecord record) { + builder.addLabel("enhanced", "true"); + } + } + @Before public void setUp() { logging = EasyMock.createStrictMock(Logging.class); @@ -347,6 +397,31 @@ public void testFlushLevel() { handler.publish(newLogRecord(Level.WARNING, MESSAGE)); } + @Test + public void testSyncWrite() { + EasyMock.expect(options.getProjectId()).andReturn(PROJECT).anyTimes(); + EasyMock.expect(options.getService()).andReturn(logging); + LogEntry entry = + LogEntry.newBuilder(Payload.StringPayload.of(MESSAGE)) + .setSeverity(Severity.DEBUG) + .addLabel("levelName", "FINEST") + .addLabel("levelValue", String.valueOf(Level.FINEST.intValue())) + .setTimestamp(123456789L) + .build(); + logging.write( + ImmutableList.of(entry), + WriteOption.logName(LOG_NAME), + WriteOption.resource(DEFAULT_RESOURCE)); + EasyMock.replay(options, logging); + LoggingHandler handler = new LoggingHandler(LOG_NAME, options); + handler.setLevel(Level.ALL); + handler.setSynchronicity(Synchronicity.SYNC); + handler.setFormatter(new TestFormatter()); + LogRecord record = new LogRecord(Level.FINEST, MESSAGE); + record.setMillis(123456789L); + handler.publish(record); + } + @Test public void testAddHandler() { EasyMock.expect(options.getProjectId()).andReturn(PROJECT).anyTimes(); @@ -369,6 +444,48 @@ public void close() { logger.log(newLogRecord(Level.FINEST, MESSAGE)); } + @Test + public void testPropertiesFile() throws IOException, InterruptedException { + EasyMock.expect(options.getProjectId()).andReturn(PROJECT).anyTimes(); + EasyMock.expect(options.getService()).andReturn(logging); + LogEntry entry = + LogEntry.newBuilder(Payload.StringPayload.of(MESSAGE)) + .setSeverity(Severity.DEBUG) + .addLabel("levelName", "FINEST") + .addLabel("levelValue", String.valueOf(Level.FINEST.intValue())) + .addLabel("enhanced", "true") + .setTimestamp(123456789L) + .build(); + logging.write( + ImmutableList.of(entry), + WriteOption.logName("testLogName"), + WriteOption.resource( + MonitoredResource.of( + "testResourceType", ImmutableMap.of("project_id", PROJECT, "enhanced", "true")))); + EasyMock.replay(options, logging); + LogManager.getLogManager() + .readConfiguration(new ByteArrayInputStream(CONFIG_FILE_STRING.getBytes())); + LoggingHandler handler = new LoggingHandler(null, options); + LogRecord record = new LogRecord(Level.FINEST, MESSAGE); + record.setMillis(123456789L); + handler.publish(record); + handler.flush(); + assertEquals(Level.ALL, handler.getLevel()); + assertNotNull(handler.getFilter()); + assertEquals( + "com.google.cloud.logging.LoggingHandlerTest$TestFilter", + handler.getFilter().getClass().getName()); + assertNotNull(handler.getFormatter()); + assertEquals( + "com.google.cloud.logging.LoggingHandlerTest$TestFormatter", + handler.getFormatter().getClass().getName()); + assertEquals(2, handler.getFlushSize()); + assertEquals(LoggingLevel.CRITICAL, handler.getFlushLevel()); + assertEquals(Synchronicity.SYNC, handler.getSynchronicity()); + + LogManager.getLogManager().readConfiguration(); + } + @Test public void testClose() throws Exception { EasyMock.expect(options.getProjectId()).andReturn(PROJECT).anyTimes();