• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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