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

org.sdase.commons.shared.tracing.TraceTokenContext Maven / Gradle / Ivy

Go to download

A libraries to bootstrap services easily that follow the patterns and specifications promoted by the SDA SE

The newest version!
package org.sdase.commons.shared.tracing;

import java.io.Closeable;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

/**
 * Manages the context for the {@code Trace-Token}. The {@code Trace-Token} is used to correlate log
 * messages across services for a single synchronous process. It will be logged as {@link
 * org.slf4j.MDC} key {@value #TRACE_TOKEN_MDC_KEY}.
 */
public class TraceTokenContext implements Closeable {

  private static final Logger LOG = LoggerFactory.getLogger(TraceTokenContext.class);

  /** The key of the trace token in the {@link org.slf4j.MDC}. */
  // not private yet for RequestTracing
  static final String TRACE_TOKEN_MDC_KEY = "Trace-Token";

  /**
   * The name used to put an incoming {@value TRACE_TOKEN_MDC_KEY} in the {@link MDC} which logging
   * trace should not be continued but related to the logs of the current synchronous process. The
   * key is used, when a new {@value TRACE_TOKEN_MDC_KEY} is created when receiving {@value
   * TRACE_TOKEN_MDC_KEY} information from an asynchronous process.
   */
  private static final String PARENT_TRACE_TOKEN_MDC_KEY = "Parent-Trace-Token";

  /** The name of the header containing the trace token in HTTP requests. */
  public static final String TRACE_TOKEN_HTTP_HEADER_NAME = TRACE_TOKEN_MDC_KEY;

  /**
   * The name of the header containing the trace token in asynchronous messages. Note that the trace
   * token context should not be continued when processing asynchronous messages. It should only be
   * used to connect a new trace token with the received trace token from the producer.
   */
  public static final String TRACE_TOKEN_MESSAGING_HEADER_NAME = "Parent-Trace-Token";

  private static final Object CREATE_SEMAPHORE = new Object();

  private final boolean created;
  private final String traceToken;

  private TraceTokenContext(boolean created, String traceToken) {
    this.created = created;
    this.traceToken = traceToken;
  }

  /**
   * @return if the {@value #TRACE_TOKEN_MDC_KEY} of this {@link TraceTokenContext} has been created
   *     for the caller of {@link #getOrCreateTraceTokenContext()}.
   */
  public boolean isCreated() {
    return created;
  }

  /**
   * @return if the {@value #TRACE_TOKEN_MDC_KEY} of this {@link TraceTokenContext} has been reused
   *     from an existing {@link TraceTokenContext} for the caller of {@link
   *     #getOrCreateTraceTokenContext()}.
   */
  public boolean isReused() {
    return !isCreated();
  }

  /**
   * @return the {@value TRACE_TOKEN_MDC_KEY} of this context.
   */
  public String get() {
    return traceToken;
  }

  /**
   * Closes this {@link TraceTokenContext} and removes all associated properties from the {@link
   * MDC} only if this {@link TraceTokenContext} represents the initial creation of the
   * context.
   */
  public void closeIfCreated() {
    if (this.created) {
      closeTraceTokenContext();
    }
  }

  /**
   * Closes this {@link TraceTokenContext} and removes all associated properties from the {@link
   * MDC} only if this {@link TraceTokenContext} represents the initial creation of the
   * context.
   */
  @Override
  public void close() {
    closeIfCreated();
  }

  /**
   * @return a {@link TraceTokenContext} either from the existing {@value TRACE_TOKEN_MDC_KEY} or a
   *     new random {@value TRACE_TOKEN_MDC_KEY}. All received instances must be {@linkplain
   *     #closeIfCreated() closed}. Only the context that started this trace will actually remove
   *     the {@value TRACE_TOKEN_MDC_KEY} from the {@link MDC}.
   */
  public static TraceTokenContext getOrCreateTraceTokenContext() {
    synchronized (CREATE_SEMAPHORE) {
      if (isTraceTokenContextActive()) {
        return new TraceTokenContext(false, currentTraceToken());
      }
      saveTraceToken(createTraceToken());
      return new TraceTokenContext(true, currentTraceToken());
    }
  }

  /**
   * Continues the {@value TRACE_TOKEN_MDC_KEY} context of a synchronous process that started
   * outside this service. Must not be used for asynchronous processes!
   *
   * @param incomingTraceToken the {@value TRACE_TOKEN_MDC_KEY} received from an external
   *     synchronous process, e.g. via HTTP header {@value TRACE_TOKEN_HTTP_HEADER_NAME} of an
   *     incoming HTTP request. May be {@code null}, if no {@value TRACE_TOKEN_MDC_KEY} context was
   *     received.
   * @return a {@link TraceTokenContext} either from the given {@code incomingTraceToken} or a new
   *     random {@value TRACE_TOKEN_MDC_KEY}. All received instances must be {@linkplain
   *     #closeIfCreated() closed}. Only the context that started this trace will actually remove
   *     the {@value TRACE_TOKEN_MDC_KEY} from the {@link MDC}. In any case, this context will be
   *     considered as created and {@link #closeIfCreated()} and {@link #close()} will end the
   *     context.
   */
  public static TraceTokenContext continueSynchronousTraceTokenContext(String incomingTraceToken) {
    synchronized (CREATE_SEMAPHORE) {
      closeTraceTokenContext();
      if (incomingTraceToken != null && !incomingTraceToken.isBlank()) {
        saveTraceToken(incomingTraceToken);
      } else {
        saveTraceToken(createTraceToken());
      }
      return new TraceTokenContext(true, currentTraceToken());
    }
  }

  /**
   * Creates a new {@link TraceTokenContext} with a reference to a parent context. This should be
   * used to keep track of asynchronous processes to have a connection from the producer process to
   * the new consumer process(es).
   *
   * @param incomingParentTraceToken the {@value PARENT_TRACE_TOKEN_MDC_KEY} received from an
   *     external asynchronous process, e.g. via message header {@value
   *     TRACE_TOKEN_MESSAGING_HEADER_NAME} of a consumed Kafka message. May be {@code null}, if no
   *     {@value TRACE_TOKEN_MESSAGING_HEADER_NAME} context was received.
   * @return a {@link TraceTokenContext} with a reference to the given {@code
   *     incomingParentTraceToken} as {@value PARENT_TRACE_TOKEN_MDC_KEY} in the {@link MDC}. If
   *     there is a current trace token context that already has a {@value
   *     PARENT_TRACE_TOKEN_MDC_KEY}, the current context is not changed. All received instances
   *     must be {@linkplain #closeIfCreated() closed}. Only the context that started this trace
   *     will actually remove the {@value TRACE_TOKEN_MDC_KEY} from the {@link MDC}.
   */
  public static TraceTokenContext createFromAsynchronousTraceTokenContext(
      String incomingParentTraceToken) {
    synchronized (CREATE_SEMAPHORE) {
      if (isTraceContextActiveWithParent()) {
        LOG.info(
            "Not adding parent trace token '{}' to existing trace token context with parent.",
            incomingParentTraceToken);
        return new TraceTokenContext(false, currentTraceToken());
      }

      if (incomingParentTraceToken != null && !incomingParentTraceToken.isBlank()) {
        MDC.put(PARENT_TRACE_TOKEN_MDC_KEY, incomingParentTraceToken);
      }

      if (isTraceTokenContextActive()) {
        return new TraceTokenContext(false, currentTraceToken());
      }

      saveTraceToken(createTraceToken());
      return new TraceTokenContext(true, currentTraceToken());
    }
  }

  /**
   * @return if there is an active {@value TRACE_TOKEN_MDC_KEY} context in the current Thread.
   */
  public static boolean isTraceTokenContextActive() {
    return currentTraceToken() != null;
  }

  private static boolean isTraceContextActiveWithParent() {
    return currentTraceToken() != null && MDC.get(PARENT_TRACE_TOKEN_MDC_KEY) != null;
  }

  private static void closeTraceTokenContext() {
    MDC.remove(TRACE_TOKEN_MDC_KEY);
    MDC.remove(PARENT_TRACE_TOKEN_MDC_KEY);
  }

  private static String createTraceToken() {
    return UUID.randomUUID().toString();
  }

  private static String currentTraceToken() {
    return MDC.get(TRACE_TOKEN_MDC_KEY);
  }

  private static void saveTraceToken(String traceToken) {
    MDC.put(TRACE_TOKEN_MDC_KEY, traceToken);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy