com.nike.wingtips.util.AsyncWingtipsHelper Maven / Gradle / Ivy
Show all versions of wingtips-java8 Show documentation
package com.nike.wingtips.util;
import com.nike.internal.util.Pair;
import com.nike.wingtips.Span;
import com.nike.wingtips.Tracer;
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.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.SupplierWithTracing;
import org.slf4j.MDC;
import java.util.Deque;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
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;
/**
* Helper class that provides methods for dealing with async stuff in Wingtips, mainly providing easy ways
* to support distributed tracing and logger MDC when hopping threads using {@link CompletableFuture}s, {@link
* Executor}s, etc. There are various {@code *WithTracing(...)} methods for wrapping
* objects with the same type that knows how to handle tracing and MDC. You can also call the various {@code
* linkTracingToCurrentThread(...)} and {@code unlinkTracingFromCurrentThread(...)} methods directly if
* you want to do it manually yourself.
*
* NOTE: This is an interface to allow for custom implementations, however if you only ever need the default behavior
* you can refer to {@link #DEFAULT_IMPL} for static access. There's also {@link AsyncWingtipsHelperStatic} that has
* static methods with the same method signatures as this class that simply pass through to {@link #DEFAULT_IMPL},
* allowing you to do static method imports to keep your code more readable at the expense of flexibility if you
* ever want to use a non-default implementation.
*
*
Here's an example of making the current thread's tracing and MDC info hop to a thread executed by an {@link Executor}:
*
* AsyncWingtipsHelper asyncHelper = AsyncWingtipsHelper.DEFAULT_IMPL;
* Executor executor = Executors.newSingleThreadExecutor();
*
* executor.execute(asyncHelper.runnableWithTracing(() -> {
* // Code that needs tracing/MDC wrapping goes here
* }));
*
*
* Functionally equivalent to the {@link Executor} example above, but with a {@link ExecutorServiceWithTracing} to
* automate the thread-hopping behavior whenever the {@link Executor} (or {@link ExecutorService}) executes something
* (be careful about this if you spin off work that *shouldn't* automatically inherit the calling thread's tracing
* state - see the warning on {@link #executorServiceWithTracing(ExecutorService)}):
*
* AsyncWingtipsHelper asyncHelper = AsyncWingtipsHelper.DEFAULT_IMPL;
* Executor executor = asyncHelper.executorServiceWithTracing(Executors.newCachedThreadPool());
*
* executor.execute(() -> {
* // Code that needs tracing/MDC wrapping goes here
* });
*
*
* And here's a similar example using {@link CompletableFuture}:
*
* AsyncWingtipsHelper asyncHelper = AsyncWingtipsHelper.DEFAULT_IMPL;
*
* CompletableFuture.supplyAsync(asyncHelper.supplierWithTracing(() -> {
* // Supplier code that needs tracing/MDC wrapping goes here.
* return foo;
* }));
*
*
* This example shows how you might accomplish tasks in an environment where the tracing information is attached
* to some request context, and you need to temporarily attach the tracing info in order to do something (e.g. log some
* messages with tracing info automatically added using MDC):
*
* AsyncWingtipsHelper asyncHelper = AsyncWingtipsHelper.DEFAULT_IMPL;
* TracingState tracingInfo = requestContext.getTracingInfo();
*
* asyncHelper.runnableWithTracing(
* () -> {
* // Code that needs tracing/MDC wrapping goes here
* },
* tracingInfo
* ).run();
*
*
* If you want to use the link and unlink methods manually to wrap some chunk of code, the general procedure looks
* like this:
*
* AsyncWingtipsHelper asyncHelper = AsyncWingtipsHelper.DEFAULT_IMPL;
* TracingState originalThreadInfo = null;
* try {
* originalThreadInfo = asyncHelper.linkTracingToCurrentThread(...);
* // Code that needs tracing/MDC wrapping goes here
* }
* finally {
* asyncHelper.unlinkTracingFromCurrentThread(originalThreadInfo);
* }
*
*
* Following this procedure (either the all-in-one {@code *WithTracing(...)} methods or the manual procedure)
* guarantees that the code you want wrapped will be wrapped successfully with whatever tracing and MDC info you want,
* and when it finishes the trace and MDC info will be put back the way it was as if your code never ran.
*
*
NOTE: If your class only needs one tracing-wrapped type then you can pull in the slightly less verbose static
* helper methods directly from the class, e.g. {@code RunnableWithTracing.withTracing(...)}, and then your
* code could be the more compact {@code withTracing(...)} rather than {@code runnableWithTracing(...)}. If you need
* multiple tracing-wrapped types in the same class then the slightly longer-named methods in this helper class can
* be used to disambiguate.
*
*
WARNING: Be careful with the manual {@code linkTracingToCurrentThread(...)} method to link tracing and MDC.
* If you fail to guarantee the associated unlink at the end then you risk having traces stomp on each other or having
* other weird interactions occur that you wouldn't expect or predict. This can mess up your tracing, so before you use
* the manual linking/unlinking procedure make sure you know what you're doing and test thoroughly in a multi-threaded
* way under load, and with failure scenarios. For this reason it's recommended that you use the
* {@code *WithTracing(...)} methods whenever possible instead of manual linking/unlinking.
*
* @author Nic Munroe
*/
public interface AsyncWingtipsHelper {
/**
* A statically-accessible default implementation of this interface. You can also use {@link
* AsyncWingtipsHelperStatic} for static access if you know you only ever want the default behavior - that will
* allow you to do static method imports to keep your code more readable at the expense of flexibility if you
* ever want to use a non-default implementation.
*/
AsyncWingtipsHelper DEFAULT_IMPL = new AsyncWingtipsHelperDefaultImpl();
/**
* @return A {@link Runnable} that wraps the given original so that the current thread's tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution.
*
*
NOTE: The current thread's tracing and MDC info will be extracted using {@link
* Tracer#getCurrentSpanStackCopy()} and {@link MDC#getCopyOfContextMap()}.
*/
@SuppressWarnings("deprecation")
default Runnable runnableWithTracing(Runnable runnable) {
return AsyncWingtipsHelperJava7.runnableWithTracing(runnable);
}
/**
* @return A {@link Runnable} that wraps the given original so that the given distributed tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution. You can pass in a {@link TracingState} for clearer less verbose code since it extends
* {@code Pair, Map>}.
*/
@SuppressWarnings("deprecation")
default Runnable runnableWithTracing(Runnable runnable,
Pair, Map> threadInfoToLink) {
return AsyncWingtipsHelperJava7.runnableWithTracing(runnable, threadInfoToLink);
}
/**
* @return A {@link Runnable} that wraps the given original so that the given distributed tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution.
*/
@SuppressWarnings("deprecation")
default Runnable runnableWithTracing(Runnable runnable,
Deque spanStackToLink,
Map mdcContextMapToLink) {
return AsyncWingtipsHelperJava7.runnableWithTracing(
runnable, spanStackToLink, mdcContextMapToLink
);
}
/**
* @return A {@link Callable} that wraps the given original so that the current thread's tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution.
*
* NOTE: The current thread's tracing and MDC info will be extracted using {@link
* Tracer#getCurrentSpanStackCopy()} and {@link MDC#getCopyOfContextMap()}.
*/
@SuppressWarnings("deprecation")
default Callable callableWithTracing(Callable callable) {
return AsyncWingtipsHelperJava7.callableWithTracing(callable);
}
/**
* @return A {@link Callable} that wraps the given original so that the given distributed tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution. You can pass in a {@link TracingState} for clearer less verbose code since it extends
* {@code Pair, Map>}.
*/
@SuppressWarnings("deprecation")
default Callable callableWithTracing(Callable callable,
Pair, Map> threadInfoToLink) {
return AsyncWingtipsHelperJava7.callableWithTracing(callable, threadInfoToLink);
}
/**
* @return A {@link Callable} that wraps the given original so that the given distributed tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution.
*/
@SuppressWarnings("deprecation")
default Callable callableWithTracing(Callable callable,
Deque spanStackToLink,
Map mdcContextMapToLink) {
return AsyncWingtipsHelperJava7.callableWithTracing(
callable, spanStackToLink, mdcContextMapToLink
);
}
/**
* @return A {@link Supplier} that wraps the given original so that the current thread's tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution.
*
* NOTE: The current thread's tracing and MDC info will be extracted using {@link
* Tracer#getCurrentSpanStackCopy()} and {@link MDC#getCopyOfContextMap()}.
*/
default Supplier supplierWithTracing(Supplier supplier) {
return new SupplierWithTracing<>(supplier);
}
/**
* @return A {@link Supplier} that wraps the given original so that the given distributed tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution. You can pass in a {@link TracingState} for clearer less verbose code since it extends
* {@code Pair, Map>}.
*/
default Supplier supplierWithTracing(Supplier supplier,
Pair, Map> threadInfoToLink) {
return new SupplierWithTracing<>(supplier, threadInfoToLink);
}
/**
* @return A {@link Supplier} that wraps the given original so that the given distributed tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution.
*/
default Supplier supplierWithTracing(Supplier supplier,
Deque spanStackToLink,
Map mdcContextMapToLink) {
return new SupplierWithTracing<>(supplier, spanStackToLink, mdcContextMapToLink);
}
/**
* @return A {@link Function} that wraps the given original so that the current thread's tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution.
*
* NOTE: The current thread's tracing and MDC info will be extracted using {@link
* Tracer#getCurrentSpanStackCopy()} and {@link MDC#getCopyOfContextMap()}.
*/
default Function functionWithTracing(Function fn) {
return new FunctionWithTracing<>(fn);
}
/**
* @return A {@link Function} that wraps the given original so that the given distributed tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution. You can pass in a {@link TracingState} for clearer less verbose code since it extends
* {@code Pair, Map>}.
*/
default Function functionWithTracing(Function fn,
Pair, Map> threadInfoToLink) {
return new FunctionWithTracing<>(fn, threadInfoToLink);
}
/**
* @return A {@link Function} that wraps the given original so that the given distributed tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution.
*/
default Function functionWithTracing(Function fn,
Deque spanStackToLink,
Map mdcContextMapToLink) {
return new FunctionWithTracing<>(fn, spanStackToLink, mdcContextMapToLink);
}
/**
* @return A {@link BiFunction} that wraps the given original so that the current thread's tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution.
*
* NOTE: The current thread's tracing and MDC info will be extracted using {@link
* Tracer#getCurrentSpanStackCopy()} and {@link MDC#getCopyOfContextMap()}.
*/
default BiFunction biFunctionWithTracing(BiFunction fn) {
return new BiFunctionWithTracing<>(fn);
}
/**
* @return A {@link BiFunction} that wraps the given original so that the given distributed tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution. You can pass in a {@link TracingState} for clearer less verbose code since it extends
* {@code Pair, Map>}.
*/
default BiFunction biFunctionWithTracing(
BiFunction fn,
Pair, Map> threadInfoToLink
) {
return new BiFunctionWithTracing<>(fn, threadInfoToLink);
}
/**
* @return A {@link BiFunction} that wraps the given original so that the given distributed tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution.
*/
default BiFunction biFunctionWithTracing(BiFunction fn,
Deque spanStackToLink,
Map mdcContextMapToLink) {
return new BiFunctionWithTracing<>(fn, spanStackToLink, mdcContextMapToLink);
}
/**
* @return A {@link Consumer} that wraps the given original so that the current thread's tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution.
*
* NOTE: The current thread's tracing and MDC info will be extracted using {@link
* Tracer#getCurrentSpanStackCopy()} and {@link MDC#getCopyOfContextMap()}.
*/
default Consumer consumerWithTracing(Consumer consumer) {
return new ConsumerWithTracing<>(consumer);
}
/**
* @return A {@link Consumer} that wraps the given original so that the given distributed tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution. You can pass in a {@link TracingState} for clearer less verbose code since it extends
* {@code Pair, Map>}.
*/
default Consumer consumerWithTracing(Consumer consumer,
Pair, Map> threadInfoToLink) {
return new ConsumerWithTracing<>(consumer, threadInfoToLink);
}
/**
* @return A {@link Consumer} that wraps the given original so that the given distributed tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution.
*/
default Consumer consumerWithTracing(Consumer consumer,
Deque spanStackToLink,
Map mdcContextMapToLink) {
return new ConsumerWithTracing<>(consumer, spanStackToLink, mdcContextMapToLink);
}
/**
* @return A {@link BiConsumer} that wraps the given original so that the current thread's tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution.
*
* NOTE: The current thread's tracing and MDC info will be extracted using {@link
* Tracer#getCurrentSpanStackCopy()} and {@link MDC#getCopyOfContextMap()}.
*/
default BiConsumer biConsumerWithTracing(BiConsumer biConsumer) {
return new BiConsumerWithTracing<>(biConsumer);
}
/**
* @return A {@link BiConsumer} that wraps the given original so that the given distributed tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution. You can pass in a {@link TracingState} for clearer less verbose code since it extends
* {@code Pair, Map>}.
*/
default BiConsumer biConsumerWithTracing(
BiConsumer biConsumer,
Pair, Map> threadInfoToLink
) {
return new BiConsumerWithTracing<>(biConsumer, threadInfoToLink);
}
/**
* @return A {@link BiConsumer} that wraps the given original so that the given distributed tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution.
*/
default BiConsumer biConsumerWithTracing(BiConsumer biConsumer,
Deque spanStackToLink,
Map mdcContextMapToLink) {
return new BiConsumerWithTracing<>(biConsumer, spanStackToLink, mdcContextMapToLink);
}
/**
* @return A {@link Predicate} that wraps the given original so that the current thread's tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution.
*
* NOTE: The current thread's tracing and MDC info will be extracted using {@link
* Tracer#getCurrentSpanStackCopy()} and {@link MDC#getCopyOfContextMap()}.
*/
default Predicate predicateWithTracing(Predicate predicate) {
return new PredicateWithTracing<>(predicate);
}
/**
* @return A {@link Predicate} that wraps the given original so that the given distributed tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution. You can pass in a {@link TracingState} for clearer less verbose code since it extends
* {@code Pair, Map>}.
*/
default Predicate predicateWithTracing(Predicate predicate,
Pair, Map> threadInfoToLink) {
return new PredicateWithTracing<>(predicate, threadInfoToLink);
}
/**
* @return A {@link Predicate} that wraps the given original so that the given distributed tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution.
*/
default Predicate predicateWithTracing(Predicate predicate,
Deque spanStackToLink,
Map mdcContextMapToLink) {
return new PredicateWithTracing<>(predicate, spanStackToLink, mdcContextMapToLink);
}
/**
* @return A {@link BiPredicate} that wraps the given original so that the current thread's tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution.
*
* NOTE: The current thread's tracing and MDC info will be extracted using {@link
* Tracer#getCurrentSpanStackCopy()} and {@link MDC#getCopyOfContextMap()}.
*/
default BiPredicate biPredicateWithTracing(BiPredicate biPredicate) {
return new BiPredicateWithTracing<>(biPredicate);
}
/**
* @return A {@link BiPredicate} that wraps the given original so that the given distributed tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution. You can pass in a {@link TracingState} for clearer less verbose code since it extends
* {@code Pair, Map>}.
*/
default BiPredicate biPredicateWithTracing(
BiPredicate biPredicate,
Pair, Map> threadInfoToLink
) {
return new BiPredicateWithTracing<>(biPredicate, threadInfoToLink);
}
/**
* @return A {@link BiPredicate} that wraps the given original so that the given distributed tracing and MDC
* information is registered with the thread and therefore available during execution and unregistered after
* execution.
*/
default BiPredicate biPredicateWithTracing(BiPredicate biPredicate,
Deque spanStackToLink,
Map mdcContextMapToLink) {
return new BiPredicateWithTracing<>(biPredicate, spanStackToLink, mdcContextMapToLink);
}
/**
* @return An {@link ExecutorService} that wraps the given delegate {@link ExecutorService} so that when
* {@link Runnable}s or {@link Callable}s are executed through it they will automatically inherit the tracing state
* of the thread that called the {@link ExecutorService} method. Equivalent to calling:
* {@code new ExecutorServiceWithTracing(delegate)}.
*
* 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!
*/
@SuppressWarnings("deprecation")
default ExecutorServiceWithTracing executorServiceWithTracing(ExecutorService delegate) {
return AsyncWingtipsHelperJava7.executorServiceWithTracing(delegate);
}
/**
* Links the given distributed tracing and logging MDC info to the current thread. Any existing tracing and MDC info
* on the current thread will be wiped out and overridden, so if you need to go back to them in the future you'll
* need to store the copy info returned by this method for later.
*
* @param threadInfoToLink
* A {@link Pair} containing the span stack and MDC info you want to link to the current thread.
* This argument can be null - if it is null then {@link Tracer} will be setup with an empty span stack (wiping
* out any existing in-progress traces) *and* {@link MDC#clear()} will be called (wiping out any
* existing MDC info). The left and/or right portion of the pair can also be null, with any null portion of the
* pair causing the corresponding portion to be emptied/cleared while letting any non-null portion link to the
* thread as expected. You can pass in a {@link TracingState} for clearer less verbose code since it extends
* {@code Pair, Map>}.
*
* @return A *COPY* of the original span stack and MDC info on the thread when this method was called (before being
* replaced with the given arguments). The returned {@link TracingState} object will never be null, but the values
* it contains may be null. A copy is returned rather than the original to prevent undesired behavior (storing the
* return value and then passing it in to {@link #unlinkTracingFromCurrentThread(Pair)} later should *guarantee*
* that after calling that unlink method the thread state is exactly as it was right *before* calling this link
* method. If we returned the original span stack this contract guarantee could be violated).
*/
@SuppressWarnings("deprecation")
default TracingState linkTracingToCurrentThread(
Pair, Map> threadInfoToLink
) {
return AsyncWingtipsHelperJava7.linkTracingToCurrentThread(threadInfoToLink);
}
/**
* Links the given distributed tracing and logging MDC info to the current thread. Any existing tracing and MDC info
* on the current thread will be wiped out and overridden, so if you need to go back to them in the future you'll
* need to store the copy info returned by this method for later.
*
* @param spanStackToLink
* The stack of distributed traces that should be associated with the current thread. This can be null - if it
* is null then {@link Tracer} will be setup with an empty span stack (wiping out any existing in-progress
* traces).
* @param mdcContextMapToLink
* The MDC context map to associate with the current thread. This can be null - if it is null then {@link
* MDC#clear()} will be called (wiping out any existing MDC info).
*
* @return A *COPY* of the original span stack and MDC info on the thread when this method was called (before being
* replaced with the given arguments). The returned {@link TracingState} object will never be null, but the values
* it contains may be null. A copy is returned rather than the original to prevent undesired behavior (storing the
* return value and then passing it in to {@link #unlinkTracingFromCurrentThread(Pair)} later should *guarantee*
* that after calling that unlink method the thread state is exactly as it was right *before* calling this link
* method. If we returned the original span stack this contract guarantee could be violated).
*/
@SuppressWarnings("deprecation")
default TracingState linkTracingToCurrentThread(
Deque spanStackToLink,
Map mdcContextMapToLink
) {
return AsyncWingtipsHelperJava7.linkTracingToCurrentThread(
spanStackToLink, mdcContextMapToLink
);
}
/**
* Helper method for calling {@link #unlinkTracingFromCurrentThread(Deque, Map)} that
* gracefully handles the case where the pair you pass in is null - if the pair you pass in is null then {@link
* #unlinkTracingFromCurrentThread(Deque, Map)} will be called with both arguments null. You can pass in a {@link
* TracingState} for clearer less verbose code since it extends {@code Pair, Map>}.
*/
@SuppressWarnings("deprecation")
default void unlinkTracingFromCurrentThread(Pair, Map> threadInfoToResetFor) {
AsyncWingtipsHelperJava7.unlinkTracingFromCurrentThread(threadInfoToResetFor);
}
/**
* Calls {@link Tracer#unregisterFromThread()} and {@link MDC#clear()} to reset this thread's tracing and
* MDC state to be completely clean, then (optionally) resets the span stack and MDC info to the arguments
* provided. If the span stack argument is null then the span stack will *not* be reset, and similarly if the MDC
* info is null then the MDC info will *not* be reset. So if both are null then when this method finishes the trace
* stack and MDC will be left in a blank state.
*/
@SuppressWarnings("deprecation")
default void unlinkTracingFromCurrentThread(Deque spanStackToResetFor,
Map mdcContextMapToResetFor) {
AsyncWingtipsHelperJava7.unlinkTracingFromCurrentThread(
spanStackToResetFor, mdcContextMapToResetFor
);
}
class AsyncWingtipsHelperDefaultImpl implements AsyncWingtipsHelper {
// Nothing beyond the default.
}
}