1 /* 2 * Copyright 2018, OpenCensus Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package io.opencensus.contrib.logcorrelation.stackdriver; 18 19 import com.google.cloud.ServiceOptions; 20 import com.google.cloud.logging.LogEntry; 21 import com.google.cloud.logging.LoggingEnhancer; 22 import io.opencensus.common.ExperimentalApi; 23 import io.opencensus.trace.Span; 24 import io.opencensus.trace.SpanContext; 25 import io.opencensus.trace.TraceId; 26 import io.opencensus.trace.unsafe.ContextUtils; 27 import java.util.logging.LogManager; 28 import javax.annotation.Nullable; 29 30 /** 31 * Stackdriver {@link LoggingEnhancer} that adds OpenCensus tracing data to log entries. 32 * 33 * <p>This feature is currently experimental. 34 * 35 * @since 0.15 36 */ 37 @ExperimentalApi 38 public final class OpenCensusTraceLoggingEnhancer implements LoggingEnhancer { 39 private static final String SAMPLED_LABEL_KEY = "opencensusTraceSampled"; 40 private static final SpanSelection DEFAULT_SPAN_SELECTION = SpanSelection.ALL_SPANS; 41 42 /** 43 * Name of the property that overrides the default project ID (overrides the value returned by 44 * {@code com.google.cloud.ServiceOptions.getDefaultProjectId()}). The name is {@value}. 45 * 46 * @since 0.15 47 */ 48 public static final String PROJECT_ID_PROPERTY_NAME = 49 "io.opencensus.contrib.logcorrelation.stackdriver.OpenCensusTraceLoggingEnhancer.projectId"; 50 51 /** 52 * Name of the property that defines the {@link SpanSelection}. The name is {@value}. 53 * 54 * @since 0.15 55 */ 56 public static final String SPAN_SELECTION_PROPERTY_NAME = 57 "io.opencensus.contrib.logcorrelation.stackdriver." 58 + "OpenCensusTraceLoggingEnhancer.spanSelection"; 59 60 private final String projectId; 61 private final SpanSelection spanSelection; 62 63 // This field caches the prefix used for the LogEntry.trace field and is derived from projectId. 64 private final String tracePrefix; 65 66 /** 67 * How to decide whether to add tracing data from the current span to a log entry. 68 * 69 * @since 0.15 70 */ 71 public enum SpanSelection { 72 73 /** 74 * Never add tracing data to log entries. This constant disables the log correlation feature. 75 * 76 * @since 0.15 77 */ 78 NO_SPANS, 79 80 /** 81 * Add tracing data to a log entry iff the current span is sampled. 82 * 83 * @since 0.15 84 */ 85 SAMPLED_SPANS, 86 87 /** 88 * Always add tracing data to log entries, even when the current span is not sampled. This is 89 * the default. 90 * 91 * @since 0.15 92 */ 93 ALL_SPANS 94 } 95 96 /** 97 * Constructor to be called by reflection, e.g., by a google-cloud-java {@code LoggingHandler} or 98 * google-cloud-logging-logback {@code LoggingAppender}. 99 * 100 * <p>This constructor looks up the project ID and {@link SpanSelection SpanSelection} from the 101 * environment. It uses the default project ID (the value returned by {@code 102 * com.google.cloud.ServiceOptions.getDefaultProjectId()}), unless the ID is overridden by the 103 * property {@value #PROJECT_ID_PROPERTY_NAME}. It looks up the {@code SpanSelection} using the 104 * property {@value #SPAN_SELECTION_PROPERTY_NAME}. Each property can be specified with a {@link 105 * java.util.logging} property or a system property, with preference given to the logging 106 * property. 107 * 108 * @since 0.15 109 */ OpenCensusTraceLoggingEnhancer()110 public OpenCensusTraceLoggingEnhancer() { 111 this(lookUpProjectId(), lookUpSpanSelectionProperty()); 112 } 113 114 /** 115 * Constructs a {@code OpenCensusTraceLoggingEnhancer} with the given project ID and {@code 116 * SpanSelection}. 117 * 118 * @param projectId the project ID for this instance. 119 * @param spanSelection the {@code SpanSelection} for this instance. 120 * @since 0.15 121 */ OpenCensusTraceLoggingEnhancer(@ullable String projectId, SpanSelection spanSelection)122 public OpenCensusTraceLoggingEnhancer(@Nullable String projectId, SpanSelection spanSelection) { 123 this.projectId = projectId == null ? "" : projectId; 124 this.spanSelection = spanSelection; 125 this.tracePrefix = "projects/" + this.projectId + "/traces/"; 126 } 127 lookUpProjectId()128 private static String lookUpProjectId() { 129 String projectIdProperty = lookUpProperty(PROJECT_ID_PROPERTY_NAME); 130 return projectIdProperty == null || projectIdProperty.isEmpty() 131 ? ServiceOptions.getDefaultProjectId() 132 : projectIdProperty; 133 } 134 lookUpSpanSelectionProperty()135 private static SpanSelection lookUpSpanSelectionProperty() { 136 String spanSelectionProperty = lookUpProperty(SPAN_SELECTION_PROPERTY_NAME); 137 return spanSelectionProperty == null || spanSelectionProperty.isEmpty() 138 ? DEFAULT_SPAN_SELECTION 139 : parseSpanSelection(spanSelectionProperty); 140 } 141 parseSpanSelection(String spanSelection)142 private static SpanSelection parseSpanSelection(String spanSelection) { 143 try { 144 return SpanSelection.valueOf(spanSelection); 145 } catch (IllegalArgumentException e) { 146 return DEFAULT_SPAN_SELECTION; 147 } 148 } 149 150 // An OpenCensusTraceLoggingEnhancer property can be set with a logging property or a system 151 // property. 152 @Nullable lookUpProperty(String name)153 private static String lookUpProperty(String name) { 154 String property = LogManager.getLogManager().getProperty(name); 155 return property == null || property.isEmpty() ? System.getProperty(name) : property; 156 } 157 158 /** 159 * Returns the project ID setting for this instance. 160 * 161 * @return the project ID setting for this instance. 162 * @since 0.15 163 */ getProjectId()164 public String getProjectId() { 165 return projectId; 166 } 167 168 /** 169 * Returns the {@code SpanSelection} setting for this instance. 170 * 171 * @return the {@code SpanSelection} setting for this instance. 172 * @since 0.15 173 */ getSpanSelection()174 public SpanSelection getSpanSelection() { 175 return spanSelection; 176 } 177 178 // This method avoids getting the current span when the feature is disabled, for efficiency. 179 @Override enhanceLogEntry(LogEntry.Builder builder)180 public void enhanceLogEntry(LogEntry.Builder builder) { 181 switch (spanSelection) { 182 case NO_SPANS: 183 return; 184 case SAMPLED_SPANS: 185 SpanContext span = getCurrentSpanContext(); 186 if (span.getTraceOptions().isSampled()) { 187 addTracingData(tracePrefix, span, builder); 188 } 189 return; 190 case ALL_SPANS: 191 addTracingData(tracePrefix, getCurrentSpanContext(), builder); 192 return; 193 } 194 throw new AssertionError("Unknown spanSelection: " + spanSelection); 195 } 196 getCurrentSpanContext()197 private static SpanContext getCurrentSpanContext() { 198 Span span = ContextUtils.CONTEXT_SPAN_KEY.get(); 199 return span == null ? SpanContext.INVALID : span.getContext(); 200 } 201 addTracingData( String tracePrefix, SpanContext span, LogEntry.Builder builder)202 private static void addTracingData( 203 String tracePrefix, SpanContext span, LogEntry.Builder builder) { 204 builder.setTrace(formatTraceId(tracePrefix, span.getTraceId())); 205 builder.setSpanId(span.getSpanId().toLowerBase16()); 206 207 // TODO(sebright): Find the correct way to add the sampling decision. 208 builder.addLabel(SAMPLED_LABEL_KEY, Boolean.toString(span.getTraceOptions().isSampled())); 209 } 210 formatTraceId(String tracePrefix, TraceId traceId)211 private static String formatTraceId(String tracePrefix, TraceId traceId) { 212 return tracePrefix + traceId.toLowerBase16(); 213 } 214 } 215