/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.camel.opentracing; import java.net.URI; import java.util.EventObject; import java.util.HashMap; import java.util.Map; import java.util.ServiceLoader; import java.util.Set; import io.opentracing.NoopTracerFactory; import io.opentracing.Span; import io.opentracing.Tracer; import io.opentracing.Tracer.SpanBuilder; import io.opentracing.contrib.tracerresolver.TracerResolver; import io.opentracing.propagation.Format; import io.opentracing.tag.Tags; import org.apache.camel.CamelContext; import org.apache.camel.CamelContextAware; import org.apache.camel.Endpoint; import org.apache.camel.Exchange; import org.apache.camel.Route; import org.apache.camel.StaticService; import org.apache.camel.api.management.ManagedResource; import org.apache.camel.management.event.ExchangeSendingEvent; import org.apache.camel.management.event.ExchangeSentEvent; import org.apache.camel.model.RouteDefinition; import org.apache.camel.opentracing.propagation.CamelHeadersExtractAdapter; import org.apache.camel.opentracing.propagation.CamelHeadersInjectAdapter; import org.apache.camel.spi.LogListener; import org.apache.camel.spi.RoutePolicy; import org.apache.camel.spi.RoutePolicyFactory; import org.apache.camel.support.EventNotifierSupport; import org.apache.camel.support.RoutePolicySupport; import org.apache.camel.support.ServiceSupport; import org.apache.camel.util.CamelLogger; import org.apache.camel.util.ObjectHelper; import org.apache.camel.util.ServiceHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * To use OpenTracing with Camel then setup this {@link OpenTracingTracer} in your Camel application. * <p/> * This class is implemented as both an {@link org.apache.camel.spi.EventNotifier} and {@link RoutePolicy} that allows * to trap when Camel starts/ends an {@link Exchange} being routed using the {@link RoutePolicy} and during the routing * if the {@link Exchange} sends messages, then we track them using the {@link org.apache.camel.spi.EventNotifier}. */ @ManagedResource(description = "OpenTracingTracer") public class OpenTracingTracer extends ServiceSupport implements RoutePolicyFactory, StaticService, CamelContextAware { private static final Logger LOG = LoggerFactory.getLogger(OpenTracingTracer.class); private static Map<String, SpanDecorator> decorators = new HashMap<>(); private final OpenTracingEventNotifier eventNotifier = new OpenTracingEventNotifier(); private final OpenTracingLogListener logListener = new OpenTracingLogListener(); private Tracer tracer; private CamelContext camelContext; static { ServiceLoader.load(SpanDecorator.class).forEach(d -> { SpanDecorator existing = decorators.get(d.getComponent()); // Add span decorator if no existing decorator for the component, // or if derived from the existing decorator's class, allowing // custom decorators to be added if they extend the standard // decorators if (existing == null || existing.getClass().isInstance(d)) { decorators.put(d.getComponent(), d); } }); } public OpenTracingTracer() { } @Override public RoutePolicy createRoutePolicy(CamelContext camelContext, String routeId, RouteDefinition route) { // ensure this opentracing tracer gets initialized when Camel starts init(camelContext); return new OpenTracingRoutePolicy(routeId); } /** * Registers this {@link OpenTracingTracer} on the {@link CamelContext} if not already registered. */ public void init(CamelContext camelContext) { if (!camelContext.hasService(this)) { try { // start this service eager so we init before Camel is starting up camelContext.addService(this, true, true); } catch (Exception e) { throw ObjectHelper.wrapRuntimeCamelException(e); } } } @Override public CamelContext getCamelContext() { return camelContext; } @Override public void setCamelContext(CamelContext camelContext) { this.camelContext = camelContext; } public Tracer getTracer() { return tracer; } public void setTracer(Tracer tracer) { this.tracer = tracer; } @Override protected void doStart() throws Exception { ObjectHelper.notNull(camelContext, "CamelContext", this); camelContext.getManagementStrategy().addEventNotifier(eventNotifier); if (!camelContext.getRoutePolicyFactories().contains(this)) { camelContext.addRoutePolicyFactory(this); } camelContext.addLogListener(logListener); if (tracer == null) { Set<Tracer> tracers = camelContext.getRegistry().findByType(Tracer.class); if (tracers.size() == 1) { tracer = tracers.iterator().next(); } } if (tracer == null) { tracer = TracerResolver.resolveTracer(); } if (tracer == null) { // No tracer is available, so setup NoopTracer tracer = NoopTracerFactory.create(); } ServiceHelper.startServices(eventNotifier); } @Override protected void doStop() throws Exception { // stop event notifier camelContext.getManagementStrategy().removeEventNotifier(eventNotifier); ServiceHelper.stopService(eventNotifier); // remove route policy camelContext.getRoutePolicyFactories().remove(this); } protected SpanDecorator getSpanDecorator(Endpoint endpoint) { SpanDecorator sd = decorators.get(URI.create(endpoint.getEndpointUri()).getScheme()); if (sd == null) { return SpanDecorator.DEFAULT; } return sd; } private final class OpenTracingEventNotifier extends EventNotifierSupport { @Override public void notify(EventObject event) throws Exception { try { if (event instanceof ExchangeSendingEvent) { ExchangeSendingEvent ese = (ExchangeSendingEvent) event; SpanDecorator sd = getSpanDecorator(ese.getEndpoint()); if (!sd.newSpan()) { return; } Span parent = ActiveSpanManager.getSpan(ese.getExchange()); SpanBuilder spanBuilder = tracer.buildSpan(sd.getOperationName(ese.getExchange(), ese.getEndpoint())) .withTag(Tags.SPAN_KIND.getKey(), sd.getInitiatorSpanKind()); // Temporary workaround to avoid adding 'null' span as a parent if (parent != null) { spanBuilder.asChildOf(parent); } Span span = spanBuilder.start(); sd.pre(span, ese.getExchange(), ese.getEndpoint()); tracer.inject(span.context(), Format.Builtin.TEXT_MAP, new CamelHeadersInjectAdapter(ese.getExchange().getIn().getHeaders())); ActiveSpanManager.activate(ese.getExchange(), span); if (LOG.isTraceEnabled()) { LOG.trace("OpenTracing: start client span=" + span); } } else if (event instanceof ExchangeSentEvent) { ExchangeSentEvent ese = (ExchangeSentEvent) event; SpanDecorator sd = getSpanDecorator(ese.getEndpoint()); if (!sd.newSpan()) { return; } Span span = ActiveSpanManager.getSpan(ese.getExchange()); if (span != null) { if (LOG.isTraceEnabled()) { LOG.trace("OpenTracing: start client span=" + span); } sd.post(span, ese.getExchange(), ese.getEndpoint()); span.finish(); ActiveSpanManager.deactivate(ese.getExchange()); } else { LOG.warn("OpenTracing: could not find managed span for exchange=" + ese.getExchange()); } } } catch (Throwable t) { // This exception is ignored LOG.warn("OpenTracing: Failed to capture tracing data", t); } } @Override public boolean isEnabled(EventObject event) { return event instanceof ExchangeSendingEvent || event instanceof ExchangeSentEvent; } @Override public String toString() { return "OpenTracingEventNotifier"; } } private final class OpenTracingRoutePolicy extends RoutePolicySupport { OpenTracingRoutePolicy(String routeId) { } @Override public void onExchangeBegin(Route route, Exchange exchange) { try { SpanDecorator sd = getSpanDecorator(route.getEndpoint()); Span span = tracer.buildSpan(sd.getOperationName(exchange, route.getEndpoint())) .asChildOf(tracer.extract(Format.Builtin.TEXT_MAP, new CamelHeadersExtractAdapter(exchange.getIn().getHeaders()))) .withTag(Tags.SPAN_KIND.getKey(), sd.getReceiverSpanKind()) .start(); sd.pre(span, exchange, route.getEndpoint()); ActiveSpanManager.activate(exchange, span); if (LOG.isTraceEnabled()) { LOG.trace("OpenTracing: start server span=" + span); } } catch (Throwable t) { // This exception is ignored LOG.warn("OpenTracing: Failed to capture tracing data", t); } } @Override public void onExchangeDone(Route route, Exchange exchange) { try { Span span = ActiveSpanManager.getSpan(exchange); if (span != null) { if (LOG.isTraceEnabled()) { LOG.trace("OpenTracing: finish server span=" + span); } SpanDecorator sd = getSpanDecorator(route.getEndpoint()); sd.post(span, exchange, route.getEndpoint()); span.finish(); ActiveSpanManager.deactivate(exchange); } else { LOG.warn("OpenTracing: could not find managed span for exchange=" + exchange); } } catch (Throwable t) { // This exception is ignored LOG.warn("OpenTracing: Failed to capture tracing data", t); } } } private final class OpenTracingLogListener implements LogListener { @Override public String onLog(Exchange exchange, CamelLogger camelLogger, String message) { try { Span span = ActiveSpanManager.getSpan(exchange); if (span != null) { Map<String, Object> fields = new HashMap<>(); fields.put("message", message); span.log(fields); } } catch (Throwable t) { // This exception is ignored LOG.warn("OpenTracing: Failed to capture tracing data", t); } return message; } } }