package brave; import brave.internal.Platform; import brave.internal.recorder.Recorder; import brave.propagation.CurrentTraceContext; import brave.propagation.Propagation; import brave.propagation.SamplingFlags; import brave.propagation.TraceContext; import brave.propagation.TraceContext.Extractor; import brave.propagation.TraceContextOrSamplingFlags; import brave.sampler.Sampler; import java.io.Closeable; import javax.annotation.Nullable; import zipkin.Endpoint; import zipkin.reporter.Reporter; /** * Using a tracer, you can create a root span capturing the critical path of a request. Child * spans can be created to allocate latency relating to outgoing requests. * * Here's a contrived example: * <pre>{@code * Span twoPhase = tracer.newTrace().name("twoPhase").start(); * try { * Span prepare = tracer.newChild(twoPhase.context()).name("prepare").start(); * try { * prepare(); * } finally { * prepare.finish(); * } * Span commit = tracer.newChild(twoPhase.context()).name("commit").start(); * try { * commit(); * } finally { * commit.finish(); * } * } finally { * twoPhase.finish(); * } * }</pre> * * @see Span * @see Propagation */ public final class Tracer { /** @deprecated Please use {@link Tracing#newBuilder()} */ @Deprecated public static Builder newBuilder() { return new Builder(); } /** @deprecated Please use {@link Tracing.Builder} */ @Deprecated public static final class Builder { final Tracing.Builder delegate = new Tracing.Builder(); /** @see Tracing.Builder#localServiceName(String) */ public Builder localServiceName(String localServiceName) { delegate.localServiceName(localServiceName); return this; } /** @see Tracing.Builder#localEndpoint(Endpoint) */ public Builder localEndpoint(Endpoint localEndpoint) { delegate.localEndpoint(localEndpoint); return this; } /** @see Tracing.Builder#reporter(Reporter) */ public Builder reporter(Reporter<zipkin.Span> reporter) { delegate.reporter(reporter); return this; } /** @see Tracing.Builder#clock(Clock) */ public Builder clock(Clock clock) { delegate.clock(clock); return this; } /** @see Tracing.Builder#sampler(Sampler) */ public Builder sampler(Sampler sampler) { delegate.sampler(sampler); return this; } /** @see Tracing.Builder#currentTraceContext(CurrentTraceContext) */ public Builder currentTraceContext(CurrentTraceContext currentTraceContext) { delegate.currentTraceContext(currentTraceContext); return this; } /** @see Tracing.Builder#traceId128Bit(boolean) */ public Builder traceId128Bit(boolean traceId128Bit) { delegate.traceId128Bit(traceId128Bit); return this; } public Tracer build() { return delegate.build().tracer(); } } final Clock clock; final Endpoint localEndpoint; final Recorder recorder; final Sampler sampler; final CurrentTraceContext currentTraceContext; final boolean traceId128Bit; Tracer(Tracing.Builder builder) { this.clock = builder.clock; this.localEndpoint = builder.localEndpoint; this.recorder = new Recorder(localEndpoint, clock, builder.reporter); this.sampler = builder.sampler; this.currentTraceContext = builder.currentTraceContext; this.traceId128Bit = builder.traceId128Bit; } /** @deprecated use {@link Tracing#clock()} */ @Deprecated public Clock clock() { return clock; } /** * Creates a new trace. If there is an existing trace, use {@link #newChild(TraceContext)} * instead. */ public Span newTrace() { return ensureSampled(nextContext(null, SamplingFlags.EMPTY)); } /** * Joining is re-using the same trace and span ids extracted from an incoming request. Here, we * ensure a sampling decision has been made. If the span passed sampling, we assume this is a * shared span, one where the caller and the current tracer report to the same span IDs. If no * sampling decision occurred yet, we have exclusive access to this span ID. * * <p>Here's an example of conditionally joining a span, depending on if a trace context was * extracted from an incoming request. * * <pre>{@code * contextOrFlags = extractor.extract(request); * span = contextOrFlags.context() != null * ? tracer.joinSpan(contextOrFlags.context()) * : tracer.newTrace(contextOrFlags.samplingFlags()); * }</pre> * * @see Propagation * @see Extractor#extract(Object) * @see TraceContextOrSamplingFlags#context() */ public final Span joinSpan(TraceContext context) { if (context == null) throw new NullPointerException("context == null"); // If we are joining a trace, we are sharing IDs with the caller return ensureSampled(context.toBuilder().shared(true).build()); } /** * Like {@link #newTrace()}, but supports parameterized sampling, for example limiting on * operation or url pattern. * * <p>For example, to sample all requests for a specific url: * <pre>{@code * Span newTrace(Request input) { * SamplingFlags flags = SamplingFlags.NONE; * if (input.url().startsWith("/experimental")) { * flags = SamplingFlags.SAMPLED; * } else if (input.url().startsWith("/static")) { * flags = SamplingFlags.NOT_SAMPLED; * } * return tracer.newTrace(flags); * } * }</pre> */ public Span newTrace(SamplingFlags samplingFlags) { return ensureSampled(nextContext(null, samplingFlags)); } /** Converts the context as-is to a Span object */ public Span toSpan(TraceContext context) { if (context == null) throw new NullPointerException("context == null"); if (context.sampled() == null || context.sampled()) { return RealSpan.create(context, clock, recorder); } return NoopSpan.create(context); } /** * Creates a new span within an existing trace. If there is no existing trace, use {@link * #newTrace()} instead. */ public Span newChild(TraceContext parent) { if (parent == null) throw new NullPointerException("parent == null"); if (Boolean.FALSE.equals(parent.sampled())) { return NoopSpan.create(parent); } return ensureSampled(nextContext(parent, parent)); } Span ensureSampled(TraceContext context) { // If the sampled flag was left unset, we need to make the decision here if (context.sampled() == null) { context = context.toBuilder() .sampled(sampler.isSampled(context.traceId())) .shared(false) .build(); } return toSpan(context); } TraceContext nextContext(@Nullable TraceContext parent, SamplingFlags samplingFlags) { long nextId = Platform.get().randomLong(); if (parent != null) { return parent.toBuilder().spanId(nextId).parentId(parent.spanId()).build(); } return TraceContext.newBuilder() .sampled(samplingFlags.sampled()) .debug(samplingFlags.debug()) .traceIdHigh(traceId128Bit ? Platform.get().randomLong() : 0L) .traceId(nextId) .spanId(nextId).build(); } /** * Makes the given span the "current span" and returns an object that exits that scope on close. * The span provided will be returned by {@link #currentSpan()} until the return value is closed. * * <p>The most convenient way to use this method is via the try-with-resources idiom. * * Ex. * <pre>{@code * // Assume a framework interceptor uses this method to set the inbound span as current * try (SpanInScope ws = tracer.withSpanInScope(span)) { * return inboundRequest.invoke(); * } finally { * span.finish(); * } * * // An unrelated framework interceptor can now lookup the correct parent for an outbound * request * Span parent = tracer.currentSpan() * Span span = tracer.nextSpan().name("outbound").start(); // parent is implicitly looked up * try (SpanInScope ws = tracer.withSpanInScope(span)) { * return outboundRequest.invoke(); * } finally { * span.finish(); * } * }</pre> * * <p>Note: While downstream code might affect the span, calling this method, and calling close on * the result have no effect on the input. For example, calling close on the result does not * finish the span. Not only is it safe to call close, you must call close to end the scope, or * risk leaking resources associated with the scope. * * @param span span to place into scope or null to clear the scope */ public SpanInScope withSpanInScope(@Nullable Span span) { return new SpanInScope(currentTraceContext.newScope(span != null ? span.context() : null)); } /** Returns the current span in scope or null if there isn't one. */ @Nullable public Span currentSpan() { TraceContext currentContext = currentTraceContext.get(); return currentContext != null ? toSpan(currentContext) : null; } /** Returns a new child span if there's a {@link #currentSpan()} or a new trace if there isn't. */ public Span nextSpan() { TraceContext parent = currentTraceContext.get(); return parent == null ? newTrace() : newChild(parent); } /** A span remains in the scope it was bound to until close is called. */ public static final class SpanInScope implements Closeable { final CurrentTraceContext.Scope scope; // This type hides the SPI type and allows us to double-check the SPI didn't return null. SpanInScope(CurrentTraceContext.Scope scope) { if (scope == null) throw new NullPointerException("scope == null"); this.scope = scope; } /** No exceptions are thrown when unbinding a span scope. */ @Override public void close() { scope.close(); } @Override public String toString() { return scope.toString(); } } }