All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.uber.jaeger.Tracer Maven / Gradle / Ivy

There is a newer version: 0.27.0
Show newest version
/*
 * Copyright (c) 2016, Uber Technologies, Inc
 *
 * 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.
 */

package com.uber.jaeger;

import com.uber.jaeger.baggage.BaggageRestrictionManager;
import com.uber.jaeger.baggage.BaggageSetter;
import com.uber.jaeger.baggage.DefaultBaggageRestrictionManager;
import com.uber.jaeger.exceptions.UnsupportedFormatException;
import com.uber.jaeger.metrics.Metrics;
import com.uber.jaeger.metrics.MetricsFactory;
import com.uber.jaeger.metrics.NoopMetricsFactory;
import com.uber.jaeger.metrics.StatsFactoryImpl;
import com.uber.jaeger.metrics.StatsReporter;
import com.uber.jaeger.propagation.Extractor;
import com.uber.jaeger.propagation.Injector;
import com.uber.jaeger.propagation.TextMapCodec;
import com.uber.jaeger.reporters.RemoteReporter;
import com.uber.jaeger.reporters.Reporter;
import com.uber.jaeger.samplers.RemoteControlledSampler;
import com.uber.jaeger.samplers.Sampler;
import com.uber.jaeger.samplers.SamplingStatus;
import com.uber.jaeger.utils.Clock;
import com.uber.jaeger.utils.SystemClock;
import com.uber.jaeger.utils.Utils;
import io.opentracing.References;
import io.opentracing.Scope;
import io.opentracing.ScopeManager;
import io.opentracing.propagation.Format;
import io.opentracing.tag.Tags;
import io.opentracing.util.ThreadLocalScopeManager;
import java.io.Closeable;
import java.io.InputStream;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;

@ToString(exclude = {"registry", "clock", "metrics", "scopeManager"})
@Slf4j
public class Tracer implements io.opentracing.Tracer, Closeable {

  private final String version;
  private final String serviceName;
  private final Reporter reporter;
  private final Sampler sampler;
  private final PropagationRegistry registry;
  private final Clock clock;
  private final Metrics metrics;
  private final int ipv4;
  private final Map tags;
  private final boolean zipkinSharedRpcSpan;
  private final ScopeManager scopeManager;
  private final BaggageSetter baggageSetter;
  private final boolean expandExceptionLogs;

  private Tracer(
      String serviceName,
      Reporter reporter,
      Sampler sampler,
      PropagationRegistry registry,
      Clock clock,
      Metrics metrics,
      Map tags,
      boolean zipkinSharedRpcSpan,
      ScopeManager scopeManager,
      BaggageRestrictionManager baggageRestrictionManager,
      boolean expandExceptionLogs) {
    this.serviceName = serviceName;
    this.reporter = reporter;
    this.sampler = sampler;
    this.registry = registry;
    this.clock = clock;
    this.metrics = metrics;
    this.zipkinSharedRpcSpan = zipkinSharedRpcSpan;
    this.scopeManager = scopeManager;
    this.baggageSetter = new BaggageSetter(baggageRestrictionManager, metrics);
    this.expandExceptionLogs = expandExceptionLogs;

    this.version = loadVersion();

    tags.put(Constants.JAEGER_CLIENT_VERSION_TAG_KEY, this.version);
    String hostname = getHostName();
    if (hostname != null) {
      tags.put(Constants.TRACER_HOSTNAME_TAG_KEY, hostname);
    }
    int ipv4 ;
    try {
      tags.put(Constants.TRACER_IP_TAG_KEY, InetAddress.getLocalHost().getHostAddress());
      ipv4 = Utils.ipToInt(Inet4Address.getLocalHost().getHostAddress());
    } catch (UnknownHostException e) {
      ipv4 = 0;
    }
    this.ipv4 = ipv4;
    this.tags = Collections.unmodifiableMap(tags);
  }

  public String getVersion() {
    return version;
  }

  public Metrics getMetrics() {
    return metrics;
  }

  public String getServiceName() {
    return serviceName;
  }

  public Map tags() {
    return tags;
  }

  public int getIpv4() {
    return ipv4;
  }

  Clock clock() {
    return clock;
  }

  Reporter getReporter() {
    return reporter;
  }

  void reportSpan(Span span) {
    reporter.report(span);
    metrics.spansFinished.inc(1);
  }

  @Override
  public ScopeManager scopeManager() {
    return scopeManager;
  }

  @Override
  public io.opentracing.Span activeSpan() {
    Scope scope = this.scopeManager.active();
    return scope == null ? null : scope.span();
  }

  @Override
  public io.opentracing.Tracer.SpanBuilder buildSpan(String operationName) {
    return new SpanBuilder(operationName);
  }

  @Override
  public  void inject(io.opentracing.SpanContext spanContext, Format format, T carrier) {
    Injector injector = registry.getInjector(format);
    if (injector == null) {
      throw new UnsupportedFormatException(format);
    }
    injector.inject((SpanContext) spanContext, carrier);
  }

  @Override
  public  io.opentracing.SpanContext extract(Format format, T carrier) {
    Extractor extractor = registry.getExtractor(format);
    if (extractor == null) {
      throw new UnsupportedFormatException(format);
    }
    return extractor.extract(carrier);
  }

  /**
   * Shuts down the {@link Reporter} and {@link Sampler}
   */
  @Override
  public void close() {
    reporter.close();
    sampler.close();
  }

  //Visible for testing
  class SpanBuilder implements io.opentracing.Tracer.SpanBuilder {

    private String operationName = null;
    private long startTimeMicroseconds;
    /**
     * In 99% situations there is only one parent (childOf), so we do not want to allocate
     * a collection of references.
     */
    private List references = Collections.emptyList();
    private final Map tags = new HashMap();
    private boolean ignoreActiveSpan = false;

    SpanBuilder(String operationName) {
      this.operationName = operationName;
    }

    @Override
    public io.opentracing.Tracer.SpanBuilder asChildOf(io.opentracing.SpanContext parent) {
      return addReference(References.CHILD_OF, parent);
    }

    @Override
    public io.opentracing.Tracer.SpanBuilder asChildOf(io.opentracing.Span parent) {
      return addReference(References.CHILD_OF, parent != null ? parent.context() : null);
    }

    @Override
    public io.opentracing.Tracer.SpanBuilder addReference(
        String referenceType, io.opentracing.SpanContext referencedContext) {

      if (!(referencedContext instanceof SpanContext)) {
        return this;
      }

      // Jaeger thrift currently does not support other reference types
      if (!References.CHILD_OF.equals(referenceType)
          && !References.FOLLOWS_FROM.equals(referenceType)) {
        return this;
      }

      if (references.isEmpty()) {
        // Optimization for 99% situations, when there is only one parent
        references = Collections.singletonList(new Reference((SpanContext) referencedContext, referenceType));
      } else {
        if (references.size() == 1) {
          references = new ArrayList(references);
        }
        references.add(new Reference((SpanContext) referencedContext, referenceType));
      }

      return this;
    }

    @Override
    public io.opentracing.Tracer.SpanBuilder withTag(String key, String value) {
      tags.put(key, value);
      return this;
    }

    @Override
    public io.opentracing.Tracer.SpanBuilder withTag(String key, boolean value) {
      tags.put(key, value);
      return this;
    }

    @Override
    public io.opentracing.Tracer.SpanBuilder withTag(String key, Number value) {
      tags.put(key, value);
      return this;
    }

    @Override
    public io.opentracing.Tracer.SpanBuilder withStartTimestamp(long microseconds) {
      this.startTimeMicroseconds = microseconds;
      return this;
    }

    private SpanContext createNewContext(String debugId) {
      long id = Utils.uniqueId();

      byte flags = 0;
      if (debugId != null) {
        flags = (byte) (flags | SpanContext.flagSampled | SpanContext.flagDebug);
        tags.put(Constants.DEBUG_ID_HEADER_KEY, debugId);
        metrics.traceStartedSampled.inc(1);
      } else {
        //TODO(prithvi): Don't assume operationName is set on creation
        SamplingStatus samplingStatus = sampler.sample(operationName, id);
        if (samplingStatus.isSampled()) {
          flags |= SpanContext.flagSampled;
          tags.putAll(samplingStatus.getTags());
          metrics.traceStartedSampled.inc(1);
        } else {
          metrics.traceStartedNotSampled.inc(1);
        }
      }

      return new SpanContext(id, id, 0, flags);
    }

    private Map createChildBaggage() {
      Map baggage = null;

      // optimization for 99% use cases, when there is only one parent
      if (references.size() == 1) {
        return references.get(0).getSpanContext().baggage();
      }

      for (Reference reference: references) {
        if (reference.getSpanContext().baggage() != null) {
          if (baggage == null) {
            baggage = new HashMap();
          }
          baggage.putAll(reference.getSpanContext().baggage());
        }
      }

      return baggage;
    }

    private SpanContext createChildContext() {
      SpanContext preferredReference = preferredReference();

      if (isRpcServer()) {
        if (isSampled()) {
          metrics.tracesJoinedSampled.inc(1);
        } else {
          metrics.tracesJoinedNotSampled.inc(1);
        }

        // Zipkin server compatibility
        if (zipkinSharedRpcSpan) {
          return preferredReference;
        }
      }

      return new SpanContext(
          preferredReference.getTraceId(),
          Utils.uniqueId(),
          preferredReference.getSpanId(),
          // should we do OR across passed references?
          preferredReference.getFlags(),
          createChildBaggage(),
          null);
    }

    //Visible for testing
    boolean isRpcServer() {
      return Tags.SPAN_KIND_SERVER.equals(tags.get(Tags.SPAN_KIND.getKey()));
    }

    private SpanContext preferredReference() {
      Reference preferredReference = references.get(0);
      for (Reference reference: references) {
        // childOf takes precedence as a preferred parent
        if (References.CHILD_OF.equals(reference.getType())
            && !References.CHILD_OF.equals(preferredReference.getType())) {
          preferredReference = reference;
          break;
        }
      }
      return preferredReference.getSpanContext();
    }

    private boolean isSampled() {
      if (references != null) {
        for (Reference reference : references) {
          if (reference.getSpanContext().isSampled()) {
            return true;
          }
        }
      }
      return false;
    }

    private String debugId() {
      if (references.size() == 1 && references.get(0).getSpanContext().isDebugIdContainerOnly()) {
        return references.get(0).getSpanContext().getDebugId();
      }
      return null;
    }

    @Override
    public io.opentracing.Span start() {
      SpanContext context;

      // Check if active span should be established as CHILD_OF relationship
      if (references.isEmpty() && !ignoreActiveSpan && null != scopeManager.active()) {
        asChildOf(scopeManager.active().span());
      }

      String debugId = debugId();
      if (references.isEmpty()) {
        context = createNewContext(null);
      } else if (debugId != null) {
        context = createNewContext(debugId);
      } else {
        context = createChildContext();
      }

      long startTimeNanoTicks = 0;
      boolean computeDurationViaNanoTicks = false;

      if (startTimeMicroseconds == 0) {
        startTimeMicroseconds = clock.currentTimeMicros();
        if (!clock.isMicrosAccurate()) {
          startTimeNanoTicks = clock.currentNanoTicks();
          computeDurationViaNanoTicks = true;
        }
      }

      Span span =
          new Span(
              Tracer.this,
              operationName,
              context,
              startTimeMicroseconds,
              startTimeNanoTicks,
              computeDurationViaNanoTicks,
              tags,
              references);
      if (context.isSampled()) {
        metrics.spansStartedSampled.inc(1);
      } else {
        metrics.spansStartedNotSampled.inc(1);
      }
      return span;
    }

    @Override
    public Scope startActive(boolean finishSpanOnClose) {
      return scopeManager.activate(start(), finishSpanOnClose);
    }

    @Override
    public io.opentracing.Tracer.SpanBuilder ignoreActiveSpan() {
      ignoreActiveSpan = true;
      return this;
    }

    @Override
    @Deprecated
    public io.opentracing.Span startManual() {
      return start();
    }
  }

  /**
   * Builds Jaeger Tracer with options.
   */
  public static final class Builder {
    private Sampler sampler;
    private Reporter reporter;
    private final PropagationRegistry registry = new PropagationRegistry();
    private Metrics metrics = new Metrics(new NoopMetricsFactory());
    private final String serviceName;
    private Clock clock = new SystemClock();
    private Map tags = new HashMap();
    private boolean zipkinSharedRpcSpan;
    private ScopeManager scopeManager = new ThreadLocalScopeManager();
    private BaggageRestrictionManager baggageRestrictionManager = new DefaultBaggageRestrictionManager();
    private boolean expandExceptionLogs;

    public Builder(String serviceName) {
      this.serviceName = checkValidServiceName(serviceName);
      TextMapCodec textMapCodec = new TextMapCodec(false);
      this.registerInjector(Format.Builtin.TEXT_MAP, textMapCodec);
      this.registerExtractor(Format.Builtin.TEXT_MAP, textMapCodec);
      TextMapCodec httpCodec = new TextMapCodec(true);
      this.registerInjector(Format.Builtin.HTTP_HEADERS, httpCodec);
      this.registerExtractor(Format.Builtin.HTTP_HEADERS, httpCodec);
      // TODO binary codec not implemented
    }

    /**
     * Use {@link #Builder(String)} and fluent API {@link #withReporter(Reporter)}, {@link #withSampler(Sampler)}
     */
    @Deprecated
    public Builder(String serviceName, Reporter reporter, Sampler sampler) {
      this(serviceName);
      this.reporter = reporter;
      this.sampler = sampler;
    }

    /**
     * @param reporter reporter.
     */
    public Builder withReporter(Reporter reporter) {
      this.reporter = reporter;
      return this;
    }

    /**
     * @param sampler sampler.
     */
    public Builder withSampler(Sampler sampler) {
      this.sampler = sampler;
      return this;
    }

    public  Builder registerInjector(Format format, Injector injector) {
      this.registry.register(format, injector);
      return this;
    }

    public  Builder registerExtractor(Format format, Extractor extractor) {
      this.registry.register(format, extractor);
      return this;
    }

    /**
     * @deprecated Use {@link #withMetricsFactory(MetricsFactory)} instead
     */
    @Deprecated
    public Builder withStatsReporter(StatsReporter statsReporter) {
      this.metrics = new Metrics(new StatsFactoryImpl(statsReporter));
      return this;
    }

    /**
     * Creates a new {@link Metrics} to be used with the tracer, backed by the given {@link MetricsFactory}
     * @param metricsFactory the metrics factory to use
     * @return this instance of the builder
     */
    public Builder withMetricsFactory(MetricsFactory metricsFactory) {
      this.metrics = new Metrics(metricsFactory);
      return this;
    }

    public Builder withScopeManager(ScopeManager scopeManager) {
      this.scopeManager = scopeManager;
      return this;
    }

    public Builder withClock(Clock clock) {
      this.clock = clock;
      return this;
    }

    public Builder withZipkinSharedRpcSpan() {
      zipkinSharedRpcSpan = true;
      return this;
    }

    public Builder withExpandExceptionLogs() {
      this.expandExceptionLogs = true;
      return this;
    }

    public Builder withMetrics(Metrics metrics) {
      this.metrics = metrics;
      return this;
    }

    public Builder withTag(String key, String value) {
      tags.put(key, value);
      return this;
    }

    public Builder withTag(String key, boolean value) {
      tags.put(key, value);
      return this;
    }

    public Builder withTag(String key, Number value) {
      tags.put(key, value);
      return this;
    }

    public Builder withTags(Map tags) {
      if (tags != null) {
        this.tags.putAll(tags);
      }
      return this;
    }

    public Builder withBaggageRestrictionManager(BaggageRestrictionManager baggageRestrictionManager) {
      this.baggageRestrictionManager = baggageRestrictionManager;
      return this;
    }

    public Tracer build() {
      if (reporter == null) {
        reporter = new RemoteReporter.Builder()
            .withMetrics(metrics)
            .build();
      }
      if (sampler == null) {
        sampler = new RemoteControlledSampler.Builder(serviceName)
            .withMetrics(metrics)
            .build();
      }
      return new Tracer(serviceName, reporter, sampler, registry, clock, metrics, tags,
          zipkinSharedRpcSpan, scopeManager, baggageRestrictionManager, expandExceptionLogs);
    }

    public static String checkValidServiceName(String serviceName) {
      if (serviceName == null || serviceName.trim().length() == 0) {
        throw new IllegalArgumentException("Service name must not be null or empty");
      }
      return serviceName;
    }
  }

  private static String loadVersion() {
    String version;
    try {
      InputStream is = Tracer.class.getResourceAsStream("jaeger.properties");
      try {
        Properties prop = new Properties();
        prop.load(is);
        version = prop.getProperty(Constants.JAEGER_CLIENT_VERSION_TAG_KEY);
      } finally {
        is.close();
      }
    } catch (Exception e) {
      throw new RuntimeException("Cannot read jaeger.properties", e);
    }
    if (version == null) {
      throw new RuntimeException("Cannot read " + Constants.JAEGER_CLIENT_VERSION_TAG_KEY + " from jaeger.properties");
    }
    return "Java-" + version;
  }

  String getHostName() {
    try {
      return InetAddress.getLocalHost().getHostName();
    } catch (UnknownHostException e) {
      log.error("Cannot obtain host name", e);
      return null;
    }
  }

  SpanContext setBaggage(Span span, String key, String value) {
    return baggageSetter.setBaggage(span, key, value);
  }

  boolean isExpandExceptionLogs() {
    return this.expandExceptionLogs;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy