Remove hibernate session spans (#4538)

This commit is contained in:
Lauri Tulmin 2021-10-29 21:08:33 +03:00 committed by GitHub
parent 37e24ec924
commit 0f01bbbb18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 961 additions and 1129 deletions

View File

@ -48,3 +48,8 @@ if (findProperty("testLatestDeps") as Boolean) {
}
}
}
tasks.withType<Test>().configureEach {
// TODO run tests both with and without experimental span attributes
jvmArgs("-Dotel.instrumentation.hibernate.experimental-span-attributes=true")
}

View File

@ -7,6 +7,7 @@ package io.opentelemetry.javaagent.instrumentation.hibernate.v3_3;
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.HibernateSingletons.instrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
@ -17,7 +18,9 @@ import io.opentelemetry.instrumentation.api.field.VirtualField;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.api.CallDepth;
import io.opentelemetry.javaagent.instrumentation.hibernate.SessionMethodUtils;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
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;
@ -51,33 +54,39 @@ public class CriteriaInstrumentation implements TypeInstrumentation {
@Advice.This Criteria criteria,
@Advice.Origin("#m") String name,
@Advice.Local("otelCallDepth") CallDepth callDepth,
@Advice.Local("otelHibernateOperation") HibernateOperation hibernateOperation,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
callDepth = CallDepth.forClass(SessionMethodUtils.class);
callDepth = CallDepth.forClass(HibernateOperation.class);
if (callDepth.getAndIncrement() > 0) {
return;
}
VirtualField<Criteria, Context> virtualField =
VirtualField.find(Criteria.class, Context.class);
String entityName = null;
if (criteria instanceof CriteriaImpl) {
entityName = ((CriteriaImpl) criteria).getEntityOrClassName();
}
context =
SessionMethodUtils.startSpanFrom(virtualField, criteria, "Criteria." + name, entityName);
if (context != null) {
scope = context.makeCurrent();
VirtualField<Criteria, SessionInfo> criteriaVirtualField =
VirtualField.find(Criteria.class, SessionInfo.class);
SessionInfo sessionInfo = criteriaVirtualField.get(criteria);
Context parentContext = Java8BytecodeBridge.currentContext();
hibernateOperation = new HibernateOperation("Criteria." + 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) {
@ -87,7 +96,7 @@ public class CriteriaInstrumentation implements TypeInstrumentation {
if (scope != null) {
scope.close();
SessionMethodUtils.end(context, throwable);
instrumenter().end(context, hibernateOperation, null, throwable);
}
}
}

View File

@ -7,6 +7,8 @@ package io.opentelemetry.javaagent.instrumentation.hibernate.v3_3;
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.HibernateSingletons.instrumenter;
import static io.opentelemetry.javaagent.instrumentation.hibernate.OperationNameUtil.getOperationNameForQuery;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
@ -17,7 +19,9 @@ import io.opentelemetry.instrumentation.api.field.VirtualField;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.api.CallDepth;
import io.opentelemetry.javaagent.instrumentation.hibernate.SessionMethodUtils;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
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;
@ -49,26 +53,35 @@ public class QueryInstrumentation implements TypeInstrumentation {
public static void startMethod(
@Advice.This Query query,
@Advice.Local("otelCallDepth") CallDepth callDepth,
@Advice.Local("otelHibernateOperation") HibernateOperation hibernateOperation,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
callDepth = CallDepth.forClass(SessionMethodUtils.class);
callDepth = CallDepth.forClass(HibernateOperation.class);
if (callDepth.getAndIncrement() > 0) {
return;
}
VirtualField<Query, Context> virtualField = VirtualField.find(Query.class, Context.class);
VirtualField<Query, SessionInfo> criteriaVirtualField =
VirtualField.find(Query.class, SessionInfo.class);
SessionInfo sessionInfo = criteriaVirtualField.get(query);
context = SessionMethodUtils.startSpanFromQuery(virtualField, query, query.getQueryString());
if (context != null) {
scope = context.makeCurrent();
Context parentContext = Java8BytecodeBridge.currentContext();
hibernateOperation =
new HibernateOperation(getOperationNameForQuery(query.getQueryString()), 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) {
@ -78,7 +91,7 @@ public class QueryInstrumentation implements TypeInstrumentation {
if (scope != null) {
scope.close();
SessionMethodUtils.end(context, throwable);
instrumenter().end(context, hibernateOperation, null, throwable);
}
}
}

View File

@ -7,18 +7,16 @@ package io.opentelemetry.javaagent.instrumentation.hibernate.v3_3;
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.HibernateSingletons.instrumenter;
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;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.field.VirtualField;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
import io.opentelemetry.javaagent.instrumentation.hibernate.SessionInfo;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
@ -56,17 +54,14 @@ public class SessionFactoryInstrumentation implements TypeInstrumentation {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void openSession(@Advice.Return Object session) {
Context parentContext = Java8BytecodeBridge.currentContext();
Context context = instrumenter().start(parentContext, "Session");
if (session instanceof Session) {
VirtualField<Session, Context> virtualField =
VirtualField.find(Session.class, Context.class);
virtualField.set((Session) session, context);
VirtualField<Session, SessionInfo> virtualField =
VirtualField.find(Session.class, SessionInfo.class);
virtualField.set((Session) session, new SessionInfo());
} else if (session instanceof StatelessSession) {
VirtualField<StatelessSession, Context> virtualField =
VirtualField.find(StatelessSession.class, Context.class);
virtualField.set((StatelessSession) session, context);
VirtualField<StatelessSession, SessionInfo> virtualField =
VirtualField.find(StatelessSession.class, SessionInfo.class);
virtualField.set((StatelessSession) session, new SessionInfo());
}
}
}

View File

@ -8,14 +8,13 @@ package io.opentelemetry.javaagent.instrumentation.hibernate.v3_3;
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.HibernateSingletons.instrumenter;
import static io.opentelemetry.javaagent.instrumentation.hibernate.SessionMethodUtils.SCOPE_ONLY_METHODS;
import static io.opentelemetry.javaagent.instrumentation.hibernate.SessionMethodUtils.getEntityName;
import static io.opentelemetry.javaagent.instrumentation.hibernate.OperationNameUtil.getEntityName;
import static io.opentelemetry.javaagent.instrumentation.hibernate.OperationNameUtil.getSessionMethodOperationName;
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 static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
@ -23,14 +22,14 @@ import io.opentelemetry.instrumentation.api.field.VirtualField;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.api.CallDepth;
import io.opentelemetry.javaagent.instrumentation.hibernate.SessionMethodUtils;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
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.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.StatelessSession;
import org.hibernate.Transaction;
public class SessionInstrumentation implements TypeInstrumentation {
@ -48,10 +47,6 @@ public class SessionInstrumentation implements TypeInstrumentation {
@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod().and(named("close")).and(takesArguments(0)),
SessionInstrumentation.class.getName() + "$SessionCloseAdvice");
// Session synchronous methods we want to instrument.
transformer.applyAdviceToMethod(
isMethod()
@ -66,10 +61,7 @@ public class SessionInstrumentation implements TypeInstrumentation {
"lock",
"refresh",
"insert",
"delete",
// Lazy-load methods.
"immediateLoad",
"internalLoad")),
"delete")),
SessionInstrumentation.class.getName() + "$SessionMethodAdvice");
// Handle the non-generic 'get' separately.
@ -78,7 +70,7 @@ public class SessionInstrumentation implements TypeInstrumentation {
SessionInstrumentation.class.getName() + "$SessionMethodAdvice");
// These methods return some object that we want to instrument, and so the Advice will pin the
// current Span to the returned object using a VirtualField.
// current SessionInfo to the returned object using a VirtualField.
transformer.applyAdviceToMethod(
isMethod()
.and(namedOneOf("beginTransaction", "getTransaction"))
@ -94,31 +86,6 @@ public class SessionInstrumentation implements TypeInstrumentation {
SessionInstrumentation.class.getName() + "$GetCriteriaAdvice");
}
@SuppressWarnings("unused")
public static class SessionCloseAdvice {
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void closeSession(
@Advice.This Object session, @Advice.Thrown Throwable throwable) {
Context sessionContext = null;
if (session instanceof Session) {
VirtualField<Session, Context> virtualField =
VirtualField.find(Session.class, Context.class);
sessionContext = virtualField.get((Session) session);
} else if (session instanceof StatelessSession) {
VirtualField<StatelessSession, Context> virtualField =
VirtualField.find(StatelessSession.class, Context.class);
sessionContext = virtualField.get((StatelessSession) session);
}
if (sessionContext == null) {
return;
}
instrumenter().end(sessionContext, null, null, throwable);
}
}
@SuppressWarnings("unused")
public static class SessionMethodAdvice {
@ -130,45 +97,35 @@ public class SessionInstrumentation implements TypeInstrumentation {
@Advice.Argument(0) Object arg0,
@Advice.Argument(value = 1, optional = true) Object arg1,
@Advice.Local("otelCallDepth") CallDepth callDepth,
@Advice.Local("otelContext") Context spanContext,
@Advice.Local("otelHibernateOperation") HibernateOperation hibernateOperation,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
callDepth = CallDepth.forClass(SessionMethodUtils.class);
callDepth = CallDepth.forClass(HibernateOperation.class);
if (callDepth.getAndIncrement() > 0) {
return;
}
Context sessionContext = null;
if (session instanceof Session) {
VirtualField<Session, Context> virtualField =
VirtualField.find(Session.class, Context.class);
sessionContext = virtualField.get((Session) session);
} else if (session instanceof StatelessSession) {
VirtualField<StatelessSession, Context> virtualField =
VirtualField.find(StatelessSession.class, Context.class);
sessionContext = virtualField.get((StatelessSession) session);
}
if (sessionContext == null) {
return; // No state found. We aren't in a Session.
}
if (!SCOPE_ONLY_METHODS.contains(name)) {
Context parentContext = Java8BytecodeBridge.currentContext();
SessionInfo sessionInfo = SessionUtil.getSessionInfo(session);
String entityName =
getEntityName(descriptor, arg0, arg1, EntityNameUtil.bestGuessEntityName(session));
spanContext =
SessionMethodUtils.startSpanFrom(sessionContext, "Session." + name, entityName);
scope = spanContext.makeCurrent();
} else {
scope = sessionContext.makeCurrent();
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("otelContext") Context spanContext,
@Advice.Local("otelHibernateOperation") HibernateOperation hibernateOperation,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
if (callDepth.decrementAndGet() > 0) {
@ -177,7 +134,7 @@ public class SessionInstrumentation implements TypeInstrumentation {
if (scope != null) {
scope.close();
SessionMethodUtils.end(spanContext, throwable);
instrumenter().end(context, hibernateOperation, null, throwable);
}
}
}
@ -188,19 +145,10 @@ public class SessionInstrumentation implements TypeInstrumentation {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void getQuery(@Advice.This Object session, @Advice.Return Query query) {
VirtualField<Query, Context> queryVirtualField =
VirtualField.find(Query.class, Context.class);
if (session instanceof Session) {
VirtualField<Session, Context> sessionVirtualField =
VirtualField.find(Session.class, Context.class);
SessionMethodUtils.attachSpanFromStore(
sessionVirtualField, (Session) session, queryVirtualField, query);
} else if (session instanceof StatelessSession) {
VirtualField<StatelessSession, Context> sessionVirtualField =
VirtualField.find(StatelessSession.class, Context.class);
SessionMethodUtils.attachSpanFromStore(
sessionVirtualField, (StatelessSession) session, queryVirtualField, query);
}
SessionInfo sessionInfo = SessionUtil.getSessionInfo(session);
VirtualField<Query, SessionInfo> queryVirtualField =
VirtualField.find(Query.class, SessionInfo.class);
queryVirtualField.set(query, sessionInfo);
}
}
@ -211,20 +159,10 @@ public class SessionInstrumentation implements TypeInstrumentation {
public static void getTransaction(
@Advice.This Object session, @Advice.Return Transaction transaction) {
VirtualField<Transaction, Context> transactionVirtualField =
VirtualField.find(Transaction.class, Context.class);
if (session instanceof Session) {
VirtualField<Session, Context> sessionVirtualField =
VirtualField.find(Session.class, Context.class);
SessionMethodUtils.attachSpanFromStore(
sessionVirtualField, (Session) session, transactionVirtualField, transaction);
} else if (session instanceof StatelessSession) {
VirtualField<StatelessSession, Context> sessionVirtualField =
VirtualField.find(StatelessSession.class, Context.class);
SessionMethodUtils.attachSpanFromStore(
sessionVirtualField, (StatelessSession) session, transactionVirtualField, transaction);
}
SessionInfo sessionInfo = SessionUtil.getSessionInfo(session);
VirtualField<Transaction, SessionInfo> transactionVirtualField =
VirtualField.find(Transaction.class, SessionInfo.class);
transactionVirtualField.set(transaction, sessionInfo);
}
}
@ -234,19 +172,10 @@ public class SessionInstrumentation implements TypeInstrumentation {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void getCriteria(@Advice.This Object session, @Advice.Return Criteria criteria) {
VirtualField<Criteria, Context> criteriaVirtualField =
VirtualField.find(Criteria.class, Context.class);
if (session instanceof Session) {
VirtualField<Session, Context> sessionVirtualField =
VirtualField.find(Session.class, Context.class);
SessionMethodUtils.attachSpanFromStore(
sessionVirtualField, (Session) session, criteriaVirtualField, criteria);
} else if (session instanceof StatelessSession) {
VirtualField<StatelessSession, Context> sessionVirtualField =
VirtualField.find(StatelessSession.class, Context.class);
SessionMethodUtils.attachSpanFromStore(
sessionVirtualField, (StatelessSession) session, criteriaVirtualField, criteria);
}
SessionInfo sessionInfo = SessionUtil.getSessionInfo(session);
VirtualField<Criteria, SessionInfo> criteriaVirtualField =
VirtualField.find(Criteria.class, SessionInfo.class);
criteriaVirtualField.set(criteria, sessionInfo);
}
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.hibernate.v3_3;
import io.opentelemetry.instrumentation.api.field.VirtualField;
import io.opentelemetry.javaagent.instrumentation.hibernate.SessionInfo;
import org.hibernate.Session;
import org.hibernate.StatelessSession;
public final class SessionUtil {
private static final VirtualField<Session, SessionInfo> sessionVirtualField =
VirtualField.find(Session.class, SessionInfo.class);
private static final VirtualField<StatelessSession, SessionInfo> statelessSessionVirtualField =
VirtualField.find(StatelessSession.class, SessionInfo.class);
private SessionUtil() {}
public static SessionInfo getSessionInfo(Object session) {
if (session instanceof Session) {
return sessionVirtualField.get((Session) session);
} else if (session instanceof StatelessSession) {
return statelessSessionVirtualField.get((StatelessSession) session);
}
return null;
}
}

View File

@ -7,6 +7,7 @@ package io.opentelemetry.javaagent.instrumentation.hibernate.v3_3;
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.HibernateSingletons.instrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
@ -17,7 +18,9 @@ import io.opentelemetry.instrumentation.api.field.VirtualField;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.api.CallDepth;
import io.opentelemetry.javaagent.instrumentation.hibernate.SessionMethodUtils;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
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;
@ -49,28 +52,34 @@ public class TransactionInstrumentation implements TypeInstrumentation {
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(SessionMethodUtils.class);
callDepth = CallDepth.forClass(HibernateOperation.class);
if (callDepth.getAndIncrement() > 0) {
return;
}
VirtualField<Transaction, Context> virtualField =
VirtualField.find(Transaction.class, Context.class);
VirtualField<Transaction, SessionInfo> transactionVirtualField =
VirtualField.find(Transaction.class, SessionInfo.class);
SessionInfo sessionInfo = transactionVirtualField.get(transaction);
context =
SessionMethodUtils.startSpanFrom(virtualField, transaction, "Transaction.commit", null);
if (context != null) {
scope = context.makeCurrent();
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) {
@ -80,7 +89,7 @@ public class TransactionInstrumentation implements TypeInstrumentation {
if (scope != null) {
scope.close();
SessionMethodUtils.end(context, throwable);
instrumenter().end(context, hibernateOperation, null, throwable);
}
}
}

View File

@ -16,6 +16,7 @@ class CriteriaTest extends AbstractHibernateTest {
def "test criteria.#methodName"() {
setup:
runWithSpan("parent") {
Session session = sessionFactory.openSession()
session.beginTransaction()
Criteria criteria = session.createCriteria(Value)
@ -24,12 +25,14 @@ class CriteriaTest extends AbstractHibernateTest {
interaction.call(criteria)
session.getTransaction().commit()
session.close()
}
expect:
def sessionId
assertTraces(1) {
trace(0, 4) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -40,6 +43,10 @@ class CriteriaTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId = it
it instanceof String
}
}
}
span(2) {
@ -61,6 +68,7 @@ class CriteriaTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId
}
}
}

View File

@ -16,25 +16,30 @@ class QueryTest extends AbstractHibernateTest {
setup:
// With Transaction
runWithSpan("parent") {
Session session = sessionFactory.openSession()
session.beginTransaction()
queryInteraction(session)
session.getTransaction().commit()
session.close()
}
// Without Transaction
if (!requiresTransaction) {
session = sessionFactory.openSession()
runWithSpan("parent2") {
Session session = sessionFactory.openSession()
queryInteraction(session)
session.close()
}
}
expect:
def sessionId
assertTraces(requiresTransaction ? 1 : 2) {
// With Transaction
trace(0, 4) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -45,6 +50,10 @@ class QueryTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId = it
it instanceof String
}
}
}
span(2) {
@ -65,6 +74,7 @@ class QueryTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId
}
}
}
@ -72,7 +82,7 @@ class QueryTest extends AbstractHibernateTest {
// Without Transaction
trace(1, 3) {
span(0) {
name "Session"
name "parent2"
kind INTERNAL
hasNoParent()
attributes {
@ -83,6 +93,7 @@ class QueryTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" String
}
}
span(2) {
@ -132,21 +143,24 @@ class QueryTest extends AbstractHibernateTest {
def "test hibernate query.iterate"() {
setup:
runWithSpan("parent") {
Session session = sessionFactory.openSession()
session.beginTransaction()
Query q = session.createQuery("from Value")
Iterator it = q.iterate()
while (it.hasNext()) {
it.next()
Iterator iterator = q.iterate()
while (iterator.hasNext()) {
iterator.next()
}
session.getTransaction().commit()
session.close()
}
expect:
def sessionId
assertTraces(1) {
trace(0, 4) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -157,6 +171,10 @@ class QueryTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId = it
it instanceof String
}
}
}
span(2) {
@ -178,6 +196,7 @@ class QueryTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId
}
}
}

View File

@ -28,6 +28,7 @@ class SessionTest extends AbstractHibernateTest {
// Test for each implementation of Session.
for (def buildSession : sessionImplementations) {
runWithSpan("parent") {
def session = buildSession()
session.beginTransaction()
@ -40,13 +41,15 @@ class SessionTest extends AbstractHibernateTest {
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 "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -57,6 +60,10 @@ class SessionTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId = it
it instanceof String
}
}
}
span(2) {
@ -78,6 +85,7 @@ class SessionTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId
}
}
}
@ -100,6 +108,7 @@ class SessionTest extends AbstractHibernateTest {
def "test hibernate statless action #testName"() {
setup:
runWithSpan("parent") {
// Test for each implementation of Session.
def session = statelessSessionBuilder()
session.beginTransaction()
@ -112,12 +121,14 @@ class SessionTest extends AbstractHibernateTest {
session.getTransaction().commit()
session.close()
}
expect:
def sessionId
assertTraces(1) {
trace(0, 4) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -128,6 +139,10 @@ class SessionTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId = it
it instanceof String
}
}
}
span(2) {
@ -135,6 +150,7 @@ class SessionTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId
}
}
span(3) {
@ -174,6 +190,7 @@ class SessionTest extends AbstractHibernateTest {
def "test hibernate replicate: #testName"() {
setup:
runWithSpan("parent") {
// Test for each implementation of Session.
def session = sessionFactory.openSession()
session.beginTransaction()
@ -186,12 +203,14 @@ class SessionTest extends AbstractHibernateTest {
session.getTransaction().commit()
session.close()
}
expect:
def sessionId
assertTraces(1) {
trace(0, 5) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -202,6 +221,10 @@ class SessionTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId = it
it instanceof String
}
}
}
span(2) {
@ -223,6 +246,7 @@ class SessionTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId
}
}
span(4) {
@ -259,6 +283,7 @@ class SessionTest extends AbstractHibernateTest {
def "test hibernate failed replicate"() {
setup:
runWithSpan("parent") {
// Test for each implementation of Session.
def session = sessionFactory.openSession()
session.beginTransaction()
@ -271,12 +296,14 @@ class SessionTest extends AbstractHibernateTest {
session.getTransaction().commit()
session.close()
}
expect:
def sessionId
assertTraces(1) {
trace(0, 3) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -288,12 +315,19 @@ class SessionTest extends AbstractHibernateTest {
childOf span(0)
status ERROR
errorEvent(MappingException, "Unknown entity: 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
}
}
}
@ -305,6 +339,7 @@ class SessionTest extends AbstractHibernateTest {
def "test hibernate commit action #testName"() {
setup:
runWithSpan("parent") {
def session = sessionBuilder()
session.beginTransaction()
@ -316,12 +351,14 @@ class SessionTest extends AbstractHibernateTest {
session.getTransaction().commit()
session.close()
}
expect:
def sessionId
assertTraces(1) {
trace(0, 4) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -332,6 +369,10 @@ class SessionTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId = it
it instanceof String
}
}
}
span(2) {
@ -339,6 +380,7 @@ class SessionTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId
}
}
span(3) {
@ -391,18 +433,21 @@ class SessionTest extends AbstractHibernateTest {
def "test attaches State to query created via #queryMethodName"() {
setup:
runWithSpan("parent") {
Session session = sessionFactory.openSession()
session.beginTransaction()
Query query = queryBuildMethod(session)
query.list()
session.getTransaction().commit()
session.close()
}
expect:
def sessionId
assertTraces(1) {
trace(0, 4) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -413,6 +458,10 @@ class SessionTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId = it
it instanceof String
}
}
}
span(2) {
@ -433,6 +482,7 @@ class SessionTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId
}
}
}
@ -468,45 +518,69 @@ class SessionTest extends AbstractHibernateTest {
}
expect:
def sessionId1
def sessionId2
def sessionId3
assertTraces(1) {
trace(0, 11) {
trace(0, 8) {
span(0) {
name "overlapping Sessions"
attributes {
}
}
span(1) {
name "Session"
name "Session.save Value"
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId1 = it
it instanceof String
}
}
}
span(2) {
name "Session.save Value"
name "Session.insert Value"
kind INTERNAL
childOf span(1)
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId2 = it
it instanceof String
}
}
}
span(3) {
name "Session.delete Value"
name "Session.save Value"
kind INTERNAL
childOf span(1)
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId3 = it
it instanceof String
}
}
}
span(4) {
name "Transaction.commit"
name "Session.delete Value"
kind INTERNAL
childOf span(1)
childOf span(0)
attributes {
"hibernate.session_id" sessionId1
}
}
span(5) {
name "Transaction.commit"
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId1
}
}
span(6) {
name "INSERT db1.Value"
kind CLIENT
childOf span(4)
childOf span(5)
attributes {
"${SemanticAttributes.DB_SYSTEM.key}" "h2"
"${SemanticAttributes.DB_NAME.key}" "db1"
@ -517,10 +591,10 @@ class SessionTest extends AbstractHibernateTest {
"${SemanticAttributes.DB_SQL_TABLE.key}" "Value"
}
}
span(6) {
span(7) {
name "DELETE db1.Value"
kind CLIENT
childOf span(4)
childOf span(5)
attributes {
"${SemanticAttributes.DB_SYSTEM.key}" "h2"
"${SemanticAttributes.DB_NAME.key}" "db1"
@ -531,36 +605,9 @@ class SessionTest extends AbstractHibernateTest {
"${SemanticAttributes.DB_SQL_TABLE.key}" "Value"
}
}
span(7) {
name "Session"
kind INTERNAL
childOf span(0)
attributes {
}
}
span(8) {
name "Session.insert Value"
kind INTERNAL
childOf span(7)
attributes {
}
}
span(9) {
name "Session"
kind INTERNAL
childOf span(0)
attributes {
}
}
span(10) {
name "Session.save Value"
kind INTERNAL
childOf span(9)
attributes {
}
}
}
}
sessionId1 != sessionId2 != sessionId3
}
}

View File

@ -16,9 +16,6 @@ testSets {
create("version5Test") {
dirName = "test"
}
create("version6Test") {
dirName = "hibernate6Test"
}
create("latestDepTest") {
dirName = "test"
@ -27,17 +24,9 @@ testSets {
tasks {
val version5Test by existing(Test::class)
val version6Test by existing(Test::class) {
filter {
// version6 doesn't have all the tests that older versions have
// this here prevents failure when running a test that is missing from version6 from ide
isFailOnNoMatchingTests = false
}
}
test {
dependsOn(version5Test)
dependsOn(version6Test)
}
}
@ -67,12 +56,12 @@ dependencies {
add("version5TestImplementation", "org.hibernate:hibernate-entitymanager:5.0.0.Final")
add("version5TestImplementation", "org.springframework.data:spring-data-jpa:2.3.0.RELEASE")
add("version6TestImplementation", "org.hibernate:hibernate-core:6.0.0.Alpha6")
add("version6TestImplementation", "org.hibernate:hibernate-entitymanager:6.0.0.Alpha6")
add("version6TestImplementation", "org.springframework.data:spring-data-jpa:2.3.0.RELEASE")
// hibernate 6 is alpha so use 5 as latest version
add("latestDepTestImplementation", "org.hibernate:hibernate-core:5.+")
add("latestDepTestImplementation", "org.hibernate:hibernate-entitymanager:5.+")
add("latestDepTestImplementation", "org.springframework.data:spring-data-jpa:(2.4.0,)")
}
tasks.withType<Test>().configureEach {
// TODO run tests both with and without experimental span attributes
jvmArgs("-Dotel.instrumentation.hibernate.experimental-span-attributes=true")
}

View File

@ -1,198 +0,0 @@
/*
* 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.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
/**
* Unfortunately this test verifies that our hibernate instrumentation doesn't currently work with Spring Data Repositories.
*/
class SpringJpaTest extends AgentInstrumentationSpecification {
@Shared
def context = new AnnotationConfigApplicationContext(PersistenceConfig)
@Shared
def repo = context.getBean(CustomerRepository)
def "test CRUD"() {
setup:
def customer = new Customer("Bob", "Anonymous")
expect:
customer.id == null
!repo.findAll().iterator().hasNext()
assertTraces(1) {
trace(0, 1) {
span(0) {
name "SELECT test.Customer"
kind CLIENT
attributes {
"${SemanticAttributes.DB_SYSTEM.key}" "hsqldb"
"${SemanticAttributes.DB_NAME.key}" "test"
"${SemanticAttributes.DB_USER.key}" "sa"
"${SemanticAttributes.DB_CONNECTION_STRING.key}" "hsqldb:mem:"
"${SemanticAttributes.DB_STATEMENT.key}" ~/select ([^.]+)\.id([^,]*), ([^.]+)\.firstName([^,]*), ([^.]+)\.lastName(.*)from Customer(.*)/
"${SemanticAttributes.DB_OPERATION.key}" "SELECT"
"${SemanticAttributes.DB_SQL_TABLE.key}" "Customer"
}
}
}
}
clearExportedData()
when:
repo.save(customer)
def savedId = customer.id
then:
customer.id != null
// Behavior changed in new version:
def extraTrace = traces.size() == 2
assertTraces(extraTrace ? 2 : 1) {
if (extraTrace) {
trace(0, 1) {
span(0) {
name "test"
kind CLIENT
attributes {
"${SemanticAttributes.DB_SYSTEM.key}" "hsqldb"
"${SemanticAttributes.DB_NAME.key}" "test"
"${SemanticAttributes.DB_USER.key}" "sa"
"${SemanticAttributes.DB_STATEMENT.key}" "call next value for hibernate_sequence"
"${SemanticAttributes.DB_CONNECTION_STRING.key}" "hsqldb:mem:"
}
}
}
}
trace(extraTrace ? 1 : 0, 1) {
span(0) {
name "INSERT test.Customer"
kind CLIENT
attributes {
"${SemanticAttributes.DB_SYSTEM.key}" "hsqldb"
"${SemanticAttributes.DB_NAME.key}" "test"
"${SemanticAttributes.DB_USER.key}" "sa"
"${SemanticAttributes.DB_CONNECTION_STRING.key}" "hsqldb:mem:"
"${SemanticAttributes.DB_STATEMENT.key}" ~/insert into Customer \(.*\) values \(.*, \?, \?\)/
"${SemanticAttributes.DB_OPERATION.key}" "INSERT"
"${SemanticAttributes.DB_SQL_TABLE.key}" "Customer"
}
}
}
}
clearExportedData()
when:
customer.firstName = "Bill"
repo.save(customer)
then:
customer.id == savedId
assertTraces(2) {
trace(0, 1) {
span(0) {
name "SELECT test.Customer"
kind CLIENT
attributes {
"${SemanticAttributes.DB_SYSTEM.key}" "hsqldb"
"${SemanticAttributes.DB_NAME.key}" "test"
"${SemanticAttributes.DB_USER.key}" "sa"
"${SemanticAttributes.DB_CONNECTION_STRING.key}" "hsqldb:mem:"
"${SemanticAttributes.DB_STATEMENT.key}" ~/select ([^.]+)\.id([^,]*), ([^.]+)\.firstName([^,]*), ([^.]+)\.lastName (.*)from Customer (.*)where ([^.]+)\.id( ?)=( ?)\?/
"${SemanticAttributes.DB_OPERATION.key}" "SELECT"
"${SemanticAttributes.DB_SQL_TABLE.key}" "Customer"
}
}
}
trace(1, 1) {
span(0) {
name "UPDATE test.Customer"
kind CLIENT
attributes {
"${SemanticAttributes.DB_SYSTEM.key}" "hsqldb"
"${SemanticAttributes.DB_NAME.key}" "test"
"${SemanticAttributes.DB_USER.key}" "sa"
"${SemanticAttributes.DB_CONNECTION_STRING.key}" "hsqldb:mem:"
"${SemanticAttributes.DB_STATEMENT.key}" "update Customer set firstName=?, lastName=? where id=?"
"${SemanticAttributes.DB_OPERATION.key}" "UPDATE"
"${SemanticAttributes.DB_SQL_TABLE.key}" "Customer"
}
}
}
}
clearExportedData()
when:
customer = repo.findByLastName("Anonymous")[0]
then:
customer.id == savedId
customer.firstName == "Bill"
assertTraces(1) {
trace(0, 1) {
span(0) {
name "SELECT test.Customer"
kind CLIENT
attributes {
"${SemanticAttributes.DB_SYSTEM.key}" "hsqldb"
"${SemanticAttributes.DB_NAME.key}" "test"
"${SemanticAttributes.DB_USER.key}" "sa"
"${SemanticAttributes.DB_CONNECTION_STRING.key}" "hsqldb:mem:"
"${SemanticAttributes.DB_STATEMENT.key}" ~/select ([^.]+)\.id([^,]*), ([^.]+)\.firstName([^,]*), ([^.]+)\.lastName (.*)from Customer (.*)(where ([^.]+)\.lastName( ?)=( ?)\?|)/
"${SemanticAttributes.DB_OPERATION.key}" "SELECT"
"${SemanticAttributes.DB_SQL_TABLE.key}" "Customer"
}
}
}
}
clearExportedData()
when:
repo.delete(customer)
then:
assertTraces(2) {
trace(0, 1) {
span(0) {
name "SELECT test.Customer"
kind CLIENT
attributes {
"${SemanticAttributes.DB_SYSTEM.key}" "hsqldb"
"${SemanticAttributes.DB_NAME.key}" "test"
"${SemanticAttributes.DB_USER.key}" "sa"
"${SemanticAttributes.DB_CONNECTION_STRING.key}" "hsqldb:mem:"
"${SemanticAttributes.DB_STATEMENT.key}" ~/select ([^.]+)\.id([^,]*), ([^.]+)\.firstName([^,]*), ([^.]+)\.lastName (.*)from Customer (.*)where ([^.]+)\.id( ?)=( ?)\?/
"${SemanticAttributes.DB_OPERATION.key}" "SELECT"
"${SemanticAttributes.DB_SQL_TABLE.key}" "Customer"
}
}
}
trace(1, 1) {
span(0) {
name "DELETE test.Customer"
kind CLIENT
attributes {
"${SemanticAttributes.DB_SYSTEM.key}" "hsqldb"
"${SemanticAttributes.DB_NAME.key}" "test"
"${SemanticAttributes.DB_USER.key}" "sa"
"${SemanticAttributes.DB_CONNECTION_STRING.key}" "hsqldb:mem:"
"${SemanticAttributes.DB_STATEMENT.key}" "delete from Customer where id=?"
"${SemanticAttributes.DB_OPERATION.key}" "DELETE"
"${SemanticAttributes.DB_SQL_TABLE.key}" "Customer"
}
}
}
}
}
}

View File

@ -1,45 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.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;
}
}

View File

@ -1,78 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package spring.jpa;
import java.util.Objects;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@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(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);
}
}

View File

@ -1,14 +0,0 @@
/*
* 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<Customer, Long> {
List<Customer> findByLastName(String lastName);
}

View File

@ -1,62 +0,0 @@
/*
* 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;
}
}

View File

@ -7,6 +7,7 @@ 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 io.opentelemetry.javaagent.instrumentation.hibernate.HibernateSingletons.instrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
@ -17,7 +18,9 @@ import io.opentelemetry.instrumentation.api.field.VirtualField;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.api.CallDepth;
import io.opentelemetry.javaagent.instrumentation.hibernate.SessionMethodUtils;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
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;
@ -51,33 +54,39 @@ public class CriteriaInstrumentation implements TypeInstrumentation {
@Advice.This Criteria criteria,
@Advice.Origin("#m") String name,
@Advice.Local("otelCallDepth") CallDepth callDepth,
@Advice.Local("otelHibernateOperation") HibernateOperation hibernateOperation,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
callDepth = CallDepth.forClass(SessionMethodUtils.class);
callDepth = CallDepth.forClass(HibernateOperation.class);
if (callDepth.getAndIncrement() > 0) {
return;
}
VirtualField<Criteria, Context> virtualField =
VirtualField.find(Criteria.class, Context.class);
String entityName = null;
if (criteria instanceof CriteriaImpl) {
entityName = ((CriteriaImpl) criteria).getEntityOrClassName();
}
context =
SessionMethodUtils.startSpanFrom(virtualField, criteria, "Criteria." + name, entityName);
if (context != null) {
scope = context.makeCurrent();
VirtualField<Criteria, SessionInfo> criteriaVirtualField =
VirtualField.find(Criteria.class, SessionInfo.class);
SessionInfo sessionInfo = criteriaVirtualField.get(criteria);
Context parentContext = Java8BytecodeBridge.currentContext();
hibernateOperation = new HibernateOperation("Criteria." + 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) {
@ -87,7 +96,7 @@ public class CriteriaInstrumentation implements TypeInstrumentation {
if (scope != null) {
scope.close();
SessionMethodUtils.end(context, throwable);
instrumenter().end(context, hibernateOperation, null, throwable);
}
}
}

View File

@ -7,6 +7,8 @@ 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 io.opentelemetry.javaagent.instrumentation.hibernate.HibernateSingletons.instrumenter;
import static io.opentelemetry.javaagent.instrumentation.hibernate.OperationNameUtil.getOperationNameForQuery;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
@ -17,7 +19,9 @@ import io.opentelemetry.instrumentation.api.field.VirtualField;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.api.CallDepth;
import io.opentelemetry.javaagent.instrumentation.hibernate.SessionMethodUtils;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
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;
@ -49,26 +53,35 @@ public class QueryInstrumentation implements TypeInstrumentation {
public static void startMethod(
@Advice.This Query query,
@Advice.Local("otelCallDepth") CallDepth callDepth,
@Advice.Local("otelHibernateOperation") HibernateOperation hibernateOperation,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
callDepth = CallDepth.forClass(SessionMethodUtils.class);
callDepth = CallDepth.forClass(HibernateOperation.class);
if (callDepth.getAndIncrement() > 0) {
return;
}
VirtualField<Query, Context> virtualField = VirtualField.find(Query.class, Context.class);
VirtualField<Query, SessionInfo> criteriaVirtualField =
VirtualField.find(Query.class, SessionInfo.class);
SessionInfo sessionInfo = criteriaVirtualField.get(query);
context = SessionMethodUtils.startSpanFromQuery(virtualField, query, query.getQueryString());
if (context != null) {
scope = context.makeCurrent();
Context parentContext = Java8BytecodeBridge.currentContext();
hibernateOperation =
new HibernateOperation(getOperationNameForQuery(query.getQueryString()), 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) {
@ -78,7 +91,7 @@ public class QueryInstrumentation implements TypeInstrumentation {
if (scope != null) {
scope.close();
SessionMethodUtils.end(context, throwable);
instrumenter().end(context, hibernateOperation, null, throwable);
}
}
}

View File

@ -7,18 +7,16 @@ 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 io.opentelemetry.javaagent.instrumentation.hibernate.HibernateSingletons.instrumenter;
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;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.field.VirtualField;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
import io.opentelemetry.javaagent.instrumentation.hibernate.SessionInfo;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
@ -53,12 +51,9 @@ public class SessionFactoryInstrumentation implements TypeInstrumentation {
@Advice.OnMethodExit(suppress = Throwable.class)
public static void openSession(@Advice.Return SharedSessionContract session) {
Context parentContext = Java8BytecodeBridge.currentContext();
Context context = instrumenter().start(parentContext, "Session");
VirtualField<SharedSessionContract, Context> virtualField =
VirtualField.find(SharedSessionContract.class, Context.class);
virtualField.set(session, context);
VirtualField<SharedSessionContract, SessionInfo> virtualField =
VirtualField.find(SharedSessionContract.class, SessionInfo.class);
virtualField.set(session, new SessionInfo());
}
}
}

View File

@ -8,15 +8,13 @@ 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 io.opentelemetry.javaagent.instrumentation.hibernate.HibernateSingletons.instrumenter;
import static io.opentelemetry.javaagent.instrumentation.hibernate.SessionMethodUtils.SCOPE_ONLY_METHODS;
import static io.opentelemetry.javaagent.instrumentation.hibernate.SessionMethodUtils.getEntityName;
import static io.opentelemetry.javaagent.instrumentation.hibernate.SessionMethodUtils.getSessionMethodSpanName;
import static io.opentelemetry.javaagent.instrumentation.hibernate.OperationNameUtil.getEntityName;
import static io.opentelemetry.javaagent.instrumentation.hibernate.OperationNameUtil.getSessionMethodOperationName;
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 static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
@ -24,7 +22,9 @@ import io.opentelemetry.instrumentation.api.field.VirtualField;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.api.CallDepth;
import io.opentelemetry.javaagent.instrumentation.hibernate.SessionMethodUtils;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
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;
@ -47,9 +47,6 @@ public class SessionInstrumentation implements TypeInstrumentation {
@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod().and(named("close")).and(takesArguments(0)),
SessionInstrumentation.class.getName() + "$SessionCloseAdvice");
// Session synchronous methods we want to instrument.
transformer.applyAdviceToMethod(
@ -66,10 +63,7 @@ public class SessionInstrumentation implements TypeInstrumentation {
"fireLock",
"refresh",
"insert",
"delete",
// Lazy-load methods.
"immediateLoad",
"internalLoad")),
"delete")),
SessionInstrumentation.class.getName() + "$SessionMethodAdvice");
// Handle the non-generic 'get' separately.
transformer.applyAdviceToMethod(
@ -80,7 +74,7 @@ public class SessionInstrumentation implements TypeInstrumentation {
SessionInstrumentation.class.getName() + "$SessionMethodAdvice");
// These methods return some object that we want to instrument, and so the Advice will pin the
// current Span to the returned object using a VirtualField.
// current SessionInfo to the returned object using a VirtualField.
transformer.applyAdviceToMethod(
isMethod()
.and(namedOneOf("beginTransaction", "getTransaction"))
@ -96,23 +90,6 @@ public class SessionInstrumentation implements TypeInstrumentation {
SessionInstrumentation.class.getName() + "$GetCriteriaAdvice");
}
@SuppressWarnings("unused")
public static class SessionCloseAdvice {
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void closeSession(
@Advice.This SharedSessionContract session, @Advice.Thrown Throwable throwable) {
VirtualField<SharedSessionContract, Context> virtualField =
VirtualField.find(SharedSessionContract.class, Context.class);
Context sessionContext = virtualField.get(session);
if (sessionContext == null) {
return;
}
instrumenter().end(sessionContext, null, null, throwable);
}
}
@SuppressWarnings("unused")
public static class SessionMethodAdvice {
@ -124,39 +101,38 @@ public class SessionInstrumentation implements TypeInstrumentation {
@Advice.Argument(0) Object arg0,
@Advice.Argument(value = 1, optional = true) Object arg1,
@Advice.Local("otelCallDepth") CallDepth callDepth,
@Advice.Local("otelContext") Context spanContext,
@Advice.Local("otelHibernateOperation") HibernateOperation hibernateOperation,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
callDepth = CallDepth.forClass(SessionMethodUtils.class);
callDepth = CallDepth.forClass(HibernateOperation.class);
if (callDepth.getAndIncrement() > 0) {
return;
}
VirtualField<SharedSessionContract, Context> virtualField =
VirtualField.find(SharedSessionContract.class, Context.class);
Context sessionContext = virtualField.get(session);
VirtualField<SharedSessionContract, SessionInfo> virtualField =
VirtualField.find(SharedSessionContract.class, SessionInfo.class);
SessionInfo sessionInfo = virtualField.get(session);
if (sessionContext == null) {
return; // No state found. We aren't in a Session.
}
if (!SCOPE_ONLY_METHODS.contains(name)) {
Context parentContext = Java8BytecodeBridge.currentContext();
String entityName =
getEntityName(descriptor, arg0, arg1, EntityNameUtil.bestGuessEntityName(session));
spanContext =
SessionMethodUtils.startSpanFrom(
sessionContext, getSessionMethodSpanName(name), entityName);
scope = spanContext.makeCurrent();
} else {
scope = sessionContext.makeCurrent();
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("otelContext") Context spanContext,
@Advice.Local("otelHibernateOperation") HibernateOperation hibernateOperation,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
if (callDepth.decrementAndGet() > 0) {
@ -165,7 +141,7 @@ public class SessionInstrumentation implements TypeInstrumentation {
if (scope != null) {
scope.close();
SessionMethodUtils.end(spanContext, throwable);
instrumenter().end(context, hibernateOperation, null, throwable);
}
}
}
@ -177,13 +153,12 @@ public class SessionInstrumentation implements TypeInstrumentation {
public static void getQuery(
@Advice.This SharedSessionContract session, @Advice.Return Query query) {
VirtualField<SharedSessionContract, Context> sessionVirtualField =
VirtualField.find(SharedSessionContract.class, Context.class);
VirtualField<Query, Context> queryVirtualField =
VirtualField.find(Query.class, Context.class);
VirtualField<SharedSessionContract, SessionInfo> sessionVirtualField =
VirtualField.find(SharedSessionContract.class, SessionInfo.class);
VirtualField<Query, SessionInfo> queryVirtualField =
VirtualField.find(Query.class, SessionInfo.class);
SessionMethodUtils.attachSpanFromStore(
sessionVirtualField, session, queryVirtualField, query);
queryVirtualField.set(query, sessionVirtualField.get(session));
}
}
@ -194,13 +169,12 @@ public class SessionInstrumentation implements TypeInstrumentation {
public static void getTransaction(
@Advice.This SharedSessionContract session, @Advice.Return Transaction transaction) {
VirtualField<SharedSessionContract, Context> sessionVirtualField =
VirtualField.find(SharedSessionContract.class, Context.class);
VirtualField<Transaction, Context> transactionVirtualField =
VirtualField.find(Transaction.class, Context.class);
VirtualField<SharedSessionContract, SessionInfo> sessionVirtualField =
VirtualField.find(SharedSessionContract.class, SessionInfo.class);
VirtualField<Transaction, SessionInfo> transactionVirtualField =
VirtualField.find(Transaction.class, SessionInfo.class);
SessionMethodUtils.attachSpanFromStore(
sessionVirtualField, session, transactionVirtualField, transaction);
transactionVirtualField.set(transaction, sessionVirtualField.get(session));
}
}
@ -211,13 +185,12 @@ public class SessionInstrumentation implements TypeInstrumentation {
public static void getCriteria(
@Advice.This SharedSessionContract session, @Advice.Return Criteria criteria) {
VirtualField<SharedSessionContract, Context> sessionVirtualField =
VirtualField.find(SharedSessionContract.class, Context.class);
VirtualField<Criteria, Context> criteriaVirtualField =
VirtualField.find(Criteria.class, Context.class);
VirtualField<SharedSessionContract, SessionInfo> sessionVirtualField =
VirtualField.find(SharedSessionContract.class, SessionInfo.class);
VirtualField<Criteria, SessionInfo> criteriaVirtualField =
VirtualField.find(Criteria.class, SessionInfo.class);
SessionMethodUtils.attachSpanFromStore(
sessionVirtualField, session, criteriaVirtualField, criteria);
criteriaVirtualField.set(criteria, sessionVirtualField.get(session));
}
}
}

View File

@ -7,6 +7,7 @@ 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 io.opentelemetry.javaagent.instrumentation.hibernate.HibernateSingletons.instrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
@ -17,7 +18,9 @@ import io.opentelemetry.instrumentation.api.field.VirtualField;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.api.CallDepth;
import io.opentelemetry.javaagent.instrumentation.hibernate.SessionMethodUtils;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
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;
@ -49,28 +52,34 @@ public class TransactionInstrumentation implements TypeInstrumentation {
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(SessionMethodUtils.class);
callDepth = CallDepth.forClass(HibernateOperation.class);
if (callDepth.getAndIncrement() > 0) {
return;
}
VirtualField<Transaction, Context> virtualField =
VirtualField.find(Transaction.class, Context.class);
VirtualField<Transaction, SessionInfo> transactionVirtualField =
VirtualField.find(Transaction.class, SessionInfo.class);
SessionInfo sessionInfo = transactionVirtualField.get(transaction);
context =
SessionMethodUtils.startSpanFrom(virtualField, transaction, "Transaction.commit", null);
if (context != null) {
scope = context.makeCurrent();
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) {
@ -80,7 +89,7 @@ public class TransactionInstrumentation implements TypeInstrumentation {
if (scope != null) {
scope.close();
SessionMethodUtils.end(context, throwable);
instrumenter().end(context, hibernateOperation, null, throwable);
}
}
}

View File

@ -16,6 +16,7 @@ class CriteriaTest extends AbstractHibernateTest {
def "test criteria.#methodName"() {
setup:
runWithSpan("parent") {
Session session = sessionFactory.openSession()
session.beginTransaction()
Criteria criteria = session.createCriteria(Value)
@ -24,12 +25,14 @@ class CriteriaTest extends AbstractHibernateTest {
interaction.call(criteria)
session.getTransaction().commit()
session.close()
}
expect:
def sessionId
assertTraces(1) {
trace(0, 4) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -40,6 +43,10 @@ class CriteriaTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId = it
it instanceof String
}
}
}
span(2) {
@ -61,6 +68,7 @@ class CriteriaTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId
}
}
}

View File

@ -38,6 +38,7 @@ class EntityManagerTest extends AbstractHibernateTest {
}
when:
runWithSpan("parent") {
try {
sessionMethodTest.call(entityManager, entity)
} catch (Exception e) {
@ -46,13 +47,15 @@ class EntityManagerTest extends AbstractHibernateTest {
entityTransaction.commit()
entityManager.close()
}
then:
boolean isPersistTest = "persist" == testName
def sessionId
assertTraces(1) {
trace(0, 4 + (isPersistTest ? 1 : 0)) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -63,6 +66,10 @@ class EntityManagerTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId = it
it instanceof String
}
}
}
@ -105,6 +112,7 @@ class EntityManagerTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId
}
}
} else {
@ -113,6 +121,7 @@ class EntityManagerTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId
}
}
span(3 + offset) {
@ -158,6 +167,7 @@ class EntityManagerTest extends AbstractHibernateTest {
@Unroll
def "test attaches State to query created via #queryMethodName"() {
setup:
runWithSpan("parent") {
EntityManager entityManager = entityManagerFactory.createEntityManager()
EntityTransaction entityTransaction = entityManager.getTransaction()
entityTransaction.begin()
@ -165,12 +175,14 @@ class EntityManagerTest extends AbstractHibernateTest {
query.getResultList()
entityTransaction.commit()
entityManager.close()
}
expect:
def sessionId
assertTraces(1) {
trace(0, 4) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -181,6 +193,10 @@ class EntityManagerTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId = it
it instanceof String
}
}
}
span(2) {
@ -202,6 +218,7 @@ class EntityManagerTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId
}
}
}

View File

@ -16,25 +16,30 @@ class QueryTest extends AbstractHibernateTest {
setup:
// With Transaction
runWithSpan("parent") {
Session session = sessionFactory.openSession()
session.beginTransaction()
queryInteraction(session)
session.getTransaction().commit()
session.close()
}
// Without Transaction
if (!requiresTransaction) {
session = sessionFactory.openSession()
runWithSpan("parent2") {
Session session = sessionFactory.openSession()
queryInteraction(session)
session.close()
}
}
expect:
def sessionId
assertTraces(requiresTransaction ? 1 : 2) {
// With Transaction
trace(0, 4) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -45,6 +50,10 @@ class QueryTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId = it
it instanceof String
}
}
}
span(2) {
@ -65,6 +74,7 @@ class QueryTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId
}
}
}
@ -72,7 +82,7 @@ class QueryTest extends AbstractHibernateTest {
// Without Transaction
trace(1, 3) {
span(0) {
name "Session"
name "parent2"
kind INTERNAL
hasNoParent()
attributes {
@ -83,6 +93,7 @@ class QueryTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" String
}
}
span(2) {
@ -132,21 +143,24 @@ class QueryTest extends AbstractHibernateTest {
def "test hibernate query.iterate"() {
setup:
runWithSpan("parent") {
Session session = sessionFactory.openSession()
session.beginTransaction()
Query q = session.createQuery("from Value")
Iterator it = q.iterate()
while (it.hasNext()) {
it.next()
Iterator iterator = q.iterate()
while (iterator.hasNext()) {
iterator.next()
}
session.getTransaction().commit()
session.close()
}
expect:
def sessionId
assertTraces(1) {
trace(0, 4) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -157,6 +171,10 @@ class QueryTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId = it
it instanceof String
}
}
}
span(2) {
@ -178,6 +196,7 @@ class QueryTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId
}
}
}

View File

@ -29,6 +29,7 @@ class SessionTest extends AbstractHibernateTest {
// Test for each implementation of Session.
for (def buildSession : sessionImplementations) {
runWithSpan("parent") {
def session = buildSession()
session.beginTransaction()
@ -41,13 +42,15 @@ class SessionTest extends AbstractHibernateTest {
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 "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -58,6 +61,10 @@ class SessionTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId = it
it instanceof String
}
}
}
span(2) {
@ -78,6 +85,7 @@ class SessionTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId
}
}
}
@ -142,6 +150,7 @@ class SessionTest extends AbstractHibernateTest {
setup:
// Test for each implementation of Session.
runWithSpan("parent") {
def session = sessionFactory.openSession()
session.beginTransaction()
@ -153,12 +162,14 @@ class SessionTest extends AbstractHibernateTest {
session.getTransaction().commit()
session.close()
}
expect:
def sessionId
assertTraces(1) {
trace(0, 5) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -169,6 +180,10 @@ class SessionTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId = it
it instanceof String
}
}
}
span(2) {
@ -190,6 +205,7 @@ class SessionTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId
}
}
span(4) {
@ -227,6 +243,7 @@ class SessionTest extends AbstractHibernateTest {
setup:
// Test for each implementation of Session.
runWithSpan("parent") {
def session = sessionFactory.openSession()
session.beginTransaction()
@ -238,12 +255,14 @@ class SessionTest extends AbstractHibernateTest {
session.getTransaction().commit()
session.close()
}
expect:
def sessionId
assertTraces(1) {
trace(0, 3) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -255,12 +274,19 @@ class SessionTest extends AbstractHibernateTest {
childOf span(0)
status ERROR
errorEvent(MappingException, "Unknown entity: 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
}
}
}
@ -272,6 +298,7 @@ class SessionTest extends AbstractHibernateTest {
def "test hibernate commit action #testName"() {
setup:
runWithSpan("parent") {
def session = sessionBuilder()
session.beginTransaction()
@ -283,12 +310,14 @@ class SessionTest extends AbstractHibernateTest {
session.getTransaction().commit()
session.close()
}
expect:
def sessionId
assertTraces(1) {
trace(0, 4) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -299,6 +328,10 @@ class SessionTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId = it
it instanceof String
}
}
}
span(2) {
@ -306,6 +339,7 @@ class SessionTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId
}
}
span(3) {
@ -378,18 +412,21 @@ class SessionTest extends AbstractHibernateTest {
def "test attaches State to query created via #queryMethodName"() {
setup:
runWithSpan("parent") {
Session session = sessionFactory.openSession()
session.beginTransaction()
Query query = queryBuildMethod(session)
query.list()
session.getTransaction().commit()
session.close()
}
expect:
def sessionId
assertTraces(1) {
trace(0, 4) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -400,6 +437,10 @@ class SessionTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId = it
it instanceof String
}
}
}
span(2) {
@ -420,6 +461,7 @@ class SessionTest extends AbstractHibernateTest {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId
}
}
}
@ -455,45 +497,42 @@ class SessionTest extends AbstractHibernateTest {
}
expect:
def sessionId1
def sessionId2
def sessionId3
assertTraces(1) {
trace(0, 12) {
trace(0, 9) {
span(0) {
name "overlapping Sessions"
attributes {
}
}
span(1) {
name "Session"
name "Session.save Value"
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId1 = it
it instanceof String
}
}
}
span(2) {
name "Session.save Value"
name "Session.insert Value"
kind INTERNAL
childOf span(1)
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId2 = it
it instanceof String
}
}
}
span(3) {
name "Session.delete Value"
kind INTERNAL
childOf span(1)
attributes {
}
}
span(4) {
name "Transaction.commit"
kind INTERNAL
childOf span(1)
attributes {
}
}
span(5) {
name "INSERT db1.Value"
kind CLIENT
childOf span(4)
childOf span(2)
attributes {
"${SemanticAttributes.DB_SYSTEM.key}" "h2"
"${SemanticAttributes.DB_NAME.key}" "db1"
@ -504,10 +543,51 @@ class SessionTest extends AbstractHibernateTest {
"${SemanticAttributes.DB_SQL_TABLE.key}" "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.key}" "h2"
"${SemanticAttributes.DB_NAME.key}" "db1"
"${SemanticAttributes.DB_USER.key}" "sa"
"${SemanticAttributes.DB_CONNECTION_STRING.key}" "h2:mem:"
"${SemanticAttributes.DB_STATEMENT.key}" ~/^insert /
"${SemanticAttributes.DB_OPERATION.key}" "INSERT"
"${SemanticAttributes.DB_SQL_TABLE.key}" "Value"
}
}
span(8) {
name "DELETE db1.Value"
kind CLIENT
childOf span(4)
childOf span(6)
attributes {
"${SemanticAttributes.DB_SYSTEM.key}" "h2"
"${SemanticAttributes.DB_NAME.key}" "db1"
@ -518,50 +598,9 @@ class SessionTest extends AbstractHibernateTest {
"${SemanticAttributes.DB_SQL_TABLE.key}" "Value"
}
}
span(7) {
name "Session"
kind INTERNAL
childOf span(0)
attributes {
}
}
span(8) {
name "Session.insert Value"
kind INTERNAL
childOf span(7)
attributes {
}
}
span(9) {
name "INSERT db1.Value"
kind CLIENT
childOf span(8)
attributes {
"${SemanticAttributes.DB_SYSTEM.key}" "h2"
"${SemanticAttributes.DB_NAME.key}" "db1"
"${SemanticAttributes.DB_USER.key}" "sa"
"${SemanticAttributes.DB_CONNECTION_STRING.key}" "h2:mem:"
"${SemanticAttributes.DB_STATEMENT.key}" ~/^insert /
"${SemanticAttributes.DB_OPERATION.key}" "INSERT"
"${SemanticAttributes.DB_SQL_TABLE.key}" "Value"
}
}
span(10) {
name "Session"
kind INTERNAL
childOf span(0)
attributes {
}
}
span(11) {
name "Session.save Value"
kind INTERNAL
childOf span(10)
attributes {
}
}
}
}
sessionId1 != sessionId2 != sessionId3
}
}

View File

@ -30,12 +30,15 @@ class SpringJpaTest extends AgentInstrumentationSpecification {
expect:
customer.id == null
!repo.findAll().iterator().hasNext()
!runWithSpan("parent") {
repo.findAll().iterator().hasNext()
}
def sessionId
assertTraces(1) {
trace(0, 4) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -46,6 +49,10 @@ class SpringJpaTest extends AgentInstrumentationSpecification {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId = it
it instanceof String
}
}
}
span(2) {
@ -67,6 +74,7 @@ class SpringJpaTest extends AgentInstrumentationSpecification {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId
}
}
}
@ -74,15 +82,18 @@ class SpringJpaTest extends AgentInstrumentationSpecification {
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 "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -93,6 +104,10 @@ class SpringJpaTest extends AgentInstrumentationSpecification {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId2 = it
it instanceof String
}
}
}
if (!isHibernate4) {
@ -113,6 +128,7 @@ class SpringJpaTest extends AgentInstrumentationSpecification {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId2
}
}
span(4) {
@ -149,6 +165,7 @@ class SpringJpaTest extends AgentInstrumentationSpecification {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId2
}
}
}
@ -158,14 +175,17 @@ class SpringJpaTest extends AgentInstrumentationSpecification {
when:
customer.firstName = "Bill"
runWithSpan("parent") {
repo.save(customer)
}
then:
customer.id == savedId
def sessionId3
assertTraces(1) {
trace(0, 5) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -176,6 +196,10 @@ class SpringJpaTest extends AgentInstrumentationSpecification {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId3 = it
it instanceof String
}
}
}
span(2) {
@ -196,6 +220,7 @@ class SpringJpaTest extends AgentInstrumentationSpecification {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" sessionId3
}
}
span(4) {
@ -216,7 +241,9 @@ class SpringJpaTest extends AgentInstrumentationSpecification {
clearExportedData()
when:
customer = repo.findByLastName("Anonymous")[0]
customer = runWithSpan("parent") {
repo.findByLastName("Anonymous")[0]
}
then:
customer.id == savedId
@ -224,7 +251,7 @@ class SpringJpaTest extends AgentInstrumentationSpecification {
assertTraces(1) {
trace(0, 3) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -235,6 +262,7 @@ class SpringJpaTest extends AgentInstrumentationSpecification {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" String
}
}
span(2) {
@ -256,13 +284,15 @@ class SpringJpaTest extends AgentInstrumentationSpecification {
clearExportedData()
when:
runWithSpan("parent") {
repo.delete(customer)
}
then:
assertTraces(1) {
trace(0, 6 + (isHibernate4 ? 0 : 1)) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -276,6 +306,7 @@ class SpringJpaTest extends AgentInstrumentationSpecification {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" String
}
}
span(2) {
@ -298,6 +329,7 @@ class SpringJpaTest extends AgentInstrumentationSpecification {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" String
}
}
if (isHibernate4) {
@ -322,6 +354,7 @@ class SpringJpaTest extends AgentInstrumentationSpecification {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" String
}
}
span(3 + offset) {
@ -329,6 +362,7 @@ class SpringJpaTest extends AgentInstrumentationSpecification {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" String
}
}
span(4 + offset) {

View File

@ -0,0 +1,29 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.hibernate;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import javax.annotation.Nullable;
class HibernateExperimentalAttributesExtractor
implements AttributesExtractor<HibernateOperation, Void> {
@Override
public void onStart(AttributesBuilder attributes, HibernateOperation hibernateOperation) {
String sessionId = hibernateOperation.getSessionId();
if (sessionId != null) {
attributes.put("hibernate.session_id", sessionId);
}
}
@Override
public void onEnd(
AttributesBuilder attributes,
HibernateOperation hibernateOperation,
@Nullable Void unused,
@Nullable Throwable error) {}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.hibernate;
public class HibernateOperation {
private final String spanName;
private final String sessionId;
public HibernateOperation(String operation, String entityName, SessionInfo sessionInfo) {
this(spanNameForOperation(operation, entityName), sessionInfo);
}
public HibernateOperation(String operation, SessionInfo sessionInfo) {
this.spanName = operation;
this.sessionId = sessionInfo != null ? sessionInfo.getSessionId() : null;
}
public String getName() {
return spanName;
}
public String getSessionId() {
return sessionId;
}
private static String spanNameForOperation(String operationName, String entityName) {
if (entityName != null) {
return operationName + " " + entityName;
}
return operationName;
}
}

View File

@ -6,20 +6,32 @@
package io.opentelemetry.javaagent.instrumentation.hibernate;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
public class HibernateSingletons {
private static final Instrumenter<String, Void> INSTANCE;
static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES =
Config.get().getBoolean("otel.instrumentation.hibernate.experimental-span-attributes", false);
private static final Instrumenter<HibernateOperation, Void> INSTANCE;
static {
INSTANCE =
Instrumenter.<String, Void>builder(
GlobalOpenTelemetry.get(), "io.opentelemetry.hibernate-common", s -> s)
.newInstrumenter();
InstrumenterBuilder<HibernateOperation, Void> instrumenterBuilder =
Instrumenter.builder(
GlobalOpenTelemetry.get(),
"io.opentelemetry.hibernate-common",
HibernateOperation::getName);
if (CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
instrumenterBuilder.addAttributesExtractor(new HibernateExperimentalAttributesExtractor());
}
public static Instrumenter<String, Void> instrumenter() {
INSTANCE = instrumenterBuilder.newInstrumenter();
}
public static Instrumenter<HibernateOperation, Void> instrumenter() {
return INSTANCE;
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.hibernate;
import io.opentelemetry.instrumentation.api.db.SqlStatementInfo;
import io.opentelemetry.instrumentation.api.db.SqlStatementSanitizer;
import java.util.function.Function;
public final class OperationNameUtil {
public static String getOperationNameForQuery(String query) {
// set operation to default value that is used when sql sanitizer fails to extract
// operation name
String operation = "Hibernate Query";
SqlStatementInfo info = SqlStatementSanitizer.sanitize(query);
if (info.getOperation() != null) {
operation = info.getOperation();
if (info.getTable() != null) {
operation += " " + info.getTable();
}
}
return operation;
}
public static String getSessionMethodOperationName(String methodName) {
if ("fireLock".equals(methodName)) {
return "Session.lock";
}
return "Session." + methodName;
}
public static String getEntityName(
String descriptor, Object arg0, Object arg1, Function<Object, String> nameFromEntity) {
String entityName = null;
// methods like save(String entityName, Object object)
// that take entity name as first argument and entity as second
// if given entity name is null compute it from entity object
if (descriptor.startsWith("(Ljava/lang/String;Ljava/lang/Object;")) {
entityName = arg0 == null ? nameFromEntity.apply(arg1) : (String) arg0;
// methods like save(Object obj)
} else if (descriptor.startsWith("(Ljava/lang/Object;")) {
entityName = nameFromEntity.apply(arg0);
// methods like get(String entityName, Serializable id)
} else if (descriptor.startsWith("(Ljava/lang/String;")) {
entityName = (String) arg0;
// methods like get(Class entityClass, Serializable id)
} else if (descriptor.startsWith("(Ljava/lang/Class;") && arg0 != null) {
entityName = ((Class<?>) arg0).getName();
}
return entityName;
}
private OperationNameUtil() {}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.hibernate;
import static io.opentelemetry.javaagent.instrumentation.hibernate.HibernateSingletons.CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES;
import java.util.UUID;
public class SessionInfo {
private final String sessionId;
public SessionInfo() {
sessionId = generateSessionId();
}
public String getSessionId() {
return sessionId;
}
private static String generateSessionId() {
if (!CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
return null;
}
return UUID.randomUUID().toString();
}
}

View File

@ -1,133 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.hibernate;
import static io.opentelemetry.javaagent.instrumentation.hibernate.HibernateSingletons.instrumenter;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.db.SqlStatementInfo;
import io.opentelemetry.instrumentation.api.db.SqlStatementSanitizer;
import io.opentelemetry.instrumentation.api.field.VirtualField;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.annotation.Nullable;
public final class SessionMethodUtils {
public static final Set<String> SCOPE_ONLY_METHODS =
new HashSet<>(Arrays.asList("immediateLoad", "internalLoad"));
public static <TARGET> Context startSpanFrom(
VirtualField<TARGET, Context> virtualField,
TARGET spanKey,
String operationName,
String entityName) {
return startSpanFrom(virtualField, spanKey, () -> operationName, entityName);
}
private static <TARGET> Context startSpanFrom(
VirtualField<TARGET, Context> virtualField,
TARGET spanKey,
Supplier<String> operationNameSupplier,
String entityName) {
Context sessionContext = virtualField.get(spanKey);
if (sessionContext == null) {
return null; // No state found. We aren't in a Session.
}
return startSpanFrom(sessionContext, operationNameSupplier.get(), entityName);
}
public static Context startSpanFrom(
Context sessionContext, String operationName, String entityName) {
return instrumenter().start(sessionContext, spanNameForOperation(operationName, entityName));
}
private static String spanNameForOperation(String operationName, String entityName) {
if (entityName != null) {
return operationName + " " + entityName;
}
return operationName;
}
public static <TARGET> Context startSpanFromQuery(
VirtualField<TARGET, Context> virtualField, TARGET spanKey, String query) {
Supplier<String> operationNameSupplier =
() -> {
// set operation to default value that is used when sql sanitizer fails to extract
// operation name
String operation = "Hibernate Query";
SqlStatementInfo info = SqlStatementSanitizer.sanitize(query);
if (info.getOperation() != null) {
operation = info.getOperation();
if (info.getTable() != null) {
operation += " " + info.getTable();
}
}
return operation;
};
return startSpanFrom(virtualField, spanKey, operationNameSupplier, null);
}
public static void end(@Nullable Context context, Throwable throwable) {
if (context == null) {
return;
}
instrumenter().end(context, null, null, throwable);
}
// Copies a span from the given Session VirtualField into the targetVirtualField. Used to
// propagate a Span from a Session to transient Session objects such as Transaction and Query.
public static <S, T> void attachSpanFromStore(
VirtualField<S, Context> sourceVirtualField,
S source,
VirtualField<T, Context> targetVirtualField,
T target) {
Context sessionContext = sourceVirtualField.get(source);
if (sessionContext == null) {
return;
}
targetVirtualField.set(target, sessionContext);
}
public static String getSessionMethodSpanName(String methodName) {
if ("fireLock".equals(methodName)) {
return "Session.lock";
}
return "Session." + methodName;
}
public static String getEntityName(
String descriptor, Object arg0, Object arg1, Function<Object, String> nameFromEntity) {
String entityName = null;
// methods like save(String entityName, Object object)
// that take entity name as first argument and entity as second
// if given entity name is null compute it from entity object
if (descriptor.startsWith("(Ljava/lang/String;Ljava/lang/Object;")) {
entityName = arg0 == null ? nameFromEntity.apply(arg1) : (String) arg0;
// methods like save(Object obj)
} else if (descriptor.startsWith("(Ljava/lang/Object;")) {
entityName = nameFromEntity.apply(arg0);
// methods like get(String entityName, Serializable id)
} else if (descriptor.startsWith("(Ljava/lang/String;")) {
entityName = (String) arg0;
// methods like get(Class entityClass, Serializable id)
} else if (descriptor.startsWith("(Ljava/lang/Class;") && arg0 != null) {
entityName = ((Class<?>) arg0).getName();
}
return entityName;
}
private SessionMethodUtils() {}
}

View File

@ -31,3 +31,8 @@ dependencies {
latestDepTestLibrary("org.hibernate:hibernate-core:5.+")
latestDepTestLibrary("org.hibernate:hibernate-entitymanager:5.+")
}
tasks.withType<Test>().configureEach {
// TODO run tests both with and without experimental span attributes
jvmArgs("-Dotel.instrumentation.hibernate.experimental-span-attributes=true")
}

View File

@ -7,6 +7,7 @@ package io.opentelemetry.javaagent.instrumentation.hibernate.v4_3;
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.HibernateSingletons.instrumenter;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
@ -16,7 +17,9 @@ import io.opentelemetry.instrumentation.api.field.VirtualField;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.api.CallDepth;
import io.opentelemetry.javaagent.instrumentation.hibernate.SessionMethodUtils;
import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
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;
@ -49,29 +52,35 @@ public class ProcedureCallInstrumentation implements TypeInstrumentation {
@Advice.This ProcedureCall call,
@Advice.Origin("#m") String name,
@Advice.Local("otelCallDepth") CallDepth callDepth,
@Advice.Local("otelHibernateOperation") HibernateOperation hibernateOperation,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
callDepth = CallDepth.forClass(SessionMethodUtils.class);
callDepth = CallDepth.forClass(HibernateOperation.class);
if (callDepth.getAndIncrement() > 0) {
return;
}
VirtualField<ProcedureCall, Context> virtualField =
VirtualField.find(ProcedureCall.class, Context.class);
VirtualField<ProcedureCall, SessionInfo> criteriaVirtualField =
VirtualField.find(ProcedureCall.class, SessionInfo.class);
SessionInfo sessionInfo = criteriaVirtualField.get(call);
context =
SessionMethodUtils.startSpanFrom(
virtualField, call, "ProcedureCall." + name, call.getProcedureName());
if (context != null) {
scope = context.makeCurrent();
Context parentContext = Java8BytecodeBridge.currentContext();
hibernateOperation =
new HibernateOperation("ProcedureCall." + name, call.getProcedureName(), 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) {
@ -81,7 +90,7 @@ public class ProcedureCallInstrumentation implements TypeInstrumentation {
if (scope != null) {
scope.close();
SessionMethodUtils.end(context, throwable);
instrumenter().end(context, hibernateOperation, null, throwable);
}
}
}

View File

@ -11,11 +11,10 @@ import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.returns;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.field.VirtualField;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.instrumentation.hibernate.SessionMethodUtils;
import io.opentelemetry.javaagent.instrumentation.hibernate.SessionInfo;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
@ -49,13 +48,12 @@ public class SessionInstrumentation implements TypeInstrumentation {
public static void getProcedureCall(
@Advice.This SharedSessionContract session, @Advice.Return ProcedureCall returned) {
VirtualField<SharedSessionContract, Context> sessionVirtualField =
VirtualField.find(SharedSessionContract.class, Context.class);
VirtualField<ProcedureCall, Context> returnedVirtualField =
VirtualField.find(ProcedureCall.class, Context.class);
VirtualField<SharedSessionContract, SessionInfo> sessionVirtualField =
VirtualField.find(SharedSessionContract.class, SessionInfo.class);
VirtualField<ProcedureCall, SessionInfo> returnedVirtualField =
VirtualField.find(ProcedureCall.class, SessionInfo.class);
SessionMethodUtils.attachSpanFromStore(
sessionVirtualField, session, returnedVirtualField, returned);
returnedVirtualField.set(returned, sessionVirtualField.get(session));
}
}
}

View File

@ -70,6 +70,7 @@ class ProcedureCallTest extends AgentInstrumentationSpecification {
def "test ProcedureCall"() {
setup:
runWithSpan("parent") {
Session session = sessionFactory.openSession()
session.beginTransaction()
@ -78,12 +79,14 @@ class ProcedureCallTest extends AgentInstrumentationSpecification {
session.getTransaction().commit()
session.close()
}
expect:
def sessionId
assertTraces(1) {
trace(0, 4) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -94,6 +97,10 @@ class ProcedureCallTest extends AgentInstrumentationSpecification {
kind INTERNAL
childOf span(0)
attributes {
"hibernate.session_id" {
sessionId = it
it instanceof String
}
}
}
span(2) {
@ -113,6 +120,7 @@ class ProcedureCallTest extends AgentInstrumentationSpecification {
name "Transaction.commit"
childOf span(0)
attributes {
"hibernate.session_id" sessionId
}
}
}
@ -122,6 +130,7 @@ class ProcedureCallTest extends AgentInstrumentationSpecification {
def "test failing ProcedureCall"() {
setup:
runWithSpan("parent") {
Session session = sessionFactory.openSession()
session.beginTransaction()
@ -137,12 +146,14 @@ class ProcedureCallTest extends AgentInstrumentationSpecification {
session.getTransaction().commit()
session.close()
}
expect:
def sessionId
assertTraces(1) {
trace(0, 3) {
span(0) {
name "Session"
name "parent"
kind INTERNAL
hasNoParent()
attributes {
@ -154,12 +165,19 @@ class ProcedureCallTest extends AgentInstrumentationSpecification {
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
}
}
}