Skip to content

Commit

Permalink
Merge pull request #25 from appoptics/NH-5774-trace-context-in-sql-query
Browse files Browse the repository at this point in the history
NH-5774: Trace context in sql query
  • Loading branch information
jiwen624 authored Feb 28, 2022
2 parents c7667f7 + 0badee3 commit cf37db8
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 4 deletions.
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ subprojects {
opentelemetryJavaagent: "1.7.2",
bytebuddy : "1.10.18",
guava : "30.1-jre",
appopticsCore : "7.0.4",
appopticsMetrics : "7.0.4",
agent : "0.5.1" // the custom distro agent version
appopticsCore : "7.1.0",
agent : "0.6.0" // the custom distro agent version
]
versions.appopticsMetrics = "${versions.appopticsCore}" // they share the same version now
versions.opentelemetryAlpha = "${versions.opentelemetry}-alpha"
versions.opentelemetryJavaagentAlpha = "${versions.opentelemetryJavaagent}-alpha"

Expand Down
17 changes: 17 additions & 0 deletions instrumentation/jdbc/build.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
apply plugin: 'groovy'
apply from: "$rootDir/gradle/instrumentation.gradle"

//apply plugin: "otel.javaagent-instrumentation"

//muzzle {
Expand All @@ -9,10 +11,13 @@ apply from: "$rootDir/gradle/instrumentation.gradle"

dependencies {
compileOnly project(":core-bootstrap")
implementation "com.appoptics.agent.java:core:${versions.appopticsCore}"

compileOnly "javax.servlet:javax.servlet-api:3.0.1"

compileOnly "io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:${versions.opentelemetryJavaagentAlpha}"

testImplementation project(path: ":instrumentation:jdbc")
testInstrumentation "io.opentelemetry.javaagent.instrumentation:opentelemetry-javaagent-servlet-common:${versions.opentelemetryJavaagentAlpha}"
testInstrumentation "io.opentelemetry.javaagent.instrumentation:opentelemetry-javaagent-servlet-2.2:${versions.opentelemetryJavaagentAlpha}"
testInstrumentation "io.opentelemetry.javaagent.instrumentation:opentelemetry-javaagent-servlet-3.0:${versions.opentelemetryJavaagentAlpha}"
Expand All @@ -27,4 +32,16 @@ dependencies {
testImplementation "org.eclipse.jetty:jetty-servlet:8.0.0.v20110901"

compileOnly "com.appoptics.agent.java:core:${versions.appopticsCore}"

// mandatory dependencies for using Spock
implementation "org.codehaus.groovy:groovy:3.0.9"
testImplementation platform("org.spockframework:spock-bom:2.1-groovy-3.0")
testImplementation 'org.spockframework:spock-core:2.0-groovy-3.0'
}

test {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.appoptics.opentelemetry.instrumentation;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
import static io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge.currentContext;
import static net.bytebuddy.matcher.ElementMatchers.*;

import com.tracelytics.joboe.config.ConfigManager;
import com.tracelytics.joboe.config.ConfigProperty;
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;

public class AoConnectionInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<ClassLoader> classLoaderOptimization() {
return hasClassesNamed("java.sql.Connection");
}

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
Boolean sqlTag = ConfigManager.getConfigOptional(ConfigProperty.AGENT_SQL_TAG, false);
if (!sqlTag) {
return none();
}
return named("com.mysql.cj.jdbc.ConnectionImpl") // only inject MySQL JDBC driver
.and(implementsInterface(named("java.sql.Connection")));
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
nameStartsWith("prepare")
.and(takesArgument(0, String.class))
// Also include CallableStatement, which is a sub type of PreparedStatement
.and(returns(implementsInterface(named("java.sql.PreparedStatement")))),
AoConnectionInstrumentation.class.getName() + "$PrepareAdvice");
}

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

@Advice.OnMethodEnter(suppress = Throwable.class)
public static void injectComment(
@Advice.Argument(value = 0, readOnly = false) String sql) {
sql = TraceContextInjector.inject(currentContext(), sql);
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public AoJdbcInstrumentationModule() {
@Override
public List<TypeInstrumentation> typeInstrumentations() {
return asList(
new AoConnectionInstrumentation(),
new AoStatementInstrumentation()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge.currentContext;
import static net.bytebuddy.matcher.ElementMatchers.*;

/**
Expand Down Expand Up @@ -46,10 +47,11 @@ public void transform(TypeTransformer transformer) {
public static class StatementAdvice {
//@Advice.OnMethodEnter(suppress = Throwable.class)
@Advice.OnMethodEnter
public static void onEnter() {
public static void onEnter(@Advice.Argument(value = 0, readOnly = false) String sql) {
if (CallDepth.forClass(Statement.class).get() != 1) { //only report back when depth is one to avoid duplications
return;
}
sql = TraceContextInjector.inject(currentContext(), sql);
AoStatementTracer.writeStackTrace(Context.current());
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.appoptics.opentelemetry.instrumentation;

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

public class TraceContextInjector {
public static String inject(Context context, String sql) {
if (sql.contains("traceparent")) {
return sql;
}

Span span = Span.fromContext(context);
SpanContext spanContext = span.getSpanContext();
if (!(spanContext.isValid() && spanContext.isSampled())) {
return sql;
}
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 String.format("%s %s", tag, sql);
}
}
29 changes: 29 additions & 0 deletions instrumentation/jdbc/src/test/groovy/InjectTraceContextTest.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import com.appoptics.opentelemetry.instrumentation.TraceContextInjector
import io.opentelemetry.api.GlobalOpenTelemetry
import io.opentelemetry.context.Context
import spock.lang.Specification
import spock.lang.Unroll

import java.util.regex.Pattern

@Unroll
class InjectTraceContextTest extends Specification {
def "inject trace context from span to sql"(String sql) {
setup:
def tracer = GlobalOpenTelemetry.getTracer("test")
def testScope = tracer.spanBuilder("test").startSpan().makeCurrent()
Pattern pattern = Pattern.compile("/\\*traceparent:'00-[a-f0-9]{32}-[a-f0-9]{16}-0[0|1]'\\*/ .+")

expect:
pattern.matcher(TraceContextInjector.inject(Context.current(), sql)).matches()

cleanup:
testScope.close()

where:
sql |_
"select name from students" |_
"insert into students values('tom', 1)" |_
"/* comment */ select name from students" |_
}
}

0 comments on commit cf37db8

Please sign in to comment.