com.palantir.tracing.Tracers Maven / Gradle / Ivy
Show all versions of tracing Show documentation
/*
* (c) Copyright 2018 Palantir Technologies Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.palantir.tracing;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.palantir.logsafe.Preconditions;
import com.palantir.tracing.api.SpanType;
import com.palantir.tracing.api.TraceHttpHeaders;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Supplier;
/**
* Utility methods for making {@link ExecutorService} and {@link Runnable} instances tracing-aware.
*/
public final class Tracers {
/**
* The key under which trace ids are inserted into SLF4J {@link org.slf4j.MDC MDCs}.
*/
public static final String TRACE_ID_KEY = "traceId";
/**
* The key under which trace sampling state are inserted into SLF4J {@link org.slf4j.MDC MDCs}. If present, the
* field can take the values of "1" or "0", where "1" indicates the trace was sampled.
*/
public static final String TRACE_SAMPLED_KEY = "_sampled";
/**
* The key under which tracing request ids are inserted into SLF4J {@link org.slf4j.MDC MDCs}.
*/
public static final String REQUEST_ID_KEY = "_requestId";
private static final String DEFAULT_ROOT_SPAN_OPERATION = "root";
private static final char[] HEX_DIGITS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
private Tracers() {}
/**
* Returns a random ID suitable for span and trace IDs.
*/
public static String randomId() {
return longToPaddedHex(ThreadLocalRandom.current().nextLong());
}
/**
* Convert a long to a big-endian hex string. Hand-coded implementation is more efficient than
* Strings.pad(Long.toHexString) because that code has to deal with mixed length longs, and then mixed length
* amounts of padding - we want to minimise the overhead of tracing.
*/
static String longToPaddedHex(long number) {
char[] data = new char[16];
data[0] = HEX_DIGITS[(int) ((number >> 60) & 0xF)];
data[1] = HEX_DIGITS[(int) ((number >> 56) & 0xF)];
data[2] = HEX_DIGITS[(int) ((number >> 52) & 0xF)];
data[3] = HEX_DIGITS[(int) ((number >> 48) & 0xF)];
data[4] = HEX_DIGITS[(int) ((number >> 44) & 0xF)];
data[5] = HEX_DIGITS[(int) ((number >> 40) & 0xF)];
data[6] = HEX_DIGITS[(int) ((number >> 36) & 0xF)];
data[7] = HEX_DIGITS[(int) ((number >> 32) & 0xF)];
data[8] = HEX_DIGITS[(int) ((number >> 28) & 0xF)];
data[9] = HEX_DIGITS[(int) ((number >> 24) & 0xF)];
data[10] = HEX_DIGITS[(int) ((number >> 20) & 0xF)];
data[11] = HEX_DIGITS[(int) ((number >> 16) & 0xF)];
data[12] = HEX_DIGITS[(int) ((number >> 12) & 0xF)];
data[13] = HEX_DIGITS[(int) ((number >> 8) & 0xF)];
data[14] = HEX_DIGITS[(int) ((number >> 4) & 0xF)];
data[15] = HEX_DIGITS[(int) ((number >> 0) & 0xF)];
return new String(data);
}
/**
* Wraps the provided executor service such that any submitted {@link Callable} (or {@link Runnable}) is
* {@link #wrap wrapped} in order to be trace-aware.
*/
public static ExecutorService wrap(ExecutorService executorService) {
return new WrappingExecutorService(executorService) {
@Override
protected Callable wrapTask(Callable callable) {
return wrap(callable);
}
@Override
protected Runnable wrapTask(Runnable command) {
return wrap(command);
}
};
}
/**
* Like {@link #wrap(ExecutorService)}, but using the given {@link String operation} is used to create a span for
* submitted tasks.
*/
public static ExecutorService wrap(String operation, ExecutorService executorService) {
return new WrappingExecutorService(executorService) {
@Override
protected Callable wrapTask(Callable callable) {
return wrap(operation, callable);
}
@Override
protected Runnable wrapTask(Runnable command) {
return wrap(operation, command);
}
};
}
/**
* Wraps the provided scheduled executor service to make submitted tasks traceable, see
* {@link #wrap(ScheduledExecutorService)}. This method should not be used to wrap a ScheduledExecutorService that
* has already been {@link #wrapWithNewTrace(ScheduledExecutorService) wrapped with new trace}. If this is done, a
* new trace will be generated for each execution, effectively bypassing the intent of this method.
*/
public static ScheduledExecutorService wrap(ScheduledExecutorService executorService) {
return new WrappingScheduledExecutorService(executorService) {
@Override
protected Runnable wrapRecurring(Runnable runnable) {
return wrapWithNewTrace(runnable);
}
@Override
protected Callable wrapTask(Callable callable) {
return wrap(callable);
}
@Override
protected Runnable wrapTask(Runnable command) {
return wrap(command);
}
};
}
/**
* Like {@link #wrap(ScheduledExecutorService)}, but using the given {@link String operation} is used to create a
* span for submitted tasks.
*/
public static ScheduledExecutorService wrap(String operation, ScheduledExecutorService executorService) {
return new WrappingScheduledExecutorService(executorService) {
@Override
protected Runnable wrapRecurring(Runnable runnable) {
return wrapWithNewTrace(operation, runnable);
}
@Override
protected Callable wrapTask(Callable callable) {
return wrap(operation, callable);
}
@Override
protected Runnable wrapTask(Runnable command) {
return wrap(operation, command);
}
};
}
/**
* Wraps the given {@link Callable} such that it uses the thread-local {@link Trace tracing state} at the time of
* it's construction during its {@link Callable#call() execution}.
*/
public static Callable wrap(Callable delegate) {
return new AnonymousTracingAwareCallable<>(delegate);
}
/**
* Like {@link #wrap(Callable)}, but using the given {@link String operation} is used to create a span for the
* execution.
*/
public static Callable wrap(String operation, Callable delegate) {
return new TracingAwareCallable<>(operation, ImmutableMap.of(), delegate);
}
/**
* Like {@link #wrap(String, Callable)}, but using the given {@link String metadata} is used to create a span for
* the execution.
*/
public static Callable wrap(String operation, Map metadata, Callable delegate) {
return new TracingAwareCallable<>(operation, metadata, delegate);
}
/**
* Wraps the given {@link Runnable} such that it uses the thread-local {@link Trace tracing state} at the time of
* it's construction during its {@link Runnable#run()} execution}.
*/
public static Runnable wrap(Runnable delegate) {
return new AnonymousTracingAwareRunnable(delegate);
}
/**
* Like {@link #wrap(Runnable)}, but the given {@link String operation} is used to create a span for the
* execution.
*/
public static Runnable wrap(String operation, Runnable delegate) {
return wrap(operation, ImmutableMap.of(), delegate);
}
/**
* Like {@link #wrap(String, Runnable)}, but using the given {@link String operation} and
* {@link Map metadata} is used to create a span for the
* execution.
*/
public static Runnable wrap(String operation, Map metadata, Runnable delegate) {
return new TracingAwareRunnable(operation, metadata, delegate);
}
/**
* Traces the the execution of the {@code delegateFactory}, completing a span when the returned
* {@link ListenableFuture#isDone() ListenableFuture is done}.
*
* Example usage:
*
{@code
* ListenableFuture future = Tracers.wrapListenableFuture(
* "remote operation",
* () -> retrofitClient.doRequest());
* }
*
* Note that this function is not named {@code wrap} in order to avoid conflicting with potential utility methods
* for {@link Supplier suppliers}.
*/
public static > U wrapListenableFuture(
String operation, Supplier delegateFactory) {
return wrapListenableFuture(operation, ImmutableMap.of(), delegateFactory);
}
public static > U wrapListenableFuture(
String operation, Map metadata, Supplier delegateFactory) {
DetachedSpan span = DetachedSpan.start(operation);
U result = null;
// n.b. This span is required to apply tracing thread state to an initial request. Otherwise if there is
// no active trace, the detached span would not be associated with work initiated by delegateFactory.
try (CloseableSpan ignored = span.attach()) {
result = Preconditions.checkNotNull(delegateFactory.get(), "Expected a ListenableFuture");
} finally {
if (result != null) {
// In the successful case we add a listener in the finally block to prevent confusing traces
// when delegateFactory returns a completed future. This way the detached span cannot complete
// prior to its child.
result.addListener(new ListenableFutureSpanListener(span, metadata), MoreExecutors.directExecutor());
} else {
// Complete the detached span, even if the delegateFactory throws.
span.complete(MapTagTranslator.INSTANCE, metadata);
}
}
return result;
}
public static void addTracingHeaders(
T state, TracingHeadersEnrichingFunction tracingHeadersEnrichingFunction) {
Optional maybeTraceMetadata = Tracer.maybeGetTraceMetadata();
if (maybeTraceMetadata.isEmpty()) {
return;
}
TraceMetadata traceMetadata = maybeTraceMetadata.get();
tracingHeadersEnrichingFunction.addHeader(TraceHttpHeaders.TRACE_ID, traceMetadata.getTraceId(), state);
tracingHeadersEnrichingFunction.addHeader(TraceHttpHeaders.SPAN_ID, traceMetadata.getSpanId(), state);
tracingHeadersEnrichingFunction.addHeader(
TraceHttpHeaders.IS_SAMPLED, Tracer.isTraceObservable() ? "1" : "0", state);
Optional forUserAgent = Tracer.getForUserAgent();
if (forUserAgent.isPresent()) {
tracingHeadersEnrichingFunction.addHeader(TraceHttpHeaders.FOR_USER_AGENT, forUserAgent.get(), state);
}
}
private static final class ListenableFutureSpanListener implements Runnable {
private final DetachedSpan span;
private final Map metadata;
ListenableFutureSpanListener(DetachedSpan span, Map metadata) {
this.span = span;
this.metadata = metadata;
}
@Override
public void run() {
span.complete(MapTagTranslator.INSTANCE, metadata);
}
@Override
public String toString() {
return "ListenableFutureSpanListener{span=" + span + ", metadata=" + metadata + '}';
}
}
/**
* Deprecated.
*
* @deprecated Use {@link #wrapWithNewTrace(String, ExecutorService)}
*/
@Deprecated
public static ExecutorService wrapWithNewTrace(ExecutorService executorService) {
return wrapWithNewTrace(DEFAULT_ROOT_SPAN_OPERATION, executorService);
}
/**
* Wraps the provided executor service to make submitted tasks traceable with a fresh {@link Trace trace} for each
* execution, see {@link #wrapWithNewTrace(String, ExecutorService)}. This method should not be used to wrap a
* ScheduledExecutorService that has already been {@link #wrap(ExecutorService) wrapped}. If this is done, a new
* trace will be generated for each execution, effectively bypassing the intent of the previous wrapping. The given
* {@link String operation} is used to create the initial span.
*/
public static ExecutorService wrapWithNewTrace(String operation, ExecutorService executorService) {
return new WrappingExecutorService(executorService) {
@Override
protected Callable wrapTask(Callable callable) {
return wrapWithNewTrace(operation, callable);
}
@Override
protected Runnable wrapTask(Runnable command) {
return wrapWithNewTrace(operation, command);
}
};
}
/**
* Deprecated.
*
* @deprecated Use {@link #wrapWithNewTrace(String, ScheduledExecutorService)}
*/
@Deprecated
public static ScheduledExecutorService wrapWithNewTrace(ScheduledExecutorService executorService) {
return wrapWithNewTrace(DEFAULT_ROOT_SPAN_OPERATION, executorService);
}
/**
* Wraps the provided scheduled executor service to make submitted tasks traceable with a fresh {@link Trace trace}
* for each execution, see {@link #wrapWithNewTrace(String, ScheduledExecutorService)}. This method should not be
* used to wrap a ScheduledExecutorService that has already been {@link #wrap(ScheduledExecutorService) wrapped}. If
* this is done, a new trace will be generated for each execution, effectively bypassing the intent of the previous
* wrapping. The given {@link String operation} is used to create the initial span.
*/
public static ScheduledExecutorService wrapWithNewTrace(
String operation, ScheduledExecutorService executorService) {
return new WrappingScheduledExecutorService(executorService) {
@Override
protected Runnable wrapRecurring(Runnable runnable) {
return wrapTask(runnable);
}
@Override
protected Callable wrapTask(Callable callable) {
return wrapWithNewTrace(operation, callable);
}
@Override
protected Runnable wrapTask(Runnable command) {
return wrapWithNewTrace(operation, command);
}
};
}
/**
* Deprecated.
*
* @deprecated Use {@link #wrapWithNewTrace(String, Callable)}
*/
@Deprecated
public static Callable wrapWithNewTrace(Callable delegate) {
return wrapWithNewTrace(DEFAULT_ROOT_SPAN_OPERATION, delegate);
}
public static Callable wrapWithNewTrace(String operation, Callable delegate) {
return wrapWithNewTrace(operation, Observability.UNDECIDED, delegate);
}
/**
* Wraps the given {@link Callable} such that it creates a fresh {@link Trace tracing state} for its execution. That
* is, the trace during its {@link Callable#call() execution} is entirely separate from the trace at construction or
* any trace already set on the thread used to execute the callable. Each execution of the callable will have a
* fresh trace. The given {@link String operation} is used to create the initial span.
*/
public static Callable wrapWithNewTrace(
String operation, Observability observability, Callable delegate) {
return () -> {
// clear the existing trace and keep it around for restoration when we're done
Optional originalTrace = Tracer.getAndClearTraceIfPresent();
try {
Tracer.initTraceWithSpan(observability, Tracers.randomId(), operation, SpanType.LOCAL);
return delegate.call();
} finally {
Tracer.fastCompleteSpan();
restoreTrace(originalTrace);
}
};
}
/**
* Deprecated.
*
* @deprecated Use {@link #wrapWithNewTrace(String, Runnable)}
*/
@Deprecated
public static Runnable wrapWithNewTrace(Runnable delegate) {
return wrapWithNewTrace(DEFAULT_ROOT_SPAN_OPERATION, delegate);
}
public static Runnable wrapWithNewTrace(String operation, Runnable delegate) {
return wrapWithNewTrace(operation, Observability.UNDECIDED, delegate);
}
/**
* Wraps the given {@link Runnable} such that it creates a fresh {@link Trace tracing state} for its execution. That
* is, the trace during its {@link Runnable#run() execution} is entirely separate from the trace at construction or
* any trace already set on the thread used to execute the runnable. Each execution of the runnable will have a
* fresh trace. The given {@link String operation} is used to create the initial span.
*/
public static Runnable wrapWithNewTrace(String operation, Observability observability, Runnable delegate) {
return () -> {
// clear the existing trace and keep it around for restoration when we're done
Optional originalTrace = Tracer.getAndClearTraceIfPresent();
try {
Tracer.initTraceWithSpan(observability, Tracers.randomId(), operation, SpanType.LOCAL);
delegate.run();
} finally {
Tracer.fastCompleteSpan();
restoreTrace(originalTrace);
}
};
}
/**
* Wraps the given {@link Callable} such that it creates a fresh {@link Trace tracing state with the given traceId}
* for its execution. That is, the trace during its {@link Callable#call() execution} will use the traceId provided
* instead of any trace already set on the thread used to execute the callable. Each execution of the callable will
* use a new {@link Trace tracing state} with the same given traceId. The given {@link String operation} is used to
* create the initial span.
*/
public static Callable wrapWithAlternateTraceId(
String traceId, String operation, Observability observability, Callable delegate) {
return () -> {
// clear the existing trace and keep it around for restoration when we're done
Optional originalTrace = Tracer.getAndClearTraceIfPresent();
try {
Tracer.initTraceWithSpan(observability, traceId, operation, SpanType.LOCAL);
return delegate.call();
} finally {
Tracer.fastCompleteSpan();
restoreTrace(originalTrace);
}
};
}
/**
* Deprecated.
*
* @deprecated Use {@link #wrapWithAlternateTraceId(String, String, Runnable)}
*/
@Deprecated
public static Runnable wrapWithAlternateTraceId(String traceId, Runnable delegate) {
return wrapWithAlternateTraceId(traceId, DEFAULT_ROOT_SPAN_OPERATION, delegate);
}
public static Runnable wrapWithAlternateTraceId(String traceId, String operation, Runnable delegate) {
return wrapWithAlternateTraceId(traceId, operation, Observability.UNDECIDED, delegate);
}
/**
* Wraps the given {@link Runnable} such that it creates a fresh {@link Trace tracing state with the given traceId}
* for its execution. That is, the trace during its {@link Runnable#run() execution} will use the traceId provided
* instead of any trace already set on the thread used to execute the runnable. Each execution of the runnable will
* use a new {@link Trace tracing state} with the same given traceId. The given {@link String operation} is used to
* create the initial span.
*/
public static Runnable wrapWithAlternateTraceId(
String traceId, String operation, Observability observability, Runnable delegate) {
return () -> {
// clear the existing trace and keep it around for restoration when we're done
Optional originalTrace = Tracer.getAndClearTraceIfPresent();
try {
Tracer.initTraceWithSpan(observability, traceId, operation, SpanType.LOCAL);
delegate.run();
} finally {
Tracer.fastCompleteSpan();
restoreTrace(originalTrace);
}
};
}
/**
* Restores or clears trace state based on provided {@link Trace}. Used to cleanup trace state for
* {@link #wrapWithNewTrace} calls.
*/
private static void restoreTrace(Optional trace) {
if (trace.isPresent()) {
Tracer.setTrace(trace.get());
} else {
// Ignoring returned value, used to clear trace only
Tracer.getAndClearTraceIfPresent();
}
}
/**
* Wraps a given callable such that its execution operates with the {@link Trace thread-local Trace} of the thread
* that constructs the {@link TracingAwareCallable} instance rather than the thread that executes the callable.
*
* The constructor is typically called by a tracing-aware executor service on the same thread on which a user
* creates {@link Callable delegate}, and the {@link #call()} method is executed on an arbitrary (likely different)
* thread with different {@link Trace tracing state}. In order to execute the task with the original (and
* intuitively expected) tracing state, we remember the original state and set it for the duration of the
* {@link #call() execution}.
*/
private static class TracingAwareCallable implements Callable {
private final Callable delegate;
private final Detached detached;
private final String operation;
private final Map metadata;
TracingAwareCallable(String operation, Map metadata, Callable delegate) {
this.delegate = delegate;
this.detached = DetachedSpan.detach();
this.operation = operation;
this.metadata = metadata;
}
@Override
public V call() throws Exception {
try (CloseableSpan ignored = detached.childSpan(operation, metadata)) {
return delegate.call();
}
}
}
private static class AnonymousTracingAwareCallable implements Callable {
private final Callable delegate;
private final Detached detached;
AnonymousTracingAwareCallable(Callable delegate) {
this.delegate = delegate;
this.detached = DetachedSpan.detach();
}
@Override
public V call() throws Exception {
try (CloseableSpan ignored = detached.attach()) {
return delegate.call();
}
}
}
/**
* Wraps a given runnable such that its execution operates with the {@link Trace thread-local Trace} of the thread
* that constructs the {@link TracingAwareRunnable} instance rather than the thread that executes the runnable.
*/
private static class TracingAwareRunnable implements Runnable {
private final Runnable delegate;
private final Detached detached;
private final String operation;
private final Map metadata;
TracingAwareRunnable(String operation, Map metadata, Runnable delegate) {
this.delegate = delegate;
this.detached = DetachedSpan.detach();
this.operation = operation;
this.metadata = metadata;
}
@Override
public void run() {
try (CloseableSpan ignored = detached.childSpan(operation, metadata)) {
delegate.run();
}
}
}
private static class AnonymousTracingAwareRunnable implements Runnable {
private final Runnable delegate;
private final Detached detached;
AnonymousTracingAwareRunnable(Runnable delegate) {
this.delegate = delegate;
this.detached = DetachedSpan.detach();
}
@Override
public void run() {
try (CloseableSpan ignored = detached.attach()) {
delegate.run();
}
}
}
public interface ThrowingCallable {
T call() throws E;
}
}