Skip to content

Commit 92faaff

Browse files
authored
Merge pull request #33 from digma-ai/prepared-stmt-bind-params
prepared statement bind parameters Closes #30
2 parents 4707395 + d130338 commit 92faaff

9 files changed

+699
-81
lines changed

build.gradle.kts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ dependencies {
3232
isTransitive = false
3333
}
3434

35+
//datasource-proxy is packaged in digma-agent and in otel extension, fortunately
36+
// both are loaded by the system class loader so there is no duplicate classed
37+
//the shadowing must be the same
38+
implementation("net.ttddyy:datasource-proxy:1.10")
39+
3540
//we don't need those annotations at runtime, it's only for development in Idea
3641
compileOnly("org.jetbrains:annotations:21.0.0")
3742

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

112+
relocate("net.ttddyy.dsproxy", "org.digma.net.ttddyy.dsproxy")
113+
107114
}
108115

109116

Lines changed: 24 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
package org.digma;
22

3-
import net.bytebuddy.ByteBuddy;
43
import org.digma.configuration.Configuration;
54
import org.digma.instrumentation.digma.agent.BuildVersion;
5+
import org.digma.jdbc.JDBCTrackingTransformer;
66

77
import java.lang.instrument.Instrumentation;
88
import java.lang.reflect.Field;
99

1010
import static org.digma.configuration.Configuration.JAVA_VERSION;
1111
import static org.digma.configuration.Configuration.OS_NAME;
12-
import static org.digma.OtelClassNames.WITH_SPAN_CLASS_NAME;
1312

1413
public class DigmaAgent {
1514

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

3534
Configuration configuration = Configuration.getInstance();
3635

37-
38-
if (configuration.getIncludePackages().isEmpty()) {
39-
Log.debug("No configured packages for instrumentation in Digma agent, doing nothing.");
40-
return;
41-
}
42-
43-
installInstrumentationOnBytebuddyAgent(inst);
44-
45-
4636
//this must be the first thing the agent does, other classes rely on non-null
4737
InstrumentationHolder.instrumentation = inst;
4838

39+
boolean agentActivated = false;
4940

50-
//todo: this is temporary until a more clever injection is implemented.
51-
// when trying to inject otel api lazy just before transforming, it works but otel doesn't
52-
// instrument the methods. its because otel agent has a class loader optimization, if it tried once to check
53-
// if class loader has WIthSpan and it was false it will not instrument classes from this class loader anymore.
54-
// if otel transformer was executed before our transformer encountered a relevant type then it will be too late
55-
// to inject.
56-
// one solution may be to add a transformer that does nothing but registers class loaders and checks if they have
57-
// a relevant package for us and if yes inject otel api if necessary.
58-
59-
//even if injection fails the agent will still install the transformer,
60-
// maybe WithSpan is available in higher class loaders, worst thing transformation will fail
61-
// and user should see the logs.
62-
makeSureWithSpanClassIsAvailable();
63-
41+
if (configuration.isExposePreparedStatementsParametersEnabled()) {
42+
System.setProperty("otel.instrumentation.jdbc.statement-sanitizer.enabled","false");
43+
JDBCTrackingTransformer.install(inst);
44+
agentActivated = true;
45+
} else {
46+
Log.debug("otel statement sanitizer is enabled , not installing jdbc tracking transformer.");
47+
}
6448

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

50+
if (configuration.isExtendedObservabilityEnabled()) {
51+
WithSpanTransformer.install(inst);
52+
agentActivated = true;
53+
} else {
54+
Log.debug("Extended observability is not configured, not installing WithSpan transformer.");
55+
}
6956

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

74-
WithSpanTransformer.install(inst);
58+
if (agentActivated) {
59+
installInstrumentationOnBytebuddyAgent(inst);
60+
}
7561

7662
} catch (Throwable ex) {
77-
// Don't rethrow.
63+
// Don't rethrow so users don't get an exception in their application.
7864
Log.error("got exception while starting Digma agent", ex);
7965
}
8066
}
8167

8268

69+
70+
8371
//see : https://github.com/raphw/byte-buddy/discussions/1658
8472
private static void installInstrumentationOnBytebuddyAgent(Instrumentation myInstrumentation) {
8573

8674
if (!OS_NAME.toLowerCase().startsWith("mac")) {
8775
return;
8876
}
89-
if(!JAVA_VERSION.startsWith("17")){
77+
if (!JAVA_VERSION.startsWith("17")) {
9078
return;
9179
}
9280

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

10089
Instrumentation instrumentation = (Instrumentation) instrumentationField.get(null);
10190
if (instrumentation == null) {
10291
instrumentationField.set(null, myInstrumentation);
10392
}
104-
instrumentationField.setAccessible(false);
93+
instrumentationField.setAccessible(isAccessible);
10594
Log.debug("Installation of Instrumentation on ByteBuddy Installer succeeded");
10695
} catch (Exception e) {
10796
Log.debug("Could not install instrumentation on bytebuddy Installer " + e);
10897
}
10998
}
11099

111100

112-
private static void makeSureWithSpanClassIsAvailable() {
113101

114-
//if configuration is true inject and return
115-
if (Configuration.getInstance().shouldInjectOtelApiToSystemClassLoader()) {
116-
Log.debug("configuration for injecting otel api to system class loader is true, injecting otel api to system class loader.");
117-
injectOtelApiToSystemClassLoader();
118-
return;
119-
}
120102

121103

122-
//if configuration exists and is false quit and don't inject
123-
if (Configuration.getInstance().shouldInjectOtelApiToSystemClassLoaderExist() &&
124-
!Configuration.getInstance().shouldInjectOtelApiToSystemClassLoader()) {
125-
Log.debug("configuration for injecting otel api to system class loader is false, not injecting.");
126-
return;
127-
}
128-
129-
//else try to inject if WithSpan is not in system classpath
130-
try {
131-
Log.debug("checking if WithSpan class is available in classpath");
132-
Class.forName(WITH_SPAN_CLASS_NAME);
133-
Log.debug("WithSpan class is available in classpath");
134-
135-
} catch (ClassNotFoundException e) {
136-
Log.debug("WithSpan class is NOT available in classpath, trying to inject otel api");
137-
injectOtelApiToSystemClassLoader();
138-
}
139-
}
140-
141-
private static void injectOtelApiToSystemClassLoader() {
142-
if (Configuration.getInstance().shouldInjectOtelApiToSystemClassLoaderExist() &&
143-
!Configuration.getInstance().shouldInjectOtelApiToSystemClassLoader()) {
144-
Log.debug("injectOtelApiToSystemClassLoader was called but configuration is false, not injecting");
145-
return;
146-
}
147-
148-
Log.debug("injecting otel api to system class loader.");
149-
try {
150-
OtelApiInjector.injectOtelApiJarToSystemClassLoader();
151-
} catch (UnsupportedOperationException e) {
152-
Log.error("got exception trying to inject otel api to system class loader. maybe this jvm doesn't support injecting a jar to " +
153-
"system class loader. please add otel api top the classpath in a different way", e);
154-
} catch (Throwable e) {
155-
Log.error("got exception trying to inject otel api to system class loader. " +
156-
"please fix the issue and try again , or add otel api to the classpath in a different way", e);
157-
}
158-
159-
}
160104

161105
}

src/main/java/org/digma/WithSpanTransformer.java

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.digma;
22

3+
import net.bytebuddy.ByteBuddy;
34
import net.bytebuddy.agent.builder.AgentBuilder;
45
import net.bytebuddy.asm.MemberAttributeExtension;
56
import net.bytebuddy.description.annotation.AnnotationDescription;
@@ -17,6 +18,97 @@ public class WithSpanTransformer {
1718

1819
public static void install(Instrumentation inst) {
1920

21+
Log.debug("Extended observability is enabled, installing WithSpan transformer.");
22+
Log.debug("Digma agent started with extended observability configuration: " +
23+
"includePackages=" + Configuration.getInstance().getIncludePackages()
24+
+ ",excludeNames=" + Configuration.getInstance().getExcludeNames());
25+
26+
27+
try {
28+
//todo: this is temporary until a more clever injection is implemented.
29+
// when trying to inject otel api lazy just before transforming, it works but otel doesn't
30+
// instrument the methods. its because otel agent has a class loader optimization, if it tried once to check
31+
// if class loader has WIthSpan and it was false it will not instrument classes from this class loader anymore.
32+
// if otel transformer was executed before our transformer encountered a relevant type then it will be too late
33+
// to inject.
34+
// one solution may be to add a transformer that does nothing but registers class loaders and checks if they have
35+
// a relevant package for us and if yes inject otel api if necessary.
36+
37+
//even if injection fails the agent will still install the transformer,
38+
// maybe WithSpan is available in higher class loaders, worst thing transformation will fail
39+
// and user should see the logs.
40+
makeSureWithSpanClassIsAvailable();
41+
} catch (Throwable e) {
42+
Log.error("failed injecting WithSpan to class loader", e);
43+
}
44+
45+
try {
46+
47+
//if we fail to load bytebuddy nothing will work
48+
Class<ByteBuddy> byteBuddyClass = ByteBuddy.class;
49+
Log.debug("byteBuddy Class " + byteBuddyClass.getName() + ", class loader: " + byteBuddyClass.getClassLoader());
50+
installTransformer(inst);
51+
52+
} catch (Throwable e) {
53+
Log.error("failed to install WithSpanTransformer in Digma agent", e);
54+
}
55+
56+
}
57+
58+
59+
private static void makeSureWithSpanClassIsAvailable() {
60+
61+
//if configuration is true inject and return
62+
if (Configuration.getInstance().shouldInjectOtelApiToSystemClassLoader()) {
63+
Log.debug("configuration for injecting otel api to system class loader is true, injecting otel api to system class loader.");
64+
injectOtelApiToSystemClassLoader();
65+
return;
66+
}
67+
68+
69+
//if configuration exists and is false quit and don't inject
70+
if (Configuration.getInstance().shouldInjectOtelApiToSystemClassLoaderExist() &&
71+
!Configuration.getInstance().shouldInjectOtelApiToSystemClassLoader()) {
72+
Log.debug("configuration for injecting otel api to system class loader is false, not injecting.");
73+
return;
74+
}
75+
76+
//else try to inject if WithSpan is not in system classpath
77+
try {
78+
Log.debug("checking if WithSpan class is available in classpath");
79+
Class.forName(WITH_SPAN_CLASS_NAME);
80+
Log.debug("WithSpan class is available in classpath");
81+
82+
} catch (ClassNotFoundException e) {
83+
Log.debug("WithSpan class is NOT available in classpath, trying to inject otel api");
84+
injectOtelApiToSystemClassLoader();
85+
}
86+
}
87+
88+
89+
private static void injectOtelApiToSystemClassLoader() {
90+
if (Configuration.getInstance().shouldInjectOtelApiToSystemClassLoaderExist() &&
91+
!Configuration.getInstance().shouldInjectOtelApiToSystemClassLoader()) {
92+
Log.debug("injectOtelApiToSystemClassLoader was called but configuration is false, not injecting");
93+
return;
94+
}
95+
96+
Log.debug("injecting otel api to system class loader.");
97+
try {
98+
OtelApiInjector.injectOtelApiJarToSystemClassLoader();
99+
} catch (UnsupportedOperationException e) {
100+
Log.error("got exception trying to inject otel api to system class loader. maybe this jvm doesn't support injecting a jar to " +
101+
"system class loader. please add otel api top the classpath in a different way", e);
102+
} catch (Throwable e) {
103+
Log.error("got exception trying to inject otel api to system class loader. " +
104+
"please fix the issue and try again , or add otel api to the classpath in a different way", e);
105+
}
106+
107+
}
108+
109+
110+
private static void installTransformer(Instrumentation inst) {
111+
20112
Log.debug("installing withSpanTransformer");
21113

22114
new AgentBuilder.Default()
@@ -63,7 +155,7 @@ private static AnnotationDescription getWithSpanAnnotationDescription(ClassLoade
63155
}
64156

65157

66-
private static AnnotationDescription getDigmaMarkerAnnotationDescription() {
158+
private static AnnotationDescription getDigmaMarkerAnnotationDescription() {
67159

68160
Log.debug("trying to load ExtendedObservability annotation class");
69161
AnnotationDescription annotationDescription = AnnotationDescription.Latent.Builder.ofType(ExtendedObservability.class).build();

src/main/java/org/digma/configuration/Configuration.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ public class Configuration {
6565
public static final String DEBUG_ENV_VAR = "DIGMA_AGENT_DEBUG";
6666

6767

68+
public static final String JDBC_PS_PARAMS_ENABLED_SYSTEM_PROPERTY = "org.digma.otel.instrumentation.jdbc.ps.params.enabled";
69+
public static final String JDBC_PS_PARAMS_ENABLED_ENABLED_ENV_VAR = "ORG_DIGMA_OTEL_INSTRUMENTATION_JDBC_PS_PARAMS_ENABLED";
70+
71+
6872
private final List<String> includePackages;
6973
private final List<String> excludeNames;
7074

@@ -123,6 +127,21 @@ public boolean isDebug() {
123127
return getBoolean(DEBUG_SYSTEM_PROPERTY, DEBUG_ENV_VAR);
124128
}
125129

130+
public boolean isExtendedObservabilityEnabled(){
131+
return !getIncludePackages().isEmpty();
132+
}
133+
134+
135+
private boolean isExposePreparedStatementsParametersExist() {
136+
return configurationReader.getEnvOrSystemProperty(JDBC_PS_PARAMS_ENABLED_SYSTEM_PROPERTY) != null ||
137+
configurationReader.getEnvOrSystemProperty(JDBC_PS_PARAMS_ENABLED_ENABLED_ENV_VAR) != null;
138+
}
139+
140+
public boolean isExposePreparedStatementsParametersEnabled() {
141+
return isExposePreparedStatementsParametersExist() &&
142+
getBoolean(JDBC_PS_PARAMS_ENABLED_SYSTEM_PROPERTY, JDBC_PS_PARAMS_ENABLED_ENABLED_ENV_VAR);
143+
}
144+
126145

127146
@NotNull
128147
public List<String> getIncludePackages() {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package org.digma.jdbc;
2+
3+
import net.ttddyy.dsproxy.ConnectionInfo;
4+
import net.ttddyy.dsproxy.proxy.ProxyConfig;
5+
import net.ttddyy.dsproxy.proxy.jdk.JdkJdbcProxyFactory;
6+
import org.digma.Log;
7+
import org.digma.configuration.Configuration;
8+
9+
import javax.sql.DataSource;
10+
import java.sql.*;
11+
12+
public class DigmaJdkJdbcProxyFactory extends JdkJdbcProxyFactory {
13+
14+
15+
@Override
16+
public PreparedStatement createPreparedStatement(PreparedStatement preparedStatement, String query, ConnectionInfo connectionInfo, Connection proxyConnection, ProxyConfig proxyConfig, boolean generateKey) {
17+
18+
Log.debug("DigmaJdkJdbcProxyFactory.createPreparedStatement for " + query);
19+
Log.debug("DigmaJdkJdbcProxyFactory.createPreparedStatement wrapping " + preparedStatement.getClass().getName());
20+
21+
if (Configuration.getInstance().isExposePreparedStatementsParametersEnabled()) {
22+
proxyConfig = ProxyConfig.Builder.create()
23+
.methodListener(new PreparedStatementMethodExecutionListener(query))
24+
.jdbcProxyFactory(new DigmaJdkJdbcProxyFactory())
25+
.build();
26+
}
27+
28+
return super.createPreparedStatement(preparedStatement, query, connectionInfo, proxyConnection, proxyConfig, generateKey);
29+
}
30+
31+
32+
@Override
33+
public DataSource createDataSource(DataSource dataSource, ProxyConfig proxyConfig) {
34+
return super.createDataSource(dataSource, proxyConfig);
35+
}
36+
37+
@Override
38+
public Connection createConnection(Connection connection, ConnectionInfo connectionInfo, ProxyConfig proxyConfig) {
39+
return super.createConnection(connection, connectionInfo, proxyConfig);
40+
}
41+
42+
@Override
43+
public Statement createStatement(Statement statement, ConnectionInfo connectionInfo, Connection proxyConnection, ProxyConfig proxyConfig) {
44+
return super.createStatement(statement, connectionInfo, proxyConnection, proxyConfig);
45+
}
46+
47+
@Override
48+
public CallableStatement createCallableStatement(CallableStatement callableStatement, String query, ConnectionInfo connectionInfo, Connection proxyConnection, ProxyConfig proxyConfig) {
49+
return super.createCallableStatement(callableStatement, query, connectionInfo, proxyConnection, proxyConfig);
50+
}
51+
52+
@Override
53+
public ResultSet createResultSet(ResultSet resultSet, ConnectionInfo connectionInfo, ProxyConfig proxyConfig) {
54+
return super.createResultSet(resultSet, connectionInfo, proxyConfig);
55+
}
56+
57+
@Override
58+
public ResultSet createGeneratedKeys(ResultSet resultSet, ConnectionInfo connectionInfo, ProxyConfig proxyConfig) {
59+
return super.createGeneratedKeys(resultSet, connectionInfo, proxyConfig);
60+
}
61+
}

0 commit comments

Comments
 (0)