@SuppressWarnings("WeakerAccess")
public class Tracer {

    /**
     * The options for how {@link Span} objects are represented when they are completed. To change how {@link Tracer} serializes spans when logging them
     * call {@link #setSpanLoggingRepresentation(SpanLoggingRepresentation)}.
     */
    public enum SpanLoggingRepresentation {
        /**
         * Causes spans to be output in the logs using {@link Span#toJSON()}.
         */
        JSON,
        /**
         * Causes spans to be output in the logs using {@link Span#toKeyValueString()}.
         */
        KEY_VALUE
    }

    /**
     * The options for which {@link Span} fields are put into and taken out of the logger {@link MDC} as {@link Tracer}
     * works with spans.
     */
    public enum SpanFieldForLoggerMdc {
        /**
         * MDC key for storing the current span's {@link Span#getTraceId()}.
         */
        TRACE_ID("traceId"),
        /**
         * MDC key for storing the current span's {@link Span#getSpanId()}.
         */
        SPAN_ID("spanId"),
        /**
         * MDC key for storing the current span's {@link Span#getParentSpanId()}.
         */
        PARENT_SPAN_ID("parentSpanId"),
        /**
         * MDC key for storing the current {@link Span} as a JSON string.
         *
         * <p>WARNING: This can get expensive in some use cases where processing hops threads frequently and the span
         * state is changed often (i.e. lots of tags). Make sure you absolutely need this before including it in a call
         * to {@link #setSpanFieldsForLoggerMdc(SpanFieldForLoggerMdc...)} or {@link #setSpanFieldsForLoggerMdc(Set)}.
         */
        FULL_SPAN_JSON("spanJson");

        /**
         * The key to use when calling {@link MDC#put(String, String)} or {@link MDC#remove(String)}.
         */
        public final String mdcKey;

        SpanFieldForLoggerMdc(String mdcKey) {
            this.mdcKey = mdcKey;
        }

        /**
         * @param span The span to grab the value from.
         * @return The string from the given span associated with this {@link SpanFieldForLoggerMdc}.
         */
        public @Nullable String getMdcValueForSpan(@NotNull Span span) {
            switch (this) {
                case TRACE_ID:
                    return span.getTraceId();
                case SPAN_ID:
                    return span.getSpanId();
                case PARENT_SPAN_ID:
                    return span.getParentSpanId();
                case FULL_SPAN_JSON:
                    return span.toJSON();
                default:
                    throw new IllegalStateException("Unhandled SpanFieldForLoggerMdc type: " + this);
            }
        }
    }

    private static final String VALID_WINGTIPS_SPAN_LOGGER_NAME = "VALID_WINGTIPS_SPANS";
    private static final String INVALID_WINGTIPS_SPAN_LOGGER_NAME = "INVALID_WINGTIPS_SPANS";

    private static final Logger classLogger = LoggerFactory.getLogger(Tracer.class);
    private static final Logger validSpanLogger = LoggerFactory.getLogger(VALID_WINGTIPS_SPAN_LOGGER_NAME);
    private static final Logger invalidSpanLogger = LoggerFactory.getLogger(INVALID_WINGTIPS_SPAN_LOGGER_NAME);

    /**
     * ThreadLocal that keeps track of the stack of {@link Span} objects associated with the thread. This is treated as a LIFO stack.
     */
    private static final ThreadLocal<Deque<Span>> currentSpanStackThreadLocal = new ThreadLocal<>();

    /**
     * The singleton instance for this class.
     */
    private static final Tracer INSTANCE = new Tracer();


    /**
     * The sampling strategy this instance will use. Default to sampling everything. Never allow this field to be set to null.
     */
    private RootSpanSamplingStrategy rootSpanSamplingStrategy = new SampleAllTheThingsStrategy();

    /**
     * The list of span lifecycle listeners that should be notified when span lifecycle events occur.
     * Note that we use a {@link CopyOnWriteArrayList} to prevent {@link java.util.ConcurrentModificationException}s
     * from occurring if spans are being started/completed/etc while this list is being changed. This is deemed
     * acceptable since iterations over this list happens frequently, but changes to the list should be rare.
     */
    private final List<SpanLifecycleListener> spanLifecycleListeners = new CopyOnWriteArrayList<>();

    /**
     * The span representation that should be used when logging completed spans.
     */
    private SpanLoggingRepresentation spanLoggingRepresentation = SpanLoggingRepresentation.JSON;

    /**
     * The set of span fields that should be put into and taken out of the logger {@link MDC} as {@link Tracer} works
     * with spans.
     */
    private SpanFieldForLoggerMdc[] spanFieldsForLoggerMdc =
        new SpanFieldForLoggerMdc[]{ SpanFieldForLoggerMdc.TRACE_ID };
    private Set<SpanFieldForLoggerMdc> cachedUnmodifiableSpanFieldsForLoggerMdc =
        Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(spanFieldsForLoggerMdc)));

    private Tracer() { /* Intentionally private to enforce singleton pattern. */ }

    /**
     * @return The singleton instance of this class.
     */
    public static Tracer getInstance() {
        return INSTANCE;
    }

    /**
     * The {@link Span} set as the "current" one for this thread.
     * <p/>
     * NOTE: If {@link #currentSpanStackThreadLocal} is null or empty for this thread it will try to reconstitute the {@link Span} from the logging {@link org.slf4j.MDC}.
     * This is useful in some situations, for example async request processing where the thread changes but the MDC is smart enough to transfer the span anyway.
     * In any case as a caller you don't have to care - you'll just get the {@link Span} appropriate for the caller, or null if one hasn't been set up yet.
     */
    public Span getCurrentSpan() {
        Deque<Span> spanStack = currentSpanStackThreadLocal.get();

        return (spanStack == null) ? null : spanStack.peek();
    }

    /**
     * Starts new span stack (i.e. new incoming request) without a parent span by creating a root span with the given span name.
     * If you have parent span info then you should call {@link #startRequestWithChildSpan(Span, String)} or
     * {@link #startRequestWithSpanInfo(String, String, String, boolean, String, SpanPurpose)} instead). The newly created root span will have a
     * span purpose of {@link SpanPurpose#SERVER}.
     * <p/>
     * NOTE: This method will cause the returned span's {@link Span#getUserId()} to contain null. If you have a user ID you should use
     * {@link #startRequestWithRootSpan(String, String)} instead.
     * <p/>
     * <b>WARNING:</b> This wipes out any existing spans on the span stack for this thread and starts fresh, therefore this should only be called at the request's
     * entry point when it's expected that the span stack should be empty. If you need to start a child span in the middle of a request somewhere then you should call
     * {@link #startSubSpan(String, SpanPurpose)} instead.
     *
     * @param spanName - The span name to use for the new span - should never be null.
     * @return The new span (which is now also the current one that will be returned by {@link #getCurrentSpan()}).
     */
    public Span startRequestWithRootSpan(String spanName) {
        return startRequestWithRootSpan(spanName, null);
    }

    /**
     * Similar to {@link #startRequestWithRootSpan(String)} but takes in an optional {@code userId} to populate the returned {@link Span#getUserId()}.
     * If {@code userId} is null then this method is equivalent to calling {@link #startRequestWithRootSpan(String)}. If you have parent span info then you should call
     * {@link #startRequestWithChildSpan(Span, String)} or {@link #startRequestWithSpanInfo(String, String, String, boolean, String, SpanPurpose)} instead).
     * The newly created root span will have a span purpose of {@link SpanPurpose#SERVER}.
     * <p/>
     * <b>WARNING:</b> This wipes out any existing spans on the span stack for this thread and starts fresh, therefore this should only be called at the request's
     * entry point when it's expected that the span stack should be empty. If you need to start a child span in the middle of a request somewhere then you should call
     * {@link #startSubSpan(String, SpanPurpose)} instead.
     *
     * @param spanName - The span name to use for the new span - should never be null.
     * @param userId - The ID of the user that should be associated with the {@link Span} - can be null.
     * @return The new span (which is now also the current one that will be returned by {@link #getCurrentSpan()}).
     */
    public Span startRequestWithRootSpan(String spanName, String userId) {
        boolean sampleable = isNextRootSpanSampleable();
        String traceId = TraceAndSpanIdGenerator.generateId();
        return doNewRequestSpan(traceId, null, spanName, sampleable, userId, SpanPurpose.SERVER);
    }

    /**
     * Starts a new span stack (i.e. new incoming request) with the given span as the new child span's parent (if you do not have a parent then you should call
     * {@link #startRequestWithRootSpan(String)} or {@link #startRequestWithRootSpan(String, String)} instead). The newly created child span will have a
     * span purpose of {@link SpanPurpose#SERVER}.
     * <p/>
     * <b>WARNING:</b> This wipes out any existing spans on the span stack for this thread and starts fresh, therefore this should only be called at the request's
     * entry point when it's expected that the span stack should be empty. If you need to start a child span in the middle of a request somewhere then you should call
     * {@link #startSubSpan(String, SpanPurpose)} instead.
     *
     * @param parentSpan The span to use as the parent span - should never be null (if you need to start a span without a parent then call
     *                   {@link #startRequestWithRootSpan(String)} instead).
     * @param childSpanName The span name to use for the new child span - should never be null.
     * @return The new child span (which is now also the current one that will be returned by {@link #getCurrentSpan()}).
     */
    public Span startRequestWithChildSpan(Span parentSpan, String childSpanName) {
        if (parentSpan == null) {
            throw new IllegalArgumentException("parentSpan cannot be null. " +
                            "If you don't have a parent span then you should call one of the startRequestWithRootSpan(...) methods instead.");
        }

        String parentIdForChildSpan = parentSpan.getSpanId();
        boolean addBadParentIdIndicatorTag = false;
        if (HttpRequestTracingUtils.hasInvalidSpanIdBecauseCallerDidNotSendOne(parentSpan)) {
            parentIdForChildSpan = null;
            addBadParentIdIndicatorTag = true;
        }

        Span result = startRequestWithSpanInfo(
            parentSpan.getTraceId(), parentIdForChildSpan, childSpanName, parentSpan.isSampleable(),
            parentSpan.getUserId(), SpanPurpose.SERVER
        );

        if (addBadParentIdIndicatorTag) {
            result.putTag(CHILD_OF_SPAN_FROM_HEADERS_WHERE_CALLER_DID_NOT_SEND_SPAN_ID_TAG_KEY, "true");
        }

        return result;
    }

    /**
     * Starts a new span stack (i.e. new incoming request) with the given span info used to create the new span. This method is agnostic on whether the span is a root span
     * or child span - the arguments you pass in (specifically the value of {@code parentSpanId}) will determine root vs child. The other {@code startRequestWith...()} methods
     * should be preferred to this where it makes sense, but if your use case doesn't match those methods then this method allows maximum flexibility in creating a span for
     * a new request.
     * <p/>
     * <b>WARNING:</b> This wipes out any existing spans on the span stack for this thread and starts fresh, therefore this should only be called at the request's
     * entry point when it's expected that the span stack should be empty. If you need to start a child span in the middle of a request somewhere then you should call
     * {@link #startSubSpan(String, SpanPurpose)} instead.
     *
     * @param traceId The new span's {@link Span#getTraceId()} - if this is null then a new random trace ID will be generated for the returned span. If you have parent span info
     *                then this should be the parent span's <b>trace</b> ID (not the parent span's span ID).
     * @param parentSpanId The parent span's {@link Span#getSpanId()} - if this is null then the returned {@link Span#getParentSpanId()} will be null causing the returned span
     *                     to be a root span. If it's non-null then the returned span will be a child span with {@link Span#getParentSpanId()} matching this value.
     * @param newSpanName The span name to use for the new span - should never be null.
     * @param sampleable The new span's {@link Span#isSampleable()} value. Determines whether or not this span should be logged when it is completed.
     * @param userId The new span's {@link Span#getUserId()} value.
     * @param spanPurpose The span's purpose, or null if the purpose is unknown. See the javadocs for {@link SpanPurpose} for details on what each enum option means.
     *                    For new request spans, this should usually be {@link SpanPurpose#SERVER}.
     *
     * @return The new span (which is now also the current one that will be returned by {@link #getCurrentSpan()}).
     */
    public Span startRequestWithSpanInfo(String traceId, String parentSpanId, String newSpanName, boolean sampleable, String userId, SpanPurpose spanPurpose) {
        return doNewRequestSpan(traceId, parentSpanId, newSpanName, sampleable, userId, spanPurpose);
    }

    /**
     * Starts a new child sub-span using the {@link #getCurrentSpan()} current span as its parent and pushes this new sub-span onto the span stack so that it becomes the
     * current span. When this new child sub-span is completed using {@link #completeSubSpan()} then it will be popped off the span stack and its parent will once again
     * become the current span.
     * <p/>
     * <b>WARNING:</b> This does *NOT* wipe out any existing spans on the span stack - it pushes a new one onto the stack. If you're calling from a spot in the code where
     * you know there should never be any existing spans on the thread's span stack (i.e. new incoming request) you should call one of the {@code startRequest...()} methods
     * instead.
     *
     * @param spanName The {@link Span#getSpanName()} to use for the new child sub-span.
     * @param spanPurpose The {@link SpanPurpose} for the new sub-span. Since this is a sub-span it should almost always be either {@link SpanPurpose#CLIENT}
     *                    (if this sub-span encompasses an outbound/downstream/out-of-process call), or {@link SpanPurpose#LOCAL_ONLY}. See the javadocs
     *                    for {@link SpanPurpose} for full details on what each enum option means.
     * @return The new child sub-span (which is now also the current one that will be returned by {@link #getCurrentSpan()}).
     */
    public Span startSubSpan(String spanName, SpanPurpose spanPurpose) {
        Span parentSpan = getCurrentSpan();
        if (parentSpan == null) {
            classLogger.error(
                    "WINGTIPS USAGE ERROR - Expected getCurrentSpan() to return a span for use as a parent for a new child sub-span but null was returned instead. This probably " +
                    "means the request's overall span was never started. The child sub-span will still be started without any parent. wingtips_usage_error=true bad_span_stack=true",
                    new Exception("Stack trace for debugging purposes")
            );
        }

        Span childSpan = (parentSpan != null)
                ? parentSpan.generateChildSpan(spanName, spanPurpose)
                : Span.generateRootSpanForNewTrace(spanName, spanPurpose).withSampleable(isNextRootSpanSampleable()).build();

        pushSpanOntoCurrentSpanStack(childSpan);

        notifySpanStarted(childSpan);
        notifyIfSpanSampled(childSpan);

        return childSpan;
    }

    /**
     * This method is here for the (hopefully rare) cases where you want to start a new span but don't control the
     * context where your code is executed (e.g. a third party library) - this method will start a new overall request
     * span or a new subspan depending on the current thread's span stack state at the time this method is called.
     * In other words this method is a shortcut for the following code:
     *
     * <pre>
     *      Tracer tracer = Tracer.getInstance();
     *      if (tracer.getCurrentSpanStackSize() == 0) {
     *          return tracer.startRequestWithRootSpan(spanName);
     *      }
     *      else {
     *          return tracer.startSubSpan(spanName, SpanPurpose.LOCAL_ONLY);
     *      }
     * </pre>
     *
     * <p>This method assumes {@link SpanPurpose#SERVER} if the returned span is an overall request span, and
     * {@link SpanPurpose#LOCAL_ONLY} if it's a subspan. If you know the span purpose already and this behavior is
     * not what you want (i.e. when surrounding a HTTP client or database call and you want to use {@link
     * SpanPurpose#CLIENT}) then use the {@link #startSpanInCurrentContext(String, SpanPurpose)} method instead.
     *
     * <p><b>WARNING:</b> As stated above, this method is here to support libraries where they need to create a span
     * for some work, but do not necessarily know how or where they are going to be used in a project, and therefore
     * don't know whether tracing has been setup yet with an overall request span. Most of the time you will know where
     * you are in relation to overall request span or subspan, and should use the appropriate
     * {@code Tracer.startRequestWith*(...)} or {@code Tracer.startSubSpan(...)} methods directly as those methods spit
     * out error logging when the span stack is not in the expected state (indicating a Wingtips usage error). Using
     * this method everywhere can swallow critical error logging that would otherwise let you know Wingtips isn't being
     * used correctly and that your distributed tracing info is potentially unreliable.
     *
     * <p><b>This method is the equivalent of swallowing exceptions when Wingtips isn't being used correctly - all
     * diagnostic debugging information will be lost. This method should not be used simply because it is
     * convenient!</b>
     *
     * @param spanName The {@link Span#getSpanName()} to use for the new span.
     * @return A new span that might be the root span of a new span stack (i.e. if the current span stack is empty),
     * or a new subspan (i.e. if the current span stack is *not* empty). NOTE: Please read the warning in this method's
     * javadoc - abusing this method can lead to broken tracing without any errors showing up in the logs.
     */
    public Span startSpanInCurrentContext(String spanName) {
        // If the current span stack is empty, then we start a new overall request span.
        //      Otherwise we start a subspan.
        if (getCurrentSpanStackSize() == 0) {
            return startRequestWithRootSpan(spanName);
        }
        else {
            return startSubSpan(spanName, SpanPurpose.LOCAL_ONLY);
        }
    }

    /**
     * This method is here for the (hopefully rare) cases where you want to start a new span but don't control the
     * context where your code is executed (e.g. a third party library) - this method will start a new overall request
     * span or a new subspan depending on the current thread's span stack state at the time this method is called.
     * In other words this method is a shortcut for the following code:
     *
     * <pre>
     *      Tracer tracer = Tracer.getInstance();
     *      if (tracer.getCurrentSpanStackSize() == 0) {
     *          boolean sampleable = tracer.isNextRootSpanSampleable();
     *          return tracer.startRequestWithSpanInfo(null, null, spanName, sampleable, null, spanPurpose);
     *      }
     *      else {
     *          return tracer.startSubSpan(spanName, spanPurpose);
     *      }
     * </pre>
     *
     * <p>This method lets you pass in the {@link SpanPurpose} for the new span. If you only need the default behavior
     * of {@link SpanPurpose#SERVER} for overall request span and {@link SpanPurpose#LOCAL_ONLY} for subspan, then
     * you can call {@link #startSpanInCurrentContext(String)} instead.
     *
     * <p><b>WARNING:</b> As stated above, this method is here to support libraries where they need to create a span
     * for some work, but do not necessarily know how or where they are going to be used in a project, and therefore
     * don't know whether tracing has been setup yet with an overall request span. Most of the time you will know where
     * you are in relation to overall request span or subspan, and should use the appropriate
     * {@code Tracer.startRequestWith*(...)} or {@code Tracer.startSubSpan(...)} methods directly as those methods spit
     * out error logging when the span stack is not in the expected state (indicating a Wingtips usage error). Using
     * this method everywhere can swallow critical error logging that would otherwise let you know Wingtips isn't being
     * used correctly and that your distributed tracing info is potentially unreliable.
     *
     * <p><b>This method is the equivalent of swallowing exceptions when Wingtips isn't being used correctly - all
     * diagnostic debugging information will be lost. This method should not be used simply because it is
     * convenient!</b>
     *
     * @param spanName The {@link Span#getSpanName()} to use for the new span.
     * @param spanPurpose The {@link SpanPurpose} for the new span. This will be honored regardless of whether the
     * returned span is an overall request span or a subspan.
     * @return A new span that might be the root span of a new span stack (i.e. if the current span stack is empty),
     * or a new subspan (i.e. if the current span stack is *not* empty). NOTE: Please read the warning in this method's
     * javadoc - abusing this method can lead to broken tracing without any errors showing up in the logs.
     */
    public Span startSpanInCurrentContext(String spanName, SpanPurpose spanPurpose) {
        // If the current span stack is empty, then we start a new overall request span. Otherwise we start a subspan.
        //      In either case, honor the passed-in spanPurpose.
        if (getCurrentSpanStackSize() == 0) {
            boolean sampleable = isNextRootSpanSampleable();
            return startRequestWithSpanInfo(null, null, spanName, sampleable, null, spanPurpose);
        }
        else {
            return startSubSpan(spanName, spanPurpose);
        }
    }

    /**
     * Helper method that starts a new span for a fresh request.
     * <p/>
     * <b>WARNING:</b> This wipes out any existing spans on the span stack for this thread and starts fresh, therefore this should only be called at the request's
     * entry point when it's expected that the span stack should be empty. If you need to start a child span in the middle of a request somewhere then you should call
     * {@link #startSubSpan(String, SpanPurpose)} instead.
     *
     * @param traceId The new span's {@link Span#getTraceId()} - if this is null then a new random trace ID will be generated for the returned span. If you have parent span info
     *                then this should be the parent span's <b>trace</b> ID (not the parent span's span ID).
     * @param parentSpanId The parent span's {@link Span#getSpanId()} - if this is null then the returned {@link Span#getParentSpanId()} will be null causing the returned span
     *                     to be a root span. If it's non-null then the returned span will be a child span with {@link Span#getParentSpanId()} matching this value.
     * @param newSpanName The span name to use for the new span - should never be null.
     * @param sampleable The new span's {@link Span#isSampleable()} value. Determines whether or not this span should be logged when it is completed.
     * @param userId The new span's {@link Span#getUserId()} value.
     * @param spanPurpose The span's purpose, or null if the purpose is unknown. See the javadocs for {@link SpanPurpose} for details on what each enum option means.
     *
     * @return The new span (which is now also the current one that will be returned by {@link #getCurrentSpan()}).
     */
    protected Span doNewRequestSpan(String traceId, String parentSpanId, String newSpanName, boolean sampleable, String userId, SpanPurpose spanPurpose) {
        if (newSpanName == null)
            throw new IllegalArgumentException("spanName cannot be null");

        Span span = Span
            .newBuilder(newSpanName, spanPurpose)
            .withTraceId(traceId)
            .withParentSpanId(parentSpanId)
            .withSampleable(sampleable)
            .withUserId(userId)
            .build();

        // Since this is a "starting from scratch/new request" call we clear out and restart the current span stack even if it already had something in it.
        startNewSpanStack(span);

        notifySpanStarted(span);
        notifyIfSpanSampled(span);

        return span;
    }

    /**
     * Helper method that starts a new span stack for a fresh request and sets it on {@link #currentSpanStackThreadLocal}. Since this is assuming a fresh request it expects
     * {@link #currentSpanStackThreadLocal} to have a clean/empty/null stack in it right now. If it has a non-empty stack then it will log an error and clear it out
     * so that the given {@code firstEntry} argument is the only thing that will be on the stack after this method call. Delegates to {@link #pushSpanOntoCurrentSpanStack(Span)}
     * to push the {@code firstEntry} onto the clean stack so it can handle the MDC and debug logging, etc.
     */
    protected void startNewSpanStack(Span firstEntry) {
        // Log an error if we don't have a null/empty existing stack.
        Deque<Span> existingStack = currentSpanStackThreadLocal.get();
        if (existingStack != null && !existingStack.isEmpty()) {
            boolean first = true;
            StringBuilder lostTraceIds = new StringBuilder();
            for (Span span : existingStack) {
                if (!first)
                    lostTraceIds.append(',');
                lostTraceIds.append(span.getTraceId());
                first = false;
            }
            classLogger.error("WINGTIPS USAGE ERROR - We were asked to start a new span stack (i.e. new request) but there was a stack already on this thread with {} old spans. " +
                    "This probably means completeRequestSpan() was not called on the previous request this thread handled. The old spans will be cleared out " +
                    "and lost. wingtips_usage_error=true, dirty_span_stack=true, lost_trace_ids={}",
                    existingStack.size(), lostTraceIds.toString(), new Exception("Stack trace for debugging purposes")
            );

        }

        currentSpanStackThreadLocal.set(new LinkedList<Span>());
        pushSpanOntoCurrentSpanStack(firstEntry);
    }

    /**
     * Uses {@link #spanLoggingRepresentation} to decide how to serialize the given span, and then returns the result of the serialization.
     */
    protected String serializeSpanToDesiredStringRepresentation(Span span) {
        switch(spanLoggingRepresentation) {
            case JSON:
                return span.toJSON();
            case KEY_VALUE:
                return span.toKeyValueString();
            default:
                throw new IllegalStateException("Unknown span logging representation type: " + spanLoggingRepresentation);
        }
    }

    /**
     * Pushes the given span onto the {@link #currentSpanStackThreadLocal} stack. If the stack is null it will create a new one. Also pushes the span info into the logging
     * {@link org.slf4j.MDC} so it is available there.
     */
    protected void pushSpanOntoCurrentSpanStack(Span pushMe) {
        Deque<Span> currentStack = currentSpanStackThreadLocal.get();
        if (currentStack == null) {
            currentStack = new LinkedList<>();
            currentSpanStackThreadLocal.set(currentStack);
        }

        currentStack.push(pushMe);
        configureMDC(pushMe);
        // We don't want to call serializeSpanToDesiredStringRepresentation(...) unless absolutely necessary, so check
        //      that debug logging is enabled before making the classLogger.debug(...) call.
        if (classLogger.isDebugEnabled()) {
            classLogger.debug("** starting sample for span {}", serializeSpanToDesiredStringRepresentation(pushMe));
        }
    }

    /**
     * Completes the current span by calling {@link #completeAndLogSpan(Span, boolean)} on it, empties the MDC by calling{@link #unconfigureMDC()}, and clears out the
     * {@link #currentSpanStackThreadLocal} stack.
     * <p/>
     * This should be called by the overall request when the request is done. At the point this method is called there should just be one span left on the
     * {@link #currentSpanStackThreadLocal} stack - the overall request span. If there is more than 1 then that indicates a bug with the usage of this class where
     * a child span is created but not completed. If this error case is detected then and *all* spans will be logged/popped and an error message will be logged with
     * details on what went wrong.
     */
    public void completeRequestSpan() {
        Deque<Span> currentSpanStack = currentSpanStackThreadLocal.get();
        if (currentSpanStack != null) {
            // Keep track of data as we go in case we need to output an error (we should only have 1 span in the stack)
            int originalSize = currentSpanStack.size();
            StringBuilder badTraceIds = new StringBuilder();

            while (!currentSpanStack.isEmpty()) {
                // Get the next span on the stack.
                Span span = currentSpanStack.pop();

                // Check if it's a "bad" span (i.e. not the last).
                boolean isBadSpan = false;
                if (!currentSpanStack.isEmpty()) {
                    // There's still at least one more span, so this one is "bad".
                    isBadSpan = true;
                    if (badTraceIds.length() > 0)
                        badTraceIds.append(',');
                    badTraceIds.append(span.getTraceId());
                }

                completeAndLogSpan(span, isBadSpan);
            }

            // Output an error message if we had any bad spans.
            if (originalSize > 1) {
                classLogger.error(
                        "WINGTIPS USAGE ERROR - We were asked to fully complete a request span (i.e. end of the request) but there was more than one span on this thread's stack (" +
                        "{} total spans when there should only be one). This probably means completeSubSpan() was not called on child sub-span(s) this thread " +
                        "generated - they should always be in finally clauses or otherwise guaranteed to complete. The bad child sub-spans were logged but the total " +
                        "time spent on the bad child sub-spans will not be correct. wingtips_usage_error=true, dirty_span_stack=true, bad_subspan_trace_ids={}",
                        originalSize, badTraceIds.toString(), new Exception("Stack trace for debugging purposes")
                );
            }
        }

        currentSpanStackThreadLocal.remove();
        unconfigureMDC();
    }

    /**
     * Completes the current child sub-span by calling {@link #completeAndLogSpan(Span, boolean)} on it and then {@link #configureMDC(Span)} on the sub-span's parent
     * (which becomes the new current span).
     * <p/>
     * <b>WARNING:</b> This only works if there are at least 2 spans in the {@link #currentSpanStackThreadLocal} stack - one for the child sub-span and one for the parent span.
     * If you're trying to complete the overall request's span you should be calling {@link #completeRequestSpan()} instead. If there are 0 or 1 spans on the stack then
     * this method will log an error and do nothing.
     */
    public void completeSubSpan() {
        Deque<Span> currentSpanStack = currentSpanStackThreadLocal.get();
        if (currentSpanStack == null || currentSpanStack.size() < 2) {
            int stackSize = (currentSpanStack == null) ? 0 : currentSpanStack.size();
            classLogger.error(
                    "WINGTIPS USAGE ERROR - Expected to find a child sub-span on the stack to complete, but the span stack was size {} instead (there should be at least 2 for " +
                    "this method to be able to find a child sub-span). wingtips_usage_error=true, bad_span_stack=true",
                    stackSize, new Exception("Stack trace for debugging purposes")
            );
            // Nothing to do
            return;
        }

        // We have at least two spans. Pop off the child sub-span and complete/log it.
        Span subSpan = currentSpanStack.pop();
        completeAndLogSpan(subSpan, false);

        // Now configure the MDC with the new current span.
        //noinspection ConstantConditions
        configureMDC(currentSpanStack.peek());
    }

    /**
     * @return the given span's *current* status relative to this {@link Tracer} on the current thread at the time this
     * method is called. This status is recalculated every time this method is called and is only relevant/correct until
     * this {@link Tracer}'s state is modified (i.e. by starting a subspan, completing a span, using any of the
     * asynchronous helper methods to modify the span stack in any way, etc), so it should only be considered relevant
     * for the moment the call is made.
     *
     * <p>NOTE: Most app-level developers should not need to worry about this at all.
     *
     * @see TracerManagedSpanStatus
     */
    public TracerManagedSpanStatus getCurrentManagedStatusForSpan(Span span) {
        // See if this span is the current span.
        if (span.equals(getCurrentSpan())) {
            // This is the current span. It is therefore managed. Now we just need to see if it's the root span or
            //      a subspan. If the span stack size is 1 then it's the root span, otherwise it's a subspan.
            if (getCurrentSpanStackSize() == 1) {
                // It's the root span.
                return TracerManagedSpanStatus.MANAGED_CURRENT_ROOT_SPAN;
            }
            else {
                // It's a subspan.
                return TracerManagedSpanStatus.MANAGED_CURRENT_SUB_SPAN;
            }
        }
        else {
            // This is not the current span - find out if it's managed or unmanaged.
            Deque<Span> currentSpanStack = currentSpanStackThreadLocal.get();
            if (currentSpanStack != null && currentSpanStack.contains(span)) {
                // It's on the stack, therefore it's managed. Now we just need to find out if it's the root span or not.
                if (span.equals(currentSpanStack.peekLast())) {
                    // It is the root span.
                    return TracerManagedSpanStatus.MANAGED_NON_CURRENT_ROOT_SPAN;
                }
                else {
                    // It is a subspan.
                    return TracerManagedSpanStatus.MANAGED_NON_CURRENT_SUB_SPAN;
                }
            }
            else {
                // This span is not in Tracer's current span stack at all, therefore it is unmanaged.
                return TracerManagedSpanStatus.UNMANAGED_SPAN;
            }
        }
    }

    /**
     * Handles the implementation of {@link Span#close()} (for {@link java.io.Closeable}) for spans to allow them to be
     * used in try-with-resources statements. We do the work here instead of in {@link Span#close()} itself since we
     * have more visibility into whether a span is valid to be closed or not given the current state of the span stack.
     * <ul>
     *     <li>
     *         If the given span is already completed ({@link Span#isCompleted()} returns true) then an error will be
     *         logged and nothing will be done.
     *     </li>
     *     <li>
     *         If the span is the current span ({@link #getCurrentSpan()} equals the given span), then {@link
     *         #completeRequestSpan()} or {@link #completeSubSpan()} will be called, whichever is appropriate.
     *     </li>
     *     <li>
     *         If the span is *not* the current span ({@link #getCurrentSpan()} does not equal the given span), then
     *         this may or may not be an error depending on whether the given span is managed by {@link Tracer} or not.
     *         <ul>
     *             <li>
     *                 If the span is managed by us (i.e. it is contained in the span stack somewhere even though it's
     *                 not the current span) then this is a wingtips usage error - the span should not be completed
     *                 yet - and an error will be logged and the given span will be completed and logged to the
     *                 "invalid span logger".
     *             </li>
     *             <li>
     *                 Otherwise the span is not managed by us, and since there may be valid use cases for manually
     *                 managing spans we must assume the call was intentional. No error will be logged, and the span
     *                 will be completed and logged to the "valid span logger".
     *             </li>
     *             <li>
     *                 In either case, the current span stack and MDC info will be left untouched if the given span
     *                 is not the current span.
     *             </li>
     *         </ul>
     *     </li>
     * </ul>
     *
     * <p>NOTE: This is intentionally package-scoped. Only {@link Span#close()} should ever call this method.
     */
    void handleSpanCloseMethod(Span span) {
        // See if this span has already been completed - if so then this method should not have been called.
        if (span.isCompleted()) {
            classLogger.debug(
                "POSSIBLE WINGTIPS USAGE ERROR - An attempt was made to close() a span that was already completed. "
                + "This call to Span.close() will be ignored. "
                + "possible_wingtips_usage_error=true, already_completed_span=true, trace_id={}, span_id={}",
                span.getTraceId(), span.getSpanId()
            );
            return;
        }

        // What we do next depends on the span's TracerManagedSpanStatus state.
        TracerManagedSpanStatus currentManagedState = getCurrentManagedStatusForSpan(span);
        switch(currentManagedState) {
            case MANAGED_CURRENT_ROOT_SPAN:
                // This is the current span, and it's the root span. Complete it as the overall request span.
                completeRequestSpan();
                break;
            case MANAGED_CURRENT_SUB_SPAN:
                // This is the current span, and it's a subspan. Complete it as a subspan.
                completeSubSpan();
                break;
            case MANAGED_NON_CURRENT_ROOT_SPAN: //intentional fall-through
            case MANAGED_NON_CURRENT_SUB_SPAN:
                // This span is one being managed by Tracer but it's not the current one, therefore this is an invalid
                //      wingtips usage situation.
                classLogger.error(
                    "WINGTIPS USAGE ERROR - An attempt was made to close() a Tracer-managed span that was not the "
                    + "current span. This span will be completed as an invalid span but Tracer's current span stack "
                    + "and the current MDC info will be left alone. "
                    + "wingtips_usage_error=true, closed_non_current_span=true, trace_id={}, span_id={}",
                    span.getTraceId(), span.getSpanId(), new Exception("Stack trace for debugging purposes")
                );
                completeAndLogSpan(span, true);
                break;
            case UNMANAGED_SPAN:
                // This span is not in Tracer's current span stack at all. Assume that this span is managed outside
                //      Tracer and that this call is intentional (not an error).
                classLogger.debug(
                    "A Span.close() call was made on a span not managed by Tracer. This is assumed to be intentional, "
                    + "but might indicate an error depending on how your application works. "
                    + "trace_id={}, span_id={}",
                    span.getTraceId(), span.getSpanId()
                );
                completeAndLogSpan(span, false);
                break;
            default:
                throw new IllegalStateException("Unhandled TracerManagedSpanStatus type: " + currentManagedState.name());
        }
    }

    /**
     * Calls {@link Span#complete()} to complete the span and logs it (but only if the span's {@link Span#isSampleable()} returns true). If the span is valid then it will
     * be logged to {@link #validSpanLogger}, and if it is invalid then it will be logged to {@link #invalidSpanLogger}.
     *
     * @param span The span to complete and log
     * @param containsIncorrectTimingInfo Pass in true if you know the given span contains incorrect timing information (e.g. a child sub-span that wasn't completed normally
     *                                    when it was supposed to have been completed), pass in false if the span's timing info is good. This affects how the span is logged.
     */
    protected void completeAndLogSpan(Span span, boolean containsIncorrectTimingInfo) {
        // Call span.complete(), and keep track of whether our call completed it (in case it was previously completed).
        boolean thisCallCompletedTheSpan = span.complete();

        if (!thisCallCompletedTheSpan) {
            // The span was completed previously (or simultaneously by another thread). We didn't complete the span,
            //      so we shouldn't do any notifications or logging. This *can* indicate an error in Wingtips usage,
            //      but there are some use cases where it can happen legitimately. So we'll log a debug warning.
            classLogger.debug(
                "POSSIBLE WINGTIPS USAGE ERROR - An attempt was made to complete a span that was already completed. "
                + "This call will be ignored. "
                + "possible_wingtips_usage_error=true, already_completed_span=true, trace_id={}, span_id={}",
                span.getTraceId(), span.getSpanId()
            );
            return;
        }

        // Notify listeners after completion but before logging to allow listeners to do final span modifications and
        //      have them be reflected in the log message (e.g. change span name, add tags/annotations, etc).
        notifySpanCompleted(span);

        // Log the span if it was sampleable.
        if (span.isSampleable()) {
            String infoTag = containsIncorrectTimingInfo ? "[INCORRECT_TIMING] " : "";
            Logger loggerToUse = containsIncorrectTimingInfo ? invalidSpanLogger : validSpanLogger;
            // Only attempt to log if loggerToUse.isInfoEnabled() returns true, so that we don't incur the cost of
            //      serialization via serializeSpanToDesiredStringRepresentation(span) unless it will actually get used.
            //      This will save CPU if the application has turned off logging in their logger config, but
            //      otherwise allowed Wingtips to function normally.
            if (loggerToUse.isInfoEnabled()) {
                loggerToUse.info(
                    "{}[DISTRIBUTED_TRACING] {}", infoTag, serializeSpanToDesiredStringRepresentation(span)
                );
            }
        }
    }

    /**
     * Sets the span variables on the MDC context.
     */
    protected void configureMDC(@NotNull Span span) {
        for (SpanFieldForLoggerMdc mdcField : this.spanFieldsForLoggerMdc) {
            MDC.put(mdcField.mdcKey, mdcField.getMdcValueForSpan(span));
        }
    }


    /**
     * Removes the MDC parameters.
     */
    protected void unconfigureMDC() {
        for (SpanFieldForLoggerMdc mdcField : this.spanFieldsForLoggerMdc) {
            MDC.remove(mdcField.mdcKey);
        }
    }

    /**
     * Allows you to set the {@link #rootSpanSamplingStrategy} used by this instance. This will throw an {@link IllegalArgumentException} if you pass in null.
     */
    public void setRootSpanSamplingStrategy(RootSpanSamplingStrategy strategy) {
        if (strategy == null)
            throw new IllegalArgumentException("RootSpanSamplingStrategy cannot be null");

        this.rootSpanSamplingStrategy = strategy;
    }

    /**
     * Delegates to {@link #rootSpanSamplingStrategy}'s {@link RootSpanSamplingStrategy#isNextRootSpanSampleable()} method to determine whether the next root span should be
     * sampled.
     * <br/>
     * NOTE: This method is not deterministic - you may get a different response every time you call it. Therefore you should not call this method multiple times for the same
     * root span. Call it once and store the result for a given root span.
     *
     * @return true when the next root span should be sampled, false otherwise.
     */
    protected boolean isNextRootSpanSampleable() {
        return rootSpanSamplingStrategy.isNextRootSpanSampleable();
    }

    /**
     * Adds the given listener to the {@link #spanLifecycleListeners} list using {@link java.util.List#add(Object)}. This method will do nothing if you pass in null.
     * <p/>
     * <b>WARNING:</b> It's important that any {@link SpanLifecycleListener} you add is extremely lightweight or you risk distributed tracing becoming a major bottleneck for
     * high throughput services. If any expensive work needs to be done in a {@link SpanLifecycleListener} then it should be done asynchronously on a thread or threadpool
     * separate from the application worker threads.
     */
    public void addSpanLifecycleListener(SpanLifecycleListener listener) {
        if (listener != null) {
            this.spanLifecycleListeners.add(listener);
        }
    }

    /**
     * Prepends the given listener to the {@link #spanLifecycleListeners} list by using {@link List#add(int, Object)}
     * and passing 0 for the index. This method will do nothing if you pass in null.
     *
     * <p><b>WARNING:</b> It's important that any {@link SpanLifecycleListener} you add is extremely lightweight or you
     * risk distributed tracing becoming a major bottleneck for high throughput services. If any expensive work needs
     * to be done in a {@link SpanLifecycleListener} then it should be done asynchronously on a thread or threadpool
     * separate from the application worker threads.
     */
    public void addSpanLifecycleListenerFirst(SpanLifecycleListener listener) {
        if (listener != null) {
            this.spanLifecycleListeners.add(0, listener);
        }
    }

    /**
     * Returns the value of calling {@link java.util.List#remove(Object)} on {@link #spanLifecycleListeners}.
     */
    public boolean removeSpanLifecycleListener(SpanLifecycleListener listener) {
        //noinspection SimplifiableIfStatement
        if (listener == null)
            return false;

        return this.spanLifecycleListeners.remove(listener);
    }

    /**
     * Calls {@link List#clear()} on {@link #spanLifecycleListeners} to remove all {@link SpanLifecycleListener}s.
     * This is here primarily to ease unit testing where you need to "reset" {@link Tracer} between tests.
     */
    public void removeAllSpanLifecycleListeners() {
        this.spanLifecycleListeners.clear();
    }

    /**
     * @return The {@link #spanLifecycleListeners} list of span lifecycle listeners associated with this instance, wrapped in a {@link Collections#unmodifiableList(List)}
     *          to prevent direct modification. This will never return null.
     */
    public List<SpanLifecycleListener> getSpanLifecycleListeners() {
        return Collections.unmodifiableList(this.spanLifecycleListeners);
    }

    /**
     * @return The currently selected option for how spans will be serialized when they are completed and logged.
     */
    public SpanLoggingRepresentation getSpanLoggingRepresentation() {
        return spanLoggingRepresentation;
    }

    /**
     * Sets the option for how spans will be serialized when they are completed and logged.
     */
    public void setSpanLoggingRepresentation(SpanLoggingRepresentation spanLoggingRepresentation) {
        if (spanLoggingRepresentation == null)
            throw new IllegalArgumentException("spanLoggingRepresentation cannot be null.");

        this.spanLoggingRepresentation = spanLoggingRepresentation;
    }

    /**
     * @return The currently selected options for which span fields will be placed in the logger {@link MDC} as
     * {@link Tracer} works with spans, wrapped in a {@link Collections#unmodifiableSet(Set)} to prevent direct
     * modification. This will never return null.
     */
    public Set<SpanFieldForLoggerMdc> getSpanFieldsForLoggerMdc() {
        return cachedUnmodifiableSpanFieldsForLoggerMdc;
    }

    /**
     * Tells {@link Tracer} which {@link Span} fields should be included in the logger {@link MDC} when the current
     * span for a thread changes. The default behavior without calling this method just includes
     * {@link SpanFieldForLoggerMdc#TRACE_ID}.
     *
     * <p>NOTE: It's recommended that you always include {@link SpanFieldForLoggerMdc#TRACE_ID}.
     *
     * @param fieldsForMdc The fields you want included in the logger {@link MDC}. You can pass null or an empty array,
     * in which case the MDC will never be updated with span info. NOTE: It's recommended that you always include
     * {@link SpanFieldForLoggerMdc#TRACE_ID}.
     */
    public void setSpanFieldsForLoggerMdc(SpanFieldForLoggerMdc ... fieldsForMdc) {
        if (fieldsForMdc == null) {
            fieldsForMdc = new SpanFieldForLoggerMdc[0];
        }
        
        setSpanFieldsForLoggerMdc(new LinkedHashSet<>(Arrays.asList(fieldsForMdc)));
    }

    /**
     * Tells {@link Tracer} which {@link Span} fields should be included in the logger {@link MDC} when the current
     * span for a thread changes. The default behavior without calling this method just includes
     * {@link SpanFieldForLoggerMdc#TRACE_ID}.
     *
     * <p>NOTE: It's recommended that you always include {@link SpanFieldForLoggerMdc#TRACE_ID}.
     *
     * @param fieldsForMdc The fields you want included in the logger {@link MDC}. You can pass null or an empty set,
     * in which case the MDC will never be updated with span info. NOTE: It's recommended that you always include
     * {@link SpanFieldForLoggerMdc#TRACE_ID}.
     */
    public void setSpanFieldsForLoggerMdc(Set<SpanFieldForLoggerMdc> fieldsForMdc) {
        if (fieldsForMdc == null) {
            fieldsForMdc = Collections.emptySet();
        }

        this.spanFieldsForLoggerMdc = fieldsForMdc.toArray(new SpanFieldForLoggerMdc[0]);
        this.cachedUnmodifiableSpanFieldsForLoggerMdc =
            Collections.unmodifiableSet(new LinkedHashSet<>(fieldsForMdc));

        if (!fieldsForMdc.contains(SpanFieldForLoggerMdc.TRACE_ID)) {
            classLogger.warn(
                "setSpanFieldsForLoggerMdc(...) was called without including TRACE_ID. This is not recommended! "
                + "It's recommended that you always include TRACE_ID unless you really know what you're doing. "
                + "span_fields_for_mdc_received={}", fieldsForMdc
            );
        }
    }

    /**
     * Notifies all listeners that the given span was started using {@link SpanLifecycleListener#spanStarted(Span)}
     */
    protected void notifySpanStarted(Span span) {
        for (SpanLifecycleListener tll : spanLifecycleListeners) {
            tll.spanStarted(span);
        }
    }

    /**
     * Notifies all listeners that the given span was sampled using {@link SpanLifecycleListener#spanSampled(Span)}, <b>but only if the span's {@link Span#isSampleable()}
     * method returns true!</b> If the span is not sampleable then this method does nothing.
     */
    protected void notifyIfSpanSampled(Span span) {
        if (span.isSampleable()) {
            for (SpanLifecycleListener tll : spanLifecycleListeners) {
                tll.spanSampled(span);
            }
        }
    }

    /**
     * Notifies all listeners that the given span was completed using {@link SpanLifecycleListener#spanCompleted(Span)}
     */
    protected void notifySpanCompleted(Span span) {
        for (SpanLifecycleListener tll : spanLifecycleListeners) {
            tll.spanCompleted(span);
        }
    }

    /**
     * @return A *copy* of the current thread's tracing information. Since this creates copies of the span stack and MDC
     * info it can have a noticeable performance impact if used too many times (i.e. tens or hundreds of times per
     * request for high throughput services). NOTE: This is usually not needed unless you're doing asynchronous
     * processing and need to pass tracing state across thread boundaries.
     */
    public TracingState getCurrentTracingStateCopy() {
        return new TracingState(getCurrentSpanStackCopy(), MDC.getCopyOfContextMap());
    }

    /**
     * IMPORTANT: This method is not for typical thread-per-request application usage - most of the time you just want {@link #getCurrentSpan()}. This method is here to
     * facilitate complex asynchronous threading situations.
     * <p/>
     * Returns a *COPY* of the current span stack. A copy is returned instead of the original to prevent you from directly modifying the stack - you should use the start/complete
     * request span/sub-span methods to manipulate the stack. But sometimes you need to see what the stack looks like, or store it at a certain state so you can reconstitute
     * it later (you can sort of do this with {@link #unregisterFromThread()} which also returns the span stack, but that unregisters everything and sometimes you need to
     * store for later without interrupting current state).
     * <p/>
     * This method may return null or an empty stack, depending on its current state.
     */
    public Deque<Span> getCurrentSpanStackCopy() {
        Deque<Span> currentStack = currentSpanStackThreadLocal.get();
        if (currentStack == null)
            return null;

        return new LinkedList<>(currentStack);
    }

    /**
     * @return The size of the current span stack - useful when you want to know the size, but don't want to incur the
     * cost of {@link #getCurrentSpanStackCopy()}.
     */
    public int getCurrentSpanStackSize() {
        Deque<Span> currentStack = currentSpanStackThreadLocal.get();
        if (currentStack == null)
            return 0;

        return currentStack.size();
    }

    /**
     * "Unregisters" the current span stack from this thread, removes span-related info from the logging MDC, and returns the span stack that was unregistered so it
     * can be stored and re-registered later (if desired). This is used in asynchronous projects/frameworks where multiple in-progress requests might be handled by the same thread
     * before any of the requests can finish (e.g. Netty, where the worker I/O threads might process a portion of request A, flip over to request B to do some work, then go back
     * to request A, etc). This method lets you unregister the tracing info from the current thread when it is about to switch to processing a different request so the tracing
     * info can be stored in some kind of request context state that follows the request, and won't pollute the thread's span stack and MDC info when the new request starts being
     * processed.
     * <br/>
     * When processing switches back to the original request, you can call {@link #registerWithThread(java.util.Deque)} to set the thread's span stack and MDC info back to the
     * way it was when you first called this unregister method for the original request.
     * <p/>
     * <b>WARNING:</b> This method should NOT be called if you're in an environment where a single thread is guaranteed to process a request from start to finish without jumping
     * to a different request in the middle. In that case just use the normal start and complete span methods and ignore this method.
     */
    public Deque<Span> unregisterFromThread() {
        Deque<Span> currentValue = currentSpanStackThreadLocal.get();
        currentSpanStackThreadLocal.remove();
        unconfigureMDC();
        return currentValue;
    }

    /**
     * @return true if the two given stacks contain the same spans in the same order, false otherwise.
     */
    protected boolean containsSameSpansInSameOrder(Deque<Span> stack, Deque<Span> other) {
        if (stack == other)
            return true;

        if (stack == null || other == null)
            return false;

        if (stack.size() != other.size())
            return false;

        Iterator<Span> stackIterator = stack.iterator();
        Iterator<Span> otherIterator = other.iterator();

        while (stackIterator.hasNext()) {
            Span t1 = stackIterator.next();
            Span t2 = otherIterator.next();

            if (t1 != t2) {
                // Not the same instance, and at least one is non-null.
                if (t1 == null || t2 == null)
                    return false;

                if (!t1.equals(t2))
                    return false;
            }
        }

        return true;
    }

    /**
     * "Registers" a *COPY* of the given span stack with this thread (sets up the ThreadLocal span stack with a copy of the given argument) and sets up the MDC appropriately
     * based on what you pass in. This is used in asynchronous projects/frameworks where multiple in-progress requests might be handled by the same thread
     * before any of the requests can finish (e.g. Netty, where the worker I/O threads might process a portion of request A, flip over to request B to do some work, then go back
     * to request A, etc). When the worker thread was about to stop working on the original request and move to a new one you would call {@link #unregisterFromThread()}
     * and store the result with some kind of context state that follows the original request, and when processing switches back to the original request you would call
     * this method to set the thread's span stack and MDC info back to the way it was so you could continue where you left off.
     * <p/>
     * In a properly functioning project/framework this thread would not have any span information in its stack when this method was called, so if there are any spans already
     * on this thread then this method will mark them as invalid, complete them, and log an appropriate error message before registering the stack passed into this method.
     * <p/>
     * NOTE: A *copy* of the given stack is registered so that changes to the stack you pass in don't affect the stack stored here. This prevents a host of subtle annoying bugs.
     * <p/>
     * <b>WARNING:</b> This method should NOT be called if you're in an environment where a single thread is guaranteed to process a request from start to finish without jumping
     * to a different request in the middle. In that case just use the normal start and complete span methods and ignore this method.
     */
    public void registerWithThread(Deque<Span> registerMe) {
        Deque<Span> currentSpanStack = currentSpanStackThreadLocal.get();

        // Do nothing if the passed-in stack is functionally identical to what we already have.
        if (!containsSameSpansInSameOrder(currentSpanStack, registerMe)) {
            // Not the same span stack. See if there was an old stale stack still around.
            if (currentSpanStack != null && currentSpanStack.size() > 0) {
                // Whoops, someone else is trying to register with this thread while it's already in the middle of handling spans.
                int originalSize = currentSpanStack.size();
                StringBuilder badTraceIds = new StringBuilder();

                // Complete and output all the spans, but they will all be marked "bad".
                while (!currentSpanStack.isEmpty()) {
                    Span span = currentSpanStack.pop();

                    if (badTraceIds.length() > 0)
                        badTraceIds.append(',');
                    badTraceIds.append(span.getTraceId());

                    completeAndLogSpan(span, true);
                }

                // Output an error message
                classLogger.error("WINGTIPS USAGE ERROR - We were asked to register a span stack with this thread (i.e. for systems that use threads asynchronously to perform work on " +
                                  "multiple requests at a time before any given request is completed) but there was already a non-empty span stack on this thread ({} total " +
                                  "spans when there should be zero). This probably means unregisterFromThread() was not called the last time this request's thread dropped it " +
                                  "to go work on a different request. Whenever a thread stops work on a request to go do something else when the request is not complete it " +
                                  "should unregisterFromThread() in a finally block or some other way to guarantee it doesn't leave an unfinished stack dangling. The bad " +
                                  "request span/sub-spans were logged but the reported total time spent on them will not be correct. wingtips_usage_error=true, dirty_span_stack=true, " +
                                  "bad_child_span_ids={}",
                        originalSize,  badTraceIds.toString(), new Exception("Stack trace for debugging purposes")
                );
            }

            // At this point any errors have been handled and we can register the new stack. Make sure we register a copy so that changes to the original don't affect our stack.
            registerMe = (registerMe == null) ? null : new LinkedList<>(registerMe);
            currentSpanStackThreadLocal.set(registerMe);
        }

        // Make sure we fix the MDC to the passed-in info.
        Span newStackLatestSpan = (registerMe == null || registerMe.isEmpty()) ? null : registerMe.peek();
        if (newStackLatestSpan == null)
            unconfigureMDC();
        else
            configureMDC(newStackLatestSpan);
    }

} public enum SpanFieldForLoggerMdc {
    /**
     * MDC key for storing the current span's {@link Span#getTraceId()}.
     */
    TRACE_ID("traceId"),
    /**
     * MDC key for storing the current span's {@link Span#getSpanId()}.
     */
    SPAN_ID("spanId"),
    /**
     * MDC key for storing the current span's {@link Span#getParentSpanId()}.
     */
    PARENT_SPAN_ID("parentSpanId"),
    /**
     * MDC key for storing the current {@link Span} as a JSON string.
     *
     * <p>WARNING: This can get expensive in some use cases where processing hops threads frequently and the span
     * state is changed often (i.e. lots of tags). Make sure you absolutely need this before including it in a call
     * to {@link #setSpanFieldsForLoggerMdc(SpanFieldForLoggerMdc...)} or {@link #setSpanFieldsForLoggerMdc(Set)}.
     */
    FULL_SPAN_JSON("spanJson");

    /**
     * The key to use when calling {@link MDC#put(String, String)} or {@link MDC#remove(String)}.
     */
    public final String mdcKey;

    SpanFieldForLoggerMdc(String mdcKey) {
        this.mdcKey = mdcKey;
    }

    /**
     * @param span The span to grab the value from.
     * @return The string from the given span associated with this {@link SpanFieldForLoggerMdc}.
     */
    public @Nullable String getMdcValueForSpan(@NotNull Span span) {
        switch (this) {
            case TRACE_ID:
                return span.getTraceId();
            case SPAN_ID:
                return span.getSpanId();
            case PARENT_SPAN_ID:
                return span.getParentSpanId();
            case FULL_SPAN_JSON:
                return span.toJSON();
            default:
                throw new IllegalStateException("Unhandled SpanFieldForLoggerMdc type: " + this);
        }
    }
} public class TestUtils {

    public static void resetTracing() {
        MDC.clear();
        Tracer.getInstance().unregisterFromThread();
        Tracer.getInstance().removeAllSpanLifecycleListeners();
    }

    public static void sleepThread(long sleepTimeMillis) {
        try {
            Thread.sleep(sleepTimeMillis);
        }
        catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public static class SpanRecorder implements SpanLifecycleListener {

        public final List<Span> completedSpans = Collections.synchronizedList(new ArrayList<>());

        @Override
        public void spanStarted(Span span) { }

        @Override
        public void spanSampled(Span span) { }

        @Override
        public void spanCompleted(Span span) {
            // Create a copy so we know what the span looked like exactly when it was completed (in case other tags
            //      are added after completion, for example).
            completedSpans.add(Span.newBuilder(span).build());
        }

        public void waitUntilSpanRecorderHasExpectedNumSpans(int expectedNumSpans, Duration timeout) {
            long timeoutMillis = timeout.toMillis();
            long startTimeMillis = System.currentTimeMillis();
            while (completedSpans.size() < expectedNumSpans) {
                sleepThread(10);

                long timeSinceStart = System.currentTimeMillis() - startTimeMillis;
                if (timeSinceStart > timeoutMillis) {
                    throw new RuntimeException(
                        "spanRecorder did not have the expected number of spans after waiting "
                        + timeoutMillis + " milliseconds"
                    );
                }
            }
        }
    }

} public static class SpanRecorder implements SpanLifecycleListener {

    public final List<Span> completedSpans = Collections.synchronizedList(new ArrayList<>());

    @Override
    public void spanStarted(Span span) { }

    @Override
    public void spanSampled(Span span) { }

    @Override
    public void spanCompleted(Span span) {
        // Create a copy so we know what the span looked like exactly when it was completed (in case other tags
        //      are added after completion, for example).
        completedSpans.add(Span.newBuilder(span).build());
    }

    public void waitUntilSpanRecorderHasExpectedNumSpans(int expectedNumSpans, Duration timeout) {
        long timeoutMillis = timeout.toMillis();
        long startTimeMillis = System.currentTimeMillis();
        while (completedSpans.size() < expectedNumSpans) {
            sleepThread(10);

            long timeSinceStart = System.currentTimeMillis() - startTimeMillis;
            if (timeSinceStart > timeoutMillis) {
                throw new RuntimeException(
                    "spanRecorder did not have the expected number of spans after waiting "
                    + timeoutMillis + " milliseconds"
                );
            }
        }
    }
} class AsyncWingtipsHelperDefaultImpl implements AsyncWingtipsHelper {
    // Nothing beyond the default, except protected static helper methods that the AsyncWingtipsHelper interface's
    //      default methods can reuse for util stuff.

    private static final Logger logger = LoggerFactory.getLogger(AsyncWingtipsHelperDefaultImpl.class);

    /**
     * @param ex The exception that may need wrapping in a {@link RuntimeException}.
     * @return The given ex if it is a RuntimeException already, otherwise a new {@link RuntimeException} will
     * be returned with the given ex as its cause.
     */
    protected static RuntimeException wrapCheckedExInRuntimeExIfNecessary(Exception ex) {
        if (ex instanceof RuntimeException) {
            return (RuntimeException)ex;
        }
        else {
            return new RuntimeException(ex);
        }
    }

    protected static <T> void doSpanTaggingWithoutExceptionPropagation(
        @Nullable Span span,
        @NotNull OperationWrapperOptions<T> options,
        @Nullable T payload,
        @Nullable Throwable error
    ) {
        //noinspection ConstantConditions
        if (span == null || options == null) {
            return;
        }

        if (options.spanTagger != null) {
            try {
                options.spanTagger.tagSpan(span, payload);
            }
            catch (Exception ex) {
                logger.error(
                    "An unexpected error occurred while executing a SpanTagger. This exception will "
                    + "be swallowed to avoid unexpected application breakage, but you should fix your "
                    + "SpanTagger so it doesn't throw an exception.",
                    ex
                );
            }
        }

        if (options.errorTagger != null && error != null) {
            try {
                options.errorTagger.tagSpanForError(span, error);
            }
            catch (Exception ex) {
                logger.error(
                    "An unexpected error occurred while executing a ErrorSpanTagger. This exception will "
                    + "be swallowed to avoid unexpected application breakage, but you should fix your "
                    + "ErrorSpanTagger so it doesn't throw an exception.",
                    ex
                );
            }
        }
    }

    protected static void doCloseSpanIfPossible(@Nullable Span span) {
        if (span == null) {
            return;
        }

        // Span.close() will do the right thing regardless of whether it's the current span on this thread.
        span.close();
    }
} @FunctionalInterface
interface CheckedRunnable {
    /**
     * Executes this {@link CheckedRunnable}.
     *
     * @throws Exception an exception may be thrown from this method.
     */
    void run() throws Exception;
} @SuppressWarnings("WeakerAccess")
public class ConsumerWithTracing<T> implements Consumer<T> {

    protected final Consumer<T> origConsumer;
    protected final Deque<Span> spanStackForExecution;
    protected final Map<String, String> mdcContextMapForExecution;

    /**
     * Constructor that extracts the current tracing and MDC information from the current thread using {@link
     * Tracer#getCurrentSpanStackCopy()} and {@link MDC#getCopyOfContextMap()}, and forwards the information to
     * the {@link ConsumerWithTracing#ConsumerWithTracing(Consumer, Deque, Map)}
     * constructor. That tracing and MDC information will be associated with the thread when the given operation is
     * executed.
     *
     * <p>The operation you pass in cannot be null (an {@link IllegalArgumentException} will be thrown if you pass in
     * null for the operation).
     */
    public ConsumerWithTracing(Consumer<T> origConsumer) {
        this(origConsumer, Tracer.getInstance().getCurrentSpanStackCopy(), MDC.getCopyOfContextMap());
    }

    /**
     * Constructor that uses the given trace and MDC information, which will be associated with the thread when the
     * given operation is executed.
     *
     * <p>The operation you pass in cannot be null (an {@link IllegalArgumentException} will be thrown if you pass in
     * null for the operation).
     *
     * <p>The {@link Pair} can be null, or you can pass null for the left and/or right side of the pair, and no error
     * will be thrown. Any trace or MDC info that is null means the corresponding info will not be available to the
     * thread when the operation is executed however.
     *
     * <p>You can pass in a {@link TracingState} for clearer less verbose code since it extends
     * {@code Pair<Deque<Span>, Map<String, String>>}.
     */
    public ConsumerWithTracing(Consumer<T> origConsumer,
                               Pair<Deque<Span>, Map<String, String>> originalThreadInfo) {
        this(
            origConsumer,
            (originalThreadInfo == null) ? null : originalThreadInfo.getLeft(),
            (originalThreadInfo == null) ? null : originalThreadInfo.getRight()
        );
    }

    /**
     * Constructor that uses the given trace and MDC information, which will be associated with the thread when the
     * given operation is executed.
     *
     * <p>The operation you pass in cannot be null (an {@link IllegalArgumentException} will be thrown if you pass in
     * null for the operation).
     *
     * <p>The trace and/or MDC info can be null and no error will be thrown, however any trace or MDC info that is null
     * means the corresponding info will not be available to the thread when the operation is executed.
     */
    public ConsumerWithTracing(Consumer<T> origConsumer,
                               Deque<Span> spanStackForExecution,
                               Map<String, String> mdcContextMapForExecution) {
        if (origConsumer == null)
            throw new IllegalArgumentException("origConsumer cannot be null");

        this.origConsumer = origConsumer;
        this.spanStackForExecution = spanStackForExecution;
        this.mdcContextMapForExecution = mdcContextMapForExecution;
    }

    /**
     * Equivalent to calling {@code new ConsumerWithTracing(origConsumer)} - this allows you to do a static method
     * import for cleaner looking code in some cases. This method ultimately extracts the current tracing and MDC
     * information from the current thread using {@link Tracer#getCurrentSpanStackCopy()} and {@link
     * MDC#getCopyOfContextMap()}. That tracing and MDC information will be associated with the thread when the given
     * operation is executed.
     *
     * <p>The operation you pass in cannot be null (an {@link IllegalArgumentException} will be thrown if you pass in
     * null for the operation).
     *
     * @return {@code new ConsumerWithTracing(origConsumer)}.
     * @see ConsumerWithTracing#ConsumerWithTracing(Consumer)
     * @see ConsumerWithTracing
     */
    public static <T> ConsumerWithTracing<T> withTracing(Consumer<T> origConsumer) {
        return new ConsumerWithTracing<>(origConsumer);
    }

    /**
     * Equivalent to calling {@code new ConsumerWithTracing(origConsumer, originalThreadInfo)} - this allows you
     * to do a static method import for cleaner looking code in some cases. This method uses the given trace and MDC
     * information, which will be associated with the thread when the given operation is executed.
     *
     * <p>The operation you pass in cannot be null (an {@link IllegalArgumentException} will be thrown if you pass in
     * null for the operation).
     *
     * <p>The {@link Pair} can be null, or you can pass null for the left and/or right side of the pair, and no error
     * will be thrown. Any trace or MDC info that is null means the corresponding info will not be available to the
     * thread when the operation is executed however.
     *
     * <p>You can pass in a {@link TracingState} for clearer less verbose code since it extends
     * {@code Pair<Deque<Span>, Map<String, String>>}.
     *
     * @return {@code new ConsumerWithTracing(origConsumer, originalThreadInfo)}.
     * @see ConsumerWithTracing#ConsumerWithTracing(Consumer, Pair)
     * @see ConsumerWithTracing
     */
    public static <T> ConsumerWithTracing<T> withTracing(Consumer<T> origConsumer,
                                                         Pair<Deque<Span>, Map<String, String>> originalThreadInfo) {
        return new ConsumerWithTracing<>(origConsumer, originalThreadInfo);
    }

    /**
     * Equivalent to calling {@code
     * new ConsumerWithTracing(origConsumer, spanStackForExecution, mdcContextMapForExecution)} -
     * this allows you to do a static method import for cleaner looking code in some cases. This method uses the given
     * trace and MDC information, which will be associated with the thread when the given operation is executed.
     *
     * <p>The operation you pass in cannot be null (an {@link IllegalArgumentException} will be thrown if you pass in
     * null for the operation).
     *
     * <p>The trace and/or MDC info can be null and no error will be thrown, however any trace or MDC info that is null
     * means the corresponding info will not be available to the thread when the operation is executed.
     *
     * @return {@code new ConsumerWithTracing(origConsumer, spanStackForExecution, mdcContextMapForExecution)}.
     * @see ConsumerWithTracing#ConsumerWithTracing(Consumer, Deque, Map)
     * @see ConsumerWithTracing
     */
    public static <T> ConsumerWithTracing<T> withTracing(Consumer<T> origConsumer,
                                                         Deque<Span> spanStackForExecution,
                                                         Map<String, String> mdcContextMapForExecution) {
        return new ConsumerWithTracing<>(origConsumer, spanStackForExecution, mdcContextMapForExecution);
    }
    
    @Override
    public void accept(T t) {
        TracingState originalThreadInfo = null;
        try {
            originalThreadInfo =
                linkTracingToCurrentThread(spanStackForExecution, mdcContextMapForExecution);

            origConsumer.accept(t);
        }
        finally {
            unlinkTracingFromCurrentThread(originalThreadInfo);
        }
    }
} public class ExecutorServiceWithTracing implements ExecutorService {

    protected final ExecutorService delegate;

    /**
     * Creates a new instance that wraps the given delegate {@link ExecutorService} so that when {@link Runnable}s
     * or {@link Callable}s are executed they will automatically inherit the tracing state of the thread that called
     * the {@link ExecutorService} method.
     *
     * <p>WARNING: Keep in mind that you should avoid using a {@link ExecutorServiceWithTracing} when spinning off
     * background threads that aren't tied to a specific trace, or in any other situation where an executed
     * {@link Runnable}/{@link Callable} should *not* automatically inherit the calling thread's tracing state!
     *
     * @param delegate The {@link ExecutorService} to delegate all calls to.
     */
    public ExecutorServiceWithTracing(ExecutorService delegate) {
        this.delegate = delegate;
    }

    /**
     * Factory method that creates a new instance that wraps the given delegate {@link ExecutorService} so that when
     * {@link Runnable}s or {@link Callable}s are executed they will automatically inherit the tracing state of the
     * thread that called the {@link ExecutorService} method. Equivalent to calling:
     * {@code new ExecutorServiceWithTracing(delegate)}.
     *
     * <p>WARNING: Keep in mind that you should avoid using a {@link ExecutorServiceWithTracing} when spinning off
     * background threads that aren't tied to a specific trace, or in any other situation where an executed
     * {@link Runnable}/{@link Callable} should *not* automatically inherit the calling thread's tracing state!
     *
     * @param delegate The {@link ExecutorService} to delegate all calls to.
     * @return {@code new ExecutorServiceWithTracing(delegate)}
     */
    public static ExecutorServiceWithTracing withTracing(ExecutorService delegate) {
        return new ExecutorServiceWithTracing(delegate);
    }

    @Override
    public void shutdown() {
        delegate.shutdown();
    }

    @Override
    public List<Runnable> shutdownNow() {
        return delegate.shutdownNow();
    }

    @Override
    public boolean isShutdown() {
        return delegate.isShutdown();
    }

    @Override
    public boolean isTerminated() {
        return delegate.isTerminated();
    }

    @Override
    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
        return delegate.awaitTermination(timeout, unit);
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        return delegate.submit(new CallableWithTracing<>(task));
    }

    @Override
    public <T> Future<T> submit(Runnable task, T result) {
        return delegate.submit(new RunnableWithTracing(task), result);
    }

    @Override
    public Future<?> submit(Runnable task) {
        return delegate.submit(new RunnableWithTracing(task));
    }

    @Override
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
        return delegate.invokeAll(convertToCallableWithTracingList(tasks));
    }

    @Override
    public <T> List<Future<T>> invokeAll(
        Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit
    ) throws InterruptedException {
        return delegate.invokeAll(convertToCallableWithTracingList(tasks), timeout, unit);
    }

    @Override
    public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
        return delegate.invokeAny(convertToCallableWithTracingList(tasks));
    }

    @Override
    public <T> T invokeAny(
        Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit
    ) throws InterruptedException, ExecutionException, TimeoutException {
        return delegate.invokeAny(convertToCallableWithTracingList(tasks), timeout, unit);
    }

    protected <T> List<Callable<T>> convertToCallableWithTracingList(Collection<? extends Callable<T>> tasks) {
        if (tasks == null) {
            return null;
        }

        List<Callable<T>> tasksWithTracing = new ArrayList<>(tasks.size());
        for (Callable<T> task : tasks) {
            Callable<T> taskWithTracing = (task == null) ? null : new CallableWithTracing<>(task);
            tasksWithTracing.add(taskWithTracing);
        }

        return tasksWithTracing;
    }

    @Override
    public void execute(Runnable command) {
        delegate.execute(new RunnableWithTracing(command));
    }
} @SuppressWarnings("WeakerAccess")
public class PredicateWithTracing<T> implements Predicate<T> {

    protected final Predicate<T> origPredicate;
    protected final Deque<Span> spanStackForExecution;
    protected final Map<String, String> mdcContextMapForExecution;

    /**
     * Constructor that extracts the current tracing and MDC information from the current thread using {@link
     * Tracer#getCurrentSpanStackCopy()} and {@link MDC#getCopyOfContextMap()}, and forwards the information to
     * the {@link PredicateWithTracing#PredicateWithTracing(Predicate, Deque, Map)}
     * constructor. That tracing and MDC information will be associated with the thread when the given operation is
     * executed.
     *
     * <p>The operation you pass in cannot be null (an {@link IllegalArgumentException} will be thrown if you pass in
     * null for the operation).
     */
    public PredicateWithTracing(Predicate<T> origPredicate) {
        this(origPredicate, Tracer.getInstance().getCurrentSpanStackCopy(), MDC.getCopyOfContextMap());
    }

    /**
     * Constructor that uses the given trace and MDC information, which will be associated with the thread when the
     * given operation is executed.
     *
     * <p>The operation you pass in cannot be null (an {@link IllegalArgumentException} will be thrown if you pass in
     * null for the operation).
     *
     * <p>The {@link Pair} can be null, or you can pass null for the left and/or right side of the pair, and no error
     * will be thrown. Any trace or MDC info that is null means the corresponding info will not be available to the
     * thread when the operation is executed however.
     *
     * <p>You can pass in a {@link TracingState} for clearer less verbose code since it extends
     * {@code Pair<Deque<Span>, Map<String, String>>}.
     */
    public PredicateWithTracing(Predicate<T> origPredicate,
                                Pair<Deque<Span>, Map<String, String>> originalThreadInfo) {
        this(
            origPredicate,
            (originalThreadInfo == null) ? null : originalThreadInfo.getLeft(),
            (originalThreadInfo == null) ? null : originalThreadInfo.getRight()
        );
    }

    /**
     * Constructor that uses the given trace and MDC information, which will be associated with the thread when the
     * given operation is executed.
     *
     * <p>The operation you pass in cannot be null (an {@link IllegalArgumentException} will be thrown if you pass in
     * null for the operation).
     *
     * <p>The trace and/or MDC info can be null and no error will be thrown, however any trace or MDC info that is null
     * means the corresponding info will not be available to the thread when the operation is executed.
     */
    public PredicateWithTracing(Predicate<T> origPredicate,
                                Deque<Span> spanStackForExecution,
                                Map<String, String> mdcContextMapForExecution) {
        if (origPredicate == null)
            throw new IllegalArgumentException("origPredicate cannot be null");

        this.origPredicate = origPredicate;
        this.spanStackForExecution = spanStackForExecution;
        this.mdcContextMapForExecution = mdcContextMapForExecution;
    }

    /**
     * Equivalent to calling {@code new PredicateWithTracing(origPredicate)} - this allows you to do a static method
     * import for cleaner looking code in some cases. This method ultimately extracts the current tracing and MDC
     * information from the current thread using {@link Tracer#getCurrentSpanStackCopy()} and {@link
     * MDC#getCopyOfContextMap()}. That tracing and MDC information will be associated with the thread when the given
     * operation is executed.
     *
     * <p>The operation you pass in cannot be null (an {@link IllegalArgumentException} will be thrown if you pass in
     * null for the operation).
     *
     * @return {@code new PredicateWithTracing(origPredicate)}.
     * @see PredicateWithTracing#PredicateWithTracing(Predicate)
     * @see PredicateWithTracing
     */
    public static <T> PredicateWithTracing<T> withTracing(Predicate<T> origPredicate) {
        return new PredicateWithTracing<>(origPredicate);
    }

    /**
     * Equivalent to calling {@code new PredicateWithTracing(origPredicate, originalThreadInfo)} - this allows you
     * to do a static method import for cleaner looking code in some cases. This method uses the given trace and MDC
     * information, which will be associated with the thread when the given operation is executed.
     *
     * <p>The operation you pass in cannot be null (an {@link IllegalArgumentException} will be thrown if you pass in
     * null for the operation).
     *
     * <p>The {@link Pair} can be null, or you can pass null for the left and/or right side of the pair, and no error
     * will be thrown. Any trace or MDC info that is null means the corresponding info will not be available to the
     * thread when the operation is executed however.
     *
     * <p>You can pass in a {@link TracingState} for clearer less verbose code since it extends
     * {@code Pair<Deque<Span>, Map<String, String>>}.
     *
     * @return {@code new PredicateWithTracing(origPredicate, originalThreadInfo)}.
     * @see PredicateWithTracing#PredicateWithTracing(Predicate, Pair)
     * @see PredicateWithTracing
     */
    public static <T> PredicateWithTracing<T> withTracing(Predicate<T> origPredicate,
                                                          Pair<Deque<Span>, Map<String, String>> originalThreadInfo) {
        return new PredicateWithTracing<>(origPredicate, originalThreadInfo);
    }

    /**
     * Equivalent to calling {@code
     * new PredicateWithTracing(origPredicate, spanStackForExecution, mdcContextMapForExecution)} -
     * this allows you to do a static method import for cleaner looking code in some cases. This method uses the given
     * trace and MDC information, which will be associated with the thread when the given operation is executed.
     *
     * <p>The operation you pass in cannot be null (an {@link IllegalArgumentException} will be thrown if you pass in
     * null for the operation).
     *
     * <p>The trace and/or MDC info can be null and no error will be thrown, however any trace or MDC info that is null
     * means the corresponding info will not be available to the thread when the operation is executed.
     *
     * @return {@code new PredicateWithTracing(origPredicate, spanStackForExecution, mdcContextMapForExecution)}.
     * @see PredicateWithTracing#PredicateWithTracing(Predicate, Deque, Map)
     * @see PredicateWithTracing
     */
    public static <T> PredicateWithTracing<T> withTracing(Predicate<T> origPredicate,
                                                          Deque<Span> spanStackForExecution,
                                                          Map<String, String> mdcContextMapForExecution) {
        return new PredicateWithTracing<>(origPredicate, spanStackForExecution, mdcContextMapForExecution);
    }

    @Override
    public boolean test(T o) {
        TracingState originalThreadInfo = null;
        try {
            originalThreadInfo =
                linkTracingToCurrentThread(spanStackForExecution, mdcContextMapForExecution);

            return origPredicate.test(o);
        }
        finally {
            unlinkTracingFromCurrentThread(originalThreadInfo);
        }
    }
} @SuppressWarnings("WeakerAccess")
public class SupplierWithTracing<U> implements Supplier<U> {

    protected final Supplier<U> origSupplier;
    protected final Deque<Span> spanStackForExecution;
    protected final Map<String, String> mdcContextMapForExecution;

    /**
     * Constructor that extracts the current tracing and MDC information from the current thread using {@link
     * Tracer#getCurrentSpanStackCopy()} and {@link MDC#getCopyOfContextMap()}, and forwards the information to
     * the {@link SupplierWithTracing#SupplierWithTracing(Supplier, Deque, Map)}
     * constructor. That tracing and MDC information will be associated with the thread when the given operation is
     * executed.
     *
     * <p>The operation you pass in cannot be null (an {@link IllegalArgumentException} will be thrown if you pass in
     * null for the operation).
     */
    public SupplierWithTracing(Supplier<U> origSupplier) {
        this(origSupplier, Tracer.getInstance().getCurrentSpanStackCopy(), MDC.getCopyOfContextMap());
    }

    /**
     * Constructor that uses the given trace and MDC information, which will be associated with the thread when the
     * given operation is executed.
     *
     * <p>The operation you pass in cannot be null (an {@link IllegalArgumentException} will be thrown if you pass in
     * null for the operation).
     *
     * <p>The {@link Pair} can be null, or you can pass null for the left and/or right side of the pair, and no error
     * will be thrown. Any trace or MDC info that is null means the corresponding info will not be available to the
     * thread when the operation is executed however.
     *
     * <p>You can pass in a {@link TracingState} for clearer less verbose code since it extends
     * {@code Pair<Deque<Span>, Map<String, String>>}.
     */
    public SupplierWithTracing(Supplier<U> origSupplier,
                               Pair<Deque<Span>, Map<String, String>> originalThreadInfo) {
        this(
            origSupplier,
            (originalThreadInfo == null) ? null : originalThreadInfo.getLeft(),
            (originalThreadInfo == null) ? null : originalThreadInfo.getRight()
        );
    }

    /**
     * Constructor that uses the given trace and MDC information, which will be associated with the thread when the
     * given operation is executed.
     *
     * <p>The operation you pass in cannot be null (an {@link IllegalArgumentException} will be thrown if you pass in
     * null for the operation).
     *
     * <p>The trace and/or MDC info can be null and no error will be thrown, however any trace or MDC info that is null
     * means the corresponding info will not be available to the thread when the operation is executed.
     */
    public SupplierWithTracing(Supplier<U> origSupplier,
                               Deque<Span> spanStackForExecution,
                               Map<String, String> mdcContextMapForExecution) {
        if (origSupplier == null)
            throw new IllegalArgumentException("origSupplier cannot be null");

        this.origSupplier = origSupplier;
        this.spanStackForExecution = spanStackForExecution;
        this.mdcContextMapForExecution = mdcContextMapForExecution;
    }

    /**
     * Equivalent to calling {@code new SupplierWithTracing(origSupplier)} - this allows you to do a static method
     * import for cleaner looking code in some cases. This method ultimately extracts the current tracing and MDC
     * information from the current thread using {@link Tracer#getCurrentSpanStackCopy()} and {@link
     * MDC#getCopyOfContextMap()}. That tracing and MDC information will be associated with the thread when the given
     * operation is executed.
     *
     * <p>The operation you pass in cannot be null (an {@link IllegalArgumentException} will be thrown if you pass in
     * null for the operation).
     *
     * @return {@code new SupplierWithTracing(origSupplier)}.
     * @see SupplierWithTracing#SupplierWithTracing(Supplier)
     * @see SupplierWithTracing
     */
    public static <U> SupplierWithTracing<U> withTracing(Supplier<U> origSupplier) {
        return new SupplierWithTracing<>(origSupplier);
    }

    /**
     * Equivalent to calling {@code new SupplierWithTracing(origSupplier, originalThreadInfo)} - this allows you
     * to do a static method import for cleaner looking code in some cases. This method uses the given trace and MDC
     * information, which will be associated with the thread when the given operation is executed.
     *
     * <p>The operation you pass in cannot be null (an {@link IllegalArgumentException} will be thrown if you pass in
     * null for the operation).
     *
     * <p>The {@link Pair} can be null, or you can pass null for the left and/or right side of the pair, and no error
     * will be thrown. Any trace or MDC info that is null means the corresponding info will not be available to the
     * thread when the operation is executed however.
     *
     * <p>You can pass in a {@link TracingState} for clearer less verbose code since it extends
     * {@code Pair<Deque<Span>, Map<String, String>>}.
     *
     * @return {@code new SupplierWithTracing(origSupplier, originalThreadInfo)}.
     * @see SupplierWithTracing#SupplierWithTracing(Supplier, Pair)
     * @see SupplierWithTracing
     */
    public static <U> SupplierWithTracing<U> withTracing(Supplier<U> origSupplier,
                                                         Pair<Deque<Span>, Map<String, String>> originalThreadInfo) {
        return new SupplierWithTracing<>(origSupplier, originalThreadInfo);
    }

    /**
     * Equivalent to calling {@code
     * new SupplierWithTracing(origSupplier, spanStackForExecution, mdcContextMapForExecution)} -
     * this allows you to do a static method import for cleaner looking code in some cases. This method uses the given
     * trace and MDC information, which will be associated with the thread when the given operation is executed.
     *
     * <p>The operation you pass in cannot be null (an {@link IllegalArgumentException} will be thrown if you pass in
     * null for the operation).
     *
     * <p>The trace and/or MDC info can be null and no error will be thrown, however any trace or MDC info that is null
     * means the corresponding info will not be available to the thread when the operation is executed.
     *
     * @return {@code new SupplierWithTracing(origSupplier, spanStackForExecution, mdcContextMapForExecution)}.
     * @see SupplierWithTracing#SupplierWithTracing(Supplier, Deque, Map)
     * @see SupplierWithTracing
     */
    public static <U> SupplierWithTracing<U> withTracing(Supplier<U> origSupplier,
                                                         Deque<Span> spanStackForExecution,
                                                         Map<String, String> mdcContextMapForExecution) {
        return new SupplierWithTracing<>(origSupplier, spanStackForExecution, mdcContextMapForExecution);
    }

    @Override
    public U get() {
        TracingState originalThreadInfo = null;
        try {
            originalThreadInfo =
                linkTracingToCurrentThread(spanStackForExecution, mdcContextMapForExecution);

            return origSupplier.get();
        }
        finally {
            unlinkTracingFromCurrentThread(originalThreadInfo);
        }
    }
} @FunctionalInterface
public interface ErrorSpanTagger extends BiConsumer<@NotNull Span, @NotNull Throwable> {

    /**
     * Reusable default thread-safe implementation of this interface. See the javadocs of {@link
     * DefaultErrorSpanTagger} for more details.
     */
    ErrorSpanTagger DEFAULT_IMPL = new DefaultErrorSpanTagger();

    /**
     * Default implementation of {@link BiConsumer} - this simply calls {@link #tagSpanForError(Span, Throwable)}.
     *
     * @param span The span to tag - should never be null.
     * @param error The error thrown by the operation associated with the given span - should never be null.
     */
    @Override
    default void accept(@NotNull Span span, @NotNull Throwable error) {
        tagSpanForError(span, error);
    }

    /**
     * Performs any desired error tagging on the given span after the operation fails with the given error but before
     * the span is completed.
     *
     * @param span The span to tag - should never be null.
     * @param error The error thrown by the operation associated with the given span - should never be null.
     */
    void tagSpanForError(@NotNull Span span, @NotNull Throwable error);

} @FunctionalInterface
public interface SpanTagger<T> extends BiConsumer<@NotNull Span, @Nullable T> {

    /**
     * Default implementation of {@link BiConsumer} - this simply calls {@link #tagSpan(Span, Object)}.
     *
     * @param span The span to tag - should never be null.
     * @param t The payload of the operation associated with the given span - may be null.
     */
    @Override
    default void accept(@NotNull Span span, @Nullable T t) {
        tagSpan(span, t);
    }

    /**
     * Performs any desired extra tagging on the given span after the operation finishes but before the span is
     * completed.
     *
     * @param span The span to tag - should never be null.
     * @param payload The payload of the operation associated with the given span - may be null.
     */
    void tagSpan(@NotNull Span span, @Nullable T payload);
} public static void sleepThread(long sleepTimeMillis) {
    try {
        Thread.sleep(sleepTimeMillis);
    }
    catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}
import com.nike.internal.util.Pair;
import com.nike.wingtips.Span;
import com.nike.wingtips.Span.SpanPurpose;
import com.nike.wingtips.Tracer;
import com.nike.wingtips.Tracer.SpanFieldForLoggerMdc;
import com.nike.wingtips.testutil.TestUtils;
import com.nike.wingtips.testutil.TestUtils.SpanRecorder;
import com.nike.wingtips.testutil.Whitebox;
import com.nike.wingtips.util.AsyncWingtipsHelper.AsyncWingtipsHelperDefaultImpl;
import com.nike.wingtips.util.AsyncWingtipsHelper.CheckedRunnable;
import com.nike.wingtips.util.asynchelperwrapper.BiConsumerWithTracing;
import com.nike.wingtips.util.asynchelperwrapper.BiFunctionWithTracing;
import com.nike.wingtips.util.asynchelperwrapper.BiPredicateWithTracing;
import com.nike.wingtips.util.asynchelperwrapper.CallableWithTracing;
import com.nike.wingtips.util.asynchelperwrapper.ConsumerWithTracing;
import com.nike.wingtips.util.asynchelperwrapper.ExecutorServiceWithTracing;
import com.nike.wingtips.util.asynchelperwrapper.FunctionWithTracing;
import com.nike.wingtips.util.asynchelperwrapper.PredicateWithTracing;
import com.nike.wingtips.util.asynchelperwrapper.RunnableWithTracing;
import com.nike.wingtips.util.asynchelperwrapper.ScheduledExecutorServiceWithTracing;
import com.nike.wingtips.util.asynchelperwrapper.SupplierWithTracing;
import com.nike.wingtips.util.operationwrapper.OperationWrapperOptions;
import com.nike.wingtips.util.spantagger.ErrorSpanTagger;
import com.nike.wingtips.util.spantagger.SpanTagger;
import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.MDC;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import static com.nike.wingtips.testutil.TestUtils.sleepThread;
import static com.nike.wingtips.util.AsyncWingtipsHelper.DEFAULT_IMPL;
import static com.nike.wingtips.util.AsyncWingtipsHelperStatic.biConsumerWithTracing;
import static com.nike.wingtips.util.AsyncWingtipsHelperStatic.biFunctionWithTracing;
import static com.nike.wingtips.util.AsyncWingtipsHelperStatic.biPredicateWithTracing;
import static com.nike.wingtips.util.AsyncWingtipsHelperStatic.callableWithTracing;
import static com.nike.wingtips.util.AsyncWingtipsHelperStatic.consumerWithTracing;
import static com.nike.wingtips.util.AsyncWingtipsHelperStatic.executorServiceWithTracing;
import static com.nike.wingtips.util.AsyncWingtipsHelperStatic.functionWithTracing;
import static com.nike.wingtips.util.AsyncWingtipsHelperStatic.linkTracingToCurrentThread;
import static com.nike.wingtips.util.AsyncWingtipsHelperStatic.predicateWithTracing;
import static com.nike.wingtips.util.AsyncWingtipsHelperStatic.runnableWithTracing;
import static com.nike.wingtips.util.AsyncWingtipsHelperStatic.scheduledExecutorServiceWithTracing;
import static com.nike.wingtips.util.AsyncWingtipsHelperStatic.supplierWithTracing;
import static com.nike.wingtips.util.AsyncWingtipsHelperStatic.unlinkTracingFromCurrentThread;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
        // given
        Pair<Deque<Span>, Map<String, String>> setupInfo = setupCurrentThreadWithTracingInfo();

        // when
        Predicate result = (useStaticMethod)
                          ? predicateWithTracing(predicateMock)
                          : DEFAULT_IMPL.predicateWithTracing(predicateMock);

        // then
        verifyPredicateWithTracing(result, predicateMock, setupInfo.getLeft(), setupInfo.getRight());
    }

    @DataProvider(value = {
        "true",
        "false"
    })
    @Test
    public void predicateWithTracing_pair_works_as_expected(boolean useStaticMethod) {
        // given
        Pair<Deque<Span>, Map<String, String>> setupInfo = generateTracingInfo();

        // when
        Predicate result = (useStaticMethod)
                          ? predicateWithTracing(predicateMock, setupInfo)
                          : DEFAULT_IMPL.predicateWithTracing(predicateMock, setupInfo);

        // then
        verifyPredicateWithTracing(result, predicateMock, setupInfo.getLeft(), setupInfo.getRight());
    }

    @DataProvider(value = {
        "true",
        "false"
    })
    @Test
    public void predicateWithTracing_separate_args_works_as_expected(boolean useStaticMethod) {
        // given
        Pair<Deque<Span>, Map<String, String>> setupInfo = generateTracingInfo();

        // when
        Predicate result = (useStaticMethod)
                          ? predicateWithTracing(predicateMock, setupInfo.getLeft(), setupInfo.getRight())
                          : DEFAULT_IMPL.predicateWithTracing(predicateMock,
                                                                   setupInfo.getLeft(), setupInfo.getRight());

        // then
        verifyPredicateWithTracing(result, predicateMock, setupInfo.getLeft(), setupInfo.getRight());
    }

    private void verifyBiPredicateWithTracing(BiPredicate result, BiPredicate expectedCoreInstance,
                                                           Deque<Span> expectedSpanStack,
                                                           Map<String, String> expectedMdcInfo) {
        assertThat(result).isInstanceOf(BiPredicateWithTracing.class);
        assertThat(Whitebox.getInternalState(result, "origBiPredicate")).isSameAs(expectedCoreInstance);
        assertThat(Whitebox.getInternalState(result, "spanStackForExecution")).isEqualTo(expectedSpanStack);
        assertThat(Whitebox.getInternalState(result, "mdcContextMapForExecution")).isEqualTo(expectedMdcInfo);
    }

    @DataProvider(value = {
        "true",
        "false"
    })
    @Test
    public void biPredicateWithTracing_using_current_thread_info_works_as_expected(boolean useStaticMethod) {
        // given
        Pair<Deque<Span>, Map<String, String>> setupInfo = setupCurrentThreadWithTracingInfo();

        // when
        BiPredicate result = (useStaticMethod)
                            ? biPredicateWithTracing(biPredicateMock)
                            : DEFAULT_IMPL.biPredicateWithTracing(biPredicateMock);

        // then
        verifyBiPredicateWithTracing(result, biPredicateMock, setupInfo.getLeft(), setupInfo.getRight());
    }

    @DataProvider(value = {
        "true",
        "false"
    })
    @Test
    public void biPredicateWithTracing_pair_works_as_expected(boolean useStaticMethod) {
        // given
        Pair<Deque<Span>, Map<String, String>> setupInfo = generateTracingInfo();

        // when
        BiPredicate result = (useStaticMethod)
                            ? biPredicateWithTracing(biPredicateMock, setupInfo)
                            : DEFAULT_IMPL.biPredicateWithTracing(biPredicateMock, setupInfo);

        // then
        verifyBiPredicateWithTracing(result, biPredicateMock, setupInfo.getLeft(), setupInfo.getRight());
    }

    @DataProvider(value = {
        "true",
        "false"
    })
    @Test
    public void biPredicateWithTracing_separate_args_works_as_expected(boolean useStaticMethod) {
        // given
        Pair<Deque<Span>, Map<String, String>> setupInfo = generateTracingInfo();

        // when
        BiPredicate result = (useStaticMethod)
                            ? biPredicateWithTracing(biPredicateMock, setupInfo.getLeft(), setupInfo.getRight())
                            : DEFAULT_IMPL.biPredicateWithTracing(biPredicateMock,
                                                                       setupInfo.getLeft(), setupInfo.getRight());

        // then
        verifyBiPredicateWithTracing(result, biPredicateMock, setupInfo.getLeft(), setupInfo.getRight());
    }

    @DataProvider(value = {
        "true",
        "false"
    })
    @Test
    public void executorServiceWithTracing_works_as_expected(boolean useStaticMethod) {
        // when
        ExecutorServiceWithTracing result = (useStaticMethod)