package org.stagemonitor.tracing.jaeger; import com.uber.jaeger.SpanContext; import com.uber.jaeger.propagation.Extractor; import com.uber.jaeger.propagation.Injector; import com.uber.jaeger.reporters.NoopReporter; import com.uber.jaeger.samplers.ConstSampler; import org.stagemonitor.core.StagemonitorPlugin; import org.stagemonitor.tracing.B3HeaderFormat; import org.stagemonitor.tracing.TracerFactory; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import java.util.TimeZone; import java.util.concurrent.TimeUnit; import io.opentracing.Tracer; import io.opentracing.propagation.Format; import io.opentracing.propagation.TextMap; public class JaegerTracerFactory extends TracerFactory { @Override public Tracer getTracer(StagemonitorPlugin.InitArguments initArguments) { final B3TextMapCodec b3TextMapCodec = new B3TextMapCodec(); final com.uber.jaeger.Tracer.Builder builder = new com.uber.jaeger.Tracer.Builder( initArguments.getMeasurementSession().getApplicationName(), new NoopReporter(), new ConstSampler(true)) .registerInjector(B3HeaderFormat.INSTANCE, b3TextMapCodec) .registerInjector(Format.Builtin.HTTP_HEADERS, b3TextMapCodec) .registerExtractor(Format.Builtin.HTTP_HEADERS, b3TextMapCodec); return builder.build(); } // TODO use jaeger-b3 module /* * Copyright (c) 2016, Uber Technologies, Inc * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /** * This format is compatible with other trace libraries such as Brave, Wingtips, zipkin-js, etc. * * <p> * Example usage: * * <pre>{@code * b3Codec = new B3TextMapCodec(); * tracer = new Tracer.Builder(serviceName, reporter, sampler) * .registerInjector(Format.Builtin.HTTP_HEADERS, b3Codec) * .registerExtractor(Format.Builtin.HTTP_HEADERS, b3Codec) * ... * }</pre> * * <p> * See <a href="http://zipkin.io/pages/instrumenting.html">Instrumenting a Library</a> */ private static final class B3TextMapCodec implements Injector<TextMap>, Extractor<TextMap> { static final String TRACE_ID_NAME = "X-B3-TraceId"; static final String SPAN_ID_NAME = "X-B3-SpanId"; static final String PARENT_SPAN_ID_NAME = "X-B3-ParentSpanId"; static final String SAMPLED_NAME = "X-B3-Sampled"; static final String FLAGS_NAME = "X-B3-Flags"; // NOTE: uber's flags aren't the same as B3/Finagle ones static final byte SAMPLED_FLAG = 1; static final byte DEBUG_FLAG = 2; @Override public void inject(SpanContext spanContext, TextMap carrier) { carrier.put(TRACE_ID_NAME, Util.toLowerHex(spanContext.getTraceID())); if (spanContext.getParentID() != 0L) { // Conventionally, parent id == 0 means the root span carrier.put(PARENT_SPAN_ID_NAME, Util.toLowerHex(spanContext.getParentID())); } carrier.put(SPAN_ID_NAME, Util.toLowerHex(spanContext.getSpanID())); carrier.put(SAMPLED_NAME, spanContext.isSampled() ? "1" : "0"); if (spanContext.isDebug()) { carrier.put(FLAGS_NAME, "1"); } } @Override public SpanContext extract(TextMap carrier) { Long traceID = null; Long spanID = null; long parentID = 0L; // Conventionally, parent id == 0 means the root span byte flags = 0; for (Map.Entry<String, String> entry : carrier) { if (entry.getKey().equalsIgnoreCase(SAMPLED_NAME)) { if (entry.getValue().equals("1") || entry.getValue().toLowerCase().equals("true")) { flags |= SAMPLED_FLAG; } } else if (entry.getKey().equalsIgnoreCase(TRACE_ID_NAME)) { traceID = Util.lowerHexToUnsignedLong(entry.getValue()); } else if (entry.getKey().equalsIgnoreCase(PARENT_SPAN_ID_NAME)) { parentID = Util.lowerHexToUnsignedLong(entry.getValue()); } else if (entry.getKey().equalsIgnoreCase(SPAN_ID_NAME)) { spanID = Util.lowerHexToUnsignedLong(entry.getValue()); } else if (entry.getKey().equalsIgnoreCase(FLAGS_NAME)) { if (entry.getValue().equals("1")) { flags |= DEBUG_FLAG; } } } if (traceID != null && spanID != null) { return new SpanContext(traceID, spanID, parentID, flags); } return null; } } /** * Copyright 2015-2017 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ private static final class Util { public static final Charset UTF_8 = Charset.forName("UTF-8"); static final TimeZone UTC = TimeZone.getTimeZone("UTC"); public static int envOr(String key, int fallback) { return System.getenv(key) != null ? Integer.parseInt(System.getenv(key)) : fallback; } public static String envOr(String key, String fallback) { return System.getenv(key) != null ? System.getenv(key) : fallback; } public static boolean equal(Object a, Object b) { return a == b || (a != null && a.equals(b)); } /** * Copy of {@code com.google.common.base.Preconditions#checkArgument}. */ public static void checkArgument(boolean expression, String errorMessageTemplate, Object... errorMessageArgs) { if (!expression) { throw new IllegalArgumentException(String.format(errorMessageTemplate, errorMessageArgs)); } } /** * Copy of {@code com.google.common.base.Preconditions#checkNotNull}. */ public static <T> T checkNotNull(T reference, String errorMessage) { if (reference == null) { // If either of these parameters is null, the right thing happens anyway throw new NullPointerException(errorMessage); } return reference; } public static <T extends Comparable<? super T>> List<T> sortedList(Collection<T> in) { if (in == null || in.isEmpty()) return Collections.emptyList(); if (in.size() == 1) return Collections.singletonList(in.iterator().next()); Object[] array = in.toArray(); Arrays.sort(array); List result = Arrays.asList(array); return Collections.unmodifiableList(result); } /** For bucketed data floored to the day. For example, dependency links. */ public static long midnightUTC(long epochMillis) { Calendar day = Calendar.getInstance(UTC); day.setTimeInMillis(epochMillis); day.set(Calendar.MILLISECOND, 0); day.set(Calendar.SECOND, 0); day.set(Calendar.MINUTE, 0); day.set(Calendar.HOUR_OF_DAY, 0); return day.getTimeInMillis(); } public static List<Date> getDays(long endTs, Long lookback) { long to = midnightUTC(endTs); long from = midnightUTC(endTs - (lookback != null ? lookback : endTs)); List<Date> days = new ArrayList<Date>(); for (long time = from; time <= to; time += TimeUnit.DAYS.toMillis(1)) { days.add(new Date(time)); } return days; } /** * Parses a 1 to 32 character lower-hex string with no prefix into an unsigned long, tossing any * bits higher than 64. */ public static long lowerHexToUnsignedLong(String lowerHex) { int length = lowerHex.length(); if (length < 1 || length > 32) throw isntLowerHexLong(lowerHex); // trim off any high bits int beginIndex = length > 16 ? length - 16 : 0; return lowerHexToUnsignedLong(lowerHex, beginIndex); } /** * Parses a 16 character lower-hex string with no prefix into an unsigned long, starting at the * spe index. */ public static long lowerHexToUnsignedLong(String lowerHex, int index) { long result = 0; for (int endIndex = Math.min(index + 16, lowerHex.length()); index < endIndex; index++) { char c = lowerHex.charAt(index); result <<= 4; if (c >= '0' && c <= '9') { result |= c - '0'; } else if (c >= 'a' && c <= 'f') { result |= c - 'a' + 10; } else { throw isntLowerHexLong(lowerHex); } } return result; } static NumberFormatException isntLowerHexLong(String lowerHex) { throw new NumberFormatException( lowerHex + " should be a 1 to 32 character lower-hex string with no prefix"); } /** Returns 16 or 32 character hex string depending on if {@code high} is zero. */ public static String toLowerHex(long high, long low) { char[] result = new char[high != 0 ? 32 : 16]; int pos = 0; if (high != 0) { writeHexLong(result, pos, high); pos += 16; } writeHexLong(result, pos, low); return new String(result); } /** Inspired by {@code okio.Buffer.writeLong} */ public static String toLowerHex(long v) { char[] data = new char[16]; writeHexLong(data, 0, v); return new String(data); } /** Inspired by {@code okio.Buffer.writeLong} */ public static void writeHexLong(char[] data, int pos, long v) { writeHexByte(data, pos + 0, (byte) ((v >>> 56L) & 0xff)); writeHexByte(data, pos + 2, (byte) ((v >>> 48L) & 0xff)); writeHexByte(data, pos + 4, (byte) ((v >>> 40L) & 0xff)); writeHexByte(data, pos + 6, (byte) ((v >>> 32L) & 0xff)); writeHexByte(data, pos + 8, (byte) ((v >>> 24L) & 0xff)); writeHexByte(data, pos + 10, (byte) ((v >>> 16L) & 0xff)); writeHexByte(data, pos + 12, (byte) ((v >>> 8L) & 0xff)); writeHexByte(data, pos + 14, (byte) (v & 0xff)); } // Taken from RxJava throwIfFatal, which was taken from scala public static void propagateIfFatal(Throwable t) { if (t instanceof VirtualMachineError) { throw (VirtualMachineError) t; } else if (t instanceof ThreadDeath) { throw (ThreadDeath) t; } else if (t instanceof LinkageError) { throw (LinkageError) t; } } static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; static void writeHexByte(char[] data, int pos, byte b) { data[pos + 0] = HEX_DIGITS[(b >> 4) & 0xf]; data[pos + 1] = HEX_DIGITS[b & 0xf]; } // throwable ctor not present in JRE 6 static AssertionError assertionError(String message, Throwable cause) { AssertionError error = new AssertionError(message); error.initCause(cause); throw error; } private Util() { } } }