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

Initialize the default MonitoredResource from a GAE environment #1535

Merged
Original file line number Diff line number Diff line change
@@ -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:
*
* <pre>
* 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
* </pre>
*
*/
public class GaeFlexLoggingEnhancer implements LoggingHandler.Enhancer {

private static final ThreadLocal<String> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -76,6 +78,11 @@
* <li>{@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}).
* <li>{@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).
* <li>{@code com.google.cloud.logging.LoggingHandler.resourceType} the type name to use when
* creating the default {@link MonitoredResource} (defaults to "global").
* </ul>
*
* <p>To add a {@code LoggingHandler} to an existing {@link Logger} and be sure to avoid infinite
Expand All @@ -99,6 +106,7 @@ public class LoggingHandler extends Handler {
private volatile Logging logging;
private Level flushLevel;
private long flushSize;
private final List<Enhancer> enhancers;

/**
* Creates an handler that publishes messages to Stackdriver Logging.
Expand Down Expand Up @@ -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<Enhancer> enhancers) {
LogConfigHelper helper = new LogConfigHelper();
String className = getClass().getName();
this.options = options != null ? options : LoggingOptions.getDefaultInstance();
Expand All @@ -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)};
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -237,6 +273,24 @@ Formatter getFormatterProperty(String name, Formatter defaultValue) {
}
return defaultValue;
}

List<Enhancer> getEnhancerProperty(String name) {
String list = manager.getProperty(name);
try {
List<Enhancer> enhancers = new ArrayList<>();
if (list != null) {
String[] items = list.split(",");
for (String e_name : items) {
Class<? extends Enhancer> clz = (Class<? extends Enhancer>) 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();
}
}

/**
Expand Down Expand Up @@ -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);
}

This comment was marked as spam.

enhanceLogEntry(builder, record);
return builder.build();
}

@Deprecated
protected void enhanceLogEntry(LogEntry.Builder builder, LogRecord record) {

This comment was marked as spam.

This comment was marked as spam.

// no-op in this class
}

private static Severity severityFor(Level level) {
Expand Down Expand Up @@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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")
Expand Down Expand Up @@ -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.<String, String>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();
Expand Down