Skip to content

Commit

Permalink
Add support for MyBatis framework (#10258)
Browse files Browse the repository at this point in the history
Co-authored-by: Lauri Tulmin <tulmin@gmail.com>
  • Loading branch information
steverao and laurit committed Feb 12, 2024
1 parent 7a044f5 commit f777c0e
Show file tree
Hide file tree
Showing 10 changed files with 320 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/supported-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ These are the supported libraries and frameworks:
| [Logback](http://logback.qos.ch/) | 1.0+ | [opentelemetry-logback-appender-1.0](../instrumentation/logback/logback-appender-1.0/library),<br>[opentelemetry-logback-mdc-1.0](../instrumentation/logback/logback-mdc-1.0/library) | none |
| [Micrometer](https://micrometer.io/) | 1.5+ | [opentelemetry-micrometer-1.5](../instrumentation/micrometer/micrometer-1.5/library) | none |
| [MongoDB Driver](https://mongodb.github.io/mongo-java-driver/) | 3.1+ | [opentelemetry-mongo-3.1](../instrumentation/mongo/mongo-3.1/library) | [Database Client Spans] |
| [MyBatis](https://mybatis.org/mybatis-3/) | 3.2+ | N/A | none |
| [Netty](https://github.com/netty/netty) | 3.8+ | [opentelemetry-netty-4.1](../instrumentation/netty/netty-4.1/library) | [HTTP Client Spans], [HTTP Client Metrics], [HTTP Server Spans], [HTTP Server Metrics] |
| [OkHttp](https://github.com/square/okhttp/) | 2.2+ | [opentelemetry-okhttp-3.0](../instrumentation/okhttp/okhttp-3.0/library) | [HTTP Client Spans], [HTTP Client Metrics] |
| [Oracle UCP](https://docs.oracle.com/database/121/JJUCP/) | 11.2+ | [opentelemetry-oracle-ucp-11.2](../instrumentation/oracle-ucp-11.2/library) | [Database Pool Metrics] |
Expand Down
26 changes: 26 additions & 0 deletions instrumentation/mybatis-3.2/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("org.mybatis")
module.set("mybatis")
versions.set("[3.2.0,)")
assertInverse.set(true)
}
}

dependencies {
library("org.mybatis:mybatis:3.2.0")

testImplementation("com.h2database:h2:1.4.191")
}

tasks.withType<Test>().configureEach {
jvmArgs("-Dotel.instrumentation.mybatis.enabled=true")

// required on jdk17
jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED")
jvmArgs("-XX:+IgnoreUnrecognizedVMOptions")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.mybatis.v3_2;

import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
import static io.opentelemetry.javaagent.instrumentation.mybatis.v3_2.MyBatisSingletons.instrumenter;
import static net.bytebuddy.matcher.ElementMatchers.named;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.apache.ibatis.binding.MapperMethod.SqlCommand;

public class MapperMethodInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("org.apache.ibatis.binding.MapperMethod");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
named("execute"), MapperMethodInstrumentation.class.getName() + "$ExecuteAdvice");
}

@SuppressWarnings("unused")
public static class ExecuteAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void getMapperInfo(
@Advice.FieldValue("command") SqlCommand command,
@Advice.Local("otelRequest") ClassAndMethod request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
if (command == null) {
return;
}
request = SqlCommandUtil.getClassAndMethod(command);
if (request == null) {
return;
}
Context parentContext = currentContext();
if (!instrumenter().shouldStart(parentContext, request)) {
return;
}
context = instrumenter().start(parentContext, request);
scope = context.makeCurrent();
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Thrown Throwable throwable,
@Advice.Local("otelRequest") ClassAndMethod request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
if (scope != null) {
scope.close();
instrumenter().end(context, request, null, throwable);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.mybatis.v3_2;

import static java.util.Arrays.asList;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import java.util.List;

@AutoService(InstrumentationModule.class)
public class MyBatisInstrumentationModule extends InstrumentationModule {

public MyBatisInstrumentationModule() {
super("mybatis", "mybatis-3.2");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return asList(new MapperMethodInstrumentation(), new SqlCommandInstrumentation());
}

@Override
public boolean defaultEnabled(ConfigProperties config) {
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.mybatis.v3_2;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesExtractor;
import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesGetter;
import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeSpanNameExtractor;
import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;

public final class MyBatisSingletons {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.mybatis-3.2";
private static final Instrumenter<ClassAndMethod, Void> INSTRUMENTER;

static {
CodeAttributesGetter<ClassAndMethod> codeAttributesGetter =
ClassAndMethod.codeAttributesGetter();

INSTRUMENTER =
Instrumenter.<ClassAndMethod, Void>builder(
GlobalOpenTelemetry.get(),
INSTRUMENTATION_NAME,
CodeSpanNameExtractor.create(codeAttributesGetter))
.addAttributesExtractor(CodeAttributesExtractor.create(codeAttributesGetter))
.buildInstrumenter(SpanKindExtractor.alwaysInternal());
}

public static Instrumenter<ClassAndMethod, Void> instrumenter() {
return INSTRUMENTER;
}

private MyBatisSingletons() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.mybatis.v3_2;

import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;

import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import java.lang.reflect.Method;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.apache.ibatis.binding.MapperMethod.SqlCommand;

public class SqlCommandInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("org.apache.ibatis.binding.MapperMethod$SqlCommand");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isConstructor().and(takesArgument(1, Class.class)).and(takesArgument(2, Method.class)),
SqlCommandInstrumentation.class.getName() + "$ConstructorAdvice");
}

@SuppressWarnings("unused")
public static class ConstructorAdvice {

@Advice.OnMethodExit(suppress = Throwable.class)
public static void onExit(
@Advice.This SqlCommand command,
@Advice.Argument(1) Class<?> mapperInterface,
@Advice.Argument(2) Method method) {
SqlCommandUtil.setClassAndMethod(command, mapperInterface, method);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.mybatis.v3_2;

import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod;
import io.opentelemetry.instrumentation.api.util.VirtualField;
import java.lang.reflect.Method;
import org.apache.ibatis.binding.MapperMethod.SqlCommand;

public final class SqlCommandUtil {
private static final VirtualField<SqlCommand, ClassAndMethod> field =
VirtualField.find(SqlCommand.class, ClassAndMethod.class);

public static void setClassAndMethod(SqlCommand command, Class<?> clazz, Method method) {
if (clazz == null || method == null || method.getName() == null) {
return;
}
field.set(command, ClassAndMethod.create(clazz, method.getName()));
}

public static ClassAndMethod getClassAndMethod(SqlCommand command) {
return field.get(command);
}

private SqlCommandUtil() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.mybatis.v3_2;

import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;

import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.semconv.SemanticAttributes;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.h2.jdbcx.JdbcDataSource;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

class MyBatisTest {

@RegisterExtension
private static final InstrumentationExtension testing = AgentInstrumentationExtension.create();

private static SqlSession sqlSession;

@BeforeAll
static void setUp() {
JdbcDataSource dataSource = new JdbcDataSource();
dataSource.setURL("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
Configuration configuration = new Configuration();
configuration.setEnvironment(new Environment("test", new JdbcTransactionFactory(), dataSource));
configuration.addMapper(TestMapper.class);
sqlSession = new SqlSessionFactoryBuilder().build(configuration).openSession();
}

@AfterAll
static void cleanUp() {
if (sqlSession != null) {
sqlSession.close();
}
}

@Test
void testSelect() {
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
testMapper.select();

testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasKind(SpanKind.INTERNAL)
.hasName("TestMapper.select")
.hasAttributesSatisfyingExactly(
equalTo(SemanticAttributes.CODE_NAMESPACE, TestMapper.class.getName()),
equalTo(SemanticAttributes.CODE_FUNCTION, "select"))));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.mybatis.v3_2;

import org.apache.ibatis.annotations.Select;

public interface TestMapper {

@Select("SELECT 1")
int select();
}
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ include(":instrumentation:mongo:mongo-3.7:javaagent")
include(":instrumentation:mongo:mongo-4.0:javaagent")
include(":instrumentation:mongo:mongo-async-3.3:javaagent")
include(":instrumentation:mongo:mongo-common:testing")
include(":instrumentation:mybatis-3.2:javaagent")
include(":instrumentation:netty:netty-3.8:javaagent")
include(":instrumentation:netty:netty-4.0:javaagent")
include(":instrumentation:netty:netty-4.1:javaagent")
Expand Down

0 comments on commit f777c0e

Please sign in to comment.