blob: 5c3e21ff2a31f2e00f7f8e2b2c1880dd2d5894e3 [file] [log] [blame]
/*
* Copyright 2018, OpenCensus Authors
*
* 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 io.opencensus.contrib.logcorrelation.stackdriver;
import com.google.cloud.ServiceOptions;
import com.google.cloud.logging.LogEntry;
import com.google.cloud.logging.LoggingEnhancer;
import io.opencensus.common.ExperimentalApi;
import io.opencensus.trace.Span;
import io.opencensus.trace.SpanContext;
import io.opencensus.trace.TraceId;
import io.opencensus.trace.unsafe.ContextUtils;
import java.util.logging.LogManager;
import javax.annotation.Nullable;
/**
* Stackdriver {@link LoggingEnhancer} that adds OpenCensus tracing data to log entries.
*
* <p>This feature is currently experimental.
*
* @since 0.15
*/
@ExperimentalApi
public final class OpenCensusTraceLoggingEnhancer implements LoggingEnhancer {
private static final String SAMPLED_LABEL_KEY = "opencensusTraceSampled";
private static final SpanSelection DEFAULT_SPAN_SELECTION = SpanSelection.ALL_SPANS;
/**
* Name of the property that overrides the default project ID (overrides the value returned by
* {@code com.google.cloud.ServiceOptions.getDefaultProjectId()}). The name is {@value}.
*
* @since 0.15
*/
public static final String PROJECT_ID_PROPERTY_NAME =
"io.opencensus.contrib.logcorrelation.stackdriver.OpenCensusTraceLoggingEnhancer.projectId";
/**
* Name of the property that defines the {@link SpanSelection}. The name is {@value}.
*
* @since 0.15
*/
public static final String SPAN_SELECTION_PROPERTY_NAME =
"io.opencensus.contrib.logcorrelation.stackdriver."
+ "OpenCensusTraceLoggingEnhancer.spanSelection";
private final String projectId;
private final SpanSelection spanSelection;
// This field caches the prefix used for the LogEntry.trace field and is derived from projectId.
private final String tracePrefix;
/**
* How to decide whether to add tracing data from the current span to a log entry.
*
* @since 0.15
*/
public enum SpanSelection {
/**
* Never add tracing data to log entries. This constant disables the log correlation feature.
*
* @since 0.15
*/
NO_SPANS,
/**
* Add tracing data to a log entry iff the current span is sampled.
*
* @since 0.15
*/
SAMPLED_SPANS,
/**
* Always add tracing data to log entries, even when the current span is not sampled. This is
* the default.
*
* @since 0.15
*/
ALL_SPANS
}
/**
* Constructor to be called by reflection, e.g., by a google-cloud-java {@code LoggingHandler} or
* google-cloud-logging-logback {@code LoggingAppender}.
*
* <p>This constructor looks up the project ID and {@link SpanSelection SpanSelection} from the
* environment. It uses the default project ID (the value returned by {@code
* com.google.cloud.ServiceOptions.getDefaultProjectId()}), unless the ID is overridden by the
* property {@value #PROJECT_ID_PROPERTY_NAME}. It looks up the {@code SpanSelection} using the
* property {@value #SPAN_SELECTION_PROPERTY_NAME}. Each property can be specified with a {@link
* java.util.logging} property or a system property, with preference given to the logging
* property.
*
* @since 0.15
*/
public OpenCensusTraceLoggingEnhancer() {
this(lookUpProjectId(), lookUpSpanSelectionProperty());
}
/**
* Constructs a {@code OpenCensusTraceLoggingEnhancer} with the given project ID and {@code
* SpanSelection}.
*
* @param projectId the project ID for this instance.
* @param spanSelection the {@code SpanSelection} for this instance.
* @since 0.15
*/
public OpenCensusTraceLoggingEnhancer(@Nullable String projectId, SpanSelection spanSelection) {
this.projectId = projectId == null ? "" : projectId;
this.spanSelection = spanSelection;
this.tracePrefix = "projects/" + this.projectId + "/traces/";
}
private static String lookUpProjectId() {
String projectIdProperty = lookUpProperty(PROJECT_ID_PROPERTY_NAME);
return projectIdProperty == null || projectIdProperty.isEmpty()
? ServiceOptions.getDefaultProjectId()
: projectIdProperty;
}
private static SpanSelection lookUpSpanSelectionProperty() {
String spanSelectionProperty = lookUpProperty(SPAN_SELECTION_PROPERTY_NAME);
return spanSelectionProperty == null || spanSelectionProperty.isEmpty()
? DEFAULT_SPAN_SELECTION
: parseSpanSelection(spanSelectionProperty);
}
private static SpanSelection parseSpanSelection(String spanSelection) {
try {
return SpanSelection.valueOf(spanSelection);
} catch (IllegalArgumentException e) {
return DEFAULT_SPAN_SELECTION;
}
}
// An OpenCensusTraceLoggingEnhancer property can be set with a logging property or a system
// property.
@Nullable
private static String lookUpProperty(String name) {
String property = LogManager.getLogManager().getProperty(name);
return property == null || property.isEmpty() ? System.getProperty(name) : property;
}
/**
* Returns the project ID setting for this instance.
*
* @return the project ID setting for this instance.
* @since 0.15
*/
public String getProjectId() {
return projectId;
}
/**
* Returns the {@code SpanSelection} setting for this instance.
*
* @return the {@code SpanSelection} setting for this instance.
* @since 0.15
*/
public SpanSelection getSpanSelection() {
return spanSelection;
}
// This method avoids getting the current span when the feature is disabled, for efficiency.
@Override
public void enhanceLogEntry(LogEntry.Builder builder) {
switch (spanSelection) {
case NO_SPANS:
return;
case SAMPLED_SPANS:
SpanContext span = getCurrentSpanContext();
if (span.getTraceOptions().isSampled()) {
addTracingData(tracePrefix, span, builder);
}
return;
case ALL_SPANS:
addTracingData(tracePrefix, getCurrentSpanContext(), builder);
return;
}
throw new AssertionError("Unknown spanSelection: " + spanSelection);
}
private static SpanContext getCurrentSpanContext() {
Span span = ContextUtils.CONTEXT_SPAN_KEY.get();
return span == null ? SpanContext.INVALID : span.getContext();
}
private static void addTracingData(
String tracePrefix, SpanContext span, LogEntry.Builder builder) {
builder.setTrace(formatTraceId(tracePrefix, span.getTraceId()));
builder.setSpanId(span.getSpanId().toLowerBase16());
// TODO(sebright): Find the correct way to add the sampling decision.
builder.addLabel(SAMPLED_LABEL_KEY, Boolean.toString(span.getTraceOptions().isSampled()));
}
private static String formatTraceId(String tracePrefix, TraceId traceId) {
return tracePrefix + traceId.toLowerBase16();
}
}