Skip to content

prepared statement bind parameters Closes #30 #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ dependencies {
isTransitive = false
}

//datasource-proxy is packaged in digma-agent and in otel extension, fortunately
// both are loaded by the system class loader so there is no duplicate classed
//the shadowing must be the same
implementation("net.ttddyy:datasource-proxy:1.10")

//we don't need those annotations at runtime, it's only for development in Idea
compileOnly("org.jetbrains:annotations:21.0.0")

Expand Down Expand Up @@ -104,6 +109,8 @@ tasks {
//https://github.com/open-telemetry/opentelemetry-java-instrumentation/discussions/11336
relocate("net.bytebuddy", "org.digma.net.bytebuddy")

relocate("net.ttddyy.dsproxy", "org.digma.net.ttddyy.dsproxy")

}


Expand Down
104 changes: 24 additions & 80 deletions src/main/java/org/digma/DigmaAgent.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package org.digma;

import net.bytebuddy.ByteBuddy;
import org.digma.configuration.Configuration;
import org.digma.instrumentation.digma.agent.BuildVersion;
import org.digma.jdbc.JDBCTrackingTransformer;

import java.lang.instrument.Instrumentation;
import java.lang.reflect.Field;

import static org.digma.configuration.Configuration.JAVA_VERSION;
import static org.digma.configuration.Configuration.OS_NAME;
import static org.digma.OtelClassNames.WITH_SPAN_CLASS_NAME;

public class DigmaAgent {

Expand All @@ -34,59 +33,48 @@ private static void startAgent(Instrumentation inst, boolean fromPremain) {

Configuration configuration = Configuration.getInstance();


if (configuration.getIncludePackages().isEmpty()) {
Log.debug("No configured packages for instrumentation in Digma agent, doing nothing.");
return;
}

installInstrumentationOnBytebuddyAgent(inst);


//this must be the first thing the agent does, other classes rely on non-null
InstrumentationHolder.instrumentation = inst;

boolean agentActivated = false;

//todo: this is temporary until a more clever injection is implemented.
// when trying to inject otel api lazy just before transforming, it works but otel doesn't
// instrument the methods. its because otel agent has a class loader optimization, if it tried once to check
// if class loader has WIthSpan and it was false it will not instrument classes from this class loader anymore.
// if otel transformer was executed before our transformer encountered a relevant type then it will be too late
// to inject.
// one solution may be to add a transformer that does nothing but registers class loaders and checks if they have
// a relevant package for us and if yes inject otel api if necessary.

//even if injection fails the agent will still install the transformer,
// maybe WithSpan is available in higher class loaders, worst thing transformation will fail
// and user should see the logs.
makeSureWithSpanClassIsAvailable();

if (configuration.isExposePreparedStatementsParametersEnabled()) {
System.setProperty("otel.instrumentation.jdbc.statement-sanitizer.enabled","false");
JDBCTrackingTransformer.install(inst);
agentActivated = true;
} else {
Log.debug("otel statement sanitizer is enabled , not installing jdbc tracking transformer.");
}

Log.debug("Digma agent started with configuration: includePackages="
+ configuration.getIncludePackages()
+ ",excludeNames=" + configuration.getExcludeNames());

if (configuration.isExtendedObservabilityEnabled()) {
WithSpanTransformer.install(inst);
agentActivated = true;
} else {
Log.debug("Extended observability is not configured, not installing WithSpan transformer.");
}

//if we fail to load bytebuddy nothing will work
Class<ByteBuddy> byteBuddyClass = ByteBuddy.class;
Log.debug("byteBuddy Class " + byteBuddyClass.getName() + ", class loader: " + byteBuddyClass.getClassLoader());

WithSpanTransformer.install(inst);
if (agentActivated) {
installInstrumentationOnBytebuddyAgent(inst);
}

} catch (Throwable ex) {
// Don't rethrow.
// Don't rethrow so users don't get an exception in their application.
Log.error("got exception while starting Digma agent", ex);
}
}




//see : https://github.com/raphw/byte-buddy/discussions/1658
private static void installInstrumentationOnBytebuddyAgent(Instrumentation myInstrumentation) {

if (!OS_NAME.toLowerCase().startsWith("mac")) {
return;
}
if(!JAVA_VERSION.startsWith("17")){
if (!JAVA_VERSION.startsWith("17")) {
return;
}

Expand All @@ -95,67 +83,23 @@ private static void installInstrumentationOnBytebuddyAgent(Instrumentation myIns
//need to change the Installer fq name otherwise gradle shadow will relocate it
Class<?> byteBuddyInstaller = Class.forName("net_bytebuddy_agent_Installer".replaceAll("_", "."), false, ClassLoader.getSystemClassLoader());
Field instrumentationField = byteBuddyInstaller.getDeclaredField("instrumentation");
boolean isAccessible = instrumentationField.isAccessible();
instrumentationField.setAccessible(true);

Instrumentation instrumentation = (Instrumentation) instrumentationField.get(null);
if (instrumentation == null) {
instrumentationField.set(null, myInstrumentation);
}
instrumentationField.setAccessible(false);
instrumentationField.setAccessible(isAccessible);
Log.debug("Installation of Instrumentation on ByteBuddy Installer succeeded");
} catch (Exception e) {
Log.debug("Could not install instrumentation on bytebuddy Installer " + e);
}
}


private static void makeSureWithSpanClassIsAvailable() {

//if configuration is true inject and return
if (Configuration.getInstance().shouldInjectOtelApiToSystemClassLoader()) {
Log.debug("configuration for injecting otel api to system class loader is true, injecting otel api to system class loader.");
injectOtelApiToSystemClassLoader();
return;
}


//if configuration exists and is false quit and don't inject
if (Configuration.getInstance().shouldInjectOtelApiToSystemClassLoaderExist() &&
!Configuration.getInstance().shouldInjectOtelApiToSystemClassLoader()) {
Log.debug("configuration for injecting otel api to system class loader is false, not injecting.");
return;
}

//else try to inject if WithSpan is not in system classpath
try {
Log.debug("checking if WithSpan class is available in classpath");
Class.forName(WITH_SPAN_CLASS_NAME);
Log.debug("WithSpan class is available in classpath");

} catch (ClassNotFoundException e) {
Log.debug("WithSpan class is NOT available in classpath, trying to inject otel api");
injectOtelApiToSystemClassLoader();
}
}

private static void injectOtelApiToSystemClassLoader() {
if (Configuration.getInstance().shouldInjectOtelApiToSystemClassLoaderExist() &&
!Configuration.getInstance().shouldInjectOtelApiToSystemClassLoader()) {
Log.debug("injectOtelApiToSystemClassLoader was called but configuration is false, not injecting");
return;
}

Log.debug("injecting otel api to system class loader.");
try {
OtelApiInjector.injectOtelApiJarToSystemClassLoader();
} catch (UnsupportedOperationException e) {
Log.error("got exception trying to inject otel api to system class loader. maybe this jvm doesn't support injecting a jar to " +
"system class loader. please add otel api top the classpath in a different way", e);
} catch (Throwable e) {
Log.error("got exception trying to inject otel api to system class loader. " +
"please fix the issue and try again , or add otel api to the classpath in a different way", e);
}

}

}
94 changes: 93 additions & 1 deletion src/main/java/org/digma/WithSpanTransformer.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.digma;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.MemberAttributeExtension;
import net.bytebuddy.description.annotation.AnnotationDescription;
Expand All @@ -17,6 +18,97 @@ public class WithSpanTransformer {

public static void install(Instrumentation inst) {

Log.debug("Extended observability is enabled, installing WithSpan transformer.");
Log.debug("Digma agent started with extended observability configuration: " +
"includePackages=" + Configuration.getInstance().getIncludePackages()
+ ",excludeNames=" + Configuration.getInstance().getExcludeNames());


try {
//todo: this is temporary until a more clever injection is implemented.
// when trying to inject otel api lazy just before transforming, it works but otel doesn't
// instrument the methods. its because otel agent has a class loader optimization, if it tried once to check
// if class loader has WIthSpan and it was false it will not instrument classes from this class loader anymore.
// if otel transformer was executed before our transformer encountered a relevant type then it will be too late
// to inject.
// one solution may be to add a transformer that does nothing but registers class loaders and checks if they have
// a relevant package for us and if yes inject otel api if necessary.

//even if injection fails the agent will still install the transformer,
// maybe WithSpan is available in higher class loaders, worst thing transformation will fail
// and user should see the logs.
makeSureWithSpanClassIsAvailable();
} catch (Throwable e) {
Log.error("failed injecting WithSpan to class loader", e);
}

try {

//if we fail to load bytebuddy nothing will work
Class<ByteBuddy> byteBuddyClass = ByteBuddy.class;
Log.debug("byteBuddy Class " + byteBuddyClass.getName() + ", class loader: " + byteBuddyClass.getClassLoader());
installTransformer(inst);

} catch (Throwable e) {
Log.error("failed to install WithSpanTransformer in Digma agent", e);
}

}


private static void makeSureWithSpanClassIsAvailable() {

//if configuration is true inject and return
if (Configuration.getInstance().shouldInjectOtelApiToSystemClassLoader()) {
Log.debug("configuration for injecting otel api to system class loader is true, injecting otel api to system class loader.");
injectOtelApiToSystemClassLoader();
return;
}


//if configuration exists and is false quit and don't inject
if (Configuration.getInstance().shouldInjectOtelApiToSystemClassLoaderExist() &&
!Configuration.getInstance().shouldInjectOtelApiToSystemClassLoader()) {
Log.debug("configuration for injecting otel api to system class loader is false, not injecting.");
return;
}

//else try to inject if WithSpan is not in system classpath
try {
Log.debug("checking if WithSpan class is available in classpath");
Class.forName(WITH_SPAN_CLASS_NAME);
Log.debug("WithSpan class is available in classpath");

} catch (ClassNotFoundException e) {
Log.debug("WithSpan class is NOT available in classpath, trying to inject otel api");
injectOtelApiToSystemClassLoader();
}
}


private static void injectOtelApiToSystemClassLoader() {
if (Configuration.getInstance().shouldInjectOtelApiToSystemClassLoaderExist() &&
!Configuration.getInstance().shouldInjectOtelApiToSystemClassLoader()) {
Log.debug("injectOtelApiToSystemClassLoader was called but configuration is false, not injecting");
return;
}

Log.debug("injecting otel api to system class loader.");
try {
OtelApiInjector.injectOtelApiJarToSystemClassLoader();
} catch (UnsupportedOperationException e) {
Log.error("got exception trying to inject otel api to system class loader. maybe this jvm doesn't support injecting a jar to " +
"system class loader. please add otel api top the classpath in a different way", e);
} catch (Throwable e) {
Log.error("got exception trying to inject otel api to system class loader. " +
"please fix the issue and try again , or add otel api to the classpath in a different way", e);
}

}


private static void installTransformer(Instrumentation inst) {

Log.debug("installing withSpanTransformer");

new AgentBuilder.Default()
Expand Down Expand Up @@ -63,7 +155,7 @@ private static AnnotationDescription getWithSpanAnnotationDescription(ClassLoade
}


private static AnnotationDescription getDigmaMarkerAnnotationDescription() {
private static AnnotationDescription getDigmaMarkerAnnotationDescription() {

Log.debug("trying to load ExtendedObservability annotation class");
AnnotationDescription annotationDescription = AnnotationDescription.Latent.Builder.ofType(ExtendedObservability.class).build();
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/org/digma/configuration/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ public class Configuration {
public static final String DEBUG_ENV_VAR = "DIGMA_AGENT_DEBUG";


public static final String JDBC_PS_PARAMS_ENABLED_SYSTEM_PROPERTY = "org.digma.otel.instrumentation.jdbc.ps.params.enabled";
public static final String JDBC_PS_PARAMS_ENABLED_ENABLED_ENV_VAR = "ORG_DIGMA_OTEL_INSTRUMENTATION_JDBC_PS_PARAMS_ENABLED";


private final List<String> includePackages;
private final List<String> excludeNames;

Expand Down Expand Up @@ -123,6 +127,21 @@ public boolean isDebug() {
return getBoolean(DEBUG_SYSTEM_PROPERTY, DEBUG_ENV_VAR);
}

public boolean isExtendedObservabilityEnabled(){
return !getIncludePackages().isEmpty();
}


private boolean isExposePreparedStatementsParametersExist() {
return configurationReader.getEnvOrSystemProperty(JDBC_PS_PARAMS_ENABLED_SYSTEM_PROPERTY) != null ||
configurationReader.getEnvOrSystemProperty(JDBC_PS_PARAMS_ENABLED_ENABLED_ENV_VAR) != null;
}

public boolean isExposePreparedStatementsParametersEnabled() {
return isExposePreparedStatementsParametersExist() &&
getBoolean(JDBC_PS_PARAMS_ENABLED_SYSTEM_PROPERTY, JDBC_PS_PARAMS_ENABLED_ENABLED_ENV_VAR);
}


@NotNull
public List<String> getIncludePackages() {
Expand Down
61 changes: 61 additions & 0 deletions src/main/java/org/digma/jdbc/DigmaJdkJdbcProxyFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.digma.jdbc;

import net.ttddyy.dsproxy.ConnectionInfo;
import net.ttddyy.dsproxy.proxy.ProxyConfig;
import net.ttddyy.dsproxy.proxy.jdk.JdkJdbcProxyFactory;
import org.digma.Log;
import org.digma.configuration.Configuration;

import javax.sql.DataSource;
import java.sql.*;

public class DigmaJdkJdbcProxyFactory extends JdkJdbcProxyFactory {


@Override
public PreparedStatement createPreparedStatement(PreparedStatement preparedStatement, String query, ConnectionInfo connectionInfo, Connection proxyConnection, ProxyConfig proxyConfig, boolean generateKey) {

Log.debug("DigmaJdkJdbcProxyFactory.createPreparedStatement for " + query);
Log.debug("DigmaJdkJdbcProxyFactory.createPreparedStatement wrapping " + preparedStatement.getClass().getName());

if (Configuration.getInstance().isExposePreparedStatementsParametersEnabled()) {
proxyConfig = ProxyConfig.Builder.create()
.methodListener(new PreparedStatementMethodExecutionListener(query))
.jdbcProxyFactory(new DigmaJdkJdbcProxyFactory())
.build();
}

return super.createPreparedStatement(preparedStatement, query, connectionInfo, proxyConnection, proxyConfig, generateKey);
}


@Override
public DataSource createDataSource(DataSource dataSource, ProxyConfig proxyConfig) {
return super.createDataSource(dataSource, proxyConfig);
}

@Override
public Connection createConnection(Connection connection, ConnectionInfo connectionInfo, ProxyConfig proxyConfig) {
return super.createConnection(connection, connectionInfo, proxyConfig);
}

@Override
public Statement createStatement(Statement statement, ConnectionInfo connectionInfo, Connection proxyConnection, ProxyConfig proxyConfig) {
return super.createStatement(statement, connectionInfo, proxyConnection, proxyConfig);
}

@Override
public CallableStatement createCallableStatement(CallableStatement callableStatement, String query, ConnectionInfo connectionInfo, Connection proxyConnection, ProxyConfig proxyConfig) {
return super.createCallableStatement(callableStatement, query, connectionInfo, proxyConnection, proxyConfig);
}

@Override
public ResultSet createResultSet(ResultSet resultSet, ConnectionInfo connectionInfo, ProxyConfig proxyConfig) {
return super.createResultSet(resultSet, connectionInfo, proxyConfig);
}

@Override
public ResultSet createGeneratedKeys(ResultSet resultSet, ConnectionInfo connectionInfo, ProxyConfig proxyConfig) {
return super.createGeneratedKeys(resultSet, connectionInfo, proxyConfig);
}
}
Loading