Skip to content

Commit f777c0e

Browse files
steveraolaurit
andauthored
Add support for MyBatis framework (#10258)
Co-authored-by: Lauri Tulmin <[email protected]>
1 parent 7a044f5 commit f777c0e

File tree

10 files changed

+320
-0
lines changed

10 files changed

+320
-0
lines changed

docs/supported-libraries.md

+1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ These are the supported libraries and frameworks:
9494
| [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 |
9595
| [Micrometer](https://micrometer.io/) | 1.5+ | [opentelemetry-micrometer-1.5](../instrumentation/micrometer/micrometer-1.5/library) | none |
9696
| [MongoDB Driver](https://mongodb.github.io/mongo-java-driver/) | 3.1+ | [opentelemetry-mongo-3.1](../instrumentation/mongo/mongo-3.1/library) | [Database Client Spans] |
97+
| [MyBatis](https://mybatis.org/mybatis-3/) | 3.2+ | N/A | none |
9798
| [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] |
9899
| [OkHttp](https://github.com/square/okhttp/) | 2.2+ | [opentelemetry-okhttp-3.0](../instrumentation/okhttp/okhttp-3.0/library) | [HTTP Client Spans], [HTTP Client Metrics] |
99100
| [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] |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
plugins {
2+
id("otel.javaagent-instrumentation")
3+
}
4+
5+
muzzle {
6+
pass {
7+
group.set("org.mybatis")
8+
module.set("mybatis")
9+
versions.set("[3.2.0,)")
10+
assertInverse.set(true)
11+
}
12+
}
13+
14+
dependencies {
15+
library("org.mybatis:mybatis:3.2.0")
16+
17+
testImplementation("com.h2database:h2:1.4.191")
18+
}
19+
20+
tasks.withType<Test>().configureEach {
21+
jvmArgs("-Dotel.instrumentation.mybatis.enabled=true")
22+
23+
// required on jdk17
24+
jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED")
25+
jvmArgs("-XX:+IgnoreUnrecognizedVMOptions")
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.mybatis.v3_2;
7+
8+
import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
9+
import static io.opentelemetry.javaagent.instrumentation.mybatis.v3_2.MyBatisSingletons.instrumenter;
10+
import static net.bytebuddy.matcher.ElementMatchers.named;
11+
12+
import io.opentelemetry.context.Context;
13+
import io.opentelemetry.context.Scope;
14+
import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod;
15+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
16+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
17+
import net.bytebuddy.asm.Advice;
18+
import net.bytebuddy.description.type.TypeDescription;
19+
import net.bytebuddy.matcher.ElementMatcher;
20+
import org.apache.ibatis.binding.MapperMethod.SqlCommand;
21+
22+
public class MapperMethodInstrumentation implements TypeInstrumentation {
23+
24+
@Override
25+
public ElementMatcher<TypeDescription> typeMatcher() {
26+
return named("org.apache.ibatis.binding.MapperMethod");
27+
}
28+
29+
@Override
30+
public void transform(TypeTransformer transformer) {
31+
transformer.applyAdviceToMethod(
32+
named("execute"), MapperMethodInstrumentation.class.getName() + "$ExecuteAdvice");
33+
}
34+
35+
@SuppressWarnings("unused")
36+
public static class ExecuteAdvice {
37+
38+
@Advice.OnMethodEnter(suppress = Throwable.class)
39+
public static void getMapperInfo(
40+
@Advice.FieldValue("command") SqlCommand command,
41+
@Advice.Local("otelRequest") ClassAndMethod request,
42+
@Advice.Local("otelContext") Context context,
43+
@Advice.Local("otelScope") Scope scope) {
44+
if (command == null) {
45+
return;
46+
}
47+
request = SqlCommandUtil.getClassAndMethod(command);
48+
if (request == null) {
49+
return;
50+
}
51+
Context parentContext = currentContext();
52+
if (!instrumenter().shouldStart(parentContext, request)) {
53+
return;
54+
}
55+
context = instrumenter().start(parentContext, request);
56+
scope = context.makeCurrent();
57+
}
58+
59+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
60+
public static void stopSpan(
61+
@Advice.Thrown Throwable throwable,
62+
@Advice.Local("otelRequest") ClassAndMethod request,
63+
@Advice.Local("otelContext") Context context,
64+
@Advice.Local("otelScope") Scope scope) {
65+
if (scope != null) {
66+
scope.close();
67+
instrumenter().end(context, request, null, throwable);
68+
}
69+
}
70+
}
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.mybatis.v3_2;
7+
8+
import static java.util.Arrays.asList;
9+
10+
import com.google.auto.service.AutoService;
11+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
12+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
13+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
14+
import java.util.List;
15+
16+
@AutoService(InstrumentationModule.class)
17+
public class MyBatisInstrumentationModule extends InstrumentationModule {
18+
19+
public MyBatisInstrumentationModule() {
20+
super("mybatis", "mybatis-3.2");
21+
}
22+
23+
@Override
24+
public List<TypeInstrumentation> typeInstrumentations() {
25+
return asList(new MapperMethodInstrumentation(), new SqlCommandInstrumentation());
26+
}
27+
28+
@Override
29+
public boolean defaultEnabled(ConfigProperties config) {
30+
return false;
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.mybatis.v3_2;
7+
8+
import io.opentelemetry.api.GlobalOpenTelemetry;
9+
import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesExtractor;
10+
import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesGetter;
11+
import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeSpanNameExtractor;
12+
import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod;
13+
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
14+
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
15+
16+
public final class MyBatisSingletons {
17+
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.mybatis-3.2";
18+
private static final Instrumenter<ClassAndMethod, Void> INSTRUMENTER;
19+
20+
static {
21+
CodeAttributesGetter<ClassAndMethod> codeAttributesGetter =
22+
ClassAndMethod.codeAttributesGetter();
23+
24+
INSTRUMENTER =
25+
Instrumenter.<ClassAndMethod, Void>builder(
26+
GlobalOpenTelemetry.get(),
27+
INSTRUMENTATION_NAME,
28+
CodeSpanNameExtractor.create(codeAttributesGetter))
29+
.addAttributesExtractor(CodeAttributesExtractor.create(codeAttributesGetter))
30+
.buildInstrumenter(SpanKindExtractor.alwaysInternal());
31+
}
32+
33+
public static Instrumenter<ClassAndMethod, Void> instrumenter() {
34+
return INSTRUMENTER;
35+
}
36+
37+
private MyBatisSingletons() {}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.mybatis.v3_2;
7+
8+
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
9+
import static net.bytebuddy.matcher.ElementMatchers.named;
10+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
11+
12+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
13+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
14+
import java.lang.reflect.Method;
15+
import net.bytebuddy.asm.Advice;
16+
import net.bytebuddy.description.type.TypeDescription;
17+
import net.bytebuddy.matcher.ElementMatcher;
18+
import org.apache.ibatis.binding.MapperMethod.SqlCommand;
19+
20+
public class SqlCommandInstrumentation implements TypeInstrumentation {
21+
@Override
22+
public ElementMatcher<TypeDescription> typeMatcher() {
23+
return named("org.apache.ibatis.binding.MapperMethod$SqlCommand");
24+
}
25+
26+
@Override
27+
public void transform(TypeTransformer transformer) {
28+
transformer.applyAdviceToMethod(
29+
isConstructor().and(takesArgument(1, Class.class)).and(takesArgument(2, Method.class)),
30+
SqlCommandInstrumentation.class.getName() + "$ConstructorAdvice");
31+
}
32+
33+
@SuppressWarnings("unused")
34+
public static class ConstructorAdvice {
35+
36+
@Advice.OnMethodExit(suppress = Throwable.class)
37+
public static void onExit(
38+
@Advice.This SqlCommand command,
39+
@Advice.Argument(1) Class<?> mapperInterface,
40+
@Advice.Argument(2) Method method) {
41+
SqlCommandUtil.setClassAndMethod(command, mapperInterface, method);
42+
}
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.mybatis.v3_2;
7+
8+
import io.opentelemetry.instrumentation.api.incubator.semconv.util.ClassAndMethod;
9+
import io.opentelemetry.instrumentation.api.util.VirtualField;
10+
import java.lang.reflect.Method;
11+
import org.apache.ibatis.binding.MapperMethod.SqlCommand;
12+
13+
public final class SqlCommandUtil {
14+
private static final VirtualField<SqlCommand, ClassAndMethod> field =
15+
VirtualField.find(SqlCommand.class, ClassAndMethod.class);
16+
17+
public static void setClassAndMethod(SqlCommand command, Class<?> clazz, Method method) {
18+
if (clazz == null || method == null || method.getName() == null) {
19+
return;
20+
}
21+
field.set(command, ClassAndMethod.create(clazz, method.getName()));
22+
}
23+
24+
public static ClassAndMethod getClassAndMethod(SqlCommand command) {
25+
return field.get(command);
26+
}
27+
28+
private SqlCommandUtil() {}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.mybatis.v3_2;
7+
8+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
9+
10+
import io.opentelemetry.api.trace.SpanKind;
11+
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
12+
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
13+
import io.opentelemetry.semconv.SemanticAttributes;
14+
import org.apache.ibatis.mapping.Environment;
15+
import org.apache.ibatis.session.Configuration;
16+
import org.apache.ibatis.session.SqlSession;
17+
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
18+
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
19+
import org.h2.jdbcx.JdbcDataSource;
20+
import org.junit.jupiter.api.AfterAll;
21+
import org.junit.jupiter.api.BeforeAll;
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.api.extension.RegisterExtension;
24+
25+
class MyBatisTest {
26+
27+
@RegisterExtension
28+
private static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
29+
30+
private static SqlSession sqlSession;
31+
32+
@BeforeAll
33+
static void setUp() {
34+
JdbcDataSource dataSource = new JdbcDataSource();
35+
dataSource.setURL("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
36+
Configuration configuration = new Configuration();
37+
configuration.setEnvironment(new Environment("test", new JdbcTransactionFactory(), dataSource));
38+
configuration.addMapper(TestMapper.class);
39+
sqlSession = new SqlSessionFactoryBuilder().build(configuration).openSession();
40+
}
41+
42+
@AfterAll
43+
static void cleanUp() {
44+
if (sqlSession != null) {
45+
sqlSession.close();
46+
}
47+
}
48+
49+
@Test
50+
void testSelect() {
51+
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
52+
testMapper.select();
53+
54+
testing.waitAndAssertTraces(
55+
trace ->
56+
trace.hasSpansSatisfyingExactly(
57+
span ->
58+
span.hasKind(SpanKind.INTERNAL)
59+
.hasName("TestMapper.select")
60+
.hasAttributesSatisfyingExactly(
61+
equalTo(SemanticAttributes.CODE_NAMESPACE, TestMapper.class.getName()),
62+
equalTo(SemanticAttributes.CODE_FUNCTION, "select"))));
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.mybatis.v3_2;
7+
8+
import org.apache.ibatis.annotations.Select;
9+
10+
public interface TestMapper {
11+
12+
@Select("SELECT 1")
13+
int select();
14+
}

settings.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,7 @@ include(":instrumentation:mongo:mongo-3.7:javaagent")
385385
include(":instrumentation:mongo:mongo-4.0:javaagent")
386386
include(":instrumentation:mongo:mongo-async-3.3:javaagent")
387387
include(":instrumentation:mongo:mongo-common:testing")
388+
include(":instrumentation:mybatis-3.2:javaagent")
388389
include(":instrumentation:netty:netty-3.8:javaagent")
389390
include(":instrumentation:netty:netty-4.0:javaagent")
390391
include(":instrumentation:netty:netty-4.1:javaagent")

0 commit comments

Comments
 (0)