Skip to content

Commit

Permalink
NH-93561: use HibernateInstrumenter to add sw span
Browse files Browse the repository at this point in the history
  • Loading branch information
cleverchuk committed Oct 17, 2024
1 parent 6550ae5 commit 99b8967
Show file tree
Hide file tree
Showing 8 changed files with 295 additions and 42 deletions.
15 changes: 15 additions & 0 deletions instrumentation/hibernate-6.0/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apply from: "$rootDir/gradle/instrumentation.gradle"

dependencies {
compileOnly("org.hibernate:hibernate-core:6.0.0.Final")
compileOnly "io.opentelemetry:opentelemetry-sdk-trace:${versions.opentelemetry}"
compileOnly "io.opentelemetry.semconv:opentelemetry-semconv:${versions.opentelemetrySemconv}"
}

tasks.test {
useJUnitPlatform()
}

compileJava {
options.release.set(11)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* © SolarWinds Worldwide, LLC. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.solarwinds.opentelemetry.instrumentation.hibernate.v6_0;

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.context.Context;

public class Commenter {
public static String generateComment(Context context) {
Span span = Span.fromContext(context);
SpanContext spanContext = span.getSpanContext();
if (!(spanContext.isValid() && spanContext.isSampled())) {
return null;
}

String flags = "01"; // only inject into sampled requests
String traceContext =
"00-" + spanContext.getTraceId() + "-" + spanContext.getSpanId() + "-" + flags;

String tag = String.format("/*traceparent='%s'*/", traceContext);
span.setAttribute("QueryTag", tag);
return tag;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package com.solarwinds.opentelemetry.instrumentation.hibernate.v6_0;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import java.util.List;
import net.bytebuddy.matcher.ElementMatcher;

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

public HibernateInstrumentationModule() {
super("swo-hibernate", "swo-hibernate-6.0");
}

@Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
return hasClassesNamed(
// not present before 6.0
"org.hibernate.query.spi.SqmQuery");
}

@Override
public boolean isHelperClass(String className) {
return className.startsWith("com.solarwinds.opentelemetry.instrumentation");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return List.of(new QueryInstrumentation());
}

@Override
public int order() {
return Integer.MAX_VALUE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* © SolarWinds Worldwide, LLC. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.solarwinds.opentelemetry.instrumentation.hibernate.v6_0;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;

public final class HibernateInstrumenter {
private static final Instrumenter<String, Void> INSTANCE =
Instrumenter.<String, Void>builder(
GlobalOpenTelemetry.get(), "com.solarwinds.jdbc", (sql) -> "sw.jdbc.context")
.buildInstrumenter();

public static Instrumenter<String, Void> getInstance() {
return INSTANCE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package com.solarwinds.opentelemetry.instrumentation.hibernate.v6_0;

import static com.solarwinds.opentelemetry.instrumentation.hibernate.v6_0.Commenter.generateComment;
import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.bootstrap.CallDepth;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.hibernate.query.CommonQueryContract;
import org.hibernate.query.Query;
import org.hibernate.query.spi.SqmQuery;

public class QueryInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<ClassLoader> classLoaderOptimization() {
return hasClassesNamed("org.hibernate.query.CommonQueryContract");
}

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return implementsInterface(named("org.hibernate.query.CommonQueryContract"));
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod()
.and(
namedOneOf(
"list",
"getResultList",
"stream",
"getResultStream",
"uniqueResult",
"getSingleResult",
"getSingleResultOrNull",
"uniqueResultOptional",
"executeUpdate",
"scroll")),
QueryInstrumentation.class.getName() + "$QueryMethodAdvice");
}

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

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void startMethod(
@Advice.This CommonQueryContract query,
@Advice.Local("swoCallDepth") CallDepth callDepth,
@Advice.Local("swoSqlContext") String swoSql,
@Advice.Local("swoContext") Context context,
@Advice.Local("swoScope") Scope scope) {
callDepth = CallDepth.forClass(CommonQueryContract.class);
if (callDepth.getAndIncrement() > 0) {
return;
}

String queryString = null;
if (query instanceof Query) {
queryString = ((Query<?>) query).getQueryString();
}

if (query instanceof SqmQuery) {
try {
queryString = ((SqmQuery) query).getSqmStatement().toHqlString();
} catch (RuntimeException exception) {
// ignore
}
}

Context parentContext = currentContext();
if (queryString == null
|| !HibernateInstrumenter.getInstance().shouldStart(parentContext, queryString)) {
return;
}

context = HibernateInstrumenter.getInstance().start(parentContext, queryString);
scope = context.makeCurrent();
String comment = generateComment(currentContext());
swoSql = queryString;

if (comment != null) {
try {
query.setComment(comment);
} catch (RuntimeException ignore) {
}
}
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void endMethod(
@Advice.Thrown Throwable throwable,
@Advice.Local("swoCallDepth") CallDepth callDepth,
@Advice.Local("swoSqlContext") String swoSql,
@Advice.Local("swoContext") Context context,
@Advice.Local("swoScope") Scope scope) {

if (callDepth == null || callDepth.decrementAndGet() > 0) {
return;
}

if (scope != null) {
scope.close();
HibernateInstrumenter.getInstance().end(context, swoSql, null, throwable);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* © SolarWinds Worldwide, LLC. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.solarwinds.opentelemetry.instrumentation.hibernate.v6_0;

import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.semconv.SemanticAttributes;
import javax.annotation.Nullable;

public class SqlAttributeExtractor implements AttributesExtractor<String, Void> {
@Override
public void onStart(AttributesBuilder attributes, Context parentContext, String sql) {
attributes.put(SemanticAttributes.DB_STATEMENT, sql);
}

@Override
public void onEnd(
AttributesBuilder attributes,
Context context,
String sql,
@Nullable Void v,
@Nullable Throwable error) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,8 @@

import com.solarwinds.joboe.config.ConfigManager;
import com.solarwinds.joboe.config.ConfigProperty;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.bootstrap.CallDepth;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import java.sql.Statement;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
Expand Down Expand Up @@ -88,46 +84,9 @@ public void transform(TypeTransformer transformer) {
public static class PrepareAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void injectComment(
@Advice.Argument(value = 0, readOnly = false) String sql,
@Advice.Local("swoCallDepth") CallDepth callDepth,
@Advice.Local("swoSqlContext") String swoSql,
@Advice.Local("swoContext") Context context,
@Advice.Local("swoScope") Scope scope) {
callDepth = CallDepth.forClass(Statement.class);
if (callDepth.getAndIncrement() > 0) {
return;
}

Context parentContext = currentContext();
if (sql == null || !JdbcInstrumenter.getInstance().shouldStart(parentContext, sql)) {
return;
}

context = JdbcInstrumenter.getInstance().start(parentContext, sql);
scope = context.makeCurrent();

public static void injectComment(@Advice.Argument(value = 0, readOnly = false) String sql) {
sql = TraceContextInjector.inject(currentContext(), sql);
StatementTracer.writeStackTraceSpec(currentContext());
swoSql = sql;
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Thrown Throwable throwable,
@Advice.Local("swoCallDepth") CallDepth callDepth,
@Advice.Local("swoSqlContext") String swoSql,
@Advice.Local("swoContext") Context context,
@Advice.Local("swoScope") Scope scope) {

if (callDepth == null || callDepth.decrementAndGet() > 0) {
return;
}

if (scope != null) {
scope.close();
JdbcInstrumenter.getInstance().end(context, swoSql, null, throwable);
}
}
}
}
3 changes: 3 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,6 @@ include 'custom:shared'
include 'agent-lambda'
include 'instrumentation:spring-webmvc:spring-webmvc-6'
include 'instrumentation:spring-webmvc'
include 'instrumentation:hibernate-6.0'
findProject(':instrumentation:hibernate-6.0')?.name = 'hibernate-6.0'

0 comments on commit 99b8967

Please sign in to comment.