diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/GaeFlexLoggingEnhancer.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/GaeFlexLoggingEnhancer.java new file mode 100644 index 000000000000..b4c80e288007 --- /dev/null +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/GaeFlexLoggingEnhancer.java @@ -0,0 +1,88 @@ +/* + * 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 java.util.logging.LogRecord; + +import com.google.cloud.MonitoredResource.Builder; + +/** + * A Logging {@link Enhancer} that enhances the logging for the + * GAE Flex environment. This enhancer can + * be configured in a logging.properties file with: + * + *
+ * handlers=com.google.cloud.logging.LoggingHandler
+ * com.google.cloud.logging.LoggingHandler.log=gaeflex.log
+ * com.google.cloud.logging.LoggingHandler.resourceType=gae_app
+ * com.google.cloud.logging.LoggingHandler.enhancers=com.google.cloud.logging.GaeFlexLoggingEnhancer
+ * com.google.cloud.logging.LoggingHandler.formatter = java.util.logging.SimpleFormatter
+ * java.util.logging.SimpleFormatter.format=%3$s: %5$s%6$s
+ * 
+ * + */ +public class GaeFlexLoggingEnhancer implements LoggingHandler.Enhancer { + + private static final ThreadLocal traceId = new ThreadLocal<>(); + + private final String gaeInstanceId; + + /** + * Set the Trace ID associated with any logging done by the current thread. + * + * @param id The traceID + */ + public static void setCurrentTraceId(String id) { + traceId.set(id); + } + + /** + * Get the Trace ID associated with any logging done by the current thread. + * + * @return id The traceID + */ + public static String getCurrentTraceId() { + return traceId.get(); + } + + public GaeFlexLoggingEnhancer() { + gaeInstanceId = System.getenv("GAE_INSTANCE"); // Are we running on a GAE instance? + } + + @Override + public void enhanceMonitoredResource(Builder builder) { + if (gaeInstanceId != null) { + if (System.getenv("GAE_SERVICE") != null) { + builder.addLabel("module_id", System.getenv("GAE_SERVICE")); + } + if (System.getenv("GAE_VERSION") != null) { + builder.addLabel("version_id", System.getenv("GAE_VERSION")); + } + } + } + + @Override + public void enhanceLogEntry(com.google.cloud.logging.LogEntry.Builder builder, LogRecord record) { + if (gaeInstanceId != null) { + builder.addLabel("appengine.googleapis.com/instance_name", gaeInstanceId); + } + String traceId = getCurrentTraceId(); + if (traceId != null) { + builder.addLabel("appengine.googleapis.com/trace_id", traceId); + } + } +} 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 d497f6e1b70b..ecb2706d9528 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,9 @@ 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.ArrayList; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.logging.ErrorManager; @@ -76,6 +78,11 @@ *
  • {@code com.google.cloud.logging.LoggingHandler.flushLevel} specifies the flush log level. * When a log with this level is published, logs are transmitted to the Stackdriver Logging * service (defaults to {@link LoggingLevel#ERROR}). + *
  • {@code com.google.cloud.logging.LoggingHandler.enhancers} specifies a comma separated list + * of {@link Enhancer} classes. This handler will call each enhancer list whenever it builds + * 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"). * * *

    To add a {@code LoggingHandler} to an existing {@link Logger} and be sure to avoid infinite @@ -99,6 +106,7 @@ public class LoggingHandler extends Handler { private volatile Logging logging; private Level flushLevel; private long flushSize; + private final List enhancers; /** * Creates an handler that publishes messages to Stackdriver Logging. @@ -131,9 +139,30 @@ public LoggingHandler(String log, LoggingOptions options) { * * @param log the name of the log to which log entries are written * @param options options for the Stackdriver Logging service - * @param monitoredResource the monitored resource to which log entries refer + * @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. When creating a default resource, if + * any {@link Enhancer} instances are configured and then each + * {@link Enhancer#enhanceMonitoredResource(com.google.cloud.MonitoredResource.Builder)} method + * is called before building the default resource. */ public LoggingHandler(String log, LoggingOptions options, MonitoredResource monitoredResource) { + this(log, options, monitoredResource,null); + } + + /** + * Creates a handler that publishes messages to Stackdriver Logging. + * + * @param log the name of the log to which log entries are written + * @param options options for the Stackdriver 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. When creating a default resource, if + * any {@link Enhancer} instances are configured and then each + * {@link Enhancer#enhanceMonitoredResource(com.google.cloud.MonitoredResource.Builder)} method + * is called before building the default resource. + * @param enhancers List of {@link Enhancer} instances used to enhance any {@link MonitoredResource} + * or {@link LogEntry} instances built by this handler. + */ + public LoggingHandler(String log, LoggingOptions options, MonitoredResource monitoredResource, List enhancers) { LogConfigHelper helper = new LogConfigHelper(); String className = getClass().getName(); this.options = options != null ? options : LoggingOptions.getDefaultInstance(); @@ -143,7 +172,9 @@ public LoggingHandler(String log, LoggingOptions options, MonitoredResource moni setFilter(helper.getFilterProperty(className + ".filter", null)); setFormatter(helper.getFormatterProperty(className + ".formatter", new SimpleFormatter())); String logName = firstNonNull(log, helper.getProperty(className + ".log", "java.log")); - MonitoredResource resource = firstNonNull(monitoredResource, getDefaultResource()); + this.enhancers = enhancers != null ? enhancers : helper.getEnhancerProperty(className + ".enhancers"); + String resourceType = helper.getProperty(className + ".resourceType", "global"); + MonitoredResource resource = monitoredResource != null ? monitoredResource : getDefaultResource(resourceType); writeOptions = new WriteOption[]{WriteOption.logName(logName), WriteOption.resource(resource)}; } @@ -178,8 +209,13 @@ private static boolean hasLoggingHandler(Logger logger) { return false; } - private MonitoredResource getDefaultResource() { - return MonitoredResource.of("global", ImmutableMap.of("project_id", options.getProjectId())); + private MonitoredResource getDefaultResource(String resourceType) { + MonitoredResource.Builder builder = MonitoredResource.newBuilder(resourceType); + builder.addLabel("project_id", options.getProjectId()); + for (Enhancer enhancer : enhancers) { + enhancer.enhanceMonitoredResource(builder); + } + return builder.build(); } private static class LogConfigHelper { @@ -237,6 +273,24 @@ Formatter getFormatterProperty(String name, Formatter defaultValue) { } return defaultValue; } + + List getEnhancerProperty(String name) { + String list = manager.getProperty(name); + try { + List enhancers = new ArrayList<>(); + if (list != null) { + String[] items = list.split(","); + for (String e_name : items) { + Class clz = (Class) ClassLoader.getSystemClassLoader().loadClass(e_name); + enhancers.add((Enhancer) clz.newInstance()); + } + } + return enhancers; + } catch (Exception ex) { + // If we cannot create the enhancers we fall back to the default + } + return Collections.emptyList(); + } } /** @@ -311,12 +365,16 @@ private LogEntry entryFor(LogRecord record) { .addLabel("levelValue", String.valueOf(level.intValue())) .setTimestamp(record.getMillis()) .setSeverity(severityFor(level)); + + for (Enhancer enhancer : enhancers) { + enhancer.enhanceLogEntry(builder, record); + } enhanceLogEntry(builder, record); return builder.build(); } + @Deprecated protected void enhanceLogEntry(LogEntry.Builder builder, LogRecord record) { - // no-op in this class } private static Severity severityFor(Level level) { @@ -430,4 +488,14 @@ public synchronized long setFlushSize(long flushSize) { public static void addHandler(Logger logger, LoggingHandler handler) { logger.addHandler(handler); } + + /** + * A Log Enhancer. + * May be used to enhance the {@link MonitoredResource} and/or the {@link LogEntry} + */ + interface Enhancer { + void enhanceMonitoredResource(MonitoredResource.Builder builder); + void enhanceLogEntry(LogEntry.Builder builder, LogRecord record); + } + } 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 a1c1697d18af..7b4f3298c1d4 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 @@ -17,11 +17,14 @@ package com.google.cloud.logging; import com.google.cloud.MonitoredResource; +import com.google.cloud.logging.LogEntry.Builder; import com.google.cloud.logging.Logging.WriteOption; import com.google.cloud.logging.Payload.StringPayload; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.Futures; + +import java.util.Collections; import java.util.logging.ErrorManager; import java.util.logging.Formatter; import java.util.logging.Handler; @@ -46,6 +49,13 @@ public class LoggingHandlerTest { .addLabel("levelValue", String.valueOf(Level.FINEST.intValue())) .setTimestamp(123456789L) .build(); + private static final LogEntry FINEST_ENHANCED_ENTRY = LogEntry.newBuilder(StringPayload.of(MESSAGE)) + .setSeverity(Severity.DEBUG) + .addLabel("levelName", "FINEST") + .addLabel("levelValue", String.valueOf(Level.FINEST.intValue())) + .addLabel("enhanced", "true") + .setTimestamp(123456789L) + .build(); private static final LogEntry FINER_ENTRY = LogEntry.newBuilder(StringPayload.of(MESSAGE)) .setSeverity(Severity.DEBUG) .addLabel("levelName", "FINER") @@ -227,6 +237,33 @@ public void testPublishCustomResource() { handler.publish(newLogRecord(Level.FINEST, MESSAGE)); } + @Test + public void testEnhancedLogEntry() { + EasyMock.expect(options.getProjectId()).andReturn(PROJECT).anyTimes(); + EasyMock.expect(options.getService()).andReturn(logging); + MonitoredResource resource = MonitoredResource.of("custom", ImmutableMap.of()); + logging.writeAsync(ImmutableList.of(FINEST_ENHANCED_ENTRY), WriteOption.logName(LOG_NAME), + WriteOption.resource(resource)); + EasyMock.expectLastCall().andReturn(Futures.immediateFuture(null)); + EasyMock.replay(options, logging); + LoggingHandler.Enhancer enhancer = new LoggingHandler.Enhancer() { + @Override + public void enhanceMonitoredResource(MonitoredResource.Builder builder) { + throw new IllegalStateException(); + } + + @Override + public void enhanceLogEntry(Builder builder, LogRecord record) { + builder.addLabel("enhanced", "true"); + } + }; + Handler handler = + new LoggingHandler(LOG_NAME, options, resource, Collections.singletonList(enhancer)); + handler.setLevel(Level.ALL); + handler.setFormatter(new TestFormatter()); + handler.publish(newLogRecord(Level.FINEST, MESSAGE)); + } + @Test public void testReportFlushError() { EasyMock.expect(options.getProjectId()).andReturn(PROJECT).anyTimes();