From 5bf3376c8c69ee322de40ad1ec540f80469c575b Mon Sep 17 00:00:00 2001 From: Alexey Zhokhov Date: Sat, 19 Jun 2021 04:33:45 +0800 Subject: [PATCH] Added jdbc library with ported version of OpenTracing JDBC Instrumentation. --- instrumentation/jdbc/library/NOTICE.txt | 19 + .../jdbc/library/jdbc-library.gradle | 1 + .../instrumentation/jdbc/Classes.java | 161 +++++ .../instrumentation/jdbc/ConnectionInfo.java | 131 ++++ .../instrumentation/jdbc/JdbcTracing.java | 65 ++ .../jdbc/JdbcTracingUtils.java | 182 +++++ .../jdbc/TracingCallableStatement.java | 631 ++++++++++++++++++ .../jdbc/TracingConnection.java | 371 ++++++++++ .../jdbc/TracingDataSource.java | 147 ++++ .../instrumentation/jdbc/TracingDriver.java | 275 ++++++++ .../jdbc/TracingPreparedStatement.java | 343 ++++++++++ .../jdbc/TracingStatement.java | 311 +++++++++ .../instrumentation/jdbc/WrapperProxy.java | 156 +++++ .../jdbc/parser/AS400URLParser.java | 43 ++ .../jdbc/parser/AbstractMatcherURLParser.java | 89 +++ .../jdbc/parser/AbstractURLParser.java | 68 ++ .../jdbc/parser/ConnectionURLParser.java | 29 + .../jdbc/parser/DB2URLParser.java | 44 ++ .../jdbc/parser/H2URLParser.java | 157 +++++ .../jdbc/parser/MariadbURLParser.java | 29 + .../jdbc/parser/MysqlURLParser.java | 116 ++++ .../jdbc/parser/OracleURLParser.java | 172 +++++ .../jdbc/parser/PostgreSQLURLParser.java | 86 +++ .../jdbc/parser/SqlServerURLParser.java | 111 +++ .../jdbc/parser/URLLocation.java | 41 ++ .../jdbc/parser/URLParser.java | 102 +++ .../META-INF/services/java.sql.Driver | 1 + settings.gradle | 1 + 28 files changed, 3882 insertions(+) create mode 100644 instrumentation/jdbc/library/NOTICE.txt create mode 100644 instrumentation/jdbc/library/jdbc-library.gradle create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/Classes.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/ConnectionInfo.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/JdbcTracing.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/JdbcTracingUtils.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingCallableStatement.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingConnection.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingDataSource.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingDriver.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingPreparedStatement.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingStatement.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/WrapperProxy.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/AS400URLParser.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/AbstractMatcherURLParser.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/AbstractURLParser.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/ConnectionURLParser.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/DB2URLParser.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/H2URLParser.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/MariadbURLParser.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/MysqlURLParser.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/OracleURLParser.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/PostgreSQLURLParser.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/SqlServerURLParser.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/URLLocation.java create mode 100644 instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/URLParser.java create mode 100644 instrumentation/jdbc/library/src/main/resources/META-INF/services/java.sql.Driver diff --git a/instrumentation/jdbc/library/NOTICE.txt b/instrumentation/jdbc/library/NOTICE.txt new file mode 100644 index 000000000000..b46b353fc4b2 --- /dev/null +++ b/instrumentation/jdbc/library/NOTICE.txt @@ -0,0 +1,19 @@ +This product contains a modified part of OpenTracing JDBC Instrumentation: + + * License: + + Copyright 2017-2021 The OpenTracing Authors + + 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 + + https://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. + + * Homepage: https://github.com/opentracing-contrib/java-jdbc diff --git a/instrumentation/jdbc/library/jdbc-library.gradle b/instrumentation/jdbc/library/jdbc-library.gradle new file mode 100644 index 000000000000..9233e87a1921 --- /dev/null +++ b/instrumentation/jdbc/library/jdbc-library.gradle @@ -0,0 +1 @@ +apply plugin: "otel.library-instrumentation" diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/Classes.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/Classes.java new file mode 100644 index 000000000000..8a2224331875 --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/Classes.java @@ -0,0 +1,161 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashSet; + +public final class Classes { + + /** + * Adds all interfaces extended by the specified {@code iface} interface {@link Class}. + * + * @param iface The interface {@link Class}. + * @param set The set into which all extended interfaces are to be added. + * @throws NullPointerException If {@code iface} or {@code set} is null. + */ + private static void recurse(final Class iface, final HashSet> set) { + if (set.contains(iface)) { + return; + } + + set.add(iface); + for (final Class extended : iface.getInterfaces()) { + recurse(extended, set); + } + } + + /** + * Returns all interfaces implemented by the class or interface represented by the specified + * class. This method differentiates itself from {@link Class#getInterfaces()} by returning + * all interfaces (full depth and breadth) instead of just the interfaces directly + * implemented by the class. + * + * @param cls The class. + * @return All interfaces implemented by the class or interface represented by the specified + * class. + * @throws NullPointerException If {@code cls} is null. + */ + public static Class[] getAllInterfaces(final Class cls) { + Class parent = cls; + Class[] ifaces = null; + HashSet> set = null; + do { + ifaces = parent.getInterfaces(); + if (ifaces.length == 0) { + continue; + } + + if (set == null) { + set = new HashSet<>(4); + } + + for (final Class iface : ifaces) { + recurse(iface, set); + } + } + while ((parent = parent.getSuperclass()) != null); + return set == null ? ifaces : set.toArray(new Class[set.size()]); + } + + /** + * Returns a Method object that reflects the specified declared method of the class or interface + * represented by {@code cls} (excluding inherited methods), or {@code null} if the method is not + * found. + *

+ * Declared methods include public, protected, default (package) access, and private visibility. + *

+ * The {@code name} parameter is a String that specifies the simple name of the desired method, + * and the {@code parameterTypes} parameter is an array of Class objects that identify the + * method's formal parameter types, in declared order. If more than one method with the same + * parameter types is declared in a class, and one of these methods has a return type that is more + * specific than any of the others, that method is returned; otherwise one of the methods is + * chosen arbitrarily. If the name is {@code ""} or {@code ""} this method returns + * {@code null}. If this Class object represents an array type, then this method does not find the + * clone() method. + *

+ * This method differentiates itself from {@link Class#getDeclaredMethod(String, Class...)} by + * returning {@code null} when a method is not found, instead of throwing {@link + * NoSuchMethodException}. + * + * @param cls The class in which to find the declared method. + * @param name The simple name of the method. + * @param parameterTypes The parameter array. + * @return A Method object that reflects the specified declared method of the class or interface + * represented by {@code cls} (excluding inherited methods), or {@code null} if the method is not + * found. + * @throws NullPointerException If {@code cls} or {@code name} is null. + */ + public static Method getDeclaredMethod(final Class cls, final String name, + final Class... parameterTypes) { + final Method[] methods = cls.getDeclaredMethods(); + for (final Method method : methods) { + if (name.equals(method.getName()) && Arrays + .equals(method.getParameterTypes(), parameterTypes)) { + return method; + } + } + + return null; + } + + /** + * Returns a Method object that reflects the specified declared method of the class or interface + * represented by {@code cls} (including inherited methods), or {@code null} if the method is not + * found. + *

+ * Declared methods include public, protected, default (package) access, and private visibility. + *

+ * The {@code name} parameter is a String that specifies the simple name of the desired method, + * and the {@code parameterTypes} parameter is an array of Class objects that identify the + * method's formal parameter types, in declared order. If more than one method with the same + * parameter types is declared in a class, and one of these methods has a return type that is more + * specific than any of the others, that method is returned; otherwise one of the methods is + * chosen arbitrarily. If the name is {@code ""} or {@code ""} this method returns + * {@code null}. If this Class object represents an array type, then this method does not find the + * clone() method. + *

+ * This method differentiates itself from {@link Class#getDeclaredMethod(String, Class...)} by + * returning {@code null} when a method is not found, instead of throwing {@link + * NoSuchMethodException}. + * + * @param cls The class in which to find the declared method. + * @param name The simple name of the method. + * @param parameterTypes The parameter array. + * @return A Method object that reflects the specified declared method of the class or interface + * represented by {@code cls} (including inherited methods), or {@code null} if the method is not + * found. + * @throws NullPointerException If {@code cls} or {@code name} is null. + */ + public static Method getDeclaredMethodDeep(Class cls, final String name, + final Class... parameterTypes) { + Method method; + do { + method = getDeclaredMethod(cls, name, parameterTypes); + } + while (method == null && (cls = cls.getSuperclass()) != null); + return method; + } + + private Classes() { + } +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/ConnectionInfo.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/ConnectionInfo.java new file mode 100644 index 000000000000..f2d87ecbd725 --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/ConnectionInfo.java @@ -0,0 +1,131 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc; + +public class ConnectionInfo { + + public static ConnectionInfo UNKNOWN_CONNECTION_INFO = new Builder("unknown_peer") + .dbType("unknown_type").dbInstance("unknown_instance").build(); + + private final String dbType; + private final String dbUser; + private final String dbInstance; + private final String dbPeer; + private final String dbPeerService; + + private ConnectionInfo(String dbType, String dbUser, String dbInstance, String dbHost, + Integer dbPort) { + this.dbType = dbType; + this.dbUser = dbUser; + this.dbInstance = dbInstance; + if (dbHost != null && dbPort != null) { + this.dbPeer = dbHost + ":" + dbPort; + } else { + this.dbPeer = ""; + } + + this.dbPeerService = makePeerService(); + } + + private ConnectionInfo(String dbType, String dbUser, String dbInstance, String dbPeer) { + this.dbType = dbType; + this.dbUser = dbUser; + this.dbInstance = dbInstance; + this.dbPeer = dbPeer; + + this.dbPeerService = makePeerService(); + } + + /** + * Make a unique serviceName that could be used in dependency diagram. + */ + private String makePeerService() { + if (null != dbInstance && !dbInstance.isEmpty()) { + return dbInstance + "[" + dbType + "(" + dbPeer + ")]"; + } else { + return dbType + "(" + dbPeer + ")"; + } + } + + public String getDbType() { + return dbType; + } + + public String getDbUser() { + return dbUser; + } + + public String getDbInstance() { + return dbInstance; + } + + public String getDbPeer() { + return dbPeer; + } + + public String getPeerService() { + return dbPeerService; + } + + public static class Builder { + + private String dbType; + private String dbUser; + private String dbInstance; + private String dbHost; + private Integer dbPort; + private String dbPeer; + + public Builder(String dbPeer) { + this.dbPeer = dbPeer; + } + + public Builder(String dbHost, Integer dbPort) { + this.dbHost = dbHost; + this.dbPort = dbPort; + } + + public Builder dbType(String dbType) { + this.dbType = dbType; + return this; + } + + public Builder dbUser(String dbUser) { + this.dbUser = dbUser; + return this; + } + + public Builder dbInstance(String dbInstance) { + this.dbInstance = dbInstance; + return this; + } + + public ConnectionInfo build() { + if (this.dbPeer != null && !dbPeer.isEmpty()) { + return new ConnectionInfo(this.dbType, this.dbUser, this.dbInstance, this.dbPeer); + } + return new ConnectionInfo(this.dbType, this.dbUser, this.dbInstance, this.dbHost, + this.dbPort); + } + + } + +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/JdbcTracing.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/JdbcTracing.java new file mode 100644 index 000000000000..261f39bf7fc3 --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/JdbcTracing.java @@ -0,0 +1,65 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc; + +public class JdbcTracing { + + private static boolean traceEnabled = true; + + /** + * Sets the {@code traceEnabled} property to enable or disable traces. + * + * @param traceEnabled The {@code traceEnabled} value. + */ + public static void setTraceEnabled(boolean traceEnabled) { + JdbcTracing.traceEnabled = traceEnabled; + } + + public static boolean isTraceEnabled() { + return JdbcTracing.traceEnabled; + } + + /** + * can be modified by application code + */ + private static int slowQueryThresholdMs = Integer + .getInteger("io.opentracing.contrib.jdbc.slowQueryThresholdMs", 0); + + public static int getSlowQueryThresholdMs() { + return slowQueryThresholdMs; + } + + public static void setSlowQueryThresholdMs(final int slowQueryThresholdMs) { + JdbcTracing.slowQueryThresholdMs = slowQueryThresholdMs; + } + + private static int excludeFastQueryThresholdMs = Integer + .getInteger("io.opentracing.contrib.jdbc.excludeFastQueryThresholdMs", 0); + + public static int getExcludeFastQueryThresholdMs() { + return excludeFastQueryThresholdMs; + } + + public static void setExcludeFastQueryThresholdMs(final int excludeFastQueryThresholdMs) { + JdbcTracing.excludeFastQueryThresholdMs = excludeFastQueryThresholdMs; + } + +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/JdbcTracingUtils.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/JdbcTracingUtils.java new file mode 100644 index 000000000000..31d0a6484cbc --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/JdbcTracingUtils.java @@ -0,0 +1,182 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +class JdbcTracingUtils { + + static final AttributeKey SLOW = AttributeKey.booleanKey("slow"); + + // TODO is it requires for OTEL? + static final AttributeKey SAMPLING_PRIORITY = AttributeKey.longKey("sampling.priority"); + + static Span buildSpan(String operationName, + String sql, + ConnectionInfo connectionInfo, + boolean withActiveSpanOnly, + Set ignoreStatements, + Tracer tracer) { + if (!JdbcTracing.isTraceEnabled() + || (withActiveSpanOnly && Span.fromContextOrNull(Context.current()) == null)) { + // noop span + return Span.getInvalid(); + } else if (ignoreStatements != null && ignoreStatements.contains(sql)) { + // noop span + return Span.getInvalid(); + } + + SpanBuilder spanBuilder = tracer.spanBuilder(operationName) + .setSpanKind(SpanKind.CLIENT); + + Span span = spanBuilder.startSpan(); + decorate(span, sql, connectionInfo); + + return span; + } + + static void execute(String operationName, + CheckedRunnable runnable, + String sql, + ConnectionInfo connectionInfo, + boolean withActiveSpanOnly, + Set ignoreStatements, + Tracer tracer) throws E { + if (!JdbcTracing.isTraceEnabled() + || (withActiveSpanOnly && Span.fromContextOrNull(Context.current()) == null)) { + runnable.run(); + return; + } + + final Span span = buildSpan(operationName, sql, connectionInfo, withActiveSpanOnly, + ignoreStatements, tracer); + long startTime = (JdbcTracing.getSlowQueryThresholdMs() > 0 + || JdbcTracing.getExcludeFastQueryThresholdMs() > 0) ? System.nanoTime() : 0; + try (Scope ignored = span.makeCurrent()) { + runnable.run(); + } catch (Exception e) { + JdbcTracingUtils.onError(e, span); + throw e; + } finally { + JdbcTracingUtils.queryThresholdChecks(span, startTime); + span.end(); + } + } + + static T call(String operationName, + CheckedCallable callable, + String sql, + ConnectionInfo connectionInfo, + boolean withActiveSpanOnly, + Set ignoreStatements, + Tracer tracer) throws E { + if (!JdbcTracing.isTraceEnabled() + || (withActiveSpanOnly && Span.fromContextOrNull(Context.current()) == null)) { + return callable.call(); + } + + final Span span = buildSpan(operationName, sql, connectionInfo, withActiveSpanOnly, + ignoreStatements, tracer); + long startTime = JdbcTracing.getSlowQueryThresholdMs() > 0 ? System.nanoTime() : 0; + try (Scope ignored = span.makeCurrent()) { + return callable.call(); + } catch (Exception e) { + JdbcTracingUtils.onError(e, span); + throw e; + } finally { + JdbcTracingUtils.queryThresholdChecks(span, startTime); + span.end(); + } + } + + private static boolean isNotEmpty(CharSequence s) { + return s != null && !"".contentEquals(s); + } + + /** + * Add tags to span. Skip empty tags to avoid reported NPE in tracers. + */ + private static void decorate(Span span, String sql, ConnectionInfo connectionInfo) { + if (isNotEmpty(sql)) { + span.setAttribute(SemanticAttributes.DB_STATEMENT, sql); + } + if (isNotEmpty(connectionInfo.getDbType())) { + span.setAttribute(SemanticAttributes.DB_SYSTEM, connectionInfo.getDbType()); + } + if (isNotEmpty(connectionInfo.getDbPeer())) { + span.setAttribute(SemanticAttributes.NET_PEER_IP, connectionInfo.getDbPeer()); + } + if (isNotEmpty(connectionInfo.getDbInstance())) { + span.setAttribute(SemanticAttributes.DB_NAME, connectionInfo.getDbInstance()); + } + if (isNotEmpty(connectionInfo.getDbUser())) { + span.setAttribute(SemanticAttributes.DB_USER, connectionInfo.getDbUser()); + } + if (isNotEmpty(connectionInfo.getPeerService())) { + span.setAttribute(SemanticAttributes.PEER_SERVICE, connectionInfo.getPeerService()); + } + } + + static void onError(Throwable throwable, Span span) { + span.setStatus(StatusCode.ERROR); + + if (throwable != null) { + span.recordException(throwable); + } + } + + private static void queryThresholdChecks(Span span, long startTime) { + long completionTime = System.nanoTime() - startTime; + if (JdbcTracing.getExcludeFastQueryThresholdMs() > 0 && completionTime < TimeUnit.MILLISECONDS + .toNanos(JdbcTracing.getExcludeFastQueryThresholdMs())) { + span.setAttribute(SAMPLING_PRIORITY, 0); + } + if (JdbcTracing.getSlowQueryThresholdMs() > 0 && completionTime > TimeUnit.MILLISECONDS + .toNanos(JdbcTracing.getSlowQueryThresholdMs())) { + span.setAttribute(SLOW, true); + } + } + + @FunctionalInterface + interface CheckedRunnable { + + void run() throws E; + + } + + @FunctionalInterface + interface CheckedCallable { + + T call() throws E; + + } + +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingCallableStatement.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingCallableStatement.java new file mode 100644 index 000000000000..fb4ab347e4ec --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingCallableStatement.java @@ -0,0 +1,631 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc; + +import io.opentelemetry.api.trace.Tracer; +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.Date; +import java.sql.NClob; +import java.sql.Ref; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLXML; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Calendar; +import java.util.Map; +import java.util.Set; + +public class TracingCallableStatement extends TracingPreparedStatement implements + CallableStatement { + + private final CallableStatement statement; + + public TracingCallableStatement(CallableStatement statement, String query, + ConnectionInfo connectionInfo, boolean withActiveSpanOnly, Set ignoreStatements, + Tracer tracer) { + super(statement, query, connectionInfo, withActiveSpanOnly, ignoreStatements, tracer); + this.statement = statement; + } + + @Override + public void registerOutParameter(int parameterIndex, int sqlType) throws SQLException { + statement.registerOutParameter(parameterIndex, sqlType); + } + + @Override + public void registerOutParameter(int parameterIndex, int sqlType, int scale) throws SQLException { + statement.registerOutParameter(parameterIndex, sqlType, scale); + } + + @Override + public boolean wasNull() throws SQLException { + return statement.wasNull(); + } + + @Override + public String getString(int parameterIndex) throws SQLException { + return statement.getString(parameterIndex); + } + + @Override + public boolean getBoolean(int parameterIndex) throws SQLException { + return statement.getBoolean(parameterIndex); + } + + @Override + public byte getByte(int parameterIndex) throws SQLException { + return statement.getByte(parameterIndex); + } + + @Override + public short getShort(int parameterIndex) throws SQLException { + return statement.getShort(parameterIndex); + } + + @Override + public int getInt(int parameterIndex) throws SQLException { + return statement.getInt(parameterIndex); + } + + @Override + public long getLong(int parameterIndex) throws SQLException { + return statement.getLong(parameterIndex); + } + + @Override + public float getFloat(int parameterIndex) throws SQLException { + return statement.getFloat(parameterIndex); + } + + @Override + public double getDouble(int parameterIndex) throws SQLException { + return statement.getDouble(parameterIndex); + } + + @Override + @Deprecated + public BigDecimal getBigDecimal(int parameterIndex, int scale) throws SQLException { + return statement.getBigDecimal(parameterIndex, scale); + } + + @Override + public byte[] getBytes(int parameterIndex) throws SQLException { + return statement.getBytes(parameterIndex); + } + + @Override + public Date getDate(int parameterIndex) throws SQLException { + return statement.getDate(parameterIndex); + } + + @Override + public Time getTime(int parameterIndex) throws SQLException { + return statement.getTime(parameterIndex); + } + + @Override + public Timestamp getTimestamp(int parameterIndex) throws SQLException { + return statement.getTimestamp(parameterIndex); + } + + @Override + public Object getObject(int parameterIndex) throws SQLException { + return statement.getObject(parameterIndex); + } + + @Override + public BigDecimal getBigDecimal(int parameterIndex) throws SQLException { + return statement.getBigDecimal(parameterIndex); + } + + @Override + public Object getObject(int parameterIndex, Map> map) throws SQLException { + return statement.getObject(parameterIndex, map); + } + + @Override + public Ref getRef(int parameterIndex) throws SQLException { + return statement.getRef(parameterIndex); + } + + @Override + public Blob getBlob(int parameterIndex) throws SQLException { + return statement.getBlob(parameterIndex); + } + + @Override + public Clob getClob(int parameterIndex) throws SQLException { + return statement.getClob(parameterIndex); + } + + @Override + public Array getArray(int parameterIndex) throws SQLException { + return statement.getArray(parameterIndex); + } + + @Override + public Date getDate(int parameterIndex, Calendar cal) throws SQLException { + return statement.getDate(parameterIndex, cal); + } + + @Override + public Time getTime(int parameterIndex, Calendar cal) throws SQLException { + return statement.getTime(parameterIndex, cal); + } + + @Override + public Timestamp getTimestamp(int parameterIndex, Calendar cal) throws SQLException { + return statement.getTimestamp(parameterIndex, cal); + } + + @Override + public void registerOutParameter(int parameterIndex, int sqlType, String typeName) + throws SQLException { + statement.registerOutParameter(parameterIndex, sqlType, typeName); + } + + @Override + public void registerOutParameter(String parameterName, int sqlType) throws SQLException { + statement.registerOutParameter(parameterName, sqlType); + } + + @Override + public void registerOutParameter(String parameterName, int sqlType, int scale) + throws SQLException { + statement.registerOutParameter(parameterName, sqlType, scale); + } + + @Override + public void registerOutParameter(String parameterName, int sqlType, String typeName) + throws SQLException { + statement.registerOutParameter(parameterName, sqlType, typeName); + } + + @Override + public URL getURL(int parameterIndex) throws SQLException { + return statement.getURL(parameterIndex); + } + + @Override + public void setURL(String parameterName, URL val) throws SQLException { + statement.setURL(parameterName, val); + } + + @Override + public void setNull(String parameterName, int sqlType) throws SQLException { + statement.setNull(parameterName, sqlType); + } + + @Override + public void setBoolean(String parameterName, boolean x) throws SQLException { + statement.setBoolean(parameterName, x); + } + + @Override + public void setByte(String parameterName, byte x) throws SQLException { + statement.setByte(parameterName, x); + } + + @Override + public void setShort(String parameterName, short x) throws SQLException { + statement.setShort(parameterName, x); + } + + @Override + public void setInt(String parameterName, int x) throws SQLException { + statement.setInt(parameterName, x); + } + + @Override + public void setLong(String parameterName, long x) throws SQLException { + statement.setLong(parameterName, x); + } + + @Override + public void setFloat(String parameterName, float x) throws SQLException { + statement.setFloat(parameterName, x); + } + + @Override + public void setDouble(String parameterName, double x) throws SQLException { + statement.setDouble(parameterName, x); + } + + @Override + public void setBigDecimal(String parameterName, BigDecimal x) throws SQLException { + statement.setBigDecimal(parameterName, x); + } + + @Override + public void setString(String parameterName, String x) throws SQLException { + statement.setString(parameterName, x); + } + + @Override + public void setBytes(String parameterName, byte[] x) throws SQLException { + statement.setBytes(parameterName, x); + } + + @Override + public void setDate(String parameterName, Date x) throws SQLException { + statement.setDate(parameterName, x); + } + + @Override + public void setTime(String parameterName, Time x) throws SQLException { + statement.setTime(parameterName, x); + } + + @Override + public void setTimestamp(String parameterName, Timestamp x) throws SQLException { + statement.setTimestamp(parameterName, x); + } + + @Override + public void setAsciiStream(String parameterName, InputStream x, int length) throws SQLException { + statement.setAsciiStream(parameterName, x, length); + } + + @Override + public void setBinaryStream(String parameterName, InputStream x, int length) throws SQLException { + statement.setBinaryStream(parameterName, x, length); + } + + @Override + public void setObject(String parameterName, Object x, int targetSqlType, int scale) + throws SQLException { + statement.setObject(parameterName, x, targetSqlType, scale); + } + + @Override + public void setObject(String parameterName, Object x, int targetSqlType) throws SQLException { + statement.setObject(parameterName, x, targetSqlType); + } + + @Override + public void setObject(String parameterName, Object x) throws SQLException { + statement.setObject(parameterName, x); + } + + @Override + public void setCharacterStream(String parameterName, Reader reader, int length) + throws SQLException { + statement.setCharacterStream(parameterName, reader, length); + } + + @Override + public void setDate(String parameterName, Date x, Calendar cal) throws SQLException { + statement.setDate(parameterName, x, cal); + } + + @Override + public void setTime(String parameterName, Time x, Calendar cal) throws SQLException { + statement.setTime(parameterName, x, cal); + } + + @Override + public void setTimestamp(String parameterName, Timestamp x, Calendar cal) throws SQLException { + statement.setTimestamp(parameterName, x, cal); + } + + @Override + public void setNull(String parameterName, int sqlType, String typeName) throws SQLException { + statement.setNull(parameterName, sqlType, typeName); + } + + @Override + public String getString(String parameterName) throws SQLException { + return statement.getString(parameterName); + } + + @Override + public boolean getBoolean(String parameterName) throws SQLException { + return statement.getBoolean(parameterName); + } + + @Override + public byte getByte(String parameterName) throws SQLException { + return statement.getByte(parameterName); + } + + @Override + public short getShort(String parameterName) throws SQLException { + return statement.getShort(parameterName); + } + + @Override + public int getInt(String parameterName) throws SQLException { + return statement.getInt(parameterName); + } + + @Override + public long getLong(String parameterName) throws SQLException { + return statement.getLong(parameterName); + } + + @Override + public float getFloat(String parameterName) throws SQLException { + return statement.getFloat(parameterName); + } + + @Override + public double getDouble(String parameterName) throws SQLException { + return statement.getDouble(parameterName); + } + + @Override + public byte[] getBytes(String parameterName) throws SQLException { + return statement.getBytes(parameterName); + } + + @Override + public Date getDate(String parameterName) throws SQLException { + return statement.getDate(parameterName); + } + + @Override + public Time getTime(String parameterName) throws SQLException { + return statement.getTime(parameterName); + } + + @Override + public Timestamp getTimestamp(String parameterName) throws SQLException { + return statement.getTimestamp(parameterName); + } + + @Override + public Object getObject(String parameterName) throws SQLException { + return statement.getObject(parameterName); + } + + @Override + public BigDecimal getBigDecimal(String parameterName) throws SQLException { + return statement.getBigDecimal(parameterName); + } + + @Override + public Object getObject(String parameterName, Map> map) throws SQLException { + return statement.getObject(parameterName, map); + } + + @Override + public Ref getRef(String parameterName) throws SQLException { + return statement.getRef(parameterName); + } + + @Override + public Blob getBlob(String parameterName) throws SQLException { + return statement.getBlob(parameterName); + } + + @Override + public Clob getClob(String parameterName) throws SQLException { + return statement.getClob(parameterName); + } + + @Override + public Array getArray(String parameterName) throws SQLException { + return statement.getArray(parameterName); + } + + @Override + public Date getDate(String parameterName, Calendar cal) throws SQLException { + return statement.getDate(parameterName, cal); + } + + @Override + public Time getTime(String parameterName, Calendar cal) throws SQLException { + return statement.getTime(parameterName, cal); + } + + @Override + public Timestamp getTimestamp(String parameterName, Calendar cal) throws SQLException { + return statement.getTimestamp(parameterName, cal); + } + + @Override + public URL getURL(String parameterName) throws SQLException { + return statement.getURL(parameterName); + } + + @Override + public RowId getRowId(int parameterIndex) throws SQLException { + return statement.getRowId(parameterIndex); + } + + @Override + public RowId getRowId(String parameterName) throws SQLException { + return statement.getRowId(parameterName); + } + + @Override + public void setRowId(String parameterName, RowId x) throws SQLException { + statement.setRowId(parameterName, x); + } + + @Override + public void setNString(String parameterName, String value) throws SQLException { + statement.setNString(parameterName, value); + } + + @Override + public void setNCharacterStream(String parameterName, Reader value, long length) + throws SQLException { + statement.setNCharacterStream(parameterName, value, length); + } + + @Override + public void setNClob(String parameterName, NClob value) throws SQLException { + statement.setNClob(parameterName, value); + } + + @Override + public void setClob(String parameterName, Reader reader, long length) throws SQLException { + statement.setClob(parameterName, reader, length); + } + + @Override + public void setBlob(String parameterName, InputStream inputStream, long length) + throws SQLException { + statement.setBlob(parameterName, inputStream, length); + } + + @Override + public void setNClob(String parameterName, Reader reader, long length) throws SQLException { + statement.setNClob(parameterName, reader, length); + } + + @Override + public NClob getNClob(int parameterIndex) throws SQLException { + return statement.getNClob(parameterIndex); + } + + @Override + public NClob getNClob(String parameterName) throws SQLException { + return statement.getNClob(parameterName); + } + + @Override + public void setSQLXML(String parameterName, SQLXML xmlObject) throws SQLException { + statement.setSQLXML(parameterName, xmlObject); + } + + @Override + public SQLXML getSQLXML(int parameterIndex) throws SQLException { + return statement.getSQLXML(parameterIndex); + } + + @Override + public SQLXML getSQLXML(String parameterName) throws SQLException { + return statement.getSQLXML(parameterName); + } + + @Override + public String getNString(int parameterIndex) throws SQLException { + return statement.getNString(parameterIndex); + } + + @Override + public String getNString(String parameterName) throws SQLException { + return statement.getNString(parameterName); + } + + @Override + public Reader getNCharacterStream(int parameterIndex) throws SQLException { + return statement.getNCharacterStream(parameterIndex); + } + + @Override + public Reader getNCharacterStream(String parameterName) throws SQLException { + return statement.getNCharacterStream(parameterName); + } + + @Override + public Reader getCharacterStream(int parameterIndex) throws SQLException { + return statement.getCharacterStream(parameterIndex); + } + + @Override + public Reader getCharacterStream(String parameterName) throws SQLException { + return statement.getCharacterStream(parameterName); + } + + @Override + public void setBlob(String parameterName, Blob x) throws SQLException { + statement.setBlob(parameterName, x); + } + + @Override + public void setClob(String parameterName, Clob x) throws SQLException { + statement.setClob(parameterName, x); + } + + @Override + public void setAsciiStream(String parameterName, InputStream x, long length) throws SQLException { + statement.setAsciiStream(parameterName, x, length); + } + + @Override + public void setBinaryStream(String parameterName, InputStream x, long length) + throws SQLException { + statement.setBinaryStream(parameterName, x, length); + } + + @Override + public void setCharacterStream(String parameterName, Reader reader, long length) + throws SQLException { + statement.setCharacterStream(parameterName, reader, length); + } + + @Override + public void setAsciiStream(String parameterName, InputStream x) throws SQLException { + statement.setAsciiStream(parameterName, x); + } + + @Override + public void setBinaryStream(String parameterName, InputStream x) throws SQLException { + statement.setBinaryStream(parameterName, x); + } + + @Override + public void setCharacterStream(String parameterName, Reader reader) throws SQLException { + statement.setCharacterStream(parameterName, reader); + } + + @Override + public void setNCharacterStream(String parameterName, Reader value) throws SQLException { + statement.setNCharacterStream(parameterName, value); + } + + @Override + public void setClob(String parameterName, Reader reader) throws SQLException { + statement.setClob(parameterName, reader); + } + + @Override + public void setBlob(String parameterName, InputStream inputStream) throws SQLException { + statement.setBlob(parameterName, inputStream); + } + + @Override + public void setNClob(String parameterName, Reader reader) throws SQLException { + statement.setNClob(parameterName, reader); + } + + @Override + public T getObject(int parameterIndex, Class type) throws SQLException { + return statement.getObject(parameterIndex, type); + } + + @Override + public T getObject(String parameterName, Class type) throws SQLException { + return statement.getObject(parameterName, type); + } + +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingConnection.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingConnection.java new file mode 100644 index 000000000000..2cd10fafb8f1 --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingConnection.java @@ -0,0 +1,371 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc; + +import io.opentelemetry.api.trace.Tracer; +import java.sql.Array; +import java.sql.Blob; +import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.NClob; +import java.sql.PreparedStatement; +import java.sql.SQLClientInfoException; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Savepoint; +import java.sql.Statement; +import java.sql.Struct; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.Executor; + +public class TracingConnection implements Connection { + + private final Connection connection; + private final ConnectionInfo connectionInfo; + private final boolean withActiveSpanOnly; + private final Set ignoreStatements; + private final Tracer tracer; + + public TracingConnection(Connection connection, ConnectionInfo connectionInfo, + boolean withActiveSpanOnly, Set ignoreStatements, Tracer tracer) { + this.connection = connection; + this.connectionInfo = connectionInfo; + this.withActiveSpanOnly = withActiveSpanOnly; + this.ignoreStatements = ignoreStatements; + this.tracer = tracer; + } + + @Override + public Statement createStatement() throws SQLException { + final Statement statement = connection.createStatement(); + return WrapperProxy + .wrap(statement, new TracingStatement(statement, connectionInfo, withActiveSpanOnly, + ignoreStatements, tracer)); + } + + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency) + throws SQLException { + final Statement statement = connection.createStatement(resultSetType, resultSetConcurrency); + return WrapperProxy.wrap(statement, new TracingStatement(statement, + connectionInfo, withActiveSpanOnly, ignoreStatements, tracer)); + } + + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency, + int resultSetHoldability) throws SQLException { + final Statement statement = connection + .createStatement(resultSetType, resultSetConcurrency, resultSetHoldability); + return WrapperProxy.wrap(statement, new TracingStatement(statement, + connectionInfo, withActiveSpanOnly, ignoreStatements, tracer)); + } + + @Override + public PreparedStatement prepareStatement(String sql) throws SQLException { + final PreparedStatement statement = connection.prepareStatement(sql); + return WrapperProxy.wrap(statement, new TracingPreparedStatement(statement, sql, connectionInfo, + withActiveSpanOnly, ignoreStatements, tracer)); + } + + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) + throws SQLException { + final PreparedStatement statement = connection + .prepareStatement(sql, resultSetType, resultSetConcurrency); + return WrapperProxy.wrap(statement, new TracingPreparedStatement(statement, sql, connectionInfo, + withActiveSpanOnly, ignoreStatements, tracer)); + } + + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, + int resultSetHoldability) throws SQLException { + final PreparedStatement statement = connection + .prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability); + return WrapperProxy.wrap(statement, new TracingPreparedStatement(statement, + sql, connectionInfo, withActiveSpanOnly, ignoreStatements, tracer)); + } + + @Override + public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { + final PreparedStatement statement = connection.prepareStatement(sql, autoGeneratedKeys); + return WrapperProxy.wrap(statement, new TracingPreparedStatement(statement, sql, + connectionInfo, withActiveSpanOnly, ignoreStatements, tracer)); + } + + @Override + public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { + final PreparedStatement statement = connection.prepareStatement(sql, columnIndexes); + return WrapperProxy.wrap(statement, new TracingPreparedStatement(statement, sql, + connectionInfo, withActiveSpanOnly, ignoreStatements, tracer)); + } + + @Override + public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { + final PreparedStatement statement = connection.prepareStatement(sql, columnNames); + return WrapperProxy.wrap(statement, new TracingPreparedStatement(statement, sql, connectionInfo, + withActiveSpanOnly, ignoreStatements, tracer)); + } + + @Override + public CallableStatement prepareCall(String sql) throws SQLException { + final CallableStatement statement = connection.prepareCall(sql); + return WrapperProxy.wrap(statement, + new TracingCallableStatement(connection.prepareCall(sql), sql, connectionInfo, + withActiveSpanOnly, ignoreStatements, tracer)); + } + + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) + throws SQLException { + final CallableStatement statement = connection + .prepareCall(sql, resultSetType, resultSetConcurrency); + return WrapperProxy.wrap(statement, new TracingCallableStatement(statement, sql, connectionInfo, + withActiveSpanOnly, ignoreStatements, tracer)); + } + + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, + int resultSetHoldability) throws SQLException { + final CallableStatement statement = connection + .prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability); + return WrapperProxy.wrap(statement, new TracingCallableStatement(statement, sql, + connectionInfo, withActiveSpanOnly, ignoreStatements, tracer)); + } + + @Override + public void commit() throws SQLException { + JdbcTracingUtils.execute("Commit", connection::commit, null, + connectionInfo, withActiveSpanOnly, null, tracer); + } + + @Override + public void rollback() throws SQLException { + JdbcTracingUtils.execute("Rollback", connection::rollback, null, + connectionInfo, withActiveSpanOnly, null, tracer); + } + + @Override + public void close() throws SQLException { + JdbcTracingUtils.execute("Close", connection::close, null, + connectionInfo, withActiveSpanOnly, null, tracer); + } + + @Override + public String nativeSQL(String sql) throws SQLException { + return connection.nativeSQL(sql); + } + + @Override + public void setAutoCommit(boolean autoCommit) throws SQLException { + connection.setAutoCommit(autoCommit); + } + + @Override + public boolean getAutoCommit() throws SQLException { + return connection.getAutoCommit(); + } + + @Override + public boolean isClosed() throws SQLException { + return connection.isClosed(); + } + + @Override + public DatabaseMetaData getMetaData() throws SQLException { + return connection.getMetaData(); + } + + @Override + public void setReadOnly(boolean readOnly) throws SQLException { + connection.setReadOnly(readOnly); + } + + @Override + public boolean isReadOnly() throws SQLException { + return connection.isReadOnly(); + } + + @Override + public void setCatalog(String catalog) throws SQLException { + connection.setCatalog(catalog); + } + + @Override + public String getCatalog() throws SQLException { + return connection.getCatalog(); + } + + @Override + public void setTransactionIsolation(int level) throws SQLException { + connection.setTransactionIsolation(level); + } + + @Override + public int getTransactionIsolation() throws SQLException { + return connection.getTransactionIsolation(); + } + + @Override + public SQLWarning getWarnings() throws SQLException { + return connection.getWarnings(); + } + + @Override + public void clearWarnings() throws SQLException { + connection.clearWarnings(); + } + + @Override + public Map> getTypeMap() throws SQLException { + return connection.getTypeMap(); + } + + @Override + public void setTypeMap(Map> map) throws SQLException { + connection.setTypeMap(map); + } + + @Override + public void setHoldability(int holdability) throws SQLException { + connection.setHoldability(holdability); + } + + @Override + public int getHoldability() throws SQLException { + return connection.getHoldability(); + } + + @Override + public Savepoint setSavepoint() throws SQLException { + return connection.setSavepoint(); + } + + @Override + public Savepoint setSavepoint(String name) throws SQLException { + return connection.setSavepoint(name); + } + + @Override + public void rollback(Savepoint savepoint) throws SQLException { + connection.rollback(savepoint); + } + + @Override + public void releaseSavepoint(Savepoint savepoint) throws SQLException { + connection.releaseSavepoint(savepoint); + } + + @Override + public Clob createClob() throws SQLException { + return connection.createClob(); + } + + @Override + public Blob createBlob() throws SQLException { + return connection.createBlob(); + } + + @Override + public NClob createNClob() throws SQLException { + return connection.createNClob(); + } + + @Override + public SQLXML createSQLXML() throws SQLException { + return connection.createSQLXML(); + } + + @Override + public boolean isValid(int timeout) throws SQLException { + return connection.isValid(timeout); + } + + @Override + public void setClientInfo(String name, String value) throws SQLClientInfoException { + connection.setClientInfo(name, value); + } + + @Override + public void setClientInfo(Properties properties) throws SQLClientInfoException { + connection.setClientInfo(properties); + } + + @Override + public String getClientInfo(String name) throws SQLException { + return connection.getClientInfo(name); + } + + @Override + public Properties getClientInfo() throws SQLException { + return connection.getClientInfo(); + } + + @Override + public Array createArrayOf(String typeName, Object[] elements) throws SQLException { + return connection.createArrayOf(typeName, elements); + } + + @Override + public Struct createStruct(String typeName, Object[] attributes) throws SQLException { + return connection.createStruct(typeName, attributes); + } + + @Override + public void setSchema(String schema) throws SQLException { + connection.setSchema(schema); + } + + @Override + public String getSchema() throws SQLException { + return connection.getSchema(); + } + + @Override + public void abort(Executor executor) throws SQLException { + connection.abort(executor); + } + + @Override + public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { + connection.setNetworkTimeout(executor, milliseconds); + } + + @Override + public int getNetworkTimeout() throws SQLException { + return connection.getNetworkTimeout(); + } + + @Override + public T unwrap(Class iface) throws SQLException { + return connection.unwrap(iface); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return connection.isWrapperFor(iface); + } + +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingDataSource.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingDataSource.java new file mode 100644 index 000000000000..9ec45c69fa6f --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingDataSource.java @@ -0,0 +1,147 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc; + +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.instrumentation.jdbc.parser.URLParser; +import java.io.PrintWriter; +import java.lang.reflect.Method; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.Collections; +import java.util.Set; +import java.util.logging.Logger; +import javax.sql.DataSource; + +public class TracingDataSource implements DataSource, AutoCloseable { + + private static final boolean DEFAULT_WITH_ACTIVE_SPAN_ONLY = false; + private static final Set DEFAULT_IGNORED_STATEMENTS = Collections.emptySet(); + + private final Tracer tracer; + private final DataSource underlying; + private final ConnectionInfo connectionInfo; + private final boolean withActiveSpanOnly; + private final Set ignoreStatements; + + public TracingDataSource(final Tracer tracer, + final DataSource underlying) { + this(tracer, underlying, null, DEFAULT_WITH_ACTIVE_SPAN_ONLY, + DEFAULT_IGNORED_STATEMENTS); + } + + public TracingDataSource(final Tracer tracer, + final DataSource underlying, + final ConnectionInfo connectionInfo, + final boolean withActiveSpanOnly, + final Set ignoreStatements) { + this.tracer = tracer; + this.underlying = underlying; + ConnectionInfo info = connectionInfo; + if (info == null) { + try { + Method method; + try { + method = underlying.getClass().getMethod("getJdbcUrl"); + } catch (NoSuchMethodException e) { + method = underlying.getClass().getMethod("getUrl"); + } + info = URLParser.parse((String) method.invoke(underlying)); + } catch (Exception ignored) { + info = ConnectionInfo.UNKNOWN_CONNECTION_INFO; + } + } + this.connectionInfo = info; + this.withActiveSpanOnly = withActiveSpanOnly; + this.ignoreStatements = ignoreStatements; + } + + public DataSource getUnderlying() { + return underlying; + } + + @Override + public Connection getConnection() throws SQLException { + final Connection connection = JdbcTracingUtils + .call("AcquireConnection", underlying::getConnection, + null, connectionInfo, withActiveSpanOnly, null, tracer); + + return WrapperProxy + .wrap(connection, new TracingConnection(connection, connectionInfo, withActiveSpanOnly, + ignoreStatements, tracer)); + } + + @Override + public Connection getConnection(final String username, final String password) + throws SQLException { + final Connection connection = JdbcTracingUtils.call("AcquireConnection", () -> + underlying.getConnection(username, password), null, connectionInfo, + withActiveSpanOnly, null, tracer); + + return WrapperProxy + .wrap(connection, new TracingConnection(connection, connectionInfo, withActiveSpanOnly, + ignoreStatements, tracer)); + } + + @Override + public PrintWriter getLogWriter() throws SQLException { + return underlying.getLogWriter(); + } + + @Override + public void setLogWriter(final PrintWriter out) throws SQLException { + underlying.setLogWriter(out); + } + + @Override + public void setLoginTimeout(final int seconds) throws SQLException { + underlying.setLoginTimeout(seconds); + } + + @Override + public int getLoginTimeout() throws SQLException { + return underlying.getLoginTimeout(); + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + return underlying.getParentLogger(); + } + + @Override + public T unwrap(final Class iface) throws SQLException { + return underlying.unwrap(iface); + } + + @Override + public boolean isWrapperFor(final Class iface) throws SQLException { + return underlying.isWrapperFor(iface); + } + + @Override + public void close() throws Exception { + if (underlying instanceof AutoCloseable) { + ((AutoCloseable) underlying).close(); + } + } + +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingDriver.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingDriver.java new file mode 100644 index 000000000000..3bc7342eec27 --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingDriver.java @@ -0,0 +1,275 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.instrumentation.jdbc.parser.URLParser; +import java.sql.Connection; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.DriverPropertyInfo; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class TracingDriver implements Driver { + + private static final Driver INSTANCE = new TracingDriver(); + + protected static final String TRACE_WITH_ACTIVE_SPAN_ONLY = "traceWithActiveSpanOnly"; + + protected static final String WITH_ACTIVE_SPAN_ONLY = TRACE_WITH_ACTIVE_SPAN_ONLY + "=true"; + + public static final String IGNORE_FOR_TRACING_REGEX = "ignoreForTracing=\"((?:\\\\\"|[^\"])*)\"[;]*"; + + protected static final Pattern PATTERN_FOR_IGNORING = Pattern.compile(IGNORE_FOR_TRACING_REGEX); + + static { + try { + DriverManager.registerDriver(INSTANCE); + } catch (SQLException e) { + throw new IllegalStateException("Could not register TracingDriver with DriverManager", e); + } + } + + /** + * @return The singleton instance of the {@code TracingDriver}. + */ + public static Driver load() { + return INSTANCE; + } + + /** + * Ensure {@code TracingDriver} be the first driver of {@link DriverManager} to make sure + * "interceptor mode" works. WARNING: Driver like Oracle JDBC may fail since it's destroyed + * forever after deregistration. + */ + public synchronized static void ensureRegisteredAsTheFirstDriver() { + try { + Enumeration enumeration = DriverManager.getDrivers(); + List drivers = null; + for (int i = 0; enumeration.hasMoreElements(); ++i) { + Driver driver = enumeration.nextElement(); + if (i == 0) { + if (driver == INSTANCE) { + return; + } + drivers = new ArrayList<>(); + } + if (driver != INSTANCE) { + drivers.add(driver); + DriverManager.deregisterDriver(driver); + } + } + for (Driver driver : drivers) { + DriverManager.registerDriver(driver); + } + } catch (SQLException e) { + throw new IllegalStateException("Could not register TracingDriver with DriverManager", e); + } + } + + /** + * Sets the {@code traceEnabled} property to enable or disable traces. + * + * @param traceEnabled The {@code traceEnabled} value. + */ + public static void setTraceEnabled(boolean traceEnabled) { + JdbcTracing.setTraceEnabled(traceEnabled); + } + + public static boolean isTraceEnabled() { + return JdbcTracing.isTraceEnabled(); + } + + private static boolean interceptorMode = false; + + /** + * Turns "interceptor mode" on or off. + * + * @param interceptorMode The {@code interceptorMode} value. + */ + public static void setInterceptorMode(final boolean interceptorMode) { + TracingDriver.interceptorMode = interceptorMode; + } + + private static boolean withActiveSpanOnly; + + /** + * Sets the {@code withActiveSpanOnly} property for "interceptor mode". + * + * @param withActiveSpanOnly The {@code withActiveSpanOnly} value. + */ + public static void setInterceptorProperty(final boolean withActiveSpanOnly) { + TracingDriver.withActiveSpanOnly = withActiveSpanOnly; + } + + private static Set ignoreStatements; + + /** + * Sets the {@code ignoreStatements} property for "interceptor mode". + * + * @param ignoreStatements The {@code ignoreStatements} value. + */ + public static void setInterceptorProperty(final Set ignoreStatements) { + TracingDriver.ignoreStatements = ignoreStatements; + } + + protected Tracer tracer; + + @Override + public Connection connect(String url, Properties info) throws SQLException { + // if there is no url, we have problems + if (url == null) { + throw new SQLException("url is required"); + } + + final Set ignoreStatements; + final boolean withActiveSpanOnly; + if (interceptorMode) { + withActiveSpanOnly = TracingDriver.withActiveSpanOnly; + ignoreStatements = TracingDriver.ignoreStatements; + } else if (acceptsURL(url)) { + withActiveSpanOnly = url.contains(WITH_ACTIVE_SPAN_ONLY); + ignoreStatements = extractIgnoredStatements(url); + } else { + return null; + } + + url = extractRealUrl(url); + + // find the real driver for the URL + final Driver wrappedDriver = findDriver(url); + + final Tracer currentTracer = getTracer(); + final ConnectionInfo connectionInfo = URLParser.parse(url); + final String realUrl = url; + final Connection connection = JdbcTracingUtils.call("AcquireConnection", () -> + wrappedDriver.connect(realUrl, info), null, connectionInfo, withActiveSpanOnly, + null, currentTracer); + + return WrapperProxy + .wrap(connection, new TracingConnection(connection, connectionInfo, withActiveSpanOnly, + ignoreStatements, currentTracer)); + } + + @Override + public boolean acceptsURL(String url) throws SQLException { + return url != null && ( + url.startsWith(getUrlPrefix()) || + (interceptorMode && url.startsWith("jdbc:")) + ); + } + + @Override + public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { + return findDriver(url).getPropertyInfo(url, info); + } + + @Override + public int getMajorVersion() { + // There is no way to get it from wrapped driver + return 1; + } + + @Override + public int getMinorVersion() { + // There is no way to get it from wrapped driver + return 0; + } + + @Override + public boolean jdbcCompliant() { + return true; + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + // There is no way to get it from wrapped driver + return null; + } + + public void setTracer(Tracer tracer) { + this.tracer = tracer; + } + + protected String getUrlPrefix() { + return "jdbc:tracing:"; + } + + protected Driver findDriver(String realUrl) throws SQLException { + if (realUrl == null || realUrl.trim().length() == 0) { + throw new IllegalArgumentException("url is required"); + } + + for (Driver candidate : Collections.list(DriverManager.getDrivers())) { + try { + if (!(candidate instanceof TracingDriver) && candidate.acceptsURL(realUrl)) { + return candidate; + } + } catch (SQLException ignored) { + // intentionally ignore exception + } + } + + throw new SQLException("Unable to find a driver that accepts url: " + realUrl); + } + + protected String extractRealUrl(String url) { + String extracted = url.startsWith(getUrlPrefix()) ? url.replace(getUrlPrefix(), "jdbc:") : url; + return extracted.replaceAll(TRACE_WITH_ACTIVE_SPAN_ONLY + "=(true|false)[;]*", "") + .replaceAll(IGNORE_FOR_TRACING_REGEX, "") + .replaceAll("\\?$", ""); + } + + protected Set extractIgnoredStatements(String url) { + + final Matcher matcher = PATTERN_FOR_IGNORING.matcher(url); + + Set results = new HashSet<>(8); + + while (matcher.find()) { + String rawValue = matcher.group(1); + String finalValue = rawValue.replace("\\\"", "\""); + results.add(finalValue); + } + + return results; + } + + Tracer getTracer() { + if (tracer == null) { + return GlobalOpenTelemetry.get().getTracer("opentelemetry-jdbc", "0.1.0"); + } + return tracer; + } + +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingPreparedStatement.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingPreparedStatement.java new file mode 100644 index 000000000000..01ed7f34927b --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingPreparedStatement.java @@ -0,0 +1,343 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc; + +import io.opentelemetry.api.trace.Tracer; +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.NClob; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLXML; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Calendar; +import java.util.Set; + +public class TracingPreparedStatement extends TracingStatement implements PreparedStatement { + + private final PreparedStatement preparedStatement; + private final String query; + + public TracingPreparedStatement(PreparedStatement preparedStatement, String query, + ConnectionInfo connectionInfo, boolean withActiveSpanOnly, Set ignoreStatements, + Tracer tracer) { + super(preparedStatement, query, connectionInfo, withActiveSpanOnly, ignoreStatements, tracer); + this.preparedStatement = preparedStatement; + this.query = query; + } + + @Override + public ResultSet executeQuery() throws SQLException { + return JdbcTracingUtils.call("Query", preparedStatement::executeQuery, + query, connectionInfo, withActiveSpanOnly, ignoreStatements, tracer); + } + + @Override + public int executeUpdate() throws SQLException { + return JdbcTracingUtils.call("Update", preparedStatement::executeUpdate, + query, connectionInfo, withActiveSpanOnly, ignoreStatements, tracer); + } + + @Override + public boolean execute() throws SQLException { + return JdbcTracingUtils.call("Execute", preparedStatement::execute, + query, connectionInfo, withActiveSpanOnly, ignoreStatements, tracer); + } + + @Override + public void setNull(int parameterIndex, int sqlType) throws SQLException { + preparedStatement.setNull(parameterIndex, sqlType); + } + + @Override + public void setBoolean(int parameterIndex, boolean x) throws SQLException { + preparedStatement.setBoolean(parameterIndex, x); + } + + @Override + public void setByte(int parameterIndex, byte x) throws SQLException { + preparedStatement.setByte(parameterIndex, x); + } + + @Override + public void setShort(int parameterIndex, short x) throws SQLException { + preparedStatement.setShort(parameterIndex, x); + } + + @Override + public void setInt(int parameterIndex, int x) throws SQLException { + preparedStatement.setInt(parameterIndex, x); + } + + @Override + public void setLong(int parameterIndex, long x) throws SQLException { + preparedStatement.setLong(parameterIndex, x); + } + + @Override + public void setFloat(int parameterIndex, float x) throws SQLException { + preparedStatement.setFloat(parameterIndex, x); + } + + @Override + public void setDouble(int parameterIndex, double x) throws SQLException { + preparedStatement.setDouble(parameterIndex, x); + } + + @Override + public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { + preparedStatement.setBigDecimal(parameterIndex, x); + } + + @Override + public void setString(int parameterIndex, String x) throws SQLException { + preparedStatement.setString(parameterIndex, x); + } + + @Override + public void setBytes(int parameterIndex, byte[] x) throws SQLException { + preparedStatement.setBytes(parameterIndex, x); + } + + @Override + public void setDate(int parameterIndex, Date x) throws SQLException { + preparedStatement.setDate(parameterIndex, x); + } + + @Override + public void setTime(int parameterIndex, Time x) throws SQLException { + preparedStatement.setTime(parameterIndex, x); + } + + @Override + public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { + preparedStatement.setTimestamp(parameterIndex, x); + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { + preparedStatement.setAsciiStream(parameterIndex, x, length); + } + + @Override + @Deprecated + public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { + preparedStatement.setUnicodeStream(parameterIndex, x, length); + } + + @Override + public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { + preparedStatement.setBinaryStream(parameterIndex, x, length); + } + + @Override + public void clearParameters() throws SQLException { + preparedStatement.clearParameters(); + } + + @Override + public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException { + preparedStatement.setObject(parameterIndex, x, targetSqlType); + } + + @Override + public void setObject(int parameterIndex, Object x) throws SQLException { + preparedStatement.setObject(parameterIndex, x); + } + + @Override + public void addBatch() throws SQLException { + preparedStatement.addBatch(); + } + + @Override + public void setCharacterStream(int parameterIndex, Reader reader, int length) + throws SQLException { + preparedStatement.setCharacterStream(parameterIndex, reader, length); + } + + @Override + public void setRef(int parameterIndex, Ref x) throws SQLException { + preparedStatement.setRef(parameterIndex, x); + } + + @Override + public void setBlob(int parameterIndex, Blob x) throws SQLException { + preparedStatement.setBlob(parameterIndex, x); + } + + @Override + public void setClob(int parameterIndex, Clob x) throws SQLException { + preparedStatement.setClob(parameterIndex, x); + } + + @Override + public void setArray(int parameterIndex, Array x) throws SQLException { + preparedStatement.setArray(parameterIndex, x); + } + + @Override + public ResultSetMetaData getMetaData() throws SQLException { + return preparedStatement.getMetaData(); + } + + @Override + public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { + preparedStatement.setDate(parameterIndex, x, cal); + } + + @Override + public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { + preparedStatement.setTime(parameterIndex, x, cal); + } + + @Override + public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { + preparedStatement.setTimestamp(parameterIndex, x, cal); + } + + @Override + public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException { + preparedStatement.setNull(parameterIndex, sqlType, typeName); + } + + @Override + public void setURL(int parameterIndex, URL x) throws SQLException { + preparedStatement.setURL(parameterIndex, x); + } + + @Override + public ParameterMetaData getParameterMetaData() throws SQLException { + return preparedStatement.getParameterMetaData(); + } + + @Override + public void setRowId(int parameterIndex, RowId x) throws SQLException { + preparedStatement.setRowId(parameterIndex, x); + } + + @Override + public void setNString(int parameterIndex, String value) throws SQLException { + preparedStatement.setNString(parameterIndex, value); + } + + @Override + public void setNCharacterStream(int parameterIndex, Reader value, long length) + throws SQLException { + preparedStatement.setNCharacterStream(parameterIndex, value, length); + } + + @Override + public void setNClob(int parameterIndex, NClob value) throws SQLException { + preparedStatement.setNClob(parameterIndex, value); + } + + @Override + public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { + preparedStatement.setClob(parameterIndex, reader, length); + } + + @Override + public void setBlob(int parameterIndex, InputStream inputStream, long length) + throws SQLException { + preparedStatement.setBlob(parameterIndex, inputStream, length); + } + + @Override + public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException { + preparedStatement.setNClob(parameterIndex, reader, length); + } + + @Override + public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException { + preparedStatement.setSQLXML(parameterIndex, xmlObject); + } + + @Override + public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) + throws SQLException { + preparedStatement.setObject(parameterIndex, x, targetSqlType, scaleOrLength); + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { + preparedStatement.setAsciiStream(parameterIndex, x, length); + } + + @Override + public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException { + preparedStatement.setBinaryStream(parameterIndex, x, length); + } + + @Override + public void setCharacterStream(int parameterIndex, Reader reader, long length) + throws SQLException { + preparedStatement.setCharacterStream(parameterIndex, reader, length); + } + + @Override + public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { + preparedStatement.setAsciiStream(parameterIndex, x); + } + + @Override + public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { + preparedStatement.setBinaryStream(parameterIndex, x); + } + + @Override + public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { + preparedStatement.setCharacterStream(parameterIndex, reader); + } + + @Override + public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { + preparedStatement.setNCharacterStream(parameterIndex, value); + } + + @Override + public void setClob(int parameterIndex, Reader reader) throws SQLException { + preparedStatement.setClob(parameterIndex, reader); + } + + @Override + public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { + preparedStatement.setBlob(parameterIndex, inputStream); + } + + @Override + public void setNClob(int parameterIndex, Reader reader) throws SQLException { + preparedStatement.setNClob(parameterIndex, reader); + } + +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingStatement.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingStatement.java new file mode 100644 index 000000000000..954219024520 --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/TracingStatement.java @@ -0,0 +1,311 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc; + +import io.opentelemetry.api.trace.Tracer; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Set; + +public class TracingStatement implements Statement { + + private final Statement statement; + private final String query; + private final ArrayList batchCommands = new ArrayList<>(); + final ConnectionInfo connectionInfo; + final boolean withActiveSpanOnly; + final Set ignoreStatements; + final Tracer tracer; + + TracingStatement(Statement statement, ConnectionInfo connectionInfo, boolean withActiveSpanOnly, + Set ignoreStatements, Tracer tracer) { + this(statement, null, connectionInfo, withActiveSpanOnly, ignoreStatements, tracer); + } + + TracingStatement(Statement statement, String query, ConnectionInfo connectionInfo, + boolean withActiveSpanOnly, Set ignoreStatements, Tracer tracer) { + this.statement = statement; + this.query = query; + this.connectionInfo = connectionInfo; + this.withActiveSpanOnly = withActiveSpanOnly; + this.ignoreStatements = ignoreStatements; + this.tracer = tracer; + } + + @Override + public ResultSet executeQuery(String sql) throws SQLException { + return JdbcTracingUtils.call("Query", () -> statement.executeQuery(sql), + sql, connectionInfo, withActiveSpanOnly, ignoreStatements, tracer); + } + + @Override + public int executeUpdate(String sql) throws SQLException { + return JdbcTracingUtils.call("Update", () -> statement.executeUpdate(sql), + sql, connectionInfo, withActiveSpanOnly, ignoreStatements, tracer); + } + + @Override + public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { + return JdbcTracingUtils.call("Update", () -> statement.executeUpdate(sql, autoGeneratedKeys), + sql, connectionInfo, withActiveSpanOnly, ignoreStatements, tracer); + } + + @Override + public int executeUpdate(String sql, int[] columnIndexes) throws SQLException { + return JdbcTracingUtils.call("Update", () -> statement.executeUpdate(sql, columnIndexes), + sql, connectionInfo, withActiveSpanOnly, ignoreStatements, tracer); + } + + @Override + public int executeUpdate(String sql, String[] columnNames) throws SQLException { + return JdbcTracingUtils.call("Update", () -> statement.executeUpdate(sql, columnNames), + sql, connectionInfo, withActiveSpanOnly, ignoreStatements, tracer); + } + + @Override + public boolean execute(String sql) throws SQLException { + return JdbcTracingUtils.call("Execute", () -> statement.execute(sql), + sql, connectionInfo, withActiveSpanOnly, ignoreStatements, tracer); + } + + @Override + public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { + return JdbcTracingUtils.call("Execute", () -> statement.execute(sql, autoGeneratedKeys), + sql, connectionInfo, withActiveSpanOnly, ignoreStatements, tracer); + } + + @Override + public boolean execute(String sql, int[] columnIndexes) throws SQLException { + return JdbcTracingUtils.call("Execute", () -> statement.execute(sql, columnIndexes), + sql, connectionInfo, withActiveSpanOnly, ignoreStatements, tracer); + } + + @Override + public boolean execute(String sql, String[] columnNames) throws SQLException { + return JdbcTracingUtils.call("Execute", () -> statement.execute(sql, columnNames), + sql, connectionInfo, withActiveSpanOnly, ignoreStatements, tracer); + } + + @Override + public int[] executeBatch() throws SQLException { + return JdbcTracingUtils.call("Update", statement::executeBatch, + buildSqlForBatch(), connectionInfo, withActiveSpanOnly, ignoreStatements, tracer); + } + + @Override + public void close() throws SQLException { + statement.close(); + } + + @Override + public int getMaxFieldSize() throws SQLException { + return statement.getMaxFieldSize(); + } + + @Override + public void setMaxFieldSize(int max) throws SQLException { + statement.setMaxFieldSize(max); + } + + @Override + public int getMaxRows() throws SQLException { + return statement.getMaxRows(); + } + + @Override + public void setMaxRows(int max) throws SQLException { + statement.setMaxRows(max); + } + + @Override + public void setEscapeProcessing(boolean enable) throws SQLException { + statement.setEscapeProcessing(enable); + } + + @Override + public int getQueryTimeout() throws SQLException { + return statement.getQueryTimeout(); + } + + @Override + public void setQueryTimeout(int seconds) throws SQLException { + statement.setQueryTimeout(seconds); + } + + @Override + public void cancel() throws SQLException { + statement.cancel(); + } + + @Override + public SQLWarning getWarnings() throws SQLException { + return statement.getWarnings(); + } + + @Override + public void clearWarnings() throws SQLException { + statement.clearWarnings(); + } + + @Override + public void setCursorName(String name) throws SQLException { + statement.setCursorName(name); + } + + @Override + public ResultSet getResultSet() throws SQLException { + return statement.getResultSet(); + } + + @Override + public int getUpdateCount() throws SQLException { + return statement.getUpdateCount(); + } + + @Override + public boolean getMoreResults() throws SQLException { + return statement.getMoreResults(); + } + + @Override + public void setFetchDirection(int direction) throws SQLException { + statement.setFetchDirection(direction); + } + + @Override + public int getFetchDirection() throws SQLException { + return statement.getFetchDirection(); + } + + @Override + public void setFetchSize(int rows) throws SQLException { + statement.setFetchSize(rows); + } + + @Override + public int getFetchSize() throws SQLException { + return statement.getFetchSize(); + } + + @Override + public int getResultSetConcurrency() throws SQLException { + return statement.getResultSetConcurrency(); + } + + @Override + public int getResultSetType() throws SQLException { + return statement.getResultSetType(); + } + + @Override + public void addBatch(String sql) throws SQLException { + statement.addBatch(sql); + batchCommands.add(sql); + } + + @Override + public void clearBatch() throws SQLException { + statement.clearBatch(); + batchCommands.clear(); + } + + @Override + public Connection getConnection() throws SQLException { + return statement.getConnection(); + } + + @Override + public boolean getMoreResults(int current) throws SQLException { + return statement.getMoreResults(current); + } + + @Override + public ResultSet getGeneratedKeys() throws SQLException { + return statement.getGeneratedKeys(); + } + + @Override + public int getResultSetHoldability() throws SQLException { + return statement.getResultSetHoldability(); + } + + @Override + public boolean isClosed() throws SQLException { + return statement.isClosed(); + } + + @Override + public void setPoolable(boolean poolable) throws SQLException { + statement.setPoolable(poolable); + } + + @Override + public boolean isPoolable() throws SQLException { + return statement.isPoolable(); + } + + @Override + public void closeOnCompletion() throws SQLException { + statement.closeOnCompletion(); + } + + @Override + public boolean isCloseOnCompletion() throws SQLException { + return statement.isCloseOnCompletion(); + } + + @Override + public T unwrap(Class iface) throws SQLException { + return statement.unwrap(iface); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return statement.isWrapperFor(iface); + } + + public String getQuery() { + return query; + } + + @Override + public String toString() { + return getQuery(); + } + + private String buildSqlForBatch() { + StringBuilder sqlBuilder = new StringBuilder(); + if (query != null) { + sqlBuilder.append(query); + } + + for (String batchCommand : batchCommands) { + sqlBuilder.append(batchCommand); + } + + return sqlBuilder.toString(); + } + +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/WrapperProxy.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/WrapperProxy.java new file mode 100644 index 000000000000..45a3d8d6a97f --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/WrapperProxy.java @@ -0,0 +1,156 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * Utility class for the creation of wrapper proxies. + */ +public final class WrapperProxy { + + private interface WrapperInvocationHandler extends InvocationHandler { + + T getObject(); + + T getWrapper(); + } + + /** + * Returns a {@linkplain Proxy proxy} with type {@code } of the specified {@code obj} wrapping + * the provided {@code wrapper} instance. Method invocations on the proxy instance will be handled + * as such: + *

    + *
  1. The {@code wrapper} instance will be invoked for methods that + * exist in the {@code wrapper} instance.
  2. + *
  3. The {@code obj} instance will be invoked for methods that do not + * exist in the {@code wrapper} instance.
  4. + *
+ * The type of the returned {@linkplain Proxy proxy} will be the composition + * of all super-interfaces of the runtime type of the specified {@code obj} + * instance. + *

+ * Note: The runtime type of the returned instance will not be + * of a concrete class, but rather a composition of all super-interfaces of + * the concrete class {@code }. It is therefore not possible to cast the + * instance returned by this method to a concrete class, but rather any + * super-interface of the concrete class. + * + * @param The type parameter of the specified arguments. + * @param obj The target object instance to wrap. + * @param wrapper The wrapping object. + * @return a {@linkplain Proxy proxy} with type {@code } of the specified {@code obj} wrapping + * wrapping the provided {@code wrapper} instance, or, {@code wrapper} if {@code obj == wrapper}, + * or if {@code target} or {@code wrapper} is null. + */ + @SuppressWarnings("unchecked") + public static T wrap(final T obj, final T wrapper) { + if (obj == null || wrapper == null || obj == wrapper) { + return wrapper; + } + + final Class objClass = obj.getClass(); + final Class wrapperClass = wrapper.getClass(); + return (T) Proxy.newProxyInstance(objClass.getClassLoader(), Classes.getAllInterfaces(objClass), + new WrapperInvocationHandler() { + @Override + public Object invoke(final Object proxy, final Method method, final Object[] args) + throws Throwable { + try { + try { + if (method.getDeclaringClass().isAssignableFrom(wrapperClass)) { + return method.invoke(wrapper, args); + } + + final Method specific = Classes + .getDeclaredMethodDeep(wrapperClass, method.getName(), + method.getParameterTypes()); + if (specific != null) { + return specific.invoke(wrapper, args); + } + } catch (final IllegalArgumentException e) { + } + + return method.invoke(obj, args); + } catch (final InvocationTargetException e) { + throw e.getCause(); + } catch (final IllegalAccessException e) { + final IllegalAccessError error = new IllegalAccessError(e.getMessage()); + error.setStackTrace(e.getStackTrace()); + throw error; + } + } + + @Override + public T getObject() { + return obj; + } + + @Override + public T getWrapper() { + return wrapper; + } + }); + } + + /** + * Tests whether the specified object is a proxy of a wrapped object. + * + * @param obj The object to test. + * @return Whether the specified object is a proxy of a wrapped object. + * @throws NullPointerException If {@code obj} is null. + */ + public static boolean isWrapper(final Object obj) { + return Proxy.isProxyClass(obj.getClass()) && Proxy + .getInvocationHandler(obj) instanceof WrapperInvocationHandler; + } + + /** + * Tests whether the specified object is a proxy of a wrapped instance type matching the provided + * {@code wrappedClass}. + * + * @param The common type of the proxy and wrapped instance. + * @param obj The object to test. + * @param wrappedClass The type of the instance wrapped by the specified {@code obj}. + * @return Whether the specified object is a proxy of a wrapped instance type matching the + * provided {@code wrappedClass}. + * @throws NullPointerException If {@code obj} or {@code wrappedClass} is null. + */ + public static boolean isWrapper(final T obj, final Class wrappedClass) { + if (!Proxy.isProxyClass(obj.getClass())) { + return false; + } + + final InvocationHandler handler = Proxy.getInvocationHandler(obj); + if (!(handler instanceof WrapperInvocationHandler)) { + return false; + } + + return wrappedClass + .isAssignableFrom(((WrapperInvocationHandler) handler).getWrapper().getClass()); + } + + private WrapperProxy() { + } +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/AS400URLParser.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/AS400URLParser.java new file mode 100644 index 000000000000..e80564b87d53 --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/AS400URLParser.java @@ -0,0 +1,43 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc.parser; + +import java.util.regex.Pattern; + +/** + * Parser for AS400 + * + * @author oburgosm + * @since 0.2.12 + */ +public class AS400URLParser extends AbstractMatcherURLParser { + + private static final Pattern AS400_URL_PATTERN = Pattern + .compile( + "jdbc:as400:\\/\\/(?[^\\/;]+)(\\/(?[^;\\/]*))?\\/?(;(?.*))?"); + + private static final String AS400_TYPE = "as400"; + + public AS400URLParser() { + super(AS400_URL_PATTERN, AS400_TYPE); + } + +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/AbstractMatcherURLParser.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/AbstractMatcherURLParser.java new file mode 100644 index 000000000000..e594443e7db7 --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/AbstractMatcherURLParser.java @@ -0,0 +1,89 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc.parser; + +import io.opentelemetry.instrumentation.jdbc.ConnectionInfo; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Parser based on regular expression + * + * @author oburgosm + * @since 0.2.12 + */ +public abstract class AbstractMatcherURLParser implements ConnectionURLParser { + + private final Pattern pattern; + + private final String dbType; + + public AbstractMatcherURLParser(Pattern pattern, String dbType) { + this.pattern = pattern; + this.dbType = dbType; + } + + /** + * Useful to modify ConnectionInfo before build + * + * @param matcher The matcher to apply. Note that the matcher must have a group named host, and + * optionally, a group named port and another named instance + * @return + */ + protected ConnectionInfo.Builder initBuilder(Matcher matcher) { + String host = matcher.group("host"); + String port = null; + try { + port = matcher.group("port"); + } catch (IllegalArgumentException e) { + // The pattern has no instance port + } + ConnectionInfo.Builder builder; + if (port == null || "".equals(port)) { + builder = new ConnectionInfo.Builder(host); + } else { + builder = new ConnectionInfo.Builder(host, Integer.valueOf(port)); + } + String instance = ConnectionInfo.UNKNOWN_CONNECTION_INFO.getDbInstance(); + try { + instance = matcher.group("instance"); + if (instance == null || "".equals(instance)) { + instance = ConnectionInfo.UNKNOWN_CONNECTION_INFO.getDbInstance(); + } + } catch (IllegalArgumentException e) { + // The pattern has no instance group + } + return builder + .dbType(this.dbType) + .dbInstance(instance); + } + + @Override + public ConnectionInfo parse(String url) { + Matcher matcher = this.pattern.matcher(url); + if (matcher.matches()) { + return this.initBuilder(matcher).build(); + } else { + return ConnectionInfo.UNKNOWN_CONNECTION_INFO; + } + } + +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/AbstractURLParser.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/AbstractURLParser.java new file mode 100644 index 000000000000..b5ea59f384dc --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/AbstractURLParser.java @@ -0,0 +1,68 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc.parser; + +public abstract class AbstractURLParser implements ConnectionURLParser { + + /** + * Fetch the index range that database host and port from connection url. + * + * @return index range that database hosts. + */ + protected abstract URLLocation fetchDatabaseHostsIndexRange(final String url); + + /** + * Fetch the index range that database name from connection url. + * + * @return index range that database name. + */ + protected abstract URLLocation fetchDatabaseNameIndexRange(final String url); + + /** + * Fetch database host(s) from connection url. + * + * @return database host(s). + */ + protected String fetchDatabaseHostsFromURL(String url) { + URLLocation hostsLocation = fetchDatabaseHostsIndexRange(url); + return url.substring(hostsLocation.startIndex(), hostsLocation.endIndex()); + } + + /** + * Fetch database name from connection url. + * + * @return database name. + */ + protected String fetchDatabaseNameFromURL(String url) { + URLLocation hostsLocation = fetchDatabaseNameIndexRange(url); + return url.substring(hostsLocation.startIndex(), hostsLocation.endIndex()); + } + + /** + * Fetch database name from connection url. + * + * @return database name. + */ + protected String fetchDatabaseNameFromURL(String url, int[] indexRange) { + return url.substring(indexRange[0], indexRange[1]); + } + +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/ConnectionURLParser.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/ConnectionURLParser.java new file mode 100644 index 000000000000..b5aeeac5a657 --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/ConnectionURLParser.java @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc.parser; + +import io.opentelemetry.instrumentation.jdbc.ConnectionInfo; + +public interface ConnectionURLParser { + + ConnectionInfo parse(final String url); + +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/DB2URLParser.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/DB2URLParser.java new file mode 100644 index 000000000000..07fec5ed5944 --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/DB2URLParser.java @@ -0,0 +1,44 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc.parser; + +import java.util.regex.Pattern; + +/** + * Parser for DB2 + * + * @author oburgosm + * @since 0.2.12 + */ +public class DB2URLParser extends AbstractMatcherURLParser { + + + private static final Pattern DB2_URL_PATTERN = Pattern + .compile( + "jdbc:db2:\\/\\/(?[^:\\/]+)(:(?\\d+))?\\/(?[^:]+)(:(?.*))?"); + + private static final String DB2_TYPE = "db2"; + + public DB2URLParser() { + super(DB2_URL_PATTERN, DB2_TYPE); + } + +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/H2URLParser.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/H2URLParser.java new file mode 100644 index 000000000000..6e7a26289165 --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/H2URLParser.java @@ -0,0 +1,157 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc.parser; + +import io.opentelemetry.instrumentation.jdbc.ConnectionInfo; + +public class H2URLParser extends AbstractURLParser { + + private static final String LOCALHOST = "localhost"; + private static final int DEFAULT_PORT = 8084; + /** + * Flag that H2 running with memory mode. + */ + private static final String MEMORY_MODE_FLAG = "mem"; + /** + * Flag that H2 running with tcp mode. + */ + private static final String TCP_MODE_FLAG = "h2:tcp"; + /** + * Flag that H2 running with file mode. + */ + private static final String FILE_MODE_FLAG = "file"; + /** + * Flag that H2 running with implicit file mode. + */ + private static final String IMPLICIT_FILE_MODE_FLAG = "jdbc:h2"; + private static final String H2_DB_TYPE = "h2"; + + @Override + protected URLLocation fetchDatabaseHostsIndexRange(String url) { + int hostLabelStartIndex = url.indexOf("//"); + int hostLabelEndIndex = url.indexOf("/", hostLabelStartIndex + 2); + return new URLLocation(hostLabelStartIndex + 2, hostLabelEndIndex); + } + + @Override + protected URLLocation fetchDatabaseNameIndexRange(String url) { + int databaseStartTag = url.lastIndexOf("/"); + int databaseEndTag = url.indexOf(";"); + if (databaseEndTag == -1) { + databaseEndTag = url.length(); + } + return new URLLocation(databaseStartTag + 1, databaseEndTag); + } + + @Override + public ConnectionInfo parse(String url) { + int[] databaseNameRangeIndex = fetchDatabaseNameRangeIndexFromURLForH2FileMode(url); + if (databaseNameRangeIndex != null) { + return new ConnectionInfo.Builder(LOCALHOST, -1).dbType(H2_DB_TYPE) + .dbInstance(fetchDatabaseNameFromURL(url, databaseNameRangeIndex)).build(); + } + + databaseNameRangeIndex = fetchDatabaseNameRangeIndexFromURLForH2MemMode(url); + if (databaseNameRangeIndex != null) { + return new ConnectionInfo.Builder(LOCALHOST, -1).dbType(H2_DB_TYPE) + .dbInstance(fetchDatabaseNameFromURL(url, databaseNameRangeIndex)).build(); + } + + databaseNameRangeIndex = fetchDatabaseNameRangeIndexFromURLForH2ImplicitFileMode(url); + if (databaseNameRangeIndex != null) { + return new ConnectionInfo.Builder(LOCALHOST, -1).dbType(H2_DB_TYPE) + .dbInstance(fetchDatabaseNameFromURL(url, databaseNameRangeIndex)).build(); + } + + String[] hostAndPort = fetchDatabaseHostsFromURL(url).split(":"); + if (hostAndPort.length == 1) { + return new ConnectionInfo.Builder(hostAndPort[0], DEFAULT_PORT).dbType(H2_DB_TYPE) + .dbInstance(fetchDatabaseNameFromURL(url)).build(); + } else { + return new ConnectionInfo.Builder(hostAndPort[0], Integer.valueOf(hostAndPort[1])) + .dbType(H2_DB_TYPE).dbInstance(fetchDatabaseNameFromURL(url)).build(); + } + } + + /** + * Fetch range index that the database name from connection url if H2 database running with file + * mode. + * + * @return range index that the database name. + */ + private int[] fetchDatabaseNameRangeIndexFromURLForH2FileMode(String url) { + int fileLabelIndex = url.indexOf(FILE_MODE_FLAG); + int parameterLabelIndex = url.indexOf(";", fileLabelIndex); + if (parameterLabelIndex == -1) { + parameterLabelIndex = url.length(); + } + + if (fileLabelIndex != -1) { + return new int[]{fileLabelIndex + FILE_MODE_FLAG.length() + 1, parameterLabelIndex}; + } else { + return null; + } + } + + /** + * Fetch range index that the database name from connection url if H2 database running with + * implicit file mode. + * + * @return range index that the database name. + */ + private int[] fetchDatabaseNameRangeIndexFromURLForH2ImplicitFileMode(String url) { + if (url.contains(TCP_MODE_FLAG)) { + return null; + } + int fileLabelIndex = url.indexOf(IMPLICIT_FILE_MODE_FLAG); + int parameterLabelIndex = url.indexOf(";", fileLabelIndex); + if (parameterLabelIndex == -1) { + parameterLabelIndex = url.length(); + } + + if (fileLabelIndex != -1) { + return new int[]{fileLabelIndex + IMPLICIT_FILE_MODE_FLAG.length() + 1, parameterLabelIndex}; + } else { + return null; + } + } + + /** + * Fetch range index that the database name from connection url if H2 database running with memory + * mode. + * + * @return range index that the database name. + */ + private int[] fetchDatabaseNameRangeIndexFromURLForH2MemMode(String url) { + int fileLabelIndex = url.indexOf(MEMORY_MODE_FLAG); + int parameterLabelIndex = url.indexOf(";", fileLabelIndex); + if (parameterLabelIndex == -1) { + parameterLabelIndex = url.length(); + } + + if (fileLabelIndex != -1) { + return new int[]{fileLabelIndex + MEMORY_MODE_FLAG.length() + 1, parameterLabelIndex}; + } else { + return null; + } + } + +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/MariadbURLParser.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/MariadbURLParser.java new file mode 100644 index 000000000000..c734ee8dfd2d --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/MariadbURLParser.java @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc.parser; + +public class MariadbURLParser extends MysqlURLParser { + + protected String dbType() { + return "mariadb"; + } + +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/MysqlURLParser.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/MysqlURLParser.java new file mode 100644 index 000000000000..1908c52e21b8 --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/MysqlURLParser.java @@ -0,0 +1,116 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc.parser; + +import io.opentelemetry.instrumentation.jdbc.ConnectionInfo; + +public class MysqlURLParser extends AbstractURLParser { + + private static final String DEFAULT_HOST = "localhost"; + + private static final int DEFAULT_PORT = 3306; + + protected String dbType() { + return "mysql"; + } + + @Override + protected URLLocation fetchDatabaseHostsIndexRange(String url) { + int hostLabelStartIndex = url.indexOf("//") + 2; + int hostLabelEndIndex = url.indexOf("/", hostLabelStartIndex); + if (hostLabelEndIndex == -1) { + int queryStringStartIndex = url.indexOf("?", hostLabelStartIndex); + if (queryStringStartIndex == -1) { + hostLabelEndIndex = url.length(); + } else { + hostLabelEndIndex = queryStringStartIndex; + } + } + return new URLLocation(hostLabelStartIndex, hostLabelEndIndex); + } + + protected String fetchDatabaseNameFromURL(String url, int startSize) { + URLLocation hostsLocation = fetchDatabaseNameIndexRange(url, startSize); + if (hostsLocation == null) { + return ""; + } + return url.substring(hostsLocation.startIndex(), hostsLocation.endIndex()); + } + + protected URLLocation fetchDatabaseNameIndexRange(String url, int startSize) { + int databaseStartTag = url.indexOf("/", startSize); + if (databaseStartTag == -1) { + return null; + } + int databaseEndTag = url.indexOf("?", databaseStartTag); + if (databaseEndTag == -1) { + databaseEndTag = url.length(); + } + return new URLLocation(databaseStartTag + 1, databaseEndTag); + } + + @Override + protected URLLocation fetchDatabaseNameIndexRange(String url) { + int databaseStartTag = url.lastIndexOf("/"); + int databaseEndTag = url.indexOf("?", databaseStartTag); + if (databaseEndTag == -1) { + databaseEndTag = url.length(); + } + return new URLLocation(databaseStartTag + 1, databaseEndTag); + } + + @Override + public ConnectionInfo parse(String url) { + URLLocation location = fetchDatabaseHostsIndexRange(url); + String hosts = url.substring(location.startIndex(), location.endIndex()); + if (hosts.isEmpty()) { + hosts = DEFAULT_HOST; + } + String[] hostSegment = hosts.split(","); + if (hostSegment.length > 1) { + StringBuilder sb = new StringBuilder(); + for (String host : hostSegment) { + if (host.split(":").length == 1) { + sb.append(host + ":" + DEFAULT_PORT + ","); + } else { + sb.append(host + ","); + } + } + if (',' == sb.charAt(sb.length() - 1)) { + sb.deleteCharAt(sb.length() - 1); + } + return new ConnectionInfo.Builder(sb.toString()).dbType(dbType()) + .dbInstance(fetchDatabaseNameFromURL(url)).build(); + } else { + String[] hostAndPort = hostSegment[0].split(":"); + if (hostAndPort.length != 1) { + return new ConnectionInfo.Builder(hostAndPort[0], Integer.valueOf(hostAndPort[1])) + .dbType(dbType()).dbInstance(fetchDatabaseNameFromURL(url, location.endIndex())) + .build(); + } else { + + return new ConnectionInfo.Builder(hostAndPort[0], DEFAULT_PORT).dbType(dbType()) + .dbInstance(fetchDatabaseNameFromURL(url, location.endIndex())).build(); + } + } + } + +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/OracleURLParser.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/OracleURLParser.java new file mode 100644 index 000000000000..6d273d27d4f0 --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/OracleURLParser.java @@ -0,0 +1,172 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc.parser; + +import io.opentelemetry.instrumentation.jdbc.ConnectionInfo; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class OracleURLParser implements ConnectionURLParser { + + public static final String DB_TYPE = "oracle"; + public static final String PREFIX_THIN = "jdbc:oracle:thin:"; + public static final String PREFIX_OCI = "jdbc:oracle:oci:"; + public static final int DEFAULT_PORT = 1521; + private static Pattern EASY_CONNECT_PATTERN = Pattern.compile( + "(?.*)@(?ldap:)?(//)?(?[^:/]+)(?:[0-9]+)?(?[:/][^:/]+)?(?:[^:/]+)?(?/[^:/]+)?"); + + @Override + public ConnectionInfo parse(final String url) { + if (url != null) { + String lowerCaseUrl = url.toLowerCase(); + if ((lowerCaseUrl.startsWith(PREFIX_THIN) || lowerCaseUrl.startsWith(PREFIX_OCI))) { + String trimmedURL; + if (lowerCaseUrl.startsWith(PREFIX_THIN)) { + trimmedURL = url.substring(PREFIX_THIN.length()); + } else { + trimmedURL = url.substring(PREFIX_OCI.length()); + } + OracleConnectionInfo connectionInfo = parseTnsName(trimmedURL); + if (connectionInfo == null) { + connectionInfo = parseEasyConnect(trimmedURL); + } + if (connectionInfo != null) { + return new ConnectionInfo.Builder(connectionInfo.getDbPeer()) // + .dbType(DB_TYPE) // + .dbInstance(connectionInfo.getDbInstance()) // + .build(); + } + } + } + return null; + } + + private OracleConnectionInfo parseTnsName(final String url) { + final String hosts = parseDatabaseHostsFromTnsUrl(url); + if (hosts != null) { + final int idxServiceName = url.indexOf("SERVICE_NAME"); + final int start = url.indexOf('=', idxServiceName) + 1; + final int end = url.indexOf(")", start); + final String serviceName = url.substring(start, end); + return new OracleConnectionInfo() // + .setDbPeer(hosts) // + .setDbInstance(serviceName); + } + return null; + } + + public static String parseDatabaseHostsFromTnsUrl(String url) { + int beginIndex = url.indexOf("DESCRIPTION"); + if (beginIndex == -1) { + return null; + } + List hosts = new ArrayList(); + do { + int hostStartIndex = url.indexOf("HOST", beginIndex); + if (hostStartIndex == -1) { + break; + } + int equalStartIndex = url.indexOf("=", hostStartIndex); + int hostEndIndex = url.indexOf(")", hostStartIndex); + String host = url.substring(equalStartIndex + 1, hostEndIndex); + + int port = DEFAULT_PORT; + int portStartIndex = url.indexOf("PORT", hostEndIndex); + int portEndIndex = url.length(); + if (portStartIndex != -1) { + int portEqualStartIndex = url.indexOf("=", portStartIndex); + portEndIndex = url.indexOf(")", portEqualStartIndex); + port = Integer.parseInt(url.substring(portEqualStartIndex + 1, portEndIndex).trim()); + } + hosts.add(host.trim() + ":" + port); + beginIndex = portEndIndex; + } while (true); + return join(",", hosts); + } + + private static String join(String delimiter, List list) { + if (list == null || list.isEmpty()) { + return ""; + } + StringBuilder builder = new StringBuilder(); + for (int i = 0, len = list.size(); i < len; i++) { + if (i == (len - 1)) { + builder.append(list.get(i)); + } else { + builder.append(list.get(i)).append(delimiter); + } + } + return builder.toString(); + } + + /** + * Implementation according to https://www.oracle.com/technetwork/database/enterprise-edition/oraclenetservices-neteasyconnect-133058.pdf + * + * @param url the url without the oracle jdbc prefix + * @return the oracle connection info if the url could be parsed, or null otherwise. + */ + public static OracleConnectionInfo parseEasyConnect(final String url) { + final Matcher matcher = EASY_CONNECT_PATTERN.matcher(url); + if (matcher.matches()) { + final OracleConnectionInfo result = new OracleConnectionInfo(); + final String host = matcher.group("host"); + final String portGroup = matcher.group("port"); + final int dbPort = + portGroup != null ? Integer.parseInt(portGroup.substring(1)) : DEFAULT_PORT; + result.setDbPeer(host + ":" + dbPort); + final String service = matcher.group("service"); + if (service != null) { + result.setDbInstance(service.substring(1)); + } else { + result.setDbInstance(host); + } + return result; + } + return null; + } + + public static class OracleConnectionInfo { + + private String dbInstance; + private String dbPeer; + + public String getDbInstance() { + return dbInstance; + } + + public OracleConnectionInfo setDbInstance(final String dbInstance) { + this.dbInstance = dbInstance; + return this; + } + + public String getDbPeer() { + return dbPeer; + } + + public OracleConnectionInfo setDbPeer(final String dbPeer) { + this.dbPeer = dbPeer; + return this; + } + } + +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/PostgreSQLURLParser.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/PostgreSQLURLParser.java new file mode 100644 index 000000000000..50b7323b6077 --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/PostgreSQLURLParser.java @@ -0,0 +1,86 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc.parser; + +import io.opentelemetry.instrumentation.jdbc.ConnectionInfo; +import java.net.URI; +import java.net.URISyntaxException; + +public class PostgreSQLURLParser extends AbstractURLParser { + + private static final int DEFAULT_PORT = 5432; + private static final String DB_TYPE = "postgresql"; + + @Override + protected URLLocation fetchDatabaseHostsIndexRange(String url) { + int hostLabelStartIndex = url.indexOf("//"); + int hostLabelEndIndex = url.indexOf("/", hostLabelStartIndex + 2); + return new URLLocation(hostLabelStartIndex + 2, hostLabelEndIndex); + } + + @Override + protected URLLocation fetchDatabaseNameIndexRange(String url) { + int hostLabelStartIndex = url.indexOf("//"); + int hostLabelEndIndex = url.indexOf("/", hostLabelStartIndex + 2); + int databaseStartTag = url.indexOf("/", hostLabelEndIndex); + int databaseEndTag = url.indexOf("?", databaseStartTag); + if (databaseEndTag == -1) { + databaseEndTag = url.length(); + } + return new URLLocation(databaseStartTag + 1, databaseEndTag); + } + + @Override + public ConnectionInfo parse(String url) { + URLLocation location = fetchDatabaseHostsIndexRange(url); + String hosts = url.substring(location.startIndex(), location.endIndex()); + String[] hostSegment = hosts.split(","); + if (hostSegment.length > 1) { + StringBuilder sb = new StringBuilder(); + for (String host : hostSegment) { + URI uri = parseHost(host); + int port = uri.getPort() == -1 ? DEFAULT_PORT : uri.getPort(); + + sb.append(uri.getHost() + ":" + port + ","); + } + if (',' == sb.charAt(sb.length() - 1)) { + sb.deleteCharAt(sb.length() - 1); + } + return new ConnectionInfo.Builder(sb.toString()).dbType(DB_TYPE) + .dbInstance(fetchDatabaseNameFromURL(url)).build(); + } else { + URI uri = parseHost(hostSegment[0]); + int port = uri.getPort() == -1 ? DEFAULT_PORT : uri.getPort(); + + return new ConnectionInfo.Builder(uri.getHost(), port) + .dbType(DB_TYPE).dbInstance(fetchDatabaseNameFromURL(url)).build(); + } + } + + private URI parseHost(String host) { + try { + return new URI("proto://" + host); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } + } + +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/SqlServerURLParser.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/SqlServerURLParser.java new file mode 100644 index 000000000000..184b5fa47fea --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/SqlServerURLParser.java @@ -0,0 +1,111 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc.parser; + +import io.opentelemetry.instrumentation.jdbc.ConnectionInfo; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +public class SqlServerURLParser implements ConnectionURLParser { + + private static final int DEFAULT_PORT = 1433; + + protected String dbType() { + return "sqlserver"; + } + + @Override + public ConnectionInfo parse(String url) { + String serverName = ""; + Integer port = DEFAULT_PORT; + String dbInstance = null; + int hostIndex = url.indexOf("://"); + if (hostIndex <= 0) { + return null; + } + + String[] split = url.split(";", 2); + if (split.length > 1) { + Map props = parseQueryParams(split[1], ";"); + serverName = props.get("serverName"); + dbInstance = props.get("databaseName"); + if (props.containsKey("portNumber")) { + String portNumber = props.get("portNumber"); + try { + port = Integer.parseInt(portNumber); + } catch (NumberFormatException e) { + } + } + } + + String urlServerName = split[0].substring(hostIndex + 3); + if (!urlServerName.isEmpty()) { + serverName = urlServerName; + } + + int portLoc = serverName.indexOf(":"); + if (portLoc > 1) { + port = Integer.parseInt(serverName.substring(portLoc + 1)); + serverName = serverName.substring(0, portLoc); + } + + int instanceLoc = serverName.indexOf("\\"); + if (instanceLoc > 1) { + serverName = serverName.substring(0, instanceLoc); + } + + if (serverName.isEmpty()) { + return null; + } + + return new ConnectionInfo.Builder(serverName, port).dbType(dbType()) + .dbInstance(dbInstance).build(); + } + + private Map parseQueryParams(String query, String separator) { + if (query == null || query.isEmpty()) { + return Collections.emptyMap(); + } + Map queryParams = new LinkedHashMap<>(); + String[] pairs = query.split(separator); + for (String pair : pairs) { + try { + int idx = pair.indexOf("="); + String key = + idx > 0 ? URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8.name()) + : pair; + if (!queryParams.containsKey(key)) { + String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder + .decode(pair.substring(idx + 1), StandardCharsets.UTF_8.name()) : null; + queryParams.put(key, value); + } + } catch (UnsupportedEncodingException e) { + // Ignore. + } + } + return queryParams; + } + +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/URLLocation.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/URLLocation.java new file mode 100644 index 000000000000..9a348f67d3fe --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/URLLocation.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc.parser; + +public class URLLocation { + + private final int startIndex; + private final int endIndex; + + public URLLocation(int startIndex, int endIndex) { + this.startIndex = startIndex; + this.endIndex = endIndex; + } + + public int startIndex() { + return startIndex; + } + + public int endIndex() { + return endIndex; + } + +} diff --git a/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/URLParser.java b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/URLParser.java new file mode 100644 index 000000000000..164af400f859 --- /dev/null +++ b/instrumentation/jdbc/library/src/main/java/io/opentelemetry/instrumentation/jdbc/parser/URLParser.java @@ -0,0 +1,102 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2017-2021 The OpenTracing Authors + * + * 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 io.opentelemetry.instrumentation.jdbc.parser; + +import io.opentelemetry.instrumentation.jdbc.ConnectionInfo; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class URLParser { + + private static final Logger log = Logger.getLogger(URLParser.class.getName()); + + private static final String MYSQL_JDBC_URL_PREFIX = "jdbc:mysql"; + private static final String ORACLE_JDBC_URL_PREFIX = "jdbc:oracle"; + private static final String H2_JDBC_URL_PREFIX = "jdbc:h2"; + private static final String POSTGRESQL_JDBC_URL_PREFIX = "jdbc:postgresql"; + private static final String MARIADB_JDBC_URL_PREFIX = "jdbc:mariadb"; + private static final String SQLSERVER_JDBC_URL_PREFIX = "jdbc:sqlserver"; + private static final String DB2_JDBC_URL_PREFIX = "jdbc:db2"; + private static final String AS400_JDBC_URL_PREFIX = "jdbc:as400"; + private static final Map parserRegister = new LinkedHashMap<>(); + + static { + // put mysql parser firstly + parserRegister.put(MYSQL_JDBC_URL_PREFIX, new MysqlURLParser()); + parserRegister.put(ORACLE_JDBC_URL_PREFIX, new OracleURLParser()); + parserRegister.put(H2_JDBC_URL_PREFIX, new H2URLParser()); + parserRegister.put(POSTGRESQL_JDBC_URL_PREFIX, new PostgreSQLURLParser()); + parserRegister.put(MARIADB_JDBC_URL_PREFIX, new MariadbURLParser()); + parserRegister.put(SQLSERVER_JDBC_URL_PREFIX, new SqlServerURLParser()); + parserRegister.put(DB2_JDBC_URL_PREFIX, new DB2URLParser()); + parserRegister.put(AS400_JDBC_URL_PREFIX, new AS400URLParser()); + } + + /** + * parse the url to the ConnectionInfo + */ + public static ConnectionInfo parse(String url) { + if (null == url) { + return ConnectionInfo.UNKNOWN_CONNECTION_INFO; + } + String lowerCaseUrl = url.toLowerCase(); + ConnectionURLParser parser = findURLParser(lowerCaseUrl); + if (parser == null) { + return ConnectionInfo.UNKNOWN_CONNECTION_INFO; + } + try { + return parser.parse(url); + } catch (Exception e) { + log.log(Level.WARNING, "error occurs when parsing jdbc url"); + } + return ConnectionInfo.UNKNOWN_CONNECTION_INFO; + } + + /** + * @deprecated use {@link #parse(String)} instead + */ + @Deprecated + public static ConnectionInfo parser(String url) { + return parse(url); + } + + private static ConnectionURLParser findURLParser(String lowerCaseUrl) { + for (Map.Entry entry : parserRegister.entrySet()) { + if (lowerCaseUrl.startsWith(entry.getKey())) { + return entry.getValue(); + } + } + return null; + } + + /** + * register new ConnectionURLParser. Can override existing parser. + */ + public static void registerConnectionParser(String urlPrefix, ConnectionURLParser parser) { + if (null == urlPrefix || parser == null) { + throw new IllegalArgumentException("urlPrefix and parser can not be null"); + } + parserRegister.put(urlPrefix.toLowerCase(), parser); + } + +} diff --git a/instrumentation/jdbc/library/src/main/resources/META-INF/services/java.sql.Driver b/instrumentation/jdbc/library/src/main/resources/META-INF/services/java.sql.Driver new file mode 100644 index 000000000000..b67485aeacf7 --- /dev/null +++ b/instrumentation/jdbc/library/src/main/resources/META-INF/services/java.sql.Driver @@ -0,0 +1 @@ +io.opentelemetry.instrumentation.jdbc.TracingDriver diff --git a/settings.gradle b/settings.gradle index 7424da25957d..f0dae2600549 100644 --- a/settings.gradle +++ b/settings.gradle @@ -179,6 +179,7 @@ include ':instrumentation:jaxws:jaxws-common:library' include ':instrumentation:jaxws:jws-1.1:javaagent' include ':instrumentation:jdbc:javaagent' include ':instrumentation:jdbc:javaagent-unit-tests' +include ':instrumentation:jdbc:library' include ':instrumentation:jedis:jedis-1.4:javaagent' include ':instrumentation:jedis:jedis-3.0:javaagent' include ':instrumentation:jetty:jetty-8.0:javaagent'