• 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.log4j2;
18 
19 import io.opencensus.contrib.logcorrelation.log4j2.OpenCensusTraceContextDataInjector.SpanSelection;
20 import io.opencensus.trace.Span;
21 import io.opencensus.trace.SpanContext;
22 import io.opencensus.trace.unsafe.ContextUtils;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Map.Entry;
28 import javax.annotation.Nullable;
29 import javax.annotation.concurrent.Immutable;
30 import org.apache.logging.log4j.ThreadContext;
31 import org.apache.logging.log4j.core.config.Property;
32 import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap;
33 import org.apache.logging.log4j.util.BiConsumer;
34 import org.apache.logging.log4j.util.ReadOnlyStringMap;
35 import org.apache.logging.log4j.util.SortedArrayStringMap;
36 import org.apache.logging.log4j.util.StringMap;
37 import org.apache.logging.log4j.util.TriConsumer;
38 
39 // Implementation of the methods inherited from ContextDataInjector.
40 //
41 // This class uses "shareable" to mean that a method's return value can be passed to another
42 // thread.
43 final class ContextDataUtils {
ContextDataUtils()44   private ContextDataUtils() {}
45 
46   // The implementation of this method is based on the example in the Javadocs for
47   // ContextDataInjector.injectContextData.
injectContextData( SpanSelection spanSelection, @Nullable List<Property> properties, StringMap reusable)48   static StringMap injectContextData(
49       SpanSelection spanSelection, @Nullable List<Property> properties, StringMap reusable) {
50     if (properties == null || properties.isEmpty()) {
51       return shareableRawContextData(spanSelection);
52     }
53     // Context data has precedence over configuration properties.
54     putProperties(properties, reusable);
55     // TODO(sebright): The following line can be optimized. See
56     //     https://github.com/census-instrumentation/opencensus-java/pull/1422/files#r216425494.
57     reusable.putAll(nonShareableRawContextData(spanSelection));
58     return reusable;
59   }
60 
putProperties(Collection<Property> properties, StringMap stringMap)61   private static void putProperties(Collection<Property> properties, StringMap stringMap) {
62     for (Property property : properties) {
63       stringMap.putValue(property.getName(), property.getValue());
64     }
65   }
66 
shareableRawContextData(SpanSelection spanSelection)67   private static StringMap shareableRawContextData(SpanSelection spanSelection) {
68     SpanContext spanContext = shouldAddTracingDataToLogEvent(spanSelection);
69     return spanContext == null
70         ? getShareableContextData()
71         : getShareableContextAndTracingData(spanContext);
72   }
73 
nonShareableRawContextData(SpanSelection spanSelection)74   static ReadOnlyStringMap nonShareableRawContextData(SpanSelection spanSelection) {
75     SpanContext spanContext = shouldAddTracingDataToLogEvent(spanSelection);
76     return spanContext == null
77         ? getNonShareableContextData()
78         : getShareableContextAndTracingData(spanContext);
79   }
80 
81   // This method returns the current span context iff tracing data should be added to the LogEvent.
82   // It avoids getting the current span when the feature is disabled, for efficiency.
83   @Nullable
shouldAddTracingDataToLogEvent(SpanSelection spanSelection)84   private static SpanContext shouldAddTracingDataToLogEvent(SpanSelection spanSelection) {
85     switch (spanSelection) {
86       case NO_SPANS:
87         return null;
88       case SAMPLED_SPANS:
89         SpanContext spanContext = getCurrentSpanContext();
90         if (spanContext.getTraceOptions().isSampled()) {
91           return spanContext;
92         } else {
93           return null;
94         }
95       case ALL_SPANS:
96         return getCurrentSpanContext();
97     }
98     throw new AssertionError("Unknown spanSelection: " + spanSelection);
99   }
100 
getShareableContextData()101   private static StringMap getShareableContextData() {
102     ReadOnlyThreadContextMap context = ThreadContext.getThreadContextMap();
103 
104     // Return a new object, since StringMap is modifiable.
105     return context == null
106         ? new SortedArrayStringMap(ThreadContext.getImmutableContext())
107         : new SortedArrayStringMap(context.getReadOnlyContextData());
108   }
109 
getNonShareableContextData()110   private static ReadOnlyStringMap getNonShareableContextData() {
111     ReadOnlyThreadContextMap context = ThreadContext.getThreadContextMap();
112     if (context != null) {
113       return context.getReadOnlyContextData();
114     } else {
115       Map<String, String> contextMap = ThreadContext.getImmutableContext();
116       return contextMap.isEmpty()
117           ? UnmodifiableReadOnlyStringMap.EMPTY
118           : new UnmodifiableReadOnlyStringMap(contextMap);
119     }
120   }
121 
getShareableContextAndTracingData(SpanContext spanContext)122   private static StringMap getShareableContextAndTracingData(SpanContext spanContext) {
123     ReadOnlyThreadContextMap context = ThreadContext.getThreadContextMap();
124     SortedArrayStringMap stringMap;
125     if (context == null) {
126       stringMap = new SortedArrayStringMap(ThreadContext.getImmutableContext());
127     } else {
128       StringMap contextData = context.getReadOnlyContextData();
129       stringMap = new SortedArrayStringMap(contextData.size() + 3);
130       stringMap.putAll(contextData);
131     }
132     // TODO(sebright): Move the calls to TraceId.toLowerBase16() and SpanId.toLowerBase16() out of
133     // the critical path by wrapping the trace and span IDs in objects that call toLowerBase16() in
134     // their toString() methods, after there is a fix for
135     // https://github.com/census-instrumentation/opencensus-java/issues/1436.
136     stringMap.putValue(
137         OpenCensusTraceContextDataInjector.TRACE_ID_CONTEXT_KEY,
138         spanContext.getTraceId().toLowerBase16());
139     stringMap.putValue(
140         OpenCensusTraceContextDataInjector.SPAN_ID_CONTEXT_KEY,
141         spanContext.getSpanId().toLowerBase16());
142     stringMap.putValue(
143         OpenCensusTraceContextDataInjector.TRACE_SAMPLED_CONTEXT_KEY,
144         spanContext.getTraceOptions().isSampled() ? "true" : "false");
145     return stringMap;
146   }
147 
getCurrentSpanContext()148   private static SpanContext getCurrentSpanContext() {
149     Span span = ContextUtils.CONTEXT_SPAN_KEY.get();
150     return span == null ? SpanContext.INVALID : span.getContext();
151   }
152 
153   @Immutable
154   private static final class UnmodifiableReadOnlyStringMap implements ReadOnlyStringMap {
155     private static final long serialVersionUID = 0L;
156 
157     static final ReadOnlyStringMap EMPTY =
158         new UnmodifiableReadOnlyStringMap(Collections.<String, String>emptyMap());
159 
160     private final Map<String, String> map;
161 
UnmodifiableReadOnlyStringMap(Map<String, String> map)162     UnmodifiableReadOnlyStringMap(Map<String, String> map) {
163       this.map = map;
164     }
165 
166     @Override
containsKey(String key)167     public boolean containsKey(String key) {
168       return map.containsKey(key);
169     }
170 
171     @Override
172     @SuppressWarnings("unchecked")
forEach(BiConsumer<String, ? super V> action)173     public <V> void forEach(BiConsumer<String, ? super V> action) {
174       for (Entry<String, String> entry : map.entrySet()) {
175         action.accept(entry.getKey(), (V) entry.getValue());
176       }
177     }
178 
179     @Override
180     @SuppressWarnings("unchecked")
forEach(TriConsumer<String, ? super V, S> action, S state)181     public <V, S> void forEach(TriConsumer<String, ? super V, S> action, S state) {
182       for (Entry<String, String> entry : map.entrySet()) {
183         action.accept(entry.getKey(), (V) entry.getValue(), state);
184       }
185     }
186 
187     @Override
188     @Nullable
189     @SuppressWarnings({
190       "unchecked",
191       "TypeParameterUnusedInFormals" // This is an overridden method.
192     })
getValue(String key)193     public <V> V getValue(String key) {
194       return (V) map.get(key);
195     }
196 
197     @Override
isEmpty()198     public boolean isEmpty() {
199       return map.isEmpty();
200     }
201 
202     @Override
size()203     public int size() {
204       return map.size();
205     }
206 
207     @Override
toMap()208     public Map<String, String> toMap() {
209       return map;
210     }
211   }
212 }
213