Adding custom trace id in sleuth

Akkave picture Akkave · Jun 18, 2018 · Viewed 7.9k times · Source

I am using sleuth 2.0.x, I want to add my own trace id apart from its own.

I`m going through the given link
https://cloud.spring.io/spring-cloud-static/spring-cloud-sleuth/2.0.0.RC2/single/spring-cloud-sleuth.html#_propagation

I want to modify trace like below code:- public static final String TAG_NAME = "X-B3-CONVID";

@Bean
@Order(TraceWebServletAutoConfiguration.TRACING_FILTER_ORDER + 1)
public GenericFilterBean customTraceFilter(final Tracer tracer) {
    return new GenericFilterBean() {

        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                FilterChain filterChain) throws IOException, ServletException {

            Span span = tracer.currentSpan();


            String existingConversationId = span.getBaggage().get(TAG_NAME.toLowerCase());

            if(existingConversationId == null){
                existingConversationId = UUID.randomUUID().toString();
                span. setBaggageItem(TAG_NAME, existingConversationId);
            }

            tracer.addTag(TAG_NAME, existingConversationId);
            MDC.put(TAG_NAME, existingConversationId);
            tracer.continueSpan(span);

            filterChain.doFilter(servletRequest, servletResponse);
        }
    };
}

But some functions like continueSpan, addTag, getBaggage removed from Sleuth 2.0.x.

Any example for X-B3-CONVID?

Answer

Marcin Grzejszczak picture Marcin Grzejszczak · Jun 18, 2018

Here you have an example of AWS with Brave that uses a different trace identifier - https://github.com/openzipkin/zipkin-aws/blob/release-0.11.2/brave-propagation-aws/src/main/java/brave/propagation/aws/AWSPropagation.java

I'm copying the code cause it happened that an answer pointing to a link wasn't accepted

/*
 * Copyright 2016-2018 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.
 */
package brave.propagation.aws;

import brave.Tracing;
import brave.internal.Nullable;
import brave.propagation.ExtraFieldPropagation;
import brave.propagation.Propagation;
import brave.propagation.SamplingFlags;
import brave.propagation.TraceContext;
import brave.propagation.TraceContextOrSamplingFlags;
import brave.propagation.TraceIdContext;
import java.util.Collections;
import java.util.List;

import static brave.internal.HexCodec.writeHexByte;
import static brave.internal.HexCodec.writeHexLong;

/**
 * Utility for working with Amazon Web Services Trace IDs, for example reading from headers or
 * environment variables. {@code x-amzn-trace-id} is primarily for Amazon's X-Ray service, but it is
 * also integrated with AWS ALB, API Gateway and Lambda.
 *
 * <p>For example, if you are in a lambda environment, you can read the incoming context like this:
 *
 * <pre>{@code
 * span = tracer.nextSpan(AWSPropagation.extractLambda());
 * }</pre>
 *
 * <h3>Details</h3>
 *
 * {@code x-amzn-trace-id} (and the lambda equivalent {@code _X_AMZN_TRACE_ID}) follows RFC 6265
 * style syntax (https://tools.ietf.org/html/rfc6265#section-2.2): fields are split on semicolon and
 * optional whitespace.
 *
 * <p>Description of the {@code Root} (or {@code Self}) field from AWS CLI help:
 *
 * <p>A trace_id consists of three numbers separated by hyphens. For example, {@code
 * 1-58406520-a006649127e371903a2de979}. This includes:
 *
 * <pre>
 * <ul>
 * <li>The version number, i.e. 1</li>
 * <li>The time of the original request, in Unix epoch time, in 8  hexadecimal digits. For example,
 * 10:00AM December 2nd, 2016 PST in epoch timeis 1480615200 seconds, or 58406520 in
 * hexadecimal.</li>
 * <li>A 96-bit identifier for the trace, globally unique, in 24 hexadecimal digits.</li>
 * </ul>
 * </pre>
 */
public final class AWSPropagation<K> implements Propagation<K> {

  public static final Propagation.Factory FACTORY =
      new Propagation.Factory() {
        @Override
        public <K> Propagation<K> create(KeyFactory<K> keyFactory) {
          return new AWSPropagation<>(keyFactory);
        }

        @Override
        public boolean requires128BitTraceId() {
          return true;
        }

        @Override
        public String toString() {
          return "AWSPropagationFactory";
        }
      };

  // Using lowercase field name as http is case-insensitive, but http/2 transport downcases */
  static final String TRACE_ID_NAME = "x-amzn-trace-id";
  static final char[] ROOT = "Root=".toCharArray();
  static final char[] PARENT = ";Parent=".toCharArray();
  static final char[] SAMPLED = ";Sampled=".toCharArray();
  public static final int ROOT_LENGTH = 35;

  final K traceIdKey;
  final List<K> fields;

  AWSPropagation(KeyFactory<K> keyFactory) {
    this.traceIdKey = keyFactory.create(TRACE_ID_NAME);
    this.fields = Collections.singletonList(traceIdKey);
  }

  /** returns the name of the header field: "x-amzn-trace-id" */
  @Override
  public List<K> keys() {
    return fields;
  }

  @Override
  public <C> TraceContext.Injector<C> injector(Setter<C, K> setter) {
    if (setter == null) throw new NullPointerException("setter == null");
    return new AWSInjector<>(this, setter);
  }

  static final class AWSInjector<C, K> implements TraceContext.Injector<C> {
    final AWSPropagation<K> propagation;
    final Setter<C, K> setter;

    AWSInjector(AWSPropagation<K> propagation, Setter<C, K> setter) {
      this.propagation = propagation;
      this.setter = setter;
    }

    /**
     * This version of propagation contains at least 74 characters corresponding to identifiers and
     * the sampling bit. It will also include extra fields where present.
     *
     * <p>Ex 74 characters: {@code
     * Root=1-67891233-abcdef012345678912345678;Parent=463ac35c9f6413ad;Sampled=1}
     *
     * <p>{@inheritDoc}
     */
    @Override
    public void inject(TraceContext traceContext, C carrier) {
      CharSequence extra = null;
      for (int i = 0, length = traceContext.extra().size(); i < length; i++) {
        Object next = traceContext.extra().get(i);
        if (next instanceof Extra) {
          extra = ((Extra) next).fields;
          break;
        }
      }
      int extraLength = extra == null ? 0 : extra.length();
      // Root=1-67891233-abcdef012345678912345678;Parent=463ac35c9f6413ad;Sampled=1
      char[] result = new char[74 + extraLength];
      System.arraycopy(ROOT, 0, result, 0, 5);
      writeRoot(traceContext, result, 5);
      System.arraycopy(PARENT, 0, result, 40, 8);
      writeHexLong(result, 48, traceContext.spanId());
      System.arraycopy(SAMPLED, 0, result, 64, 9);
      Boolean sampled = traceContext.sampled();
      // Sampled status is same as B3, but ? means downstream decides (like omitting X-B3-Sampled)
      // https://github.com/aws/aws-xray-sdk-go/blob/391885218b556c43ed05a1e736a766d70fc416f1/header/header.go#L50
      result[73] = sampled == null ? '?' : sampled ? '1' : '0';
      for (int i = 0; i < extraLength; i++) {
        result[i + 74] = extra.charAt(i);
      }
      setter.put(carrier, propagation.traceIdKey, new String(result));
    }
  }

  /** Returns the current {@link #traceId(TraceContext)} or null if not available */
  @Nullable
  public static String currentTraceId() {
    Tracing tracing = Tracing.current();
    if (tracing == null) return null;
    TraceContext context = tracing.currentTraceContext().get();
    if (context == null) return null;
    return traceId(context);
  }

  /**
   * Used for log correlation or {@link brave.Span#tag(String, String) tag values}
   *
   * @return a formatted Root field like "1-58406520-a006649127e371903a2de979" or null if the
   *     context was not created from an instance of {@link AWSPropagation}.
   */
  @Nullable
  public static String traceId(TraceContext context) {
    for (int i = 0, length = context.extra().size(); i < length; i++) {
      Object next = context.extra().get(i);
      if (next instanceof Extra) {
        char[] result = new char[ROOT_LENGTH];
        writeRoot(context, result, 0);
        return new String(result);
      }
    }
    // See if we have the field as a pass-through
    String maybeHeader = ExtraFieldPropagation.get(context, TRACE_ID_NAME);
    if (maybeHeader == null) return null;
    int i = maybeHeader.indexOf("Root=");
    if (i == -1) return null;
    i += 5; // Root=
    if (maybeHeader.length() < i + ROOT_LENGTH) return null;
    return maybeHeader.substring(i, i + ROOT_LENGTH);
  }

  /** Writes 35 characters representing the input trace ID to the buffer at the given offset */
  static void writeRoot(TraceContext context, char[] result, int offset) {
    result[offset] = '1'; // version
    result[offset + 1] = '-'; // delimiter
    long high = context.traceIdHigh();
    writeHexByte(result, offset + 2, (byte) ((high >>> 56L) & 0xff));
    writeHexByte(result, offset + 4, (byte) ((high >>> 48L) & 0xff));
    writeHexByte(result, offset + 6, (byte) ((high >>> 40L) & 0xff));
    writeHexByte(result, offset + 8, (byte) ((high >>> 32L) & 0xff));
    result[offset + 10] = '-';
    writeHexByte(result, offset + 11, (byte) ((high >>> 24L) & 0xff));
    writeHexByte(result, offset + 13, (byte) ((high >>> 16L) & 0xff));
    writeHexByte(result, offset + 15, (byte) ((high >>> 8L) & 0xff));
    writeHexByte(result, offset + 17, (byte) (high & 0xff));
    writeHexLong(result, offset + 19, context.traceId());
  }

  @Override
  public <C> TraceContext.Extractor<C> extractor(Getter<C, K> getter) {
    if (getter == null) throw new NullPointerException("getter == null");
    return new AWSExtractor<>(this, getter);
  }

  static final AWSExtractor<String, String> STRING_EXTRACTOR =
      new AWSExtractor<>(
          new AWSPropagation<>(KeyFactory.STRING),
          new Getter<String, String>() {
            @Override
            public String get(String carrier, String key) {
              return carrier;
            }
          });

  /**
   * This is used for extracting from the AWS lambda environment variable {@code _X_AMZN_TRACE_ID}.
   *
   * @see #extract(String)
   */
  public static TraceContextOrSamplingFlags extractLambda() {
    return STRING_EXTRACTOR.extract(System.getenv("_X_AMZN_TRACE_ID"));
  }

  /** Like {@link TraceContext.Extractor#extract(Object)} except reading from a single field. */
  public static TraceContextOrSamplingFlags extract(String amznTraceId) {
    if (amznTraceId == null) return EMPTY;
    return STRING_EXTRACTOR.extract(amznTraceId);
  }

  static final class AWSExtractor<C, K> implements TraceContext.Extractor<C> {
    final AWSPropagation<K> propagation;
    final Getter<C, K> getter;

    AWSExtractor(AWSPropagation<K> propagation, Getter<C, K> getter) {
      this.propagation = propagation;
      this.getter = getter;
    }

    enum Op {
      SKIP,
      ROOT,
      PARENT,
      SAMPLED,
      EXTRA
    }

    @Override
    public TraceContextOrSamplingFlags extract(C carrier) {
      if (carrier == null) throw new NullPointerException("carrier == null");
      String traceIdString = getter.get(carrier, propagation.traceIdKey);
      if (traceIdString == null) return EMPTY;

      Boolean sampled = null;
      long traceIdHigh = 0L, traceId = 0L;
      Long parent = null;
      StringBuilder currentString = new StringBuilder(7 /* Sampled.length */), extraFields = null;
      Op op = null;
      OUTER:
      for (int i = 0, length = traceIdString.length(); i < length; i++) {
        char c = traceIdString.charAt(i);
        if (c == ' ') continue; // trim whitespace
        if (c == '=') { // we reached a field name
          if (++i == length) break; // skip '=' character
          if (currentString.indexOf("Root") == 0) {
            op = Op.ROOT;
          } else if (currentString.indexOf("Parent") == 0) {
            op = Op.PARENT;
          } else if (currentString.indexOf("Sampled") == 0) {
            op = Op.SAMPLED;
          } else if (currentString.indexOf("Self") == 0) {
            // ALB implements Trace ID chaining using self so that customers not using X-Ray
            // (I.e. request logs) can do the correlation themselves. We drop these
            op = Op.SKIP;
          } else {
            op = Op.EXTRA;
            if (extraFields == null) extraFields = new StringBuilder();
            extraFields.append(';').append(currentString);
          }
          currentString.setLength(0);
        } else if (op == null) {
          currentString.append(c);
          continue;
        }
        // no longer whitespace
        switch (op) {
          case EXTRA:
            extraFields.append(c);
            while (i < length && (c = traceIdString.charAt(i)) != ';') {
              extraFields.append(c);
              i++;
            }
            break;
          case SKIP:
            while (++i < length && traceIdString.charAt(i) != ';') {
              // skip until we hit a delimiter
            }
            break;
          case ROOT:
            if (i + ROOT_LENGTH > length // 35 = length of 1-67891233-abcdef012345678912345678
                || traceIdString.charAt(i++) != '1'
                || traceIdString.charAt(i++) != '-') {
              break OUTER; // invalid version or format
            }
            // Parse the epoch seconds and high 32 of the 96 bit trace ID into traceID high
            for (int hyphenIndex = i + 8, endIndex = hyphenIndex + 1 + 8; i < endIndex; i++) {
              c = traceIdString.charAt(i);
              if (c == '-' && i == hyphenIndex) continue; // skip delimiter between epoch and random
              traceIdHigh <<= 4;
              if (c >= '0' && c <= '9') {
                traceIdHigh |= c - '0';
              } else if (c >= 'a' && c <= 'f') {
                traceIdHigh |= c - 'a' + 10;
              } else {
                break OUTER; // invalid format
              }
            }
            // Parse the low 64 of the 96 bit trace ID into traceId
            for (int endIndex = i + 16; i < endIndex; i++) {
              c = traceIdString.charAt(i);
              traceId <<= 4;
              if (c >= '0' && c <= '9') {
                traceId |= c - '0';
              } else if (c >= 'a' && c <= 'f') {
                traceId |= c - 'a' + 10;
              } else {
                break OUTER; // invalid format
              }
            }
            break;
          case PARENT:
            long parentId = 0L;
            for (int endIndex = i + 16; i < endIndex; i++) {
              c = traceIdString.charAt(i);
              parentId <<= 4;
              if (c >= '0' && c <= '9') {
                parentId |= c - '0';
              } else if (c >= 'a' && c <= 'f') {
                parentId |= c - 'a' + 10;
              } else {
                break OUTER; // invalid format
              }
            }
            parent = parentId;
            break;
          case SAMPLED:
            c = traceIdString.charAt(i++);
            if (c == '1') {
              sampled = true;
            } else if (c == '0') {
              sampled = false;
            }
            break;
        }
        op = null;
      }

      List<Object> extra;
      if (extraFields == null) {
        extra = DEFAULT_EXTRA;
      } else {
        Extra e = new Extra();
        e.fields = extraFields;
        extra = Collections.singletonList(e);
      }

      if (traceIdHigh == 0L) { // traceIdHigh cannot be null, so just return sampled
        return TraceContextOrSamplingFlags.newBuilder()
            .extra(extra)
            .samplingFlags(SamplingFlags.Builder.build(sampled))
            .build();
      } else if (parent == null) {
        return TraceContextOrSamplingFlags.newBuilder()
            .extra(extra)
            .traceIdContext(
                TraceIdContext.newBuilder()
                    .traceIdHigh(traceIdHigh)
                    .traceId(traceId)
                    .sampled(sampled)
                    .build())
            .build();
      }
      return TraceContextOrSamplingFlags.create(
          TraceContext.newBuilder()
              .traceIdHigh(traceIdHigh)
              .traceId(traceId)
              .spanId(parent)
              .sampled(sampled)
              .extra(extra)
              .build());
    }
  }

  /** When present, this context was created with AWSPropagation */
  static final Extra MARKER = new Extra();

  static final List<Object> DEFAULT_EXTRA = Collections.singletonList(MARKER);
  static final TraceContextOrSamplingFlags EMPTY =
      TraceContextOrSamplingFlags.EMPTY.toBuilder().extra(DEFAULT_EXTRA).build();

  static final class Extra { // hidden intentionally
    CharSequence fields;

    @Override
    public String toString() {
      return "AWSPropagation{" + (fields != null ? ("fields=" + fields.toString()) : "") + "}";
    }
  }
}