Skip to content

Commit

Permalink
apache camel 2.20.x instrumentation (#1397)
Browse files Browse the repository at this point in the history
* apache camel 2.20.x instrumentation

* removed instrumentation advice from helper classes

* code review #1

* code review #2 / experimental server name update

* code review changes / new tests, improved direct, new license header

* rebase changes

* code review changes

* code review changes, REST tests added

* changes after rebase to newest master

* code review changes

* code review

* code review

* rebase to master
  • Loading branch information
kuba-wu committed Nov 3, 2020
1 parent a528dae commit 049358e
Show file tree
Hide file tree
Showing 33 changed files with 2,370 additions and 1 deletion.
2 changes: 1 addition & 1 deletion gradle/spotless.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apply plugin: 'com.diffplug.spotless'
spotless {
java {
googleJavaFormat()
licenseHeaderFile rootProject.file('gradle/enforcement/spotless.license.java'), '(package|import|public)'
licenseHeaderFile rootProject.file('gradle/enforcement/spotless.license.java'), '(package|import|public|// Includes work from:)'
target 'src/**/*.java'
}
groovy {
Expand Down
35 changes: 35 additions & 0 deletions instrumentation/apache-camel-2.20/apache-camel-2.20.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
ext {
minJavaVersionForTests = JavaVersion.VERSION_1_8
}

apply from: "$rootDir/gradle/instrumentation.gradle"

muzzle {
pass {
group = "org.apache.camel"
module = "camel-core"
versions = "[2.20.1,3)"
}
}

dependencies {
library group: 'org.apache.camel', name: 'camel-core', version: '2.20.1'

testImplementation project(':instrumentation:apache-httpclient:apache-httpclient-2.0')
testImplementation project(':instrumentation:servlet:servlet-3.0')

testImplementation group: 'org.spockframework', name: 'spock-spring', version: "$versions.spock"

testImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: '1.5.17.RELEASE'
testImplementation group: 'org.springframework.boot', name: 'spring-boot-starter', version: '1.5.17.RELEASE'

testImplementation group: 'org.apache.camel', name: 'camel-spring-boot-starter', version: '2.20.1'
testImplementation group: 'org.apache.camel', name: 'camel-jetty-starter', version: '2.20.1'
testImplementation group: 'org.apache.camel', name: 'camel-http-starter', version: '2.20.1'
testImplementation group: 'org.apache.camel', name: 'camel-jaxb-starter', version: '2.20.1'
testImplementation group: 'org.apache.camel', name: 'camel-undertow', version: '2.20.1'

testImplementation 'javax.xml.bind:jaxb-api:2.3.1'

latestDepTestLibrary group: 'org.apache.camel', name: 'camel-core', version: '2.+'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

// Includes work from:
/*
* Apache Camel Opentracing Component
*
* 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
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>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 io.opentelemetry.javaagent.instrumentation.apachecamel;

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Scope;
import org.apache.camel.Exchange;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** Utility class for managing active spans as a stack associated with an exchange. */
class ActiveSpanManager {

private static final String ACTIVE_SPAN_PROPERTY = "OpenTelemetry.activeSpan";

private static final Logger LOG = LoggerFactory.getLogger(ActiveSpanManager.class);

private ActiveSpanManager() {}

public static Span getSpan(Exchange exchange) {
SpanWithScope spanWithScope = exchange.getProperty(ACTIVE_SPAN_PROPERTY, SpanWithScope.class);
if (spanWithScope != null) {
return spanWithScope.getSpan();
}
return null;
}

/**
* This method activates the supplied span for the supplied exchange. If an existing span is found
* for the exchange, this will be pushed onto a stack.
*
* @param exchange The exchange
* @param span The span
*/
public static void activate(Exchange exchange, Span span) {

SpanWithScope parent = exchange.getProperty(ACTIVE_SPAN_PROPERTY, SpanWithScope.class);
SpanWithScope spanWithScope = SpanWithScope.activate(span, parent);
exchange.setProperty(ACTIVE_SPAN_PROPERTY, spanWithScope);
if (LOG.isTraceEnabled()) {
LOG.trace("Activated a span: " + spanWithScope);
}
}

/**
* This method deactivates an existing active span associated with the supplied exchange. Once
* deactivated, if a parent span is found associated with the stack for the exchange, it will be
* restored as the current span for that exchange.
*
* @param exchange The exchange
*/
public static void deactivate(Exchange exchange) {

SpanWithScope spanWithScope = exchange.getProperty(ACTIVE_SPAN_PROPERTY, SpanWithScope.class);

if (spanWithScope != null) {
spanWithScope.deactivate();
exchange.setProperty(ACTIVE_SPAN_PROPERTY, spanWithScope.getParent());
if (LOG.isTraceEnabled()) {
LOG.trace("Deactivated span: " + spanWithScope);
}
}
}

public static class SpanWithScope {
@Nullable private final SpanWithScope parent;
private final Span span;
private final Scope scope;

public SpanWithScope(SpanWithScope parent, Span span, Scope scope) {
this.parent = parent;
this.span = span;
this.scope = scope;
}

public static SpanWithScope activate(Span span, SpanWithScope parent) {
Scope scope = CamelTracer.TRACER.startScope(span);
return new SpanWithScope(parent, span, scope);
}

public SpanWithScope getParent() {
return parent;
}

public Span getSpan() {
return span;
}

public void deactivate() {
span.end();
scope.close();
}

@Override
public String toString() {
return "SpanWithScope [span=" + span + ", scope=" + scope + "]";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.apachecamel;

import static io.opentelemetry.javaagent.tooling.ClassLoaderMatcher.hasClassesNamed;
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.implementsInterface;
import static net.bytebuddy.matcher.ElementMatchers.isAbstract;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.tooling.Instrumenter;
import java.util.Collections;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.apache.camel.CamelContext;

@AutoService(Instrumenter.class)
public class CamelContextInstrumentation extends Instrumenter.Default {

public CamelContextInstrumentation() {
super("apachecamel", "apache-camel");
}

@Override
public ElementMatcher<ClassLoader> classLoaderMatcher() {
// Optimization for expensive typeMatcher.
return hasClassesNamed("org.apache.camel.CamelContext");
}

@Override
public ElementMatcher<TypeDescription> typeMatcher() {

return not(isAbstract()).and(implementsInterface(named("org.apache.camel.CamelContext")));
}

@Override
public String[] helperClassNames() {
return new String[] {
"io.opentelemetry.javaagent.instrumentation.apachecamel.CamelDirection",
"io.opentelemetry.javaagent.instrumentation.apachecamel.SpanDecorator",
"io.opentelemetry.javaagent.instrumentation.apachecamel.decorators.BaseSpanDecorator",
"io.opentelemetry.javaagent.instrumentation.apachecamel.decorators.DbSpanDecorator",
"io.opentelemetry.javaagent.instrumentation.apachecamel.decorators.MessagingSpanDecorator",
"io.opentelemetry.javaagent.instrumentation.apachecamel.decorators.HttpSpanDecorator",
"io.opentelemetry.javaagent.instrumentation.apachecamel.decorators.InternalSpanDecorator",
"io.opentelemetry.javaagent.instrumentation.apachecamel.decorators.KafkaSpanDecorator",
"io.opentelemetry.javaagent.instrumentation.apachecamel.decorators.LogSpanDecorator",
"io.opentelemetry.javaagent.instrumentation.apachecamel.decorators.RestSpanDecorator",
"io.opentelemetry.javaagent.instrumentation.apachecamel.decorators.TimerSpanDecorator",
"io.opentelemetry.javaagent.instrumentation.apachecamel.decorators.DecoratorRegistry",
"io.opentelemetry.javaagent.instrumentation.apachecamel.ActiveSpanManager",
"io.opentelemetry.javaagent.instrumentation.apachecamel.ActiveSpanManager$SpanWithScope",
"io.opentelemetry.javaagent.instrumentation.apachecamel.CamelPropagationUtil",
"io.opentelemetry.javaagent.instrumentation.apachecamel.CamelPropagationUtil$MapGetter",
"io.opentelemetry.javaagent.instrumentation.apachecamel.CamelPropagationUtil$MapSetter",
"io.opentelemetry.javaagent.instrumentation.apachecamel.CamelTracer",
"io.opentelemetry.javaagent.instrumentation.apachecamel.CamelEventNotifier",
"io.opentelemetry.javaagent.instrumentation.apachecamel.CamelRoutePolicy",
"io.opentelemetry.javaagent.instrumentation.apachecamel.CamelTracingService"
};
}

@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {

return Collections.singletonMap(
named("start").and(isPublic()).and(takesArguments(0)),
CamelContextInstrumentation.class.getName() + "$ContextAdvice");
}

public static class ContextAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onContextStart(@Advice.This final CamelContext context) throws Exception {

if (context.hasService(CamelTracingService.class) == null) {
// start this service eager so we init before Camel is starting up
context.addService(new CamelTracingService(context), true, true);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.apachecamel;

public enum CamelDirection {
INBOUND,
OUTBOUND;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

// Includes work from:
/*
* Apache Camel Opentracing Component
*
* 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
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>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 io.opentelemetry.javaagent.instrumentation.apachecamel;

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.context.Context;
import java.util.EventObject;
import org.apache.camel.management.event.ExchangeSendingEvent;
import org.apache.camel.management.event.ExchangeSentEvent;
import org.apache.camel.support.EventNotifierSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

final class CamelEventNotifier extends EventNotifierSupport {

private static final Logger LOG = LoggerFactory.getLogger(CamelEventNotifier.class);

@Override
public void notify(EventObject event) {

try {
if (event instanceof ExchangeSendingEvent) {
onExchangeSending((ExchangeSendingEvent) event);
} else if (event instanceof ExchangeSentEvent) {
onExchangeSent((ExchangeSentEvent) event);
}
} catch (Throwable t) {
LOG.warn("Failed to capture tracing data", t);
}
}

/** Camel about to send (outbound). */
private void onExchangeSending(ExchangeSendingEvent ese) {
SpanDecorator sd = CamelTracer.TRACER.getSpanDecorator(ese.getEndpoint());
if (!sd.shouldStartNewSpan()) {
return;
}

String name =
sd.getOperationName(ese.getExchange(), ese.getEndpoint(), CamelDirection.OUTBOUND);
Span span = CamelTracer.TRACER.startSpan(name, sd.getInitiatorSpanKind());
sd.pre(span, ese.getExchange(), ese.getEndpoint(), CamelDirection.OUTBOUND);
CamelPropagationUtil.injectParent(Context.current(), ese.getExchange().getIn().getHeaders());
ActiveSpanManager.activate(ese.getExchange(), span);

if (LOG.isTraceEnabled()) {
LOG.trace("[Exchange sending] Initiator span started " + span);
}
}

/** Camel finished sending (outbound). Finish span and remove it from CAMEL holder. */
private void onExchangeSent(ExchangeSentEvent event) {
ExchangeSentEvent ese = event;
SpanDecorator sd = CamelTracer.TRACER.getSpanDecorator(ese.getEndpoint());
if (!sd.shouldStartNewSpan()) {
return;
}

Span span = ActiveSpanManager.getSpan(ese.getExchange());
if (span != null) {
if (LOG.isTraceEnabled()) {
LOG.trace("[Exchange sent] Initiator span finished " + span);
}
sd.post(span, ese.getExchange(), ese.getEndpoint());
ActiveSpanManager.deactivate(ese.getExchange());
} else {
LOG.warn("Could not find managed span for exchange " + ese.getExchange());
}
}

@Override
public boolean isEnabled(EventObject event) {
return event instanceof ExchangeSendingEvent || event instanceof ExchangeSentEvent;
}

@Override
public String toString() {
return "OpenTelemetryCamelEventNotifier";
}
}
Loading

0 comments on commit 049358e

Please sign in to comment.