|
| 1 | +package datadog.trace.instrumentation.r2dbc; |
| 2 | + |
| 3 | +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; |
| 4 | +import static datadog.trace.instrumentation.r2dbc.R2dbcDecorator.DECORATE; |
| 5 | +import static net.bytebuddy.matcher.ElementMatchers.isMethod; |
| 6 | +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; |
| 7 | +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; |
| 8 | + |
| 9 | +import com.google.auto.service.AutoService; |
| 10 | +import datadog.trace.agent.tooling.Instrumenter; |
| 11 | +import datadog.trace.agent.tooling.InstrumenterModule; |
| 12 | +import datadog.trace.api.Config; |
| 13 | +import io.r2dbc.proxy.core.ConnectionInfo; |
| 14 | +import io.r2dbc.spi.ConnectionFactoryOptions; |
| 15 | +import java.lang.reflect.Method; |
| 16 | +import net.bytebuddy.asm.Advice; |
| 17 | + |
| 18 | +/** |
| 19 | + * Instruments {@code io.r2dbc.proxy.callback.ConnectionCallbackHandler} to inject DBM SQL comments |
| 20 | + * into queries before they reach the database driver. This is the R2DBC equivalent of JDBC's {@code |
| 21 | + * DBMCompatibleConnectionInstrumentation}. |
| 22 | + * |
| 23 | + * <p>The r2dbc-proxy library uses JDK dynamic proxies for Connection objects, so we cannot |
| 24 | + * instrument them with ByteBuddy directly. Instead, we intercept the callback handler's {@code |
| 25 | + * invoke} method which is called for every method on the proxied Connection. When {@code |
| 26 | + * createStatement(String)} is invoked, we inject the SQL comment into the first argument. |
| 27 | + */ |
| 28 | +@AutoService(InstrumenterModule.class) |
| 29 | +public class R2dbcConnectionCallbackInstrumentation extends InstrumenterModule.Tracing |
| 30 | + implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { |
| 31 | + |
| 32 | + public R2dbcConnectionCallbackInstrumentation() { |
| 33 | + super("r2dbc"); |
| 34 | + } |
| 35 | + |
| 36 | + @Override |
| 37 | + public String instrumentedType() { |
| 38 | + return "io.r2dbc.proxy.callback.ConnectionCallbackHandler"; |
| 39 | + } |
| 40 | + |
| 41 | + @Override |
| 42 | + public String[] helperClassNames() { |
| 43 | + return new String[] { |
| 44 | + // See R2dbcInstrumentation#helperClassNames for why the full r2dbc-proxy class set |
| 45 | + // (rather than a hand-picked subset) is required, and for the topological ordering |
| 46 | + // rationale (supertypes must be injected before their implementing classes). |
| 47 | + "io.r2dbc.proxy.callback.AfterQueryCallbackInvoker", |
| 48 | + "io.r2dbc.proxy.callback.CallbackHandler", |
| 49 | + "io.r2dbc.proxy.callback.CallbackHandlerSupport", |
| 50 | + "io.r2dbc.proxy.callback.BatchCallbackHandler", |
| 51 | + "io.r2dbc.proxy.callback.CallbackHandlerSupport$MethodInvocationStrategy", |
| 52 | + "io.r2dbc.proxy.callback.ConnectionCallbackHandler", |
| 53 | + "io.r2dbc.proxy.callback.ConnectionFactoryCallbackHandler", |
| 54 | + "io.r2dbc.proxy.callback.MethodInvocationSubscriber", |
| 55 | + "io.r2dbc.proxy.callback.ConnectionFactoryCreateMethodInvocationSubscriber", |
| 56 | + "io.r2dbc.proxy.callback.ConnectionHolder", |
| 57 | + "io.r2dbc.proxy.callback.ConnectionIdManager", |
| 58 | + "io.r2dbc.proxy.callback.DefaultConnectionIdManager", |
| 59 | + "io.r2dbc.proxy.core.ConnectionInfo", |
| 60 | + "io.r2dbc.proxy.callback.DefaultConnectionInfo", |
| 61 | + "io.r2dbc.proxy.callback.DelegatingContextView", |
| 62 | + "io.r2dbc.proxy.callback.ProxyFactory", |
| 63 | + "io.r2dbc.proxy.callback.JdkProxyFactory", |
| 64 | + "io.r2dbc.proxy.callback.JdkProxyFactory$CallbackInvocationHandler", |
| 65 | + "io.r2dbc.proxy.callback.ProxyFactoryFactory", |
| 66 | + "io.r2dbc.proxy.callback.JdkProxyFactoryFactory", |
| 67 | + "io.r2dbc.proxy.core.BindInfo", |
| 68 | + "io.r2dbc.proxy.callback.MutableBindInfo", |
| 69 | + "io.r2dbc.proxy.core.MethodExecutionInfo", |
| 70 | + "io.r2dbc.proxy.callback.MutableMethodExecutionInfo", |
| 71 | + "io.r2dbc.proxy.core.QueryExecutionInfo", |
| 72 | + "io.r2dbc.proxy.callback.MutableQueryExecutionInfo", |
| 73 | + "io.r2dbc.proxy.core.StatementInfo", |
| 74 | + "io.r2dbc.proxy.callback.MutableStatementInfo", |
| 75 | + "io.r2dbc.proxy.callback.ProxyConfig", |
| 76 | + "io.r2dbc.proxy.callback.ProxyConfig$1", |
| 77 | + "io.r2dbc.proxy.callback.ProxyConfig$Builder", |
| 78 | + "io.r2dbc.proxy.callback.ProxyConfigHolder", |
| 79 | + "io.r2dbc.proxy.callback.ProxyUtils", |
| 80 | + "io.r2dbc.proxy.callback.QueriesExecutionContext", |
| 81 | + "io.r2dbc.proxy.callback.QueryInvocationSubscriber", |
| 82 | + "io.r2dbc.proxy.callback.ResultCallbackHandler", |
| 83 | + "io.r2dbc.proxy.callback.ResultInvocationSubscriber", |
| 84 | + "io.r2dbc.proxy.callback.RowCallbackHandler", |
| 85 | + "io.r2dbc.proxy.callback.StatementCallbackHandler", |
| 86 | + "io.r2dbc.proxy.callback.StopWatch", |
| 87 | + "io.r2dbc.proxy.core.Binding", |
| 88 | + "io.r2dbc.proxy.core.Bindings", |
| 89 | + "io.r2dbc.proxy.core.Bindings$1", |
| 90 | + "io.r2dbc.proxy.core.Bindings$IndexBinding", |
| 91 | + "io.r2dbc.proxy.core.Bindings$NamedBinding", |
| 92 | + "io.r2dbc.proxy.core.BoundValue", |
| 93 | + "io.r2dbc.proxy.core.BoundValue$DefaultBoundValue", |
| 94 | + "io.r2dbc.proxy.core.ValueStore", |
| 95 | + "io.r2dbc.proxy.core.DefaultValueStore", |
| 96 | + "io.r2dbc.proxy.core.ExecutionType", |
| 97 | + "io.r2dbc.proxy.core.ProxyEventType", |
| 98 | + "io.r2dbc.proxy.core.QueryInfo", |
| 99 | + "io.r2dbc.proxy.core.R2dbcProxyException", |
| 100 | + "io.r2dbc.proxy.listener.BindParameterConverter", |
| 101 | + "io.r2dbc.proxy.listener.BindParameterConverter$1", |
| 102 | + "io.r2dbc.proxy.listener.BindParameterConverter$BindOperation", |
| 103 | + "io.r2dbc.proxy.listener.ProxyExecutionListener", |
| 104 | + "io.r2dbc.proxy.listener.CompositeProxyExecutionListener", |
| 105 | + "io.r2dbc.proxy.listener.LastExecutionAwareListener", |
| 106 | + "io.r2dbc.proxy.listener.ProxyMethodExecutionListener", |
| 107 | + "io.r2dbc.proxy.listener.ProxyMethodExecutionListenerAdapter", |
| 108 | + "io.r2dbc.proxy.listener.ResultRowConverter", |
| 109 | + "io.r2dbc.proxy.listener.ResultRowConverter$GetOperation", |
| 110 | + "io.r2dbc.proxy.ProxyConnectionFactory", |
| 111 | + "io.r2dbc.proxy.ProxyConnectionFactory$1", |
| 112 | + "io.r2dbc.proxy.ProxyConnectionFactory$Builder", |
| 113 | + "io.r2dbc.proxy.ProxyConnectionFactory$Builder$1", |
| 114 | + "io.r2dbc.proxy.ProxyConnectionFactory$Builder$2", |
| 115 | + "io.r2dbc.proxy.ProxyConnectionFactory$Builder$3", |
| 116 | + "io.r2dbc.proxy.ProxyConnectionFactory$Builder$4", |
| 117 | + "io.r2dbc.proxy.ProxyConnectionFactory$Builder$5", |
| 118 | + "io.r2dbc.proxy.ProxyConnectionFactoryProvider", |
| 119 | + "io.r2dbc.proxy.support.FormatterUtils", |
| 120 | + "io.r2dbc.proxy.support.MethodExecutionInfoFormatter", |
| 121 | + "io.r2dbc.proxy.support.QueryExecutionInfoFormatter", |
| 122 | + "io.r2dbc.proxy.util.Assert", |
| 123 | + packageName + ".R2dbcDecorator", |
| 124 | + packageName + ".R2dbcSqlCommentInjector", |
| 125 | + packageName + ".R2dbcTracingSupport", |
| 126 | + packageName + ".R2dbcTracingSupport$ConnectionMetadataListener", |
| 127 | + packageName + ".TraceProxyExecutionListener", |
| 128 | + }; |
| 129 | + } |
| 130 | + |
| 131 | + @Override |
| 132 | + public void methodAdvice(MethodTransformer transformer) { |
| 133 | + transformer.applyAdvice( |
| 134 | + isMethod() |
| 135 | + .and(named("invoke")) |
| 136 | + .and(takesArguments(3)) |
| 137 | + .and(takesArgument(0, Object.class)) |
| 138 | + .and(takesArgument(1, Method.class)) |
| 139 | + .and(takesArgument(2, Object[].class)), |
| 140 | + getClass().getName() + "$InvokeAdvice"); |
| 141 | + } |
| 142 | + |
| 143 | + public static class InvokeAdvice { |
| 144 | + |
| 145 | + @Advice.OnMethodEnter(suppress = Throwable.class) |
| 146 | + public static void onEnter( |
| 147 | + @Advice.Argument(1) final Method method, |
| 148 | + @Advice.Argument(value = 2, readOnly = false) Object[] args, |
| 149 | + @Advice.FieldValue("connectionInfo") final ConnectionInfo connectionInfo) { |
| 150 | + if (args == null || args.length == 0) { |
| 151 | + return; |
| 152 | + } |
| 153 | + if (!"createStatement".equals(method.getName())) { |
| 154 | + return; |
| 155 | + } |
| 156 | + if (!(args[0] instanceof String)) { |
| 157 | + return; |
| 158 | + } |
| 159 | + |
| 160 | + String dbmMode = Config.get().getDbmPropagationMode(); |
| 161 | + boolean injectComment = |
| 162 | + Config.DBM_PROPAGATION_MODE_FULL.equals(dbmMode) |
| 163 | + || Config.DBM_PROPAGATION_MODE_STATIC.equals(dbmMode) |
| 164 | + || Config.DBM_PROPAGATION_MODE_DYNAMIC_SERVICE.equals(dbmMode); |
| 165 | + if (!injectComment) { |
| 166 | + return; |
| 167 | + } |
| 168 | + |
| 169 | + String sql = (String) args[0]; |
| 170 | + |
| 171 | + // Look up connection metadata from the map maintained by R2dbcTracingSupport |
| 172 | + String hostname = null; |
| 173 | + String dbName = null; |
| 174 | + String dbService = null; |
| 175 | + String dbType = null; |
| 176 | + |
| 177 | + ConnectionFactoryOptions options = R2dbcTracingSupport.CONNECTION_OPTIONS.get(connectionInfo); |
| 178 | + if (options != null) { |
| 179 | + dbType = DECORATE.extractDbType(options); |
| 180 | + dbService = DECORATE.getDbService(options); |
| 181 | + CharSequence hostnameSeq = null; |
| 182 | + if (options.hasOption(ConnectionFactoryOptions.HOST)) { |
| 183 | + Object host = options.getValue(ConnectionFactoryOptions.HOST); |
| 184 | + if (host != null) { |
| 185 | + hostnameSeq = host.toString(); |
| 186 | + } |
| 187 | + } |
| 188 | + hostname = hostnameSeq != null ? hostnameSeq.toString() : null; |
| 189 | + if (options.hasOption(ConnectionFactoryOptions.DATABASE)) { |
| 190 | + Object db = options.getValue(ConnectionFactoryOptions.DATABASE); |
| 191 | + dbName = db != null ? db.toString() : null; |
| 192 | + } |
| 193 | + } |
| 194 | + |
| 195 | + String injected = R2dbcSqlCommentInjector.inject(sql, dbService, dbType, hostname, dbName); |
| 196 | + if (!sql.equals(injected)) { |
| 197 | + // Replace the SQL argument with the injected version. |
| 198 | + // We must create a new array because ByteBuddy advice cannot mutate the original |
| 199 | + // array reference in place for @Advice.Argument(readOnly=false). |
| 200 | + Object[] newArgs = new Object[args.length]; |
| 201 | + System.arraycopy(args, 0, newArgs, 0, args.length); |
| 202 | + newArgs[0] = injected; |
| 203 | + args = newArgs; |
| 204 | + } |
| 205 | + } |
| 206 | + } |
| 207 | +} |
0 commit comments