|
| 1 | +package datadog.trace.core.propagation; |
| 2 | + |
| 3 | +import static datadog.trace.api.TracePropagationStyle.TRACECONTEXT; |
| 4 | +import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_DROP; |
| 5 | +import static datadog.trace.api.sampling.PrioritySampling.SAMPLER_KEEP; |
| 6 | +import static datadog.trace.core.propagation.HttpCodec.firstHeaderValue; |
| 7 | +import static datadog.trace.core.propagation.PropagationTags.HeaderType.W3C; |
| 8 | + |
| 9 | +import static java.util.concurrent.TimeUnit.MILLISECONDS; |
| 10 | +import static java.util.concurrent.TimeUnit.NANOSECONDS; |
| 11 | + |
| 12 | +import datadog.trace.api.Config; |
| 13 | +import datadog.trace.api.DD128bTraceId; |
| 14 | +import datadog.trace.api.DDSpanId; |
| 15 | +import datadog.trace.api.DDTags; |
| 16 | +import datadog.trace.api.DDTraceId; |
| 17 | +import datadog.trace.api.TraceConfig; |
| 18 | +import datadog.trace.api.TracePropagationStyle; |
| 19 | +import datadog.trace.api.internal.util.LongStringUtils; |
| 20 | +import datadog.trace.api.sampling.PrioritySampling; |
| 21 | +import datadog.trace.api.sampling.SamplingMechanism; |
| 22 | +import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; |
| 23 | +import datadog.trace.bootstrap.instrumentation.api.TagContext; |
| 24 | +import datadog.trace.core.DDSpanContext; |
| 25 | + |
| 26 | +import java.util.Map; |
| 27 | +import java.util.TreeMap; |
| 28 | +import java.util.function.Supplier; |
| 29 | + |
| 30 | +import java.net.URLEncoder; |
| 31 | +import java.nio.charset.StandardCharsets; |
| 32 | + |
| 33 | +import org.slf4j.Logger; |
| 34 | +import org.slf4j.LoggerFactory; |
| 35 | + |
| 36 | +/** A codec designed for HTTP transport via headers using W3C "baggage" header */ |
| 37 | +class W3CBaggageHttpCodec { |
| 38 | + private static final Logger log = LoggerFactory.getLogger(W3CHttpCodec.class); |
| 39 | + |
| 40 | + static final String BAGGAGE_KEY = "baggage"; |
| 41 | + |
| 42 | + private W3CBaggageHttpCodec() { |
| 43 | + // This class should not be created. This also makes code coverage checks happy. |
| 44 | + } |
| 45 | + |
| 46 | + public static HttpCodec.Injector newInjector(Map<String, String> invertedBaggageMapping) { |
| 47 | + return new Injector(invertedBaggageMapping); |
| 48 | + } |
| 49 | + |
| 50 | + private static class Injector implements HttpCodec.Injector { |
| 51 | + |
| 52 | + private final Map<String, String> invertedBaggageMapping; |
| 53 | + |
| 54 | + public Injector(Map<String, String> invertedBaggageMapping) { |
| 55 | + assert invertedBaggageMapping != null; |
| 56 | + this.invertedBaggageMapping = invertedBaggageMapping; |
| 57 | + } |
| 58 | + |
| 59 | + @Override |
| 60 | + public <C> void inject( |
| 61 | + final DDSpanContext context, final C carrier, final AgentPropagation.Setter<C> setter) { |
| 62 | + StringBuilder baggageHeader = new StringBuilder(); |
| 63 | + |
| 64 | + for (final Map.Entry<String, String> entry : context.baggageItems()) { |
| 65 | + if (baggageHeader.length() > 0) { |
| 66 | + baggageHeader.append(","); |
| 67 | + } |
| 68 | + |
| 69 | + String header = invertedBaggageMapping.get(entry.getKey()); |
| 70 | + header = header != null ? header : entry.getKey(); |
| 71 | + String value = HttpCodec.encodeBaggage(entry.getValue()); |
| 72 | + |
| 73 | + baggageHeader.append(header).append("=").append(value); |
| 74 | + } |
| 75 | + |
| 76 | + setter.set(carrier, BAGGAGE_KEY, baggageHeader.toString()); |
| 77 | + } |
| 78 | + } |
| 79 | + |
| 80 | + public static HttpCodec.Extractor newExtractor( |
| 81 | + Config config, Supplier<TraceConfig> traceConfigSupplier) { |
| 82 | + return new TagContextExtractor(traceConfigSupplier, () -> new W3CBaggageContextInterpreter(config)); |
| 83 | + } |
| 84 | + |
| 85 | + private static class W3CBaggageContextInterpreter extends ContextInterpreter { |
| 86 | + |
| 87 | + private W3CBaggageContextInterpreter(Config config) { |
| 88 | + super(config); |
| 89 | + } |
| 90 | + |
| 91 | + @Override |
| 92 | + public TracePropagationStyle style() { |
| 93 | + return BAGGAGE; |
| 94 | + } |
| 95 | + |
| 96 | + @Override |
| 97 | + public boolean accept(String key, String value) { |
| 98 | + if (null == key || key.isEmpty()) { |
| 99 | + return true; |
| 100 | + } |
| 101 | + if (LOG_EXTRACT_HEADER_NAMES) { |
| 102 | + log.debug("Header: {}", key); |
| 103 | + } |
| 104 | + |
| 105 | + if (first == 'b' && BAGGAGE_KEY.equalsIgnoreCase(key)) { |
| 106 | + try { |
| 107 | + if (baggage.isEmpty()) { |
| 108 | + baggage = new TreeMap<>(); |
| 109 | + } |
| 110 | + |
| 111 | + // Split by comma |
| 112 | + for (String entry : header.split(",")) { |
| 113 | + // Split into key=value |
| 114 | + String[] keyValue = entry.split("=", 2); |
| 115 | + if (keyValue.length == 2) { |
| 116 | + baggage.put( |
| 117 | + HttpCodec.decode(keyValue[0].trim()), |
| 118 | + HttpCodec.decode(keyValue[1].trim()) |
| 119 | + ); |
| 120 | + } |
| 121 | + } |
| 122 | + } catch (RuntimeException e) { |
| 123 | + invalidateContext(); |
| 124 | + log.debug("Exception when extracting baggage", e); |
| 125 | + return false; |
| 126 | + } |
| 127 | + } |
| 128 | + return true; |
| 129 | + } |
| 130 | + |
| 131 | + private long extractEndToEndStartTime(String value) { |
| 132 | + try { |
| 133 | + return MILLISECONDS.toNanos(Long.parseLong(value)); |
| 134 | + } catch (RuntimeException e) { |
| 135 | + log.debug("Ignoring invalid end-to-end start time {}", value, e); |
| 136 | + return 0; |
| 137 | + } |
| 138 | + } |
| 139 | + |
| 140 | + private boolean storeTraceParent(String value) { |
| 141 | + String trimmed = trim(value); |
| 142 | + if (traceparentHeader != null) { |
| 143 | + // We should not accept multiple traceparent headers |
| 144 | + if (log.isDebugEnabled()) { |
| 145 | + log.debug( |
| 146 | + "Multiple traceparent headers. Had '{}' and got '{}'", traceparentHeader, trimmed); |
| 147 | + } |
| 148 | + onlyTagContext(); |
| 149 | + } else { |
| 150 | + traceparentHeader = trimmed; |
| 151 | + } |
| 152 | + return true; |
| 153 | + } |
| 154 | + |
| 155 | + private boolean storeTraceState(String value) { |
| 156 | + String trimmed = trim(value); |
| 157 | + if (!trimmed.isEmpty()) { |
| 158 | + // Yes, this is not efficient for multiple headers, but that is hopefully not common |
| 159 | + tracestateHeader = tracestateHeader == null ? trimmed : tracestateHeader + "," + trimmed; |
| 160 | + } |
| 161 | + return true; |
| 162 | + } |
| 163 | + |
| 164 | + private static String trim(String input) { |
| 165 | + if (input == null) { |
| 166 | + return ""; |
| 167 | + } |
| 168 | + final int last = input.length() - 1; |
| 169 | + if (last == 0) { |
| 170 | + return input; |
| 171 | + } |
| 172 | + int start; |
| 173 | + for (start = 0; start <= last; start++) { |
| 174 | + char c = input.charAt(start); |
| 175 | + if (c != '\t' && c != ' ') { |
| 176 | + break; |
| 177 | + } |
| 178 | + } |
| 179 | + int end; |
| 180 | + for (end = last; end > start; end--) { |
| 181 | + char c = input.charAt(end); |
| 182 | + if (c != '\t' && c != ' ') { |
| 183 | + break; |
| 184 | + } |
| 185 | + } |
| 186 | + if (start == 0 && end == last) { |
| 187 | + return input; |
| 188 | + } else { |
| 189 | + return input.substring(start, end + 1); |
| 190 | + } |
| 191 | + } |
| 192 | + } |
| 193 | +} |
0 commit comments