spanReporter) {
if (spanReporter == null) throw new NullPointerException("spanReporter == null");
this.spanReporter = spanReporter;
return this;
}
/**
* Assigns microsecond-resolution timestamp source for operations like {@link Span#start()}.
* Defaults to JRE-specific platform time.
*
* Note: timestamps are read once per trace, then {@link System#nanoTime() ticks}
* thereafter. This ensures there's no clock skew problems inside a single trace.
*
* See {@link Tracing#clock(TraceContext)}
*/
public Builder clock(Clock clock) {
if (clock == null) throw new NullPointerException("clock == null");
this.clock = clock;
return this;
}
/**
* Sampler is responsible for deciding if a particular trace should be "sampled", i.e. whether
* the overhead of tracing will occur and/or if a trace will be reported to Zipkin.
*
* @see Tracer#nextSpan(SamplerFunction, Object) for temporary overrides
*/
public Builder sampler(Sampler sampler) {
if (sampler == null) throw new NullPointerException("sampler == null");
this.sampler = sampler;
return this;
}
/**
* Responsible for implementing {@link Tracer#startScopedSpan(String)}, {@link
* Tracer#currentSpanCustomizer()}, {@link Tracer#currentSpan()} and {@link
* Tracer#withSpanInScope(Span)}.
*
*
By default a simple thread-local is used. Override to support other mechanisms or to
* synchronize with other mechanisms such as SLF4J's MDC.
*/
public Builder currentTraceContext(CurrentTraceContext currentTraceContext) {
if (currentTraceContext == null) {
throw new NullPointerException("currentTraceContext == null");
}
this.currentTraceContext = currentTraceContext;
return this;
}
/**
* Controls how trace contexts are injected or extracted from remote requests, such as from http
* headers. Defaults to {@link B3Propagation#FACTORY}
*/
public Builder propagationFactory(Propagation.Factory propagationFactory) {
if (propagationFactory == null) throw new NullPointerException("propagationFactory == null");
this.propagationFactory = propagationFactory;
return this;
}
/** When true, new root spans will have 128-bit trace IDs. Defaults to false (64-bit) */
public Builder traceId128Bit(boolean traceId128Bit) {
this.traceId128Bit = traceId128Bit;
return this;
}
/**
* True means the tracing system supports sharing a span ID between a {@link Span.Kind#CLIENT}
* and {@link Span.Kind#SERVER} span. Defaults to true.
*
*
Set this to false when the tracing system requires the opposite. For example, if
* ultimately spans are sent to Amazon X-Ray or Google Stackdriver Trace, you should set this to
* false.
*
*
This is implicitly set to false when {@link Propagation.Factory#supportsJoin()} is false,
* as in that case, sharing IDs isn't possible anyway.
*
* @see Propagation.Factory#supportsJoin()
*/
public Builder supportsJoin(boolean supportsJoin) {
this.supportsJoin = supportsJoin;
return this;
}
public Builder errorParser(ErrorParser errorParser) {
this.errorParser = errorParser;
return this;
}
/**
* Similar to {@link #spanReporter(Reporter)} except it can read the trace context and create
* more efficient or completely different data structures. Importantly, the input is mutable for
* customization purposes.
*
*
These handlers execute before the {@link #spanReporter(Reporter) span reporter}, which
* means any mutations occur prior to Zipkin.
*
*
Advanced notes
*
* This is named firehose as it can receive data even when spans are not sampled remotely.
* For example, {@link FinishedSpanHandler#alwaysSampleLocal()} will generate data for all
* traced requests while not affecting headers. This setting is often used for metrics
* aggregation.
*
*
*
Your handler can also be a custom span transport. When this is the case, set the {@link
* #spanReporter(Reporter) span reporter} to {@link Reporter#NOOP} to avoid redundant conversion
* overhead.
*
* @param handler skipped if {@link FinishedSpanHandler#NOOP} or already added
* @see #alwaysReportSpans()
* @see TraceContext#sampledLocal()
*/
public Builder addFinishedSpanHandler(FinishedSpanHandler handler) {
if (handler == null) throw new NullPointerException("finishedSpanHandler == null");
if (handler != FinishedSpanHandler.NOOP) { // lenient on config bug
if (!finishedSpanHandlers.add(handler)) {
Platform.get().log("Please check configuration as %s was added twice", handler, null);
}
}
return this;
}
/**
* When true, all spans {@link TraceContext#sampledLocal() sampled locally} are reported to the
* {@link #spanReporter(Reporter) span reporter}, even if they aren't sampled remotely. Defaults
* to false.
*
*
The primary use case is to implement a sampling
* overlay, such as boosting the sample rate for a subset of the network depending on the
* value of a {@link BaggageField baggage field}. This means that data will report when either
* the trace is normally sampled, or secondarily sampled via a custom header.
*
*
This is simpler than {@link #addFinishedSpanHandler(FinishedSpanHandler)}, because you
* don't have to duplicate transport mechanics already implemented in the {@link
* #spanReporter(Reporter) span reporter}. However, this assumes your backend can properly
* process the partial traces implied when using conditional sampling. For example, if your
* sampling condition is not consistent on a call tree, the resulting data could appear broken.
*
* @see #addFinishedSpanHandler(FinishedSpanHandler)
* @see TraceContext#sampledLocal()
* @since 5.8
*/
public Builder alwaysReportSpans() {
this.alwaysReportSpans = true;
return this;
}
/**
* When true, this logs the caller which orphaned a span to the category "brave.Tracer" at
* {@link Level#FINE}. Defaults to false.
*
*
If you see data with the annotation "brave.flush", you may have an instrumentation bug.
* To see which code was involved, set this and ensure the logger {@link Tracing} is at {@link
* Level#FINE}. Do not do this in production as tracking orphaned data incurs higher overhead.
*
* @see FinishedSpanHandler#supportsOrphans()
* @since 5.9
*/
public Builder trackOrphans() {
this.trackOrphans = true;
return this;
}
public Tracing build() {
if (clock == null) clock = Platform.get().clock();
if (localIp == null) localIp = Platform.get().linkLocalIp();
if (spanReporter == null) spanReporter = new LoggingReporter();
return new Default(this);
}
Builder() {
}
}
static final class LoggingReporter implements Reporter {
final Logger logger = Logger.getLogger(Tracer.class.getName());
@Override public void report(zipkin2.Span span) {
if (span == null) throw new NullPointerException("span == null");
if (!logger.isLoggable(Level.INFO)) return;
logger.info(span.toString());
}
@Override public String toString() {
return "LoggingReporter{name=" + logger.getName() + "}";
}
}
static final class Default extends Tracing {
final Tracer tracer;
final Propagation.Factory propagationFactory;
final Propagation stringPropagation;
final CurrentTraceContext currentTraceContext;
final Sampler sampler;
final Clock clock;
final ErrorParser errorParser;
final AtomicBoolean noop;
Default(Builder builder) {
this.clock = builder.clock;
this.errorParser = builder.errorParser;
this.propagationFactory = builder.propagationFactory;
this.stringPropagation = builder.propagationFactory.create(Propagation.KeyFactory.STRING);
this.currentTraceContext = builder.currentTraceContext;
this.sampler = builder.sampler;
this.noop = new AtomicBoolean();
FinishedSpanHandler zipkinHandler = builder.spanReporter != Reporter.NOOP
? new ZipkinFinishedSpanHandler(builder.spanReporter, errorParser,
builder.localServiceName, builder.localIp, builder.localPort, builder.alwaysReportSpans)
: FinishedSpanHandler.NOOP;
FinishedSpanHandler finishedSpanHandler =
zipkinReportingFinishedSpanHandler(builder.finishedSpanHandlers, zipkinHandler, noop);
Set orphanedSpanHandlers = new LinkedHashSet<>();
for (FinishedSpanHandler handler : builder.finishedSpanHandlers) {
if (handler.supportsOrphans()) orphanedSpanHandlers.add(handler);
}
FinishedSpanHandler orphanedSpanHandler = finishedSpanHandler;
boolean allHandlersSupportOrphans = builder.finishedSpanHandlers.equals(orphanedSpanHandlers);
if (!allHandlersSupportOrphans) {
orphanedSpanHandler =
zipkinReportingFinishedSpanHandler(orphanedSpanHandlers, zipkinHandler, noop);
}
this.tracer = new Tracer(
builder.clock,
builder.propagationFactory,
finishedSpanHandler,
new PendingSpans(clock, orphanedSpanHandler, builder.trackOrphans, noop),
builder.sampler,
builder.currentTraceContext,
builder.traceId128Bit || propagationFactory.requires128BitTraceId(),
builder.supportsJoin && propagationFactory.supportsJoin(),
finishedSpanHandler.alwaysSampleLocal(),
noop
);
// assign current IFF there's no instance already current
CURRENT.compareAndSet(null, this);
}
@Override public Tracer tracer() {
return tracer;
}
@Override public Propagation propagation() {
return stringPropagation;
}
@Override public Propagation.Factory propagationFactory() {
return propagationFactory;
}
@Override public Sampler sampler() {
return sampler;
}
@Override public CurrentTraceContext currentTraceContext() {
return currentTraceContext;
}
@Override public ErrorParser errorParser() {
return errorParser;
}
@Override public boolean isNoop() {
return noop.get();
}
@Override public void setNoop(boolean noop) {
this.noop.set(noop);
}
@Override public String toString() {
return tracer.toString();
}
@Override public void close() {
// only set null if we are the outer-most instance
CURRENT.compareAndSet(this, null);
}
}
static FinishedSpanHandler zipkinReportingFinishedSpanHandler(
Set input, FinishedSpanHandler zipkinHandler, AtomicBoolean noop) {
ArrayList defensiveCopy = new ArrayList<>(input);
// When present, the Zipkin handler is invoked after the user-supplied finished span handlers.
if (zipkinHandler != FinishedSpanHandler.NOOP) defensiveCopy.add(zipkinHandler);
// Make sure any exceptions caused by handlers don't crash callers
return NoopAwareFinishedSpanHandler.create(defensiveCopy, noop);
}
Tracing() { // intentionally hidden constructor
}
}