package rocks.inspectit.agent.java.sdk.opentracing.internal.impl; import java.util.Map; import java.util.Stack; import java.util.concurrent.ConcurrentHashMap; import io.opentracing.References; import io.opentracing.SpanContext; import io.opentracing.propagation.Format; import rocks.inspectit.agent.java.sdk.opentracing.ExtendedTracer; import rocks.inspectit.agent.java.sdk.opentracing.Reporter; import rocks.inspectit.agent.java.sdk.opentracing.Timer; import rocks.inspectit.agent.java.sdk.opentracing.TracerProvider; import rocks.inspectit.agent.java.sdk.opentracing.internal.TracerLogger; import rocks.inspectit.agent.java.sdk.opentracing.internal.propagation.TextMapPropagator; import rocks.inspectit.agent.java.sdk.opentracing.internal.propagation.UrlEncodingPropagator; import rocks.inspectit.agent.java.sdk.opentracing.noop.NoopReporter; import rocks.inspectit.agent.java.sdk.opentracing.propagation.Propagator; import rocks.inspectit.agent.java.sdk.opentracing.util.SystemTimer; /** * The io.opentracing tracer implementation. This tracer keeps the thread context state. Every time * a span is started it will be added to the current thread span stack. Every time span is finished * it will be removed from the thread stack. * <p> * The tracer uses {@link Timer} for time measurement. inspectIT SDK provides a simple timer * implementation ({@link SystemTimer}), as it needs to be compatible with java 6. * <p> * The tracer uses {@link Reporter} for reporting finished spans. In inspectIT SDK there is an * option to explicitly state that span should not be reported, as inspectIT itself adds other * information to the span it creates and reports them itself. User created spans will always be * reported if not explicitly stated otherwise. * * @author Ivan Senic * */ public class TracerImpl implements ExtendedTracer { /** * {@link TracerLogger} of this class. */ private static final TracerLogger LOGGER = TracerLoggerWrapper.getTraceLogger(TracerImpl.class); /** * Span stack. */ private final ThreadLocal<Stack<SpanImpl>> spanStack = new ThreadLocal<Stack<SpanImpl>>() { /** * {@inheritDoc} */ @Override protected Stack<SpanImpl> initialValue() { return new Stack<SpanImpl>(); } }; /** * Timer for measuring times. */ private Timer timer; /** * Reporter to report spans to. */ private final Reporter reporter; /** * Usable propagators. */ private final Map<Format<?>, Propagator<?>> propagators = new ConcurrentHashMap<Format<?>, Propagator<?>>(4, 1f); /** * Initializes the tracer with {@link SystemTimer} and {@link NoopReporter}. Please use * {@link TracerImpl#TracerImpl(Timer, Reporter, boolean)} for initialization with reporter of * your choice. */ public TracerImpl() { this(new SystemTimer(), new NoopReporter(), false); } /** * Default constructor. Timer and Reporter for this tracer must be provided. * * @param timer * {@link Timer} * @param reporter * {@link Reporter} * @param setToTracerProvider * If this tracer should be set to the {@link TracerProvider} class for static usage. */ public TracerImpl(Timer timer, Reporter reporter, boolean setToTracerProvider) { if (null == timer) { throw new IllegalArgumentException("Timer can not be null."); } if (null == reporter) { throw new IllegalArgumentException("Reporter can not be null."); } this.timer = timer; this.reporter = reporter; registerDefaultPropagators(); if (setToTracerProvider) { TracerProvider.set(this); } } /** * Registers default propagators. Users can overwrite by using * {@link #registerPropagator(Format, Propagator)}. */ private void registerDefaultPropagators() { registerPropagator(Format.Builtin.TEXT_MAP, new TextMapPropagator()); registerPropagator(Format.Builtin.HTTP_HEADERS, new UrlEncodingPropagator()); } /** * Registers propagator. * * @param <C> * format type * @param format * opentracing {@link Format} * @param propagator * {@link Propagator} */ @Override public <C> void registerPropagator(Format<C> format, Propagator<C> propagator) { propagators.put(format, propagator); if (LOGGER.isDebugEnabled()) { LOGGER.debug("New propagator registered for the format" + format.toString() + "."); } } /** * Builds span with no operation name. * * @return {@link SpanBuilder}. */ @Override public SpanBuilderImpl buildSpan() { return buildSpan(null); } /** * {@inheritDoc} * <p> * Note that as tracer is thread span context aware this method will automatically add reference * (CHILD_OF) to the current thread span context if the one exists. If you want to manually * specify reference type or want to ignore the current thread context then use * {@link #buildSpan(String, String, boolean)}. */ @Override public SpanBuilderImpl buildSpan(String operationName) { return buildSpan(operationName, References.CHILD_OF, true); } /** * Creates {@link SpanBuilder} that optionally adds the reference to the current thread context * span. * * @param operationName * Operation name of the span. * @param referenceType * Reference type to the current context. * @param useThreadContext * If thread context should be used. * @return {@link SpanBuilder}. */ @Override public SpanBuilderImpl buildSpan(String operationName, String referenceType, boolean useThreadContext) { SpanBuilderImpl spanBuilder = new SpanBuilderImpl(this, operationName); if (useThreadContext) { // check the current thread context SpanContextImpl threadContext = getCurrentContext(); if (threadContext != null) { spanBuilder.addReference(referenceType, threadContext); } } return spanBuilder; } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @Override public <C> void inject(SpanContext spanContext, Format<C> format, C carrier) { if ((format == null) || (carrier == null)) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Context can not be injected, both format and carrier must be provided."); } return; } if (spanContext instanceof SpanContextImpl) { Propagator<C> propagator = (Propagator<C>) propagators.get(format); if (null != propagator) { propagator.inject((SpanContextImpl) spanContext, carrier); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Context can not be injected, propagator does not exists for the format " + format.toString() + "."); } } } } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @Override public <C> SpanContext extract(Format<C> format, C carrier) { if ((format == null) || (carrier == null)) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Context can not be extracted, both format and carrier must be provided."); } return null; } Propagator<C> propagator = (Propagator<C>) propagators.get(format); if (null != propagator) { SpanContextImpl context = propagator.extract(carrier); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Context extracted: " + context); } return context; } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Context can not be extracted, propagator does not exists for the format " + format.toString() + "."); } return null; } } /** * {@inheritDoc} */ @Override public SpanContextImpl getCurrentContext() { Stack<SpanImpl> stack = spanStack.get(); if (!stack.isEmpty()) { return stack.peek().context(); } return null; } /** * Returns if the thread context exists. * * @return Returns if the thread context exists. */ public boolean isCurrentContextExisting() { return !spanStack.get().isEmpty(); } /** * Reports the span to the tracer once the span is started. * * @param span * Span. */ void spanStarted(SpanImpl span) { if (null == span) { return; } spanStack.get().push(span); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Span started " + span); } } /** * Reports the span to the tracer once the span is finished. * * @param span * Span. */ void spanEnded(SpanImpl span) { if (null == span) { return; } // check if we have the span in the stack Stack<SpanImpl> stack = spanStack.get(); if (stack.contains(span)) { // if so clear the stack until we reach it // it should be the first one, but just for safety // (users might forget to finish spans or could finish them in wrong order) SpanImpl removed = stack.pop(); boolean wrongEndOrder = false; while (!stack.isEmpty() && !removed.equals(span)) { wrongEndOrder = true; stack.pop(); } if (wrongEndOrder && LOGGER.isWarnEnabled()) { LOGGER.warn("Finishing of spans is not done in starting order, span " + span.toString() + " is not the last started one by current thread. Thread context state can be affected."); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Span finished " + span); } } // check if we need to report the span if (span.isReport()) { reporter.report(span); } } /** * Gets {@link #timer}. * * @return {@link #timer} */ Timer getTimer() { return this.timer; } /** * {@inheritDoc} */ @Override public void setTimer(Timer timer) { if (null == timer) { throw new IllegalArgumentException("Timer must not be null."); } this.timer = timer; } }