From d847bfcad5d690ea85e9e0e36369a9198afd8649 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Tue, 14 Feb 2023 00:39:27 +0200 Subject: [PATCH] Add instrumentation for hibernate 6 (#7773) --- .../hibernate/v3_3/QueryInstrumentation.java | 4 +- .../v3_3/SessionInstrumentation.java | 2 + .../hibernate/v4_0/QueryInstrumentation.java | 4 +- .../v4_0/SessionFactoryInstrumentation.java | 3 +- .../v4_0/SessionInstrumentation.java | 4 +- .../hibernate-6.0/javaagent/build.gradle.kts | 41 ++ .../hibernate/v6_0/EntityNameUtil.java | 34 + .../hibernate/v6_0/Hibernate6Singletons.java | 22 + .../v6_0/HibernateInstrumentationModule.java | 39 ++ .../hibernate/v6_0/QueryInstrumentation.java | 124 ++++ .../v6_0/SessionFactoryInstrumentation.java | 58 ++ .../v6_0/SessionInstrumentation.java | 195 ++++++ .../v6_0/TransactionInstrumentation.java | 96 +++ .../test/groovy/AbstractHibernateTest.groovy | 39 ++ .../src/test/groovy/CriteriaTest.groovy | 87 +++ .../src/test/groovy/EntityManagerTest.groovy | 233 +++++++ .../src/test/groovy/ProcedureCallTest.groovy | 175 +++++ .../src/test/groovy/SessionTest.groovy | 603 ++++++++++++++++++ .../javaagent/src/test/java/Value.java | 45 ++ .../test/resources/META-INF/persistence.xml | 18 + .../src/test/resources/hibernate.cfg.xml | 29 + .../procedure-call-hibernate.cfg.xml | 24 + .../spring-testing/build.gradle.kts | 26 + .../src/test/groovy/SpringJpaTest.groovy | 385 +++++++++++ .../src/test/java/spring/jpa/Customer.java | 79 +++ .../java/spring/jpa/CustomerRepository.java | 14 + .../java/spring/jpa/PersistenceConfig.java | 62 ++ .../src/test/groovy/ProcedureCallTest.groovy | 16 +- .../src/test/resources/hibernate.cfg.xml | 14 - settings.gradle.kts | 2 + 30 files changed, 2442 insertions(+), 35 deletions(-) create mode 100644 instrumentation/hibernate/hibernate-6.0/javaagent/build.gradle.kts create mode 100644 instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/EntityNameUtil.java create mode 100644 instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/Hibernate6Singletons.java create mode 100644 instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/HibernateInstrumentationModule.java create mode 100644 instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/QueryInstrumentation.java create mode 100644 instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/SessionFactoryInstrumentation.java create mode 100644 instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/SessionInstrumentation.java create mode 100644 instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/TransactionInstrumentation.java create mode 100644 instrumentation/hibernate/hibernate-6.0/javaagent/src/test/groovy/AbstractHibernateTest.groovy create mode 100644 instrumentation/hibernate/hibernate-6.0/javaagent/src/test/groovy/CriteriaTest.groovy create mode 100644 instrumentation/hibernate/hibernate-6.0/javaagent/src/test/groovy/EntityManagerTest.groovy create mode 100644 instrumentation/hibernate/hibernate-6.0/javaagent/src/test/groovy/ProcedureCallTest.groovy create mode 100644 instrumentation/hibernate/hibernate-6.0/javaagent/src/test/groovy/SessionTest.groovy create mode 100644 instrumentation/hibernate/hibernate-6.0/javaagent/src/test/java/Value.java create mode 100644 instrumentation/hibernate/hibernate-6.0/javaagent/src/test/resources/META-INF/persistence.xml create mode 100644 instrumentation/hibernate/hibernate-6.0/javaagent/src/test/resources/hibernate.cfg.xml create mode 100644 instrumentation/hibernate/hibernate-6.0/javaagent/src/test/resources/procedure-call-hibernate.cfg.xml create mode 100644 instrumentation/hibernate/hibernate-6.0/spring-testing/build.gradle.kts create mode 100644 instrumentation/hibernate/hibernate-6.0/spring-testing/src/test/groovy/SpringJpaTest.groovy create mode 100644 instrumentation/hibernate/hibernate-6.0/spring-testing/src/test/java/spring/jpa/Customer.java create mode 100644 instrumentation/hibernate/hibernate-6.0/spring-testing/src/test/java/spring/jpa/CustomerRepository.java create mode 100644 instrumentation/hibernate/hibernate-6.0/spring-testing/src/test/java/spring/jpa/PersistenceConfig.java diff --git a/instrumentation/hibernate/hibernate-3.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v3_3/QueryInstrumentation.java b/instrumentation/hibernate/hibernate-3.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v3_3/QueryInstrumentation.java index 288d5b55c6..5a9742dcbc 100644 --- a/instrumentation/hibernate/hibernate-3.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v3_3/QueryInstrumentation.java +++ b/instrumentation/hibernate/hibernate-3.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v3_3/QueryInstrumentation.java @@ -62,9 +62,9 @@ public class QueryInstrumentation implements TypeInstrumentation { return; } - VirtualField criteriaVirtualField = + VirtualField queryVirtualField = VirtualField.find(Query.class, SessionInfo.class); - SessionInfo sessionInfo = criteriaVirtualField.get(query); + SessionInfo sessionInfo = queryVirtualField.get(query); Context parentContext = Java8BytecodeBridge.currentContext(); hibernateOperation = diff --git a/instrumentation/hibernate/hibernate-3.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v3_3/SessionInstrumentation.java b/instrumentation/hibernate/hibernate-3.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v3_3/SessionInstrumentation.java index ca3795c48e..8cdefda3d5 100644 --- a/instrumentation/hibernate/hibernate-3.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v3_3/SessionInstrumentation.java +++ b/instrumentation/hibernate/hibernate-3.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v3_3/SessionInstrumentation.java @@ -10,6 +10,7 @@ import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers. import static io.opentelemetry.javaagent.instrumentation.hibernate.OperationNameUtil.getEntityName; import static io.opentelemetry.javaagent.instrumentation.hibernate.OperationNameUtil.getSessionMethodOperationName; import static io.opentelemetry.javaagent.instrumentation.hibernate.v3_3.Hibernate3Singletons.instrumenter; +import static net.bytebuddy.matcher.ElementMatchers.any; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; @@ -50,6 +51,7 @@ public class SessionInstrumentation implements TypeInstrumentation { // Session synchronous methods we want to instrument. transformer.applyAdviceToMethod( isMethod() + .and(takesArgument(0, any())) .and( namedOneOf( "save", diff --git a/instrumentation/hibernate/hibernate-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v4_0/QueryInstrumentation.java b/instrumentation/hibernate/hibernate-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v4_0/QueryInstrumentation.java index ebd79176a0..af0618e6c7 100644 --- a/instrumentation/hibernate/hibernate-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v4_0/QueryInstrumentation.java +++ b/instrumentation/hibernate/hibernate-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v4_0/QueryInstrumentation.java @@ -62,9 +62,9 @@ public class QueryInstrumentation implements TypeInstrumentation { return; } - VirtualField criteriaVirtualField = + VirtualField queryVirtualField = VirtualField.find(Query.class, SessionInfo.class); - SessionInfo sessionInfo = criteriaVirtualField.get(query); + SessionInfo sessionInfo = queryVirtualField.get(query); Context parentContext = Java8BytecodeBridge.currentContext(); hibernateOperation = diff --git a/instrumentation/hibernate/hibernate-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v4_0/SessionFactoryInstrumentation.java b/instrumentation/hibernate/hibernate-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v4_0/SessionFactoryInstrumentation.java index e48a41ebf7..5f82adf80b 100644 --- a/instrumentation/hibernate/hibernate-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v4_0/SessionFactoryInstrumentation.java +++ b/instrumentation/hibernate/hibernate-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v4_0/SessionFactoryInstrumentation.java @@ -8,7 +8,6 @@ package io.opentelemetry.javaagent.instrumentation.hibernate.v4_0; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; import static net.bytebuddy.matcher.ElementMatchers.isMethod; -import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; import static net.bytebuddy.matcher.ElementMatchers.returns; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; @@ -32,7 +31,7 @@ public class SessionFactoryInstrumentation implements TypeInstrumentation { @Override public ElementMatcher typeMatcher() { return implementsInterface( - named("org.hibernate.SessionFactory").or(named("org.hibernate.SessionBuilder"))); + namedOneOf("org.hibernate.SessionFactory", "org.hibernate.SessionBuilder")); } @Override diff --git a/instrumentation/hibernate/hibernate-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v4_0/SessionInstrumentation.java b/instrumentation/hibernate/hibernate-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v4_0/SessionInstrumentation.java index 6d00d30138..e5e9fbc511 100644 --- a/instrumentation/hibernate/hibernate-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v4_0/SessionInstrumentation.java +++ b/instrumentation/hibernate/hibernate-4.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v4_0/SessionInstrumentation.java @@ -10,6 +10,7 @@ import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers. import static io.opentelemetry.javaagent.instrumentation.hibernate.OperationNameUtil.getEntityName; import static io.opentelemetry.javaagent.instrumentation.hibernate.OperationNameUtil.getSessionMethodOperationName; import static io.opentelemetry.javaagent.instrumentation.hibernate.v4_0.Hibernate4Singletons.instrumenter; +import static net.bytebuddy.matcher.ElementMatchers.any; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; @@ -51,6 +52,7 @@ public class SessionInstrumentation implements TypeInstrumentation { // Session synchronous methods we want to instrument. transformer.applyAdviceToMethod( isMethod() + .and(takesArgument(0, any())) .and( namedOneOf( "save", @@ -68,7 +70,7 @@ public class SessionInstrumentation implements TypeInstrumentation { // Handle the non-generic 'get' separately. transformer.applyAdviceToMethod( isMethod() - .and(named("get").or(named("find"))) + .and(namedOneOf("get", "find")) .and(returns(Object.class)) .and(takesArgument(0, String.class).or(takesArgument(0, Class.class))), SessionInstrumentation.class.getName() + "$SessionMethodAdvice"); diff --git a/instrumentation/hibernate/hibernate-6.0/javaagent/build.gradle.kts b/instrumentation/hibernate/hibernate-6.0/javaagent/build.gradle.kts new file mode 100644 index 0000000000..0b7a9c5d33 --- /dev/null +++ b/instrumentation/hibernate/hibernate-6.0/javaagent/build.gradle.kts @@ -0,0 +1,41 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +muzzle { + pass { + group.set("org.hibernate") + module.set("hibernate-core") + versions.set("[6.0.0.Final,)") + assertInverse.set(true) + } +} + +dependencies { + library("org.hibernate:hibernate-core:6.0.0.Final") + + implementation(project(":instrumentation:hibernate:hibernate-common:javaagent")) + + testInstrumentation(project(":instrumentation:jdbc:javaagent")) + // Added to ensure cross compatibility: + testInstrumentation(project(":instrumentation:hibernate:hibernate-3.3:javaagent")) + testInstrumentation(project(":instrumentation:hibernate:hibernate-4.0:javaagent")) + testInstrumentation(project(":instrumentation:hibernate:hibernate-procedure-call-4.3:javaagent")) + + testImplementation("com.h2database:h2:1.4.197") + testImplementation("javax.xml.bind:jaxb-api:2.2.11") + testImplementation("com.sun.xml.bind:jaxb-core:2.2.11") + testImplementation("com.sun.xml.bind:jaxb-impl:2.2.11") + testImplementation("javax.activation:activation:1.1.1") + testImplementation("org.hsqldb:hsqldb:2.0.0") + testImplementation("org.springframework.data:spring-data-jpa:3.0.0") +} + +otelJava { + minJavaVersionSupported.set(JavaVersion.VERSION_11) +} + +tasks.withType().configureEach { + // TODO run tests both with and without experimental span attributes + jvmArgs("-Dotel.instrumentation.hibernate.experimental-span-attributes=true") +} diff --git a/instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/EntityNameUtil.java b/instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/EntityNameUtil.java new file mode 100644 index 0000000000..998234b482 --- /dev/null +++ b/instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/EntityNameUtil.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.hibernate.v6_0; + +import java.util.function.Function; +import org.hibernate.SharedSessionContract; +import org.hibernate.internal.SessionImpl; +import org.hibernate.internal.StatelessSessionImpl; + +public final class EntityNameUtil { + + private EntityNameUtil() {} + + private static String bestGuessEntityName(SharedSessionContract session, Object entity) { + if (entity == null) { + return null; + } + + if (session instanceof SessionImpl) { + return ((SessionImpl) session).bestGuessEntityName(entity); + } else if (session instanceof StatelessSessionImpl) { + return ((StatelessSessionImpl) session).bestGuessEntityName(entity); + } + + return null; + } + + public static Function bestGuessEntityName(SharedSessionContract session) { + return (entity) -> bestGuessEntityName(session, entity); + } +} diff --git a/instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/Hibernate6Singletons.java b/instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/Hibernate6Singletons.java new file mode 100644 index 0000000000..48a3d07023 --- /dev/null +++ b/instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/Hibernate6Singletons.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.hibernate.v6_0; + +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.javaagent.instrumentation.hibernate.HibernateInstrumenterFactory; +import io.opentelemetry.javaagent.instrumentation.hibernate.HibernateOperation; + +public class Hibernate6Singletons { + + private static final Instrumenter INSTANCE = + HibernateInstrumenterFactory.createInstrumenter("io.opentelemetry.hibernate-6.0"); + + public static Instrumenter instrumenter() { + return INSTANCE; + } + + private Hibernate6Singletons() {} +} diff --git a/instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/HibernateInstrumentationModule.java b/instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/HibernateInstrumentationModule.java new file mode 100644 index 0000000000..3758ac451c --- /dev/null +++ b/instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/HibernateInstrumentationModule.java @@ -0,0 +1,39 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.hibernate.v6_0; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +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 java.util.List; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(InstrumentationModule.class) +public class HibernateInstrumentationModule extends InstrumentationModule { + + public HibernateInstrumentationModule() { + super("hibernate", "hibernate-6.0"); + } + + @Override + public ElementMatcher.Junction classLoaderMatcher() { + return hasClassesNamed( + // not present before 6.0 + "org.hibernate.query.Query"); + } + + @Override + public List typeInstrumentations() { + return asList( + new QueryInstrumentation(), + new SessionFactoryInstrumentation(), + new SessionInstrumentation(), + new TransactionInstrumentation()); + } +} diff --git a/instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/QueryInstrumentation.java b/instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/QueryInstrumentation.java new file mode 100644 index 0000000000..141175c302 --- /dev/null +++ b/instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/QueryInstrumentation.java @@ -0,0 +1,124 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.hibernate.v6_0; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; +import static io.opentelemetry.javaagent.instrumentation.hibernate.OperationNameUtil.getOperationNameForQuery; +import static io.opentelemetry.javaagent.instrumentation.hibernate.v6_0.Hibernate6Singletons.instrumenter; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.util.VirtualField; +import io.opentelemetry.javaagent.bootstrap.CallDepth; +import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.opentelemetry.javaagent.instrumentation.hibernate.HibernateOperation; +import io.opentelemetry.javaagent.instrumentation.hibernate.SessionInfo; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.hibernate.query.CommonQueryContract; +import org.hibernate.query.Query; +import org.hibernate.query.spi.SqmQuery; + +public class QueryInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed("org.hibernate.query.CommonQueryContract"); + } + + @Override + public ElementMatcher typeMatcher() { + return implementsInterface(named("org.hibernate.query.CommonQueryContract")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod() + .and( + namedOneOf( + "list", + "getResultList", + "stream", + "getResultStream", + "uniqueResult", + "getSingleResult", + "getSingleResultOrNull", + "uniqueResultOptional", + "executeUpdate", + "scroll")), + QueryInstrumentation.class.getName() + "$QueryMethodAdvice"); + } + + @SuppressWarnings("unused") + public static class QueryMethodAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void startMethod( + @Advice.This CommonQueryContract query, + @Advice.Local("otelCallDepth") CallDepth callDepth, + @Advice.Local("otelHibernateOperation") HibernateOperation hibernateOperation, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + + callDepth = CallDepth.forClass(HibernateOperation.class); + if (callDepth.getAndIncrement() > 0) { + return; + } + + String queryString = null; + if (query instanceof Query) { + queryString = ((Query) query).getQueryString(); + } + if (query instanceof SqmQuery) { + try { + queryString = ((SqmQuery) query).getSqmStatement().toHqlString(); + } catch (RuntimeException exception) { + // ignore + } + } + + VirtualField queryVirtualField = + VirtualField.find(CommonQueryContract.class, SessionInfo.class); + SessionInfo sessionInfo = queryVirtualField.get(query); + + Context parentContext = Java8BytecodeBridge.currentContext(); + hibernateOperation = + new HibernateOperation(getOperationNameForQuery(queryString), sessionInfo); + if (!instrumenter().shouldStart(parentContext, hibernateOperation)) { + return; + } + + context = instrumenter().start(parentContext, hibernateOperation); + scope = context.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void endMethod( + @Advice.Thrown Throwable throwable, + @Advice.Local("otelCallDepth") CallDepth callDepth, + @Advice.Local("otelHibernateOperation") HibernateOperation hibernateOperation, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + + if (callDepth.decrementAndGet() > 0) { + return; + } + + if (scope != null) { + scope.close(); + instrumenter().end(context, hibernateOperation, null, throwable); + } + } + } +} diff --git a/instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/SessionFactoryInstrumentation.java b/instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/SessionFactoryInstrumentation.java new file mode 100644 index 0000000000..116c926baa --- /dev/null +++ b/instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/SessionFactoryInstrumentation.java @@ -0,0 +1,58 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.hibernate.v6_0; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import io.opentelemetry.instrumentation.api.util.VirtualField; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.opentelemetry.javaagent.instrumentation.hibernate.SessionInfo; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.hibernate.SharedSessionContract; + +public class SessionFactoryInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed("org.hibernate.SessionFactory", "org.hibernate.SessionBuilder"); + } + + @Override + public ElementMatcher typeMatcher() { + return implementsInterface( + namedOneOf("org.hibernate.SessionFactory", "org.hibernate.SessionBuilder")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod() + .and(namedOneOf("openSession", "openStatelessSession")) + .and(takesArguments(0)) + .and(returns(namedOneOf("org.hibernate.Session", "org.hibernate.StatelessSession"))), + SessionFactoryInstrumentation.class.getName() + "$SessionFactoryAdvice"); + } + + @SuppressWarnings("unused") + public static class SessionFactoryAdvice { + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void openSession(@Advice.Return SharedSessionContract session) { + + VirtualField virtualField = + VirtualField.find(SharedSessionContract.class, SessionInfo.class); + virtualField.set(session, new SessionInfo()); + } + } +} diff --git a/instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/SessionInstrumentation.java b/instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/SessionInstrumentation.java new file mode 100644 index 0000000000..46bafd78fb --- /dev/null +++ b/instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/SessionInstrumentation.java @@ -0,0 +1,195 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.hibernate.v6_0; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; +import static io.opentelemetry.javaagent.instrumentation.hibernate.OperationNameUtil.getEntityName; +import static io.opentelemetry.javaagent.instrumentation.hibernate.OperationNameUtil.getSessionMethodOperationName; +import static io.opentelemetry.javaagent.instrumentation.hibernate.v6_0.Hibernate6Singletons.instrumenter; +import static net.bytebuddy.matcher.ElementMatchers.any; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.util.VirtualField; +import io.opentelemetry.javaagent.bootstrap.CallDepth; +import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.opentelemetry.javaagent.instrumentation.hibernate.HibernateOperation; +import io.opentelemetry.javaagent.instrumentation.hibernate.SessionInfo; +import jakarta.persistence.criteria.CriteriaQuery; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.hibernate.SharedSessionContract; +import org.hibernate.Transaction; +import org.hibernate.query.CommonQueryContract; + +public class SessionInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed("org.hibernate.SharedSessionContract"); + } + + @Override + public ElementMatcher typeMatcher() { + return implementsInterface(named("org.hibernate.SharedSessionContract")); + } + + @Override + public void transform(TypeTransformer transformer) { + + // Session synchronous methods we want to instrument. + transformer.applyAdviceToMethod( + isMethod() + .and(takesArgument(0, any())) + .and( + namedOneOf( + "save", + "replicate", + "saveOrUpdate", + "update", + "merge", + "persist", + "lock", + "fireLock", + "refresh", + "insert", + "delete")), + SessionInstrumentation.class.getName() + "$SessionMethodAdvice"); + // Handle the non-generic 'get' separately. + transformer.applyAdviceToMethod( + isMethod() + .and(namedOneOf("get", "find")) + .and(returns(Object.class)) + .and(takesArgument(0, String.class).or(takesArgument(0, Class.class))), + SessionInstrumentation.class.getName() + "$SessionMethodAdvice"); + + // These methods return some object that we want to instrument, and so the Advice will pin the + // current SessionInfo to the returned object using a VirtualField. + transformer.applyAdviceToMethod( + isMethod() + .and(namedOneOf("beginTransaction", "getTransaction")) + .and(returns(named("org.hibernate.Transaction"))), + SessionInstrumentation.class.getName() + "$GetTransactionAdvice"); + + transformer.applyAdviceToMethod( + isMethod() + .and(returns(implementsInterface(named("org.hibernate.query.CommonQueryContract")))), + SessionInstrumentation.class.getName() + "$GetQueryAdvice"); + } + + @SuppressWarnings("unused") + public static class SessionMethodAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void startMethod( + @Advice.This SharedSessionContract session, + @Advice.Origin("#m") String name, + @Advice.Origin("#d") String descriptor, + @Advice.Argument(0) Object arg0, + @Advice.Argument(value = 1, optional = true) Object arg1, + @Advice.Local("otelCallDepth") CallDepth callDepth, + @Advice.Local("otelHibernateOperation") HibernateOperation hibernateOperation, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + + callDepth = CallDepth.forClass(HibernateOperation.class); + if (callDepth.getAndIncrement() > 0) { + return; + } + + VirtualField virtualField = + VirtualField.find(SharedSessionContract.class, SessionInfo.class); + SessionInfo sessionInfo = virtualField.get(session); + + Context parentContext = Java8BytecodeBridge.currentContext(); + String entityName = + getEntityName(descriptor, arg0, arg1, EntityNameUtil.bestGuessEntityName(session)); + hibernateOperation = + new HibernateOperation(getSessionMethodOperationName(name), entityName, sessionInfo); + if (!instrumenter().shouldStart(parentContext, hibernateOperation)) { + return; + } + + context = instrumenter().start(parentContext, hibernateOperation); + scope = context.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void endMethod( + @Advice.Thrown Throwable throwable, + @Advice.Local("otelCallDepth") CallDepth callDepth, + @Advice.Local("otelHibernateOperation") HibernateOperation hibernateOperation, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + + if (callDepth.decrementAndGet() > 0) { + return; + } + + if (scope != null) { + scope.close(); + instrumenter().end(context, hibernateOperation, null, throwable); + } + } + } + + @SuppressWarnings("unused") + public static class GetQueryAdvice { + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void getQuery( + @Advice.This SharedSessionContract session, @Advice.Return CommonQueryContract query) { + + VirtualField sessionVirtualField = + VirtualField.find(SharedSessionContract.class, SessionInfo.class); + VirtualField queryVirtualField = + VirtualField.find(CommonQueryContract.class, SessionInfo.class); + + queryVirtualField.set(query, sessionVirtualField.get(session)); + } + } + + @SuppressWarnings("unused") + public static class GetTransactionAdvice { + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void getTransaction( + @Advice.This SharedSessionContract session, @Advice.Return Transaction transaction) { + + VirtualField sessionVirtualField = + VirtualField.find(SharedSessionContract.class, SessionInfo.class); + VirtualField transactionVirtualField = + VirtualField.find(Transaction.class, SessionInfo.class); + + transactionVirtualField.set(transaction, sessionVirtualField.get(session)); + } + } + + @SuppressWarnings("unused") + public static class GetCriteriaAdvice { + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void getCriteria( + @Advice.This SharedSessionContract session, @Advice.Return CriteriaQuery criteria) { + + VirtualField sessionVirtualField = + VirtualField.find(SharedSessionContract.class, SessionInfo.class); + VirtualField, SessionInfo> criteriaVirtualField = + VirtualField.find(CriteriaQuery.class, SessionInfo.class); + + criteriaVirtualField.set(criteria, sessionVirtualField.get(session)); + } + } +} diff --git a/instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/TransactionInstrumentation.java b/instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/TransactionInstrumentation.java new file mode 100644 index 0000000000..711773e4e5 --- /dev/null +++ b/instrumentation/hibernate/hibernate-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/hibernate/v6_0/TransactionInstrumentation.java @@ -0,0 +1,96 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.hibernate.v6_0; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; +import static io.opentelemetry.javaagent.instrumentation.hibernate.v6_0.Hibernate6Singletons.instrumenter; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.util.VirtualField; +import io.opentelemetry.javaagent.bootstrap.CallDepth; +import io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import io.opentelemetry.javaagent.instrumentation.hibernate.HibernateOperation; +import io.opentelemetry.javaagent.instrumentation.hibernate.SessionInfo; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; +import org.hibernate.Transaction; + +public class TransactionInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed("org.hibernate.Transaction"); + } + + @Override + public ElementMatcher typeMatcher() { + return implementsInterface(named("org.hibernate.Transaction")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod().and(named("commit")).and(takesArguments(0)), + TransactionInstrumentation.class.getName() + "$TransactionCommitAdvice"); + } + + @SuppressWarnings("unused") + public static class TransactionCommitAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void startCommit( + @Advice.This Transaction transaction, + @Advice.Local("otelCallDepth") CallDepth callDepth, + @Advice.Local("otelHibernateOperation") HibernateOperation hibernateOperation, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + + callDepth = CallDepth.forClass(HibernateOperation.class); + if (callDepth.getAndIncrement() > 0) { + return; + } + + VirtualField transactionVirtualField = + VirtualField.find(Transaction.class, SessionInfo.class); + SessionInfo sessionInfo = transactionVirtualField.get(transaction); + + Context parentContext = Java8BytecodeBridge.currentContext(); + hibernateOperation = new HibernateOperation("Transaction.commit", sessionInfo); + if (!instrumenter().shouldStart(parentContext, hibernateOperation)) { + return; + } + + context = instrumenter().start(parentContext, hibernateOperation); + scope = context.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void endCommit( + @Advice.Thrown Throwable throwable, + @Advice.Local("otelCallDepth") CallDepth callDepth, + @Advice.Local("otelHibernateOperation") HibernateOperation hibernateOperation, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + + if (callDepth.decrementAndGet() > 0) { + return; + } + + if (scope != null) { + scope.close(); + instrumenter().end(context, hibernateOperation, null, throwable); + } + } + } +} diff --git a/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/groovy/AbstractHibernateTest.groovy b/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/groovy/AbstractHibernateTest.groovy new file mode 100644 index 0000000000..79c3b8ddf2 --- /dev/null +++ b/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/groovy/AbstractHibernateTest.groovy @@ -0,0 +1,39 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification +import org.hibernate.Session +import org.hibernate.SessionFactory +import org.hibernate.cfg.Configuration +import spock.lang.Shared + +abstract class AbstractHibernateTest extends AgentInstrumentationSpecification { + + @Shared + protected SessionFactory sessionFactory + + @Shared + protected List prepopulated + + def setupSpec() { + sessionFactory = new Configuration().configure().buildSessionFactory() + // Pre-populate the DB, so delete/update can be tested. + Session writer = sessionFactory.openSession() + writer.beginTransaction() + prepopulated = new ArrayList<>() + for (int i = 0; i < 5; i++) { + prepopulated.add(new Value("Hello :) " + i)) + writer.save(prepopulated.get(i)) + } + writer.getTransaction().commit() + writer.close() + } + + def cleanupSpec() { + if (sessionFactory != null) { + sessionFactory.close() + } + } +} diff --git a/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/groovy/CriteriaTest.groovy b/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/groovy/CriteriaTest.groovy new file mode 100644 index 0000000000..3b7fe642b4 --- /dev/null +++ b/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/groovy/CriteriaTest.groovy @@ -0,0 +1,87 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes +import jakarta.persistence.criteria.CriteriaBuilder +import jakarta.persistence.criteria.CriteriaQuery +import jakarta.persistence.criteria.Root +import org.hibernate.Session + +import static io.opentelemetry.api.trace.SpanKind.CLIENT +import static io.opentelemetry.api.trace.SpanKind.INTERNAL + +class CriteriaTest extends AbstractHibernateTest { + + def "test criteria query.#methodName"() { + setup: + runWithSpan("parent") { + Session session = sessionFactory.openSession() + session.beginTransaction() + CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder() + CriteriaQuery createQuery = criteriaBuilder.createQuery(Value) + Root root = createQuery.from(Value) + createQuery.select(root) + .where(criteriaBuilder.like(root.get("name"), "Hello")) + .orderBy(criteriaBuilder.desc(root.get("name"))) + def query= session.createQuery(createQuery) + interaction.call(query) + session.getTransaction().commit() + session.close() + } + + expect: + def sessionId + assertTraces(1) { + trace(0, 4) { + span(0) { + name "parent" + kind INTERNAL + hasNoParent() + attributes { + } + } + span(1) { + name "SELECT Value" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" { + sessionId = it + it instanceof String + } + } + } + span(2) { + name "SELECT db1.Value" + kind CLIENT + childOf span(1) + attributes { + "$SemanticAttributes.DB_SYSTEM" "h2" + "$SemanticAttributes.DB_NAME" "db1" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:" + "$SemanticAttributes.DB_STATEMENT" ~/^select / + "$SemanticAttributes.DB_OPERATION" "SELECT" + "$SemanticAttributes.DB_SQL_TABLE" "Value" + } + } + span(3) { + name "Transaction.commit" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" sessionId + } + } + } + } + + where: + methodName | interaction + "getResultList" | { c -> c.getResultList() } + "uniqueResult" | { c -> c.uniqueResult() } + "getSingleResultOrNull" | { c -> c.getSingleResultOrNull() } + } +} diff --git a/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/groovy/EntityManagerTest.groovy b/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/groovy/EntityManagerTest.groovy new file mode 100644 index 0000000000..0f0daac398 --- /dev/null +++ b/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/groovy/EntityManagerTest.groovy @@ -0,0 +1,233 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes +import jakarta.persistence.EntityManager +import jakarta.persistence.EntityManagerFactory +import jakarta.persistence.EntityTransaction +import jakarta.persistence.LockModeType +import jakarta.persistence.Persistence +import jakarta.persistence.Query +import spock.lang.Shared +import spock.lang.Unroll + +import static io.opentelemetry.api.trace.SpanKind.CLIENT +import static io.opentelemetry.api.trace.SpanKind.INTERNAL + +class EntityManagerTest extends AbstractHibernateTest { + + @Shared + EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("test-pu") + + @Unroll + def "test hibernate action #testName"() { + setup: + EntityManager entityManager = entityManagerFactory.createEntityManager() + EntityTransaction entityTransaction = entityManager.getTransaction() + entityTransaction.begin() + + def entity = prepopulated.get(0) + if (attach) { + entity = runWithSpan("setup") { + entityManager.merge(prepopulated.get(0)) + } + ignoreTracesAndClear(1) + } + + when: + runWithSpan("parent") { + try { + sessionMethodTest.call(entityManager, entity) + } catch (Exception e) { + // We expected this, we should see the error field set on the span. + } + + entityTransaction.commit() + entityManager.close() + } + + then: + boolean isPersistTest = "persist" == testName + def sessionId + assertTraces(1) { + trace(0, 4 + (isPersistTest ? 1 : 0)) { + span(0) { + name "parent" + kind INTERNAL + hasNoParent() + attributes { + } + } + span(1) { + name ~/Session.$methodName $resource/ + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" { + sessionId = it + it instanceof String + } + } + } + + def offset = 0 + if (isPersistTest) { + // persist test has an extra query for getting id of inserted element + offset = 1 + span(2) { + name "SELECT db1.Value" + childOf span(1) + kind CLIENT + attributes { + "$SemanticAttributes.DB_SYSTEM" "h2" + "$SemanticAttributes.DB_NAME" "db1" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:" + "$SemanticAttributes.DB_STATEMENT" String + "$SemanticAttributes.DB_OPERATION" String + "$SemanticAttributes.DB_SQL_TABLE" "Value" + } + } + } + + if (!flushOnCommit) { + span(2 + offset) { + childOf span(1) + kind CLIENT + attributes { + "$SemanticAttributes.DB_SYSTEM" "h2" + "$SemanticAttributes.DB_NAME" "db1" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:" + "$SemanticAttributes.DB_STATEMENT" String + "$SemanticAttributes.DB_OPERATION" String + "$SemanticAttributes.DB_SQL_TABLE" "Value" + } + } + span(3 + offset) { + name "Transaction.commit" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" sessionId + } + } + } else { + span(2 + offset) { + name "Transaction.commit" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" sessionId + } + } + span(3 + offset) { + childOf span(2 + offset) + kind CLIENT + attributes { + "$SemanticAttributes.DB_SYSTEM" "h2" + "$SemanticAttributes.DB_NAME" "db1" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:" + "$SemanticAttributes.DB_STATEMENT" String + "$SemanticAttributes.DB_OPERATION" String + "$SemanticAttributes.DB_SQL_TABLE" "Value" + } + } + } + } + } + + where: + testName | methodName | resource | attach | flushOnCommit | sessionMethodTest + "lock" | "lock" | "Value" | true | false | { em, val -> + em.lock(val, LockModeType.PESSIMISTIC_READ) + } + "refresh" | "refresh" | "Value" | true | false | { em, val -> + em.refresh(val) + } + "find" | "(get|find)" | "Value" | false | false | { em, val -> + em.find(Value, val.getId()) + } + "persist" | "persist" | "Value" | false | true | { em, val -> + em.persist(new Value("insert me")) + } + "merge" | "merge" | "Value" | true | true | { em, val -> + val.setName("New name") + em.merge(val) + } + "remove" | "delete" | "Value" | true | true | { em, val -> + em.remove(val) + } + } + + @Unroll + def "test attaches State to query created via #queryMethodName"() { + setup: + runWithSpan("parent") { + EntityManager entityManager = entityManagerFactory.createEntityManager() + EntityTransaction entityTransaction = entityManager.getTransaction() + entityTransaction.begin() + Query query = queryBuildMethod(entityManager) + query.getResultList() + entityTransaction.commit() + entityManager.close() + } + + expect: + def sessionId + assertTraces(1) { + trace(0, 4) { + span(0) { + name "parent" + kind INTERNAL + hasNoParent() + attributes { + } + } + span(1) { + name resource + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" { + sessionId = it + it instanceof String + } + } + } + span(2) { + name "SELECT db1.Value" + kind CLIENT + childOf span(1) + attributes { + "$SemanticAttributes.DB_SYSTEM" "h2" + "$SemanticAttributes.DB_NAME" "db1" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:" + "$SemanticAttributes.DB_STATEMENT" String + "$SemanticAttributes.DB_OPERATION" "SELECT" + "$SemanticAttributes.DB_SQL_TABLE" "Value" + } + } + span(3) { + name "Transaction.commit" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" sessionId + } + } + } + } + + where: + queryMethodName | resource | queryBuildMethod + "createQuery" | "SELECT Value" | { em -> em.createQuery("from Value") } + "getNamedQuery" | "SELECT Value" | { em -> em.createNamedQuery("TestNamedQuery") } + "createSQLQuery" | "SELECT Value" | { em -> em.createNativeQuery("SELECT * FROM Value") } + } + +} diff --git a/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/groovy/ProcedureCallTest.groovy b/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/groovy/ProcedureCallTest.groovy new file mode 100644 index 0000000000..6c0c0a56de --- /dev/null +++ b/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/groovy/ProcedureCallTest.groovy @@ -0,0 +1,175 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes +import jakarta.persistence.ParameterMode +import org.hibernate.Session +import org.hibernate.SessionFactory +import org.hibernate.cfg.Configuration +import org.hibernate.exception.SQLGrammarException +import org.hibernate.procedure.ProcedureCall +import spock.lang.Shared + +import java.sql.Connection +import java.sql.DriverManager +import java.sql.Statement + +import static io.opentelemetry.api.trace.SpanKind.CLIENT +import static io.opentelemetry.api.trace.SpanKind.INTERNAL +import static io.opentelemetry.api.trace.StatusCode.ERROR + +class ProcedureCallTest extends AgentInstrumentationSpecification { + + @Shared + protected SessionFactory sessionFactory + + @Shared + protected List prepopulated + + def setupSpec() { + sessionFactory = new Configuration().configure("procedure-call-hibernate.cfg.xml").buildSessionFactory() + // Pre-populate the DB, so delete/update can be tested. + Session writer = sessionFactory.openSession() + writer.beginTransaction() + prepopulated = new ArrayList<>() + for (int i = 0; i < 2; i++) { + prepopulated.add(new Value("Hello :) " + i)) + writer.save(prepopulated.get(i)) + } + writer.getTransaction().commit() + writer.close() + + // Create a stored procedure. + Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:test", "sa", "1") + Statement stmt = conn.createStatement() + stmt.execute("CREATE PROCEDURE TEST_PROC() MODIFIES SQL DATA BEGIN ATOMIC INSERT INTO Value VALUES (420, 'fred'); END") + stmt.close() + conn.close() + } + + def cleanupSpec() { + if (sessionFactory != null) { + sessionFactory.close() + } + } + + def "test ProcedureCall"() { + setup: + + runWithSpan("parent") { + Session session = sessionFactory.openSession() + session.beginTransaction() + + ProcedureCall call = session.createStoredProcedureCall("TEST_PROC") + call.getOutputs() + + session.getTransaction().commit() + session.close() + } + + expect: + def sessionId + assertTraces(1) { + trace(0, 4) { + span(0) { + name "parent" + kind INTERNAL + hasNoParent() + attributes { + } + } + span(1) { + name "ProcedureCall.getOutputs TEST_PROC" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" { + sessionId = it + it instanceof String + } + } + } + span(2) { + name "CALL test.TEST_PROC" + kind CLIENT + childOf span(1) + attributes { + "$SemanticAttributes.DB_SYSTEM" "hsqldb" + "$SemanticAttributes.DB_NAME" "test" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_STATEMENT" "{call TEST_PROC()}" + "$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:" + "$SemanticAttributes.DB_OPERATION" "CALL" + } + } + span(3) { + kind INTERNAL + name "Transaction.commit" + childOf span(0) + attributes { + "hibernate.session_id" sessionId + } + } + } + } + } + + def "test failing ProcedureCall"() { + setup: + + runWithSpan("parent") { + Session session = sessionFactory.openSession() + session.beginTransaction() + + ProcedureCall call = session.createStoredProcedureCall("TEST_PROC") + def parameterRegistration = call.registerParameter("nonexistent", Long, ParameterMode.IN) + call.setParameter(parameterRegistration, 420L) + try { + call.getOutputs() + } catch (Exception e) { + // We expected this. + } + + session.getTransaction().commit() + session.close() + } + + expect: + def sessionId + assertTraces(1) { + trace(0, 3) { + span(0) { + name "parent" + kind INTERNAL + hasNoParent() + attributes { + } + } + span(1) { + name "ProcedureCall.getOutputs TEST_PROC" + kind INTERNAL + childOf span(0) + status ERROR + errorEvent(SQLGrammarException, "could not prepare statement") + attributes { + "hibernate.session_id" { + sessionId = it + it instanceof String + } + } + } + span(2) { + name "Transaction.commit" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" sessionId + } + } + } + } + } +} diff --git a/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/groovy/SessionTest.groovy b/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/groovy/SessionTest.groovy new file mode 100644 index 0000000000..fffbbbf682 --- /dev/null +++ b/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/groovy/SessionTest.groovy @@ -0,0 +1,603 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes +import org.hibernate.LockMode +import org.hibernate.LockOptions +import org.hibernate.ReplicationMode +import org.hibernate.Session +import org.hibernate.UnknownEntityTypeException +import spock.lang.Shared + +import static io.opentelemetry.api.trace.SpanKind.CLIENT +import static io.opentelemetry.api.trace.SpanKind.INTERNAL +import static io.opentelemetry.api.trace.StatusCode.ERROR + +class SessionTest extends AbstractHibernateTest { + + @Shared + private Closure sessionBuilder = { return sessionFactory.openSession() } + @Shared + private Closure statelessSessionBuilder = { return sessionFactory.openStatelessSession() } + + + def "test hibernate action #testName"() { + setup: + + // Test for each implementation of Session. + for (def buildSession : sessionImplementations) { + runWithSpan("parent") { + def session = buildSession() + session.beginTransaction() + + try { + sessionMethodTest.call(session, prepopulated.get(0)) + } catch (Exception e) { + // We expected this, we should see the error field set on the span. + } + + session.getTransaction().commit() + session.close() + } + } + + expect: + def sessionId + assertTraces(sessionImplementations.size()) { + for (int i = 0; i < sessionImplementations.size(); i++) { + trace(i, 4) { + span(0) { + name "parent" + kind INTERNAL + hasNoParent() + attributes { + } + } + span(1) { + name "Session.$methodName $resource" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" { + sessionId = it + it instanceof String + } + } + } + span(2) { + childOf span(1) + kind CLIENT + attributes { + "$SemanticAttributes.DB_SYSTEM" "h2" + "$SemanticAttributes.DB_NAME" "db1" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:" + "$SemanticAttributes.DB_STATEMENT" String + "$SemanticAttributes.DB_OPERATION" String + "$SemanticAttributes.DB_SQL_TABLE" "Value" + } + } + span(3) { + name "Transaction.commit" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" sessionId + } + } + } + } + } + + where: + testName | methodName | resource | sessionImplementations | sessionMethodTest + "lock" | "lock" | "Value" | [sessionBuilder] | { sesh, val -> + sesh.lock(val, LockMode.READ) + } + "lock with entity name" | "lock" | "Value" | [sessionBuilder] | { sesh, val -> + sesh.lock("Value", val, LockMode.READ) + } + "lock with null name" | "lock" | "Value" | [sessionBuilder] | { sesh, val -> + sesh.lock(null, val, LockMode.READ) + } + "buildLockRequest" | "lock" | "Value" | [sessionBuilder] | { sesh, val -> + sesh.buildLockRequest(LockOptions.READ) + .lock(val) + } + "refresh" | "refresh" | "Value" | [sessionBuilder, statelessSessionBuilder] | { sesh, val -> + sesh.refresh(val) + } + "refresh with entity name" | "refresh" | "Value" | [sessionBuilder, statelessSessionBuilder] | { sesh, val -> + sesh.refresh("Value", val) + } + "get with entity name" | "get" | "Value" | [sessionBuilder, statelessSessionBuilder] | { sesh, val -> + sesh.get("Value", val.getId()) + } + "get with entity class" | "get" | "Value" | [sessionBuilder, statelessSessionBuilder] | { sesh, val -> + sesh.get(Value, val.getId()) + } + "insert" | "insert" | "Value" | [statelessSessionBuilder] | { sesh, val -> + sesh.insert(new Value("insert me")) + } + "insert with entity name" | "insert" | "Value" | [statelessSessionBuilder] | { sesh, val -> + sesh.insert("Value", new Value("insert me")) + } + "insert with null entity name" | "insert" | "Value" | [statelessSessionBuilder] | { sesh, val -> + sesh.insert(null, new Value("insert me")) + } + "update (StatelessSession)" | "update" | "Value" | [statelessSessionBuilder] | { sesh, val -> + val.setName("New name") + sesh.update(val) + } + "update with entity name (StatelessSession)" | "update" | "Value" | [statelessSessionBuilder] | { sesh, val -> + val.setName("New name") + sesh.update("Value", val) + } + "delete (Session)" | "delete" | "Value" | [statelessSessionBuilder] | { sesh, val -> + sesh.delete(val) + prepopulated.remove(val) + } + "delete with entity name (Session)" | "delete" | "Value" | [statelessSessionBuilder] | { sesh, val -> + sesh.delete("Value", val) + prepopulated.remove(val) + } + } + + def "test hibernate replicate: #testName"() { + setup: + + // Test for each implementation of Session. + runWithSpan("parent") { + def session = sessionFactory.openSession() + session.beginTransaction() + + try { + sessionMethodTest.call(session, prepopulated.get(0)) + } catch (Exception e) { + // We expected this, we should see the error field set on the span. + } + + session.getTransaction().commit() + session.close() + } + + expect: + def sessionId + assertTraces(1) { + trace(0, 5) { + span(0) { + name "parent" + kind INTERNAL + hasNoParent() + attributes { + } + } + span(1) { + name "Session.$methodName $resource" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" { + sessionId = it + it instanceof String + } + } + } + span(2) { + name "SELECT db1.Value" + kind CLIENT + childOf span(1) + attributes { + "$SemanticAttributes.DB_SYSTEM" "h2" + "$SemanticAttributes.DB_NAME" "db1" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:" + "$SemanticAttributes.DB_STATEMENT" ~/^select / + "$SemanticAttributes.DB_OPERATION" "SELECT" + "$SemanticAttributes.DB_SQL_TABLE" "Value" + } + } + span(3) { + name "Transaction.commit" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" sessionId + } + } + span(4) { + kind CLIENT + childOf span(3) + attributes { + "$SemanticAttributes.DB_SYSTEM" "h2" + "$SemanticAttributes.DB_NAME" "db1" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:" + "$SemanticAttributes.DB_STATEMENT" String + "$SemanticAttributes.DB_OPERATION" String + "$SemanticAttributes.DB_SQL_TABLE" "Value" + } + } + } + + } + + where: + testName | methodName | resource | sessionMethodTest + "replicate" | "replicate" | "Value" | { sesh, val -> + Value replicated = new Value(val.getName() + " replicated") + replicated.setId(val.getId()) + sesh.replicate(replicated, ReplicationMode.OVERWRITE) + } + "replicate by entityName" | "replicate" | "Value" | { sesh, val -> + Value replicated = new Value(val.getName() + " replicated") + replicated.setId(val.getId()) + sesh.replicate("Value", replicated, ReplicationMode.OVERWRITE) + } + } + + def "test hibernate failed replicate"() { + setup: + + // Test for each implementation of Session. + runWithSpan("parent") { + def session = sessionFactory.openSession() + session.beginTransaction() + + try { + session.replicate(new Long(123) /* Not a valid entity */, ReplicationMode.OVERWRITE) + } catch (Exception e) { + // We expected this, we should see the error field set on the span. + } + + session.getTransaction().commit() + session.close() + } + + expect: + def sessionId + assertTraces(1) { + trace(0, 3) { + span(0) { + name "parent" + kind INTERNAL + hasNoParent() + attributes { + } + } + span(1) { + name "Session.replicate java.lang.Long" + kind INTERNAL + childOf span(0) + status ERROR + errorEvent(UnknownEntityTypeException, "Unable to locate persister: java.lang.Long") + attributes { + "hibernate.session_id" { + sessionId = it + it instanceof String + } + } + } + span(2) { + name "Transaction.commit" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" sessionId + } + } + } + + } + } + + + def "test hibernate commit action #testName"() { + setup: + + runWithSpan("parent") { + def session = sessionBuilder() + session.beginTransaction() + + try { + sessionMethodTest.call(session, prepopulated.get(0)) + } catch (Exception e) { + // We expected this, we should see the error field set on the span. + } + + session.getTransaction().commit() + session.close() + } + + expect: + def sessionId + assertTraces(1) { + trace(0, 4) { + span(0) { + name "parent" + kind INTERNAL + hasNoParent() + attributes { + } + } + span(1) { + name "Session.$methodName $resource" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" { + sessionId = it + it instanceof String + } + } + } + span(2) { + name "Transaction.commit" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" sessionId + } + } + span(3) { + kind CLIENT + childOf span(2) + attributes { + "$SemanticAttributes.DB_SYSTEM" "h2" + "$SemanticAttributes.DB_NAME" "db1" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:" + "$SemanticAttributes.DB_STATEMENT" String + "$SemanticAttributes.DB_OPERATION" String + "$SemanticAttributes.DB_SQL_TABLE" "Value" + } + } + } + } + + where: + testName | methodName | resource | sessionMethodTest + "save" | "save" | "Value" | { sesh, val -> + sesh.save(new Value("Another value")) + } + "save with entity name" | "save" | "Value" | { sesh, val -> + sesh.save("Value", new Value("Another value")) + } + "saveOrUpdate save" | "saveOrUpdate" | "Value" | { sesh, val -> + sesh.saveOrUpdate(new Value("Value")) + } + "saveOrUpdate save with entity name" | "saveOrUpdate" | "Value" | { sesh, val -> + sesh.saveOrUpdate("Value", new Value("Value")) + } + "saveOrUpdate update with entity name" | "saveOrUpdate" | "Value" | { sesh, val -> + val.setName("New name") + sesh.saveOrUpdate("Value", val) + } + "merge" | "merge" | "Value" | { sesh, val -> + sesh.merge(new Value("merge me in")) + } + "merge with entity name" | "merge" | "Value" | { sesh, val -> + sesh.merge("Value", new Value("merge me in")) + } + "persist" | "persist" | "Value" | { sesh, val -> + sesh.persist(new Value("merge me in")) + } + "persist with entity name" | "persist" | "Value" | { sesh, val -> + sesh.persist("Value", new Value("merge me in")) + } + "persist with null entity name" | "persist" | "Value" | { sesh, val -> + sesh.persist(null, new Value("merge me in")) + } + "update (Session)" | "update" | "Value" | { sesh, val -> + val.setName("New name") + sesh.update(val) + } + "update by entityName (Session)" | "update" | "Value" | { sesh, val -> + val.setName("New name") + sesh.update("Value", val) + } + "delete (Session)" | "delete" | "Value" | { sesh, val -> + sesh.delete(val) + prepopulated.remove(val) + } + "delete by entityName (Session)" | "delete" | "Value" | { sesh, val -> + sesh.delete("Value", val) + prepopulated.remove(val) + } + } + + def "test attaches State to query created via #queryMethodName"() { + setup: + runWithSpan("parent") { + Session session = sessionFactory.openSession() + session.beginTransaction() + def query = queryBuildMethod(session) + query.list() + session.getTransaction().commit() + session.close() + } + + expect: + def sessionId + assertTraces(1) { + trace(0, 4) { + span(0) { + name "parent" + kind INTERNAL + hasNoParent() + attributes { + } + } + span(1) { + name resource + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" { + sessionId = it + it instanceof String + } + } + } + span(2) { + kind CLIENT + childOf span(1) + attributes { + "$SemanticAttributes.DB_SYSTEM" "h2" + "$SemanticAttributes.DB_NAME" "db1" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:" + "$SemanticAttributes.DB_STATEMENT" String + "$SemanticAttributes.DB_OPERATION" "SELECT" + "$SemanticAttributes.DB_SQL_TABLE" "Value" + } + } + span(3) { + name "Transaction.commit" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" sessionId + } + } + } + } + + where: + queryMethodName | resource | queryBuildMethod + "createQuery" | "SELECT Value" | { sess -> sess.createQuery("from Value") } + "getNamedQuery" | "SELECT Value" | { sess -> sess.getNamedQuery("TestNamedQuery") } + "createNativeQuery" | "SELECT Value" | { sess -> sess.createNativeQuery("SELECT * FROM Value") } + "createSelectionQuery" | "SELECT Value" | { sess -> sess.createSelectionQuery("from Value") } + } + + def "test hibernate overlapping Sessions"() { + setup: + + runWithSpan("overlapping Sessions") { + def session1 = sessionFactory.openSession() + session1.beginTransaction() + def session2 = sessionFactory.openStatelessSession() + def session3 = sessionFactory.openSession() + + def value1 = new Value("Value 1") + session1.save(value1) + session2.insert(new Value("Value 2")) + session3.save(new Value("Value 3")) + session1.delete(value1) + + session2.close() + session1.getTransaction().commit() + session1.close() + session3.close() + } + + expect: + def sessionId1 + def sessionId2 + def sessionId3 + assertTraces(1) { + trace(0, 9) { + span(0) { + name "overlapping Sessions" + attributes { + } + } + span(1) { + name "Session.save Value" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" { + sessionId1 = it + it instanceof String + } + } + } + span(2) { + name "Session.insert Value" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" { + sessionId2 = it + it instanceof String + } + } + } + span(3) { + name "INSERT db1.Value" + kind CLIENT + childOf span(2) + attributes { + "$SemanticAttributes.DB_SYSTEM" "h2" + "$SemanticAttributes.DB_NAME" "db1" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:" + "$SemanticAttributes.DB_STATEMENT" ~/^insert / + "$SemanticAttributes.DB_OPERATION" "INSERT" + "$SemanticAttributes.DB_SQL_TABLE" "Value" + } + } + span(4) { + name "Session.save Value" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" { + sessionId3 = it + it instanceof String + } + } + } + span(5) { + name "Session.delete Value" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" sessionId1 + } + } + span(6) { + name "Transaction.commit" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" sessionId1 + } + } + span(7) { + name "INSERT db1.Value" + kind CLIENT + childOf span(6) + attributes { + "$SemanticAttributes.DB_SYSTEM" "h2" + "$SemanticAttributes.DB_NAME" "db1" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:" + "$SemanticAttributes.DB_STATEMENT" ~/^insert / + "$SemanticAttributes.DB_OPERATION" "INSERT" + "$SemanticAttributes.DB_SQL_TABLE" "Value" + } + } + span(8) { + name "DELETE db1.Value" + kind CLIENT + childOf span(6) + attributes { + "$SemanticAttributes.DB_SYSTEM" "h2" + "$SemanticAttributes.DB_NAME" "db1" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_CONNECTION_STRING" "h2:mem:" + "$SemanticAttributes.DB_STATEMENT" ~/^delete / + "$SemanticAttributes.DB_OPERATION" "DELETE" + "$SemanticAttributes.DB_SQL_TABLE" "Value" + } + } + } + } + sessionId1 != sessionId2 != sessionId3 + } +} diff --git a/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/java/Value.java b/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/java/Value.java new file mode 100644 index 0000000000..aeeccb474a --- /dev/null +++ b/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/java/Value.java @@ -0,0 +1,45 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.NamedQuery; + +@Entity +@Table +@NamedQuery(name = "TestNamedQuery", query = "from Value") +public class Value { + + private Long id; + private String name; + + public Value() {} + + public Value(String name) { + this.name = name; + } + + @Id + @GeneratedValue(generator = "increment") + @GenericGenerator(name = "increment", strategy = "increment") + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String title) { + name = title; + } +} diff --git a/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/resources/META-INF/persistence.xml b/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/resources/META-INF/persistence.xml new file mode 100644 index 0000000000..3cb3eed07e --- /dev/null +++ b/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/resources/META-INF/persistence.xml @@ -0,0 +1,18 @@ + + + + Value + true + + + + + + + + + + \ No newline at end of file diff --git a/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/resources/hibernate.cfg.xml b/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/resources/hibernate.cfg.xml new file mode 100644 index 0000000000..fe5e5de6b1 --- /dev/null +++ b/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/resources/hibernate.cfg.xml @@ -0,0 +1,29 @@ + + + + + + + + + org.h2.Driver + jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE + sa + + org.hibernate.dialect.H2Dialect + + 3 + org.hibernate.cache.internal.NoCacheProvider + true + + + create + + + + + + + diff --git a/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/resources/procedure-call-hibernate.cfg.xml b/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/resources/procedure-call-hibernate.cfg.xml new file mode 100644 index 0000000000..bbc3827dda --- /dev/null +++ b/instrumentation/hibernate/hibernate-6.0/javaagent/src/test/resources/procedure-call-hibernate.cfg.xml @@ -0,0 +1,24 @@ + + + + + + + + class,hbm + org.hibernate.dialect.HSQLDialect + true + org.hsqldb.jdbcDriver + sa + 1 + jdbc:hsqldb:mem:test + create + + + + + + + diff --git a/instrumentation/hibernate/hibernate-6.0/spring-testing/build.gradle.kts b/instrumentation/hibernate/hibernate-6.0/spring-testing/build.gradle.kts new file mode 100644 index 0000000000..d081da37d5 --- /dev/null +++ b/instrumentation/hibernate/hibernate-6.0/spring-testing/build.gradle.kts @@ -0,0 +1,26 @@ +plugins { + id("otel.javaagent-testing") +} + +dependencies { + library("org.hibernate:hibernate-core:6.0.0.Final") + + testInstrumentation(project(":instrumentation:hibernate:hibernate-6.0:javaagent")) + testInstrumentation(project(":instrumentation:jdbc:javaagent")) + // Added to ensure cross compatibility: + testInstrumentation(project(":instrumentation:hibernate:hibernate-3.3:javaagent")) + testInstrumentation(project(":instrumentation:hibernate:hibernate-4.0:javaagent")) + testInstrumentation(project(":instrumentation:hibernate:hibernate-procedure-call-4.3:javaagent")) + + testImplementation("org.hsqldb:hsqldb:2.0.0") + testImplementation("org.springframework.data:spring-data-jpa:3.0.0") +} + +otelJava { + minJavaVersionSupported.set(JavaVersion.VERSION_17) +} + +tasks.withType().configureEach { + // TODO run tests both with and without experimental span attributes + jvmArgs("-Dotel.instrumentation.hibernate.experimental-span-attributes=true") +} diff --git a/instrumentation/hibernate/hibernate-6.0/spring-testing/src/test/groovy/SpringJpaTest.groovy b/instrumentation/hibernate/hibernate-6.0/spring-testing/src/test/groovy/SpringJpaTest.groovy new file mode 100644 index 0000000000..7eda918d7e --- /dev/null +++ b/instrumentation/hibernate/hibernate-6.0/spring-testing/src/test/groovy/SpringJpaTest.groovy @@ -0,0 +1,385 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes +import org.hibernate.Version +import org.springframework.context.annotation.AnnotationConfigApplicationContext +import spock.lang.Shared +import spring.jpa.Customer +import spring.jpa.CustomerRepository +import spring.jpa.PersistenceConfig + +import static io.opentelemetry.api.trace.SpanKind.CLIENT +import static io.opentelemetry.api.trace.SpanKind.INTERNAL + +class SpringJpaTest extends AgentInstrumentationSpecification { + + @Shared + def context = new AnnotationConfigApplicationContext(PersistenceConfig) + + @Shared + def repo = context.getBean(CustomerRepository) + + def "test CRUD"() { + setup: + def isHibernate4 = Version.getVersionString().startsWith("4.") + def customer = new Customer("Bob", "Anonymous") + + expect: + customer.id == null + !runWithSpan("parent") { + repo.findAll().iterator().hasNext() + } + + def sessionId + assertTraces(1) { + trace(0, 4) { + span(0) { + name "parent" + kind INTERNAL + hasNoParent() + attributes { + } + } + span(1) { + name "SELECT spring.jpa.Customer" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" { + sessionId = it + it instanceof String + } + } + } + span(2) { + name "SELECT test.Customer" + kind CLIENT + childOf span(1) + attributes { + "$SemanticAttributes.DB_SYSTEM" "hsqldb" + "$SemanticAttributes.DB_NAME" "test" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:" + "$SemanticAttributes.DB_STATEMENT" ~/select ([^.]+)\.id([^,]*),([^.]+)\.firstName([^,]*),([^.]+)\.lastName(.*)from Customer(.*)/ + "$SemanticAttributes.DB_OPERATION" "SELECT" + "$SemanticAttributes.DB_SQL_TABLE" "Customer" + } + } + span(3) { + name "Transaction.commit" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" sessionId + } + } + } + } + clearExportedData() + + when: + runWithSpan("parent") { + repo.save(customer) + } + def savedId = customer.id + + then: + customer.id != null + def sessionId2 + assertTraces(1) { + trace(0, 4 + (isHibernate4 ? 0 : 1)) { + span(0) { + name "parent" + kind INTERNAL + hasNoParent() + attributes { + } + } + span(1) { + name "Session.persist spring.jpa.Customer" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" { + sessionId2 = it + it instanceof String + } + } + } + if (!isHibernate4) { + span(2) { + name "CALL test" + kind CLIENT + childOf span(1) + attributes { + "$SemanticAttributes.DB_SYSTEM" "hsqldb" + "$SemanticAttributes.DB_NAME" "test" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_STATEMENT" "call next value for Customer_SEQ" + "$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:" + "$SemanticAttributes.DB_OPERATION" "CALL" + } + } + span(3) { + name "Transaction.commit" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" sessionId2 + } + } + span(4) { + name "INSERT test.Customer" + kind CLIENT + childOf span(3) + attributes { + "$SemanticAttributes.DB_SYSTEM" "hsqldb" + "$SemanticAttributes.DB_NAME" "test" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:" + "$SemanticAttributes.DB_STATEMENT" ~/insert into Customer \(.*\) values \(.*, \?, \?\)/ + "$SemanticAttributes.DB_OPERATION" "INSERT" + "$SemanticAttributes.DB_SQL_TABLE" "Customer" + } + } + } else { + span(2) { + name "INSERT test.Customer" + kind CLIENT + childOf span(1) + attributes { + "$SemanticAttributes.DB_SYSTEM" "hsqldb" + "$SemanticAttributes.DB_NAME" "test" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:" + "$SemanticAttributes.DB_STATEMENT" ~/insert into Customer \(.*\) values \(.*, \?, \?\)/ + "$SemanticAttributes.DB_OPERATION" "INSERT" + "$SemanticAttributes.DB_SQL_TABLE" "Customer" + } + } + span(3) { + name "Transaction.commit" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" sessionId2 + } + } + } + } + } + clearExportedData() + + when: + customer.firstName = "Bill" + runWithSpan("parent") { + repo.save(customer) + } + + then: + customer.id == savedId + def sessionId3 + assertTraces(1) { + trace(0, 5) { + span(0) { + name "parent" + kind INTERNAL + hasNoParent() + attributes { + } + } + span(1) { + name "Session.merge spring.jpa.Customer" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" { + sessionId3 = it + it instanceof String + } + } + } + span(2) { + name "SELECT test.Customer" + kind CLIENT + attributes { + "$SemanticAttributes.DB_SYSTEM" "hsqldb" + "$SemanticAttributes.DB_NAME" "test" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:" + "$SemanticAttributes.DB_STATEMENT" ~/select ([^.]+)\.id([^,]*),([^.]+)\.firstName([^,]*),([^.]+)\.lastName (.*)from Customer (.*)where ([^.]+)\.id( ?)=( ?)\?/ + "$SemanticAttributes.DB_OPERATION" "SELECT" + "$SemanticAttributes.DB_SQL_TABLE" "Customer" + } + } + span(3) { + name "Transaction.commit" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" sessionId3 + } + } + span(4) { + name "UPDATE test.Customer" + kind CLIENT + attributes { + "$SemanticAttributes.DB_SYSTEM" "hsqldb" + "$SemanticAttributes.DB_NAME" "test" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:" + "$SemanticAttributes.DB_STATEMENT" "update Customer set firstName=?, lastName=? where id=?" + "$SemanticAttributes.DB_OPERATION" "UPDATE" + "$SemanticAttributes.DB_SQL_TABLE" "Customer" + } + } + } + } + clearExportedData() + + when: + customer = runWithSpan("parent") { + repo.findByLastName("Anonymous")[0] + } + + then: + customer.id == savedId + customer.firstName == "Bill" + assertTraces(1) { + trace(0, 3) { + span(0) { + name "parent" + kind INTERNAL + hasNoParent() + attributes { + } + } + span(1) { + name { it == "SELECT spring.jpa.Customer" || it == "Hibernate Query" } + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" String + } + } + span(2) { + name "SELECT test.Customer" + kind CLIENT + childOf span(1) + attributes { + "$SemanticAttributes.DB_SYSTEM" "hsqldb" + "$SemanticAttributes.DB_NAME" "test" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:" + "$SemanticAttributes.DB_STATEMENT" ~/select ([^.]+)\.id([^,]*),([^.]+)\.firstName([^,]*),([^.]+)\.lastName (.*)from Customer (.*)(where ([^.]+)\.lastName( ?)=( ?)\?|)/ + "$SemanticAttributes.DB_OPERATION" "SELECT" + "$SemanticAttributes.DB_SQL_TABLE" "Customer" + } + } + } + } + clearExportedData() + + when: + runWithSpan("parent") { + repo.delete(customer) + } + + then: + assertTraces(1) { + trace(0, 6 + (isHibernate4 ? 0 : 1)) { + span(0) { + name "parent" + kind INTERNAL + hasNoParent() + attributes { + } + } + def offset = 0 + if (!isHibernate4) { + offset = 2 + span(1) { + name ~/Session.(get|find) spring.jpa.Customer/ + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" String + } + } + span(2) { + name "SELECT test.Customer" + kind CLIENT + childOf span(1) + attributes { + "$SemanticAttributes.DB_SYSTEM" "hsqldb" + "$SemanticAttributes.DB_NAME" "test" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:" + "$SemanticAttributes.DB_STATEMENT" ~/select ([^.]+)\.id([^,]*),([^.]+)\.firstName([^,]*),([^.]+)\.lastName (.*)from Customer (.*)where ([^.]+)\.id( ?)=( ?)\?/ + "$SemanticAttributes.DB_OPERATION" "SELECT" + "$SemanticAttributes.DB_SQL_TABLE" "Customer" + } + } + } + span(1 + offset) { + name "Session.merge spring.jpa.Customer" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" String + } + } + if (isHibernate4) { + offset = 1 + span(2) { + name "SELECT test.Customer" + kind CLIENT + childOf span(1) + attributes { + "$SemanticAttributes.DB_SYSTEM" "hsqldb" + "$SemanticAttributes.DB_NAME" "test" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:" + "$SemanticAttributes.DB_STATEMENT" ~/select ([^.]+)\.id([^,]*),([^.]+)\.firstName([^,]*),([^.]+)\.lastName (.*)from Customer (.*)where ([^.]+)\.id( ?)=( ?)\?/ + "$SemanticAttributes.DB_OPERATION" "SELECT" + "$SemanticAttributes.DB_SQL_TABLE" "Customer" + } + } + } + span(2 + offset) { + name "Session.delete spring.jpa.Customer" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" String + } + } + span(3 + offset) { + name "Transaction.commit" + kind INTERNAL + childOf span(0) + attributes { + "hibernate.session_id" String + } + } + span(4 + offset) { + name "DELETE test.Customer" + kind CLIENT + attributes { + "$SemanticAttributes.DB_SYSTEM" "hsqldb" + "$SemanticAttributes.DB_NAME" "test" + "$SemanticAttributes.DB_USER" "sa" + "$SemanticAttributes.DB_CONNECTION_STRING" "hsqldb:mem:" + "$SemanticAttributes.DB_STATEMENT" "delete from Customer where id=?" + "$SemanticAttributes.DB_OPERATION" "DELETE" + "$SemanticAttributes.DB_SQL_TABLE" "Customer" + } + } + } + } + } +} diff --git a/instrumentation/hibernate/hibernate-6.0/spring-testing/src/test/java/spring/jpa/Customer.java b/instrumentation/hibernate/hibernate-6.0/spring-testing/src/test/java/spring/jpa/Customer.java new file mode 100644 index 0000000000..c98d8046d5 --- /dev/null +++ b/instrumentation/hibernate/hibernate-6.0/spring-testing/src/test/java/spring/jpa/Customer.java @@ -0,0 +1,79 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package spring.jpa; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import java.util.Objects; +import javax.annotation.Nullable; + +@Entity +public class Customer { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String firstName; + private String lastName; + + protected Customer() {} + + public Customer(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + @Override + public String toString() { + return String.format("Customer[id=%d, firstName='%s', lastName='%s']", id, firstName, lastName); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Customer)) { + return false; + } + Customer other = (Customer) obj; + return Objects.equals(id, other.id) + && Objects.equals(firstName, other.firstName) + && Objects.equals(lastName, other.lastName); + } + + @Override + public int hashCode() { + return Objects.hash(id, firstName, lastName); + } +} diff --git a/instrumentation/hibernate/hibernate-6.0/spring-testing/src/test/java/spring/jpa/CustomerRepository.java b/instrumentation/hibernate/hibernate-6.0/spring-testing/src/test/java/spring/jpa/CustomerRepository.java new file mode 100644 index 0000000000..9662f2a4b5 --- /dev/null +++ b/instrumentation/hibernate/hibernate-6.0/spring-testing/src/test/java/spring/jpa/CustomerRepository.java @@ -0,0 +1,14 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package spring.jpa; + +import java.util.List; +import org.springframework.data.repository.CrudRepository; + +public interface CustomerRepository extends CrudRepository { + + List findByLastName(String lastName); +} diff --git a/instrumentation/hibernate/hibernate-6.0/spring-testing/src/test/java/spring/jpa/PersistenceConfig.java b/instrumentation/hibernate/hibernate-6.0/spring-testing/src/test/java/spring/jpa/PersistenceConfig.java new file mode 100644 index 0000000000..d161e7526a --- /dev/null +++ b/instrumentation/hibernate/hibernate-6.0/spring-testing/src/test/java/spring/jpa/PersistenceConfig.java @@ -0,0 +1,62 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package spring.jpa; + +import java.util.Properties; +import javax.sql.DataSource; +import org.springframework.context.annotation.Bean; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.jdbc.datasource.DriverManagerDataSource; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.Database; +import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; +import org.springframework.transaction.PlatformTransactionManager; + +@EnableJpaRepositories(basePackages = "spring/jpa") +public class PersistenceConfig { + + @Bean(name = "transactionManager") + public PlatformTransactionManager dbTransactionManager() { + JpaTransactionManager transactionManager = new JpaTransactionManager(); + transactionManager.setEntityManagerFactory(entityManagerFactory().getObject()); + return transactionManager; + } + + @Bean + public LocalContainerEntityManagerFactoryBean entityManagerFactory() { + + HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); + vendorAdapter.setDatabase(Database.HSQL); + vendorAdapter.setGenerateDdl(true); + + LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); + em.setDataSource(dataSource()); + em.setPackagesToScan("spring/jpa"); + em.setJpaVendorAdapter(vendorAdapter); + em.setJpaProperties(additionalProperties()); + + return em; + } + + @Bean + public DataSource dataSource() { + DriverManagerDataSource dataSource = new DriverManagerDataSource(); + dataSource.setDriverClassName("org.hsqldb.jdbcDriver"); + dataSource.setUrl("jdbc:hsqldb:mem:test"); + dataSource.setUsername("sa"); + dataSource.setPassword("1"); + return dataSource; + } + + private static Properties additionalProperties() { + Properties properties = new Properties(); + properties.setProperty("hibernate.show_sql", "true"); + properties.setProperty("hibernate.hbm2ddl.auto", "create"); + properties.setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect"); + return properties; + } +} diff --git a/instrumentation/hibernate/hibernate-procedure-call-4.3/javaagent/src/test/groovy/ProcedureCallTest.groovy b/instrumentation/hibernate/hibernate-procedure-call-4.3/javaagent/src/test/groovy/ProcedureCallTest.groovy index 69384b279c..86345ba2f8 100644 --- a/instrumentation/hibernate/hibernate-procedure-call-4.3/javaagent/src/test/groovy/ProcedureCallTest.groovy +++ b/instrumentation/hibernate/hibernate-procedure-call-4.3/javaagent/src/test/groovy/ProcedureCallTest.groovy @@ -10,7 +10,6 @@ import org.hibernate.SessionFactory import org.hibernate.cfg.Configuration import org.hibernate.exception.SQLGrammarException import org.hibernate.procedure.ProcedureCall -import org.junit.jupiter.api.Assumptions import spock.lang.Shared import javax.persistence.ParameterMode @@ -57,16 +56,6 @@ class ProcedureCallTest extends AgentInstrumentationSpecification { } } - def callProcedure(ProcedureCall call) { - try { - call.getOutputs() - } catch (Exception exception) { - // ignore failures on hibernate 6 where this functionality has not been implemented yet - Assumptions.assumeFalse("org.hibernate.NotYetImplementedFor6Exception" == exception.getClass().getName()) - throw exception - } - } - def "test ProcedureCall"() { setup: @@ -75,7 +64,7 @@ class ProcedureCallTest extends AgentInstrumentationSpecification { session.beginTransaction() ProcedureCall call = session.createStoredProcedureCall("TEST_PROC") - callProcedure(call) + call.getOutputs() session.getTransaction().commit() session.close() @@ -137,10 +126,9 @@ class ProcedureCallTest extends AgentInstrumentationSpecification { ProcedureCall call = session.createStoredProcedureCall("TEST_PROC") def parameterRegistration = call.registerParameter("nonexistent", Long, ParameterMode.IN) - Assumptions.assumeTrue(parameterRegistration.metaClass.getMetaMethod("bindValue", Object) != null) parameterRegistration.bindValue(420L) try { - callProcedure(call) + call.getOutputs() } catch (Exception e) { // We expected this. } diff --git a/instrumentation/hibernate/hibernate-procedure-call-4.3/javaagent/src/test/resources/hibernate.cfg.xml b/instrumentation/hibernate/hibernate-procedure-call-4.3/javaagent/src/test/resources/hibernate.cfg.xml index 3fb8f505cb..bbc3827dda 100644 --- a/instrumentation/hibernate/hibernate-procedure-call-4.3/javaagent/src/test/resources/hibernate.cfg.xml +++ b/instrumentation/hibernate/hibernate-procedure-call-4.3/javaagent/src/test/resources/hibernate.cfg.xml @@ -7,20 +7,6 @@ - - - - - - - - - - - - - - class,hbm org.hibernate.dialect.HSQLDialect true diff --git a/settings.gradle.kts b/settings.gradle.kts index 4e0ff186ab..575a9e9147 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -232,6 +232,8 @@ hideFromDependabot(":instrumentation:guava-10.0:library") hideFromDependabot(":instrumentation:gwt-2.0:javaagent") hideFromDependabot(":instrumentation:hibernate:hibernate-3.3:javaagent") hideFromDependabot(":instrumentation:hibernate:hibernate-4.0:javaagent") +hideFromDependabot(":instrumentation:hibernate:hibernate-6.0:javaagent") +hideFromDependabot(":instrumentation:hibernate:hibernate-6.0:spring-testing") hideFromDependabot(":instrumentation:hibernate:hibernate-common:javaagent") hideFromDependabot(":instrumentation:hibernate:hibernate-procedure-call-4.3:javaagent") hideFromDependabot(":instrumentation:hikaricp-3.0:javaagent")