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

com.datadoghq.trace.DDTracer Maven / Gradle / Ivy

There is a newer version: 0.2.12
Show newest version
package com.datadoghq.trace;

import com.datadoghq.trace.integration.AbstractDecorator;
import com.datadoghq.trace.propagation.Codec;
import com.datadoghq.trace.propagation.HTTPCodec;
import com.datadoghq.trace.sampling.AllSampler;
import com.datadoghq.trace.sampling.Sampler;
import com.datadoghq.trace.writer.LoggingWriter;
import com.datadoghq.trace.writer.Writer;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.opentracing.ActiveSpan;
import io.opentracing.ActiveSpanSource;
import io.opentracing.BaseSpan;
import io.opentracing.SpanContext;
import io.opentracing.propagation.Format;
import io.opentracing.util.ThreadLocalActiveSpanSource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ThreadLocalRandom;
import lombok.extern.slf4j.Slf4j;

/** DDTracer makes it easy to send traces and span to DD using the OpenTracing integration. */
@Slf4j
public class DDTracer extends ThreadLocalActiveSpanSource implements io.opentracing.Tracer {

  public static final String UNASSIGNED_DEFAULT_SERVICE_NAME = "unnamed-java-app";
  public static final Writer UNASSIGNED_WRITER = new LoggingWriter();
  public static final Sampler UNASSIGNED_SAMPLER = new AllSampler();

  /** Writer is an charge of reporting traces and spans to the desired endpoint */
  private final Writer writer;
  /** Sampler defines the sampling policy in order to reduce the number of traces for instance */
  private final Sampler sampler;

  /** Default service name if none provided on the trace or span */
  private final String defaultServiceName;

  /** Span context decorators */
  private final Map> spanContextDecorators = new HashMap<>();

  private final CodecRegistry registry;
  private final Map services = new HashMap<>();

  /** Default constructor, trace/spans are logged, no trace/span dropped */
  public DDTracer() {
    this(UNASSIGNED_WRITER);
  }

  public DDTracer(final Writer writer) {
    this(writer, new AllSampler());
  }

  public DDTracer(final Writer writer, final Sampler sampler) {
    this(UNASSIGNED_DEFAULT_SERVICE_NAME, writer, sampler);
  }

  public DDTracer(final String defaultServiceName, final Writer writer, final Sampler sampler) {
    this.defaultServiceName = defaultServiceName;
    this.writer = writer;
    this.writer.start();
    this.sampler = sampler;
    registry = new CodecRegistry();
    registry.register(Format.Builtin.HTTP_HEADERS, new HTTPCodec());
    log.debug(
        "New tracer instance, default-service={}, writer={}, sampler={}",
        defaultServiceName,
        writer.getClass().getSimpleName(),
        sampler.getClass().getSimpleName());
  }

  /**
   * Returns the list of span context decorators
   *
   * @return the list of span context decorators
   */
  public List getSpanContextDecorators(final String tag) {
    return spanContextDecorators.get(tag);
  }

  /**
   * Add a new decorator in the list ({@link AbstractDecorator})
   *
   * @param decorator The decorator in the list
   */
  public void addDecorator(final AbstractDecorator decorator) {

    List list = spanContextDecorators.get(decorator.getMatchingTag());
    if (list == null) {
      list = new ArrayList<>();
    }
    list.add(decorator);

    spanContextDecorators.put(decorator.getMatchingTag(), list);
  }

  @Override
  public DDSpanBuilder buildSpan(final String operationName) {
    return new DDSpanBuilder(operationName, this);
  }

  @Override
  public  void inject(final SpanContext spanContext, final Format format, final T carrier) {

    final Codec codec = registry.get(format);
    if (codec == null) {
      log.warn("Unsupported format for propagation - {}", format.getClass().getName());
    } else {
      codec.inject((DDSpanContext) spanContext, carrier);
    }
  }

  @Override
  public  SpanContext extract(final Format format, final T carrier) {

    final Codec codec = registry.get(format);
    if (codec == null) {
      log.warn("Unsupported format for propagation - {}", format.getClass().getName());
    } else {
      return codec.extract(carrier);
    }
    return null;
  }

  /**
   * We use the sampler to know if the trace has to be reported/written. The sampler is called on
   * the first span (root span) of the trace. If the trace is marked as a sample, we report it.
   *
   * @param trace a list of the spans related to the same trace
   */
  public void write(final Queue> trace) {
    if (trace.isEmpty()) {
      return;
    }
    if (this.sampler.sample(trace.peek())) {
      this.writer.write(new ArrayList<>(trace));
    }
  }

  public void close() {
    writer.close();
  }

  @Override
  public String toString() {
    return "DDTracer{" + "writer=" + writer + ", sampler=" + sampler + '}';
  }

  /**
   * Register additional information about a service. Service additional information are a Datadog
   * feature only. Services are reported through a specific Datadog endpoint.
   *
   * @param service additional service information
   */
  public void addServiceInfo(final Service service) {
    services.put(service.getName(), service);
    // Update the writer
    try {
      // We don't bother to send multiple times the list of services at this time
      writer.writeServices(services);
    } catch (final Throwable ex) {
      log.warn("Failed to report additional service information, reason: {}", ex.getMessage());
    }
  }

  /**
   * Return the list of additional service information registered
   *
   * @return the list of additional service information
   */
  @JsonIgnore
  public Map getServiceInfo() {
    return services;
  }

  private static class CodecRegistry {

    private final Map, Codec> codecs = new HashMap<>();

     Codec get(final Format format) {
      return (Codec) codecs.get(format);
    }

    public  void register(final Format format, final Codec codec) {
      codecs.put(format, codec);
    }
  }

  /** Spans are built using this builder */
  public class DDSpanBuilder implements SpanBuilder {
    private final ActiveSpanSource spanSource;

    /** Each span must have an operationName according to the opentracing specification */
    private final String operationName;

    // Builder attributes
    private Map tags = Collections.emptyMap();
    private long timestamp;
    private SpanContext parent;
    private String serviceName;
    private String resourceName;
    private boolean errorFlag;
    private String spanType;
    private boolean ignoreActiveSpan = false;

    public DDSpanBuilder(final String operationName, final ActiveSpanSource spanSource) {
      this.operationName = operationName;
      this.spanSource = spanSource;
    }

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

    private DDSpan startSpan() {
      return new DDSpan(this.timestamp, buildSpanContext());
    }

    @Override
    public ActiveSpan startActive() {
      final DDSpan span = startSpan();
      final ActiveSpan activeSpan = spanSource.makeActive(span);
      log.debug("Starting a new active span: {}", span);
      return activeSpan;
    }

    @Override
    public DDSpan startManual() {
      final DDSpan span = startSpan();
      log.debug("Starting a new manual span: {}", span);
      return span;
    }

    @Override
    @Deprecated
    public DDSpan start() {
      return startManual();
    }

    @Override
    public DDSpanBuilder withTag(final String tag, final Number number) {
      return withTag(tag, (Object) number);
    }

    @Override
    public DDSpanBuilder withTag(final String tag, final String string) {
      if (tag.equals(DDTags.SERVICE_NAME)) {
        return withServiceName(string);
      } else if (tag.equals(DDTags.RESOURCE_NAME)) {
        return withResourceName(string);
      } else if (tag.equals(DDTags.SPAN_TYPE)) {
        return withSpanType(string);
      } else {
        return withTag(tag, (Object) string);
      }
    }

    @Override
    public DDSpanBuilder withTag(final String tag, final boolean bool) {
      return withTag(tag, (Object) bool);
    }

    @Override
    public DDSpanBuilder withStartTimestamp(final long timestampMillis) {
      this.timestamp = timestampMillis;
      return this;
    }

    public DDSpanBuilder withServiceName(final String serviceName) {
      this.serviceName = serviceName;
      return this;
    }

    public DDSpanBuilder withResourceName(final String resourceName) {
      this.resourceName = resourceName;
      return this;
    }

    public DDSpanBuilder withErrorFlag() {
      this.errorFlag = true;
      return this;
    }

    public DDSpanBuilder withSpanType(final String spanType) {
      this.spanType = spanType;
      return this;
    }

    public Iterable> baggageItems() {
      if (parent == null) {
        return Collections.emptyList();
      }
      return parent.baggageItems();
    }

    @Override
    public DDSpanBuilder asChildOf(final BaseSpan span) {
      return asChildOf(span == null ? null : span.context());
    }

    @Override
    public DDSpanBuilder asChildOf(final SpanContext spanContext) {
      this.parent = spanContext;
      return this;
    }

    @Override
    public DDSpanBuilder addReference(final String referenceType, final SpanContext spanContext) {
      log.debug("`addReference` method is not implemented. Doing nothing");
      return this;
    }

    // Private methods
    private DDSpanBuilder withTag(final String tag, final Object value) {
      if (this.tags.isEmpty()) {
        this.tags = new HashMap<>();
      }
      this.tags.put(tag, value);
      return this;
    }

    private long generateNewId() {
      // Ensure the generated ID is in a valid range:
      return ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE);
    }

    /**
     * Build the SpanContext, if the actual span has a parent, the following attributes must be
     * propagated: - ServiceName - Baggage - Trace (a list of all spans related) - SpanType
     *
     * @return the context
     */
    private DDSpanContext buildSpanContext() {
      final long traceId;
      final long spanId = generateNewId();
      final long parentSpanId;
      final Map baggage;
      final Queue> parentTrace;

      final DDSpanContext context;
      SpanContext parentContext = this.parent;
      if (parentContext == null && !ignoreActiveSpan) {
        // use the ActiveSpan as parent unless overridden or ignored.
        final ActiveSpan activeSpan = activeSpan();
        if (activeSpan != null) parentContext = activeSpan.context();
      }

      if (parentContext instanceof DDSpanContext) {
        final DDSpanContext ddsc = (DDSpanContext) parentContext;
        traceId = ddsc.getTraceId();
        parentSpanId = ddsc.getSpanId();
        baggage = ddsc.getBaggageItems();
        parentTrace = ddsc.getTrace();

        if (this.serviceName == null) this.serviceName = ddsc.getServiceName();
        if (this.spanType == null) this.spanType = ddsc.getSpanType();
      } else {
        traceId = generateNewId();
        parentSpanId = 0L;
        baggage = null;
        parentTrace = null;
      }

      if (serviceName == null) {
        serviceName = defaultServiceName;
      }

      final String operationName =
          this.operationName != null ? this.operationName : this.resourceName;

      // this.operationName, this.tags,

      // some attributes are inherited from the parent
      context =
          new DDSpanContext(
              traceId,
              spanId,
              parentSpanId,
              serviceName,
              operationName,
              this.resourceName,
              baggage,
              errorFlag,
              spanType,
              this.tags,
              parentTrace,
              DDTracer.this);

      return context;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy