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