diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index 63c9e3c3..2a6078aa 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -121,7 +121,13 @@ jobs: JACKSON_OSON_USERNAME=${{ secrets.TEST_JACKSON_OSON_USERNAME }}\n JACKSON_OSON_PASSWORD=${{ secrets.TEST_JACKSON_OSON_PASSWORD }}\n " >> ojdbc-provider-jackson-oson/test.properties - + + # Generate ojdbc-provider-observability/test.properties + echo -e "OBSERVABILITY_URL=${{ secrets.TEST_JACKSON_OSON_URL }}\n + OBSERVABILITY_USERNAME=${{ secrets.TEST_JACKSON_OSON_USERNAME }}\n + OBSERVABILITY_PASSWORD=${{ secrets.TEST_JACKSON_OSON_PASSWORD }}\n + " >> ojdbc-provider-observability/test.properties + # Generate ojdbc-provider-aws/test.properties echo -e "AWS_S3_URL=${{ secrets.TEST_AWS_S3_URL }}\n AWS_SECRETS_MANAGER_URL=${{ secrets.TEST_AWS_SECRETS_MANAGER_URL }}\n @@ -141,5 +147,7 @@ jobs: rm ojdbc-provider-azure/test.properties rm ojdbc-provider-jackson-oson/test.properties + + rm ojdbc-provider-observability/test.properties rm ojdbc-provider-aws/test.properties diff --git a/ojdbc-provider-jackson-oson/pom.xml b/ojdbc-provider-jackson-oson/pom.xml index 179e20b7..6d6f9b6f 100644 --- a/ojdbc-provider-jackson-oson/pom.xml +++ b/ojdbc-provider-jackson-oson/pom.xml @@ -21,7 +21,6 @@ com.oracle.database.jdbc ojdbc-provider-common - 1.0.3 com.oracle.database.jdbc diff --git a/ojdbc-provider-observability/README.md b/ojdbc-provider-observability/README.md new file mode 100644 index 00000000..dcf99623 --- /dev/null +++ b/ojdbc-provider-observability/README.md @@ -0,0 +1,133 @@ +# Oracle JDBC Observability Provider + +This module contains a provider that adds tracing capabilities to the Oracle +JDBC driver. Two tracers are available: + * OTEL: adds Open Telemetry tracing capabilities. + * JFR: exports events to Java Flight Recorder. + +This provider implements the TraceEventListener interface provided by the JDBC +driver which will be notified whenever events are generated in the driver and +will publish these events into Open Telemetry. These events include: + * roundtrips to the database server + * AC begin and success + * VIP down event + +The following attributes are added the traces for each event: + * **Roundtrips** + * Connection ID + * Database Operation + * Database User + * Database Tenant + * SQL ID + * Original SQL Text *(only present if sensitive data is enabled)* + * Actual SQL Text *(only present if sensitive data is enabled)* + * **AC begin and success** + * Error Message + * Error code + * SQL state + * Current replay retry count + * **VIP down event** + * Error message + * VIP address + * Protocol *(only present if sensitive data is enabled)* + * Host *(only present if sensitive data is enabled)* + * Port *(only present if sensitive data is enabled)* + * Service name *(only present if sensitive data is enabled)* + * SID *(only present if sensitive data is enabled)* + * Connection data *(only present if sensitive data is enabled)* + +## Installation + +This provider is distributed as single jar on the Maven Central Repository. The +jar is compiled for JDK 11, and is forward compatible with later JDK versions. +The coordinates for the latest release are: + +```xml + + com.oracle.database.jdbc + ojdbc-provider-observability + 1.0.3 + +``` + +## Usage + +To use the Oracle JDBC observability provider just add the artifact to the +application's classpath and set the following connection property: + +```java +oracle.jdbc.provider.traceEventListener=observability-trace-event-listener-provider +``` + +A unique identifier connection property allows to identify the trace event +listener. Connections setting the same unique identifier use the same trace +event listener and share the same configuration. + +```java +oracle.jdbc.provider.traceEventListener.unique_identifier= +``` + +If no unique identifier is provided, the unique idetifier "default" is used. + +## Configuration + +The provider can be configured by: +* using system properties, +```java +System.setProperty("oracle.jdbc.provider.observability.enabledTracers", "OTEL,JFR"); +System.setProperty("oracle.jdbc.provider.observability.sensitiveDataEnabled", "true"); +``` +* or using the MBean. +```java +ObservabilityTraceEventListener listener = ObservabilityTraceEventListener.getTraceEventListener(""); +ObjectName objectName = new ObjectName(listener.getMBeanObjectName()); +MBeanServer server = ManagementFactory.getPlatformMBeanServer(); +server.setAttribute(objectName, new Attribute("EnabledTracers", "OTEL,JFR")); +server.setAttribute(objectName, new Attribute("SensitiveDataEnabled", "true")); +``` +* it is also possible to use the ObservabilityConfiguration object directly by +calling the +```java +ObservabilityConfiguration configuration = ObservabilityTraceEventListener.getObservabilityConfiguration(""); +configuration.setEnabledTracers("OTEL,JFR"); +configuration.setSensitiveDataEnabled(true); +``` + +## Backward compatibility + +### Usage + +The provider can also be used by setting the following connection property: + +```java +oracle.jdbc.provider.traceEventListener=open-telemetry-trace-event-listener-provider +``` + +When this property is used only the OTEL tracer can be used. + +### Configuration + +To ensure backward compatibility Oracle JDBC provider for Open Telemetry configuration +properties and MBean have been kept. When these properties and MBean are used only +the Open Telemetry tracer will be enabled. + +The Oracle JDBC provider for Open Telemetry can be configured using system properties +or a MBean. Two parameters can be configured: + * **Enabled**: when enabled (*true*) traces will be exported to Open + Telemetry. This property is **enabled by default**. + * **Sensitive data enabled**: when enabled (*true*) attributes containing + sensitive information like SQL statements and connection URL will be included + in the traces. This property is **disabled by default**. + +The system properties are "oracle.jdbc.provider.opentelemetry.enabled" and +"oracle.jdbc.provider.opentelemetry.sensitive-enabled" respectively and the MBean +exposes two attributes "Enabled" and "SensitiveDataEnabled". + + The sample code below shows how to retrieve the value of an attribute: +```java +ObservabilityTraceEventListener listener = ObservabilityTraceEventListener.getTraceEventListener(""); +ObjectName objectName = new ObjectName(listener.getMBeanObjectName()); +MBeanServer server = ManagementFactory.getPlatformMBeanServer(); +boolean isEnabled = Boolean.valueOf(server.getAttribute(objectName, "Enabled").toString()) + .booleanValue(); +``` \ No newline at end of file diff --git a/ojdbc-provider-observability/example-test.properties b/ojdbc-provider-observability/example-test.properties new file mode 100644 index 00000000..85a2cb3e --- /dev/null +++ b/ojdbc-provider-observability/example-test.properties @@ -0,0 +1,86 @@ +################################################################################ +# Copyright (c) 2025 Oracle and/or its affiliates. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or data +# (collectively the "Software"), free of charge and under any and all copyright +# rights in the Software, and any and all patent rights owned or freely +# licensable by each licensor hereunder covering either (i) the unmodified +# Software as contributed to or provided by such licensor, or (ii) the Larger +# Works (as defined below), to deal in both +# +# (a) the Software, and +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software (each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# The above copyright notice and either this complete permission notice or at +# a minimum a reference to the UPL must be included in all copies or +# substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +################################################################################ + +# This file provides examples of properties that configure tests in this +# module. +# +# QUICK GUIDE TO RUNNING TESTS: +# 1. Create a copy of this file named "test.properties": +# cp example-test.properties test.properties +# 2. In test.properties, replace example values with real values (the file is +# .gitignore'd, so sensitive info won't be checked in to the repo) +# 3. Comment out any lines for which a value can not be provided (tests are +# skipped if no value is configured). +# 4. mvn clean verify +# +# CONFIGURING TEST PROPERTIES +# Test properties are read from a properties file by the TestProperties class. +# The TestProperties class can be found in: +# ojdbc-provider-common/src/testFixtures/java/oracle/jdbc/provider/TestProperties.java +# The default behavior of TestProperties is to read a file named +# "test.properties" in the current directory. A non-default location may be +# specified as a JVM system property: +# mvn clean verify -Doracle.jdbc.provider.TestProperties=/path/to/my-test.properties +# +# MAINTAINING THIS FILE +# Project maintainers should add an example to this file anytime they write a +# test which requires a new property. Not doing so will inflict pain and +# suffering upon our fellow programmers, and will also lead to increased +# maintenance costs. +# +# IGNORING UNCONFIGURED PROPERTIES +# No test should cause a build failure due to an unconfigured property. +# Using JUnit terminology: A test should "abort" rather than "fail" when a +# property is not configured. This means that the test does not pass, but it +# does not cause the build to fail either. +# Methods of the TestProperties class will automatically abort a test if a +# property is not configured. The org.junit.jupiter.api.Assumptions class may +# also be used directly to abort a test. +# There is NO environment in which ALL tests can be run. Some tests may +# require authentication as a managed identity in an Azure VM, while other +# tests require authentication as an instance principal in an OCI compute +# instance; These environments are mutually exclusive. This is one reason why +# tests can not fail the build if a required property is not set. +# A more practical reason is that developers may not need to run all tests if +# their changes are isolated to single module. For instance, a developer +# working on an OCI provider should not need to set up an Azure tenancy to test +# their changes. + +OBSERVABILITY_URL= +OBSERVABILITY_USERNAME= +OBSERVABILITY_PASSWORD= diff --git a/ojdbc-provider-observability/pom.xml b/ojdbc-provider-observability/pom.xml new file mode 100644 index 00000000..8a7601cb --- /dev/null +++ b/ojdbc-provider-observability/pom.xml @@ -0,0 +1,73 @@ + + + 4.0.0 + + + com.oracle.database.jdbc + ojdbc-extensions + 1.0.3 + + + Oracle JDBC Observability Provider + com.oracle.database.jdbc + ojdbc-provider-observability + + + 1.44.1 + 11 + 11 + + + + + com.oracle.database.jdbc + ojdbc11 + + + io.opentelemetry + opentelemetry-api + ${opentelemetry.version} + + + org.mockito + mockito-core + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + + + ojdbc-provider-common + com.oracle.database.jdbc + tests + test-jar + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + none + alphabetical + + + + + \ No newline at end of file diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfiguration.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfiguration.java new file mode 100644 index 00000000..0fc81a11 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfiguration.java @@ -0,0 +1,333 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ +package oracle.jdbc.provider.observability; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; + +import oracle.jdbc.provider.observability.tracers.ObservabilityTracer; +import oracle.jdbc.provider.observability.tracers.jfr.JFRTracer; +import oracle.jdbc.provider.observability.tracers.otel.OTelTracer; + +/** + *

+ * Implementation of {@link ObservabilityConfigurationMBean} that allows to + * configure the Oracle JDBC Observability Provider. The system properties that + * can be used to configure the Observability Provider depend on the provider + * used: + *

+ * If {@link ObservabilityTraceEventListenerProvider} is being used: + *
    + *
  • {@link ObservabilityConfiguration#ENABLED_TRACERS}: comma separated list + * of enabled tracers, default "JFR,OTEL"
  • + *
  • {@link ObservabilityConfiguration#SENSITIVE_DATA_ENABLED}: true if + * sensitive data is enabled, default false
  • + *
+ * If {@link OpenTelemetryTraceEventListenerProvider} is being used: + *
    + *
  • {@link ObservabilityConfiguration#OPEN_TELEMETRY_TRACE_EVENT_LISTENER_ENABLED}: + * true if OTEL tracer is enabled, otherwise false. Default true.
  • + *
  • {@link ObservabilityConfiguration#OPEN_TELEMETRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED}: + * true if sensitive data is enabled, default false
  • + *
+ */ +public class ObservabilityConfiguration implements ObservabilityConfigurationMBean { + + /** + *

+ * System property used to enabled/disable tracers. The value of this system property should be a comma separated list + * of tracers to enable. + *

+ *

+ * This extension implements two tracers: + *

+ *
    + *
  • OTEL: which exports traces to Open Telemetry {@link OTelTracer}
  • + *
  • JFR: which exports traces to Java Flight recorder {@link JFRTracer}
  • + *
+ *

+ * By default all tracers will be enabled. + */ + public static final String ENABLED_TRACERS = "oracle.jdbc.provider.observability.enabledTracers"; + + /** + * System property used to enable/disable exporting sensitive data. Set the property to true to enable sensitive data. + * By default exporting sensitive data is disabled. + */ + public static final String SENSITIVE_DATA_ENABLED = "oracle.jdbc.provider.observability.sensitiveDataEnabled"; + + /** + * This property is kept for backward compatibility. It is used to enable/disable the previous verison of the provider: + * "open-telemetry-trace-event-listener-provider". + */ + public static final String OPEN_TELEMETRY_TRACE_EVENT_LISTENER_ENABLED = "oracle.jdbc.provider.opentelemetry.enabled"; + + /** + * This property is kept for backward compatibility. It allows to enable/disable sensitive data when using the previous + * version of the provider: "open-telemetry-trace-event-listener-provider". + */ + public static final String OPEN_TELEMETRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED = "oracle.jdbc.provider.opentelemetry.sensitive-enabled"; + + /** + * Default values + */ + private static final String DEFAULT_ENABLED_TRACERS = "OTEL,JFR"; + private static final String DEFAULT_SENSITIVE_DATA_ENABLED = "false"; + private static final String DEFAULT_OPEN_TELEMETRY_ENABLED = "true"; + + /** + * Lock used to ensure that only one thread can access the configuration at a time. + */ + private static final ReentrantLock observabilityConfigurationLock = new ReentrantLock(); + + /** + * Indicates whether traces are enabled + */ + private boolean enabled = true; + + /** + * Indicates whether sensitive data is enabled + */ + private boolean sensitiveDataEnabled; + + /** + * List of enabled tracers + */ + private List enabledTracers = new ArrayList<>(); + + /** + * Maps registered tracer's name to its instance. + */ + Map registeredTracers = new HashMap<>(2, 1); + + + /** + * Types of configuration. For backward compatibility allows to use OTEL for + * Oracle JDBC Open Telemetry Provider configuration properties. + */ + public enum ObservabilityConfigurationType { + /** + * Use Oracle JDBC Open Telemetry Provider configuration properties + */ + OTEL, + /** + * USE Oracle JDBC Observability Provider configuration properties + */ + OBSERVABILITY + } + + /** + * Constructor + */ + public ObservabilityConfiguration() { + this(ObservabilityConfigurationType.OBSERVABILITY); + } + + /** + * Constructor used by {@link ObservabilityTraceEventListener} to create a configuration. + * + * @param configurationType indicates which system properties to use. When + * {@link ObservabilityConfigurationType#OTEL}, the previous + * verison of system properties are used. + */ + ObservabilityConfiguration(ObservabilityConfigurationType configurationType) { + String enabledTracers = DEFAULT_ENABLED_TRACERS; + String sensitiveDataEnabled = DEFAULT_SENSITIVE_DATA_ENABLED; + if (ObservabilityConfigurationType.OBSERVABILITY.equals(configurationType)) { + enabledTracers = System.getProperty(ENABLED_TRACERS, DEFAULT_ENABLED_TRACERS); + sensitiveDataEnabled = System.getProperty(SENSITIVE_DATA_ENABLED, DEFAULT_SENSITIVE_DATA_ENABLED); + } else { + String otelEnabled = System.getProperty(OPEN_TELEMETRY_TRACE_EVENT_LISTENER_ENABLED, DEFAULT_OPEN_TELEMETRY_ENABLED); + if (otelEnabled != null) { + enabledTracers = "OTEL"; + this.enabled = Boolean.parseBoolean(otelEnabled); + } + String otelSensitiveDataEnabled = System.getProperty(OPEN_TELEMETRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED, DEFAULT_SENSITIVE_DATA_ENABLED); + if(otelSensitiveDataEnabled != null) { + sensitiveDataEnabled = otelSensitiveDataEnabled; + } + } + + setEnabledTracers(enabledTracers); + setSensitiveDataEnabled(Boolean.parseBoolean(sensitiveDataEnabled)); + } + + /** + * Returns true if the provider is enabled, otherwise false. + * + * @return true if the provider is enabled, otherwise false. + */ + @Override + public boolean getEnabled() { + observabilityConfigurationLock.lock(); + try { + return enabled; + } finally { + observabilityConfigurationLock.unlock(); + } + } + + /** + * Enables/disables the provider. + * + * @param enabled true to enable the provider, otherwise false. + */ + @Override + public void setEnabled(boolean enabled) { + observabilityConfigurationLock.lock(); + try { + this.enabled = enabled; + } finally { + observabilityConfigurationLock.unlock(); + } + } + + /** + * Returns a comma separated list of enabled tracers. Not {@code null}. + */ + @Override + public String getEnabledTracers() { + observabilityConfigurationLock.lock(); + try { + return enabledTracers == null ? + "" : + enabledTracers.stream().collect(Collectors.joining(",")); + } finally { + observabilityConfigurationLock.unlock(); + } + } + + /** + * Enables the tracers. + *

+ * This extension implements two tracers: + *

+ *
    + *
  • OTEL: which exports traces to Open Telemetry {@link OTelTracer}
  • + *
  • JFR: which exports traces to Java Flight recorder {@link JFRTracer}
  • + *
+ *

+ * Other tracer can be registered using the {@link ObservabilityConfiguration#registeredTracers} + * method. + *

+ * @param tracers comma separated list of enabled tracers. + */ + @Override + public void setEnabledTracers(String tracers){ + observabilityConfigurationLock.lock(); + try { + String[] items = tracers.replaceAll("\\s", "").split(","); + enabledTracers = Arrays.asList(items); + } finally { + observabilityConfigurationLock.unlock(); + } + + } + + /** + * Returns true if sensitive data is enabled, otherwise false. + * @return true if sensitive data is enabled, otherwise false. + */ + @Override + public boolean getSensitiveDataEnabled() { + observabilityConfigurationLock.lock(); + try { + return sensitiveDataEnabled; + } finally { + observabilityConfigurationLock.unlock(); + } + } + + /** + * Enables/disables sensitive data. + * + * @param sensitiveDataEnabled true to enable sensitive data, otherwise false. + */ + @Override + public void setSensitiveDataEnabled(boolean sensitiveDataEnabled) { + observabilityConfigurationLock.lock(); + try { + this.sensitiveDataEnabled = sensitiveDataEnabled; + } finally { + observabilityConfigurationLock.unlock(); + } + } + + /** + * Returns a list of enabled tracers. + * @return then list of enabled tracers. + */ + public List getEnabledTracersAsList() { + observabilityConfigurationLock.lock(); + try { + return enabledTracers; + } finally { + observabilityConfigurationLock.unlock(); + } + } + + /** + * Returns the tracer registered with that name. + * @param tracerName the name of the tracer. + * @return returns the registered tracer that was registered using that name. + */ + public ObservabilityTracer getTracer(String tracerName) { + return registeredTracers.get(tracerName); + } + + /** + * Registeres a tracer. + * + * @param tracer the tracer to register + */ + public void registerTracer(ObservabilityTracer tracer) { + observabilityConfigurationLock.lock(); + try { + registeredTracers.put(tracer.getName(), tracer); + } finally { + observabilityConfigurationLock.unlock(); + } + } + + +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfigurationMBean.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfigurationMBean.java new file mode 100644 index 00000000..f516dafa --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityConfigurationMBean.java @@ -0,0 +1,96 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ +package oracle.jdbc.provider.observability; + +import oracle.jdbc.provider.observability.tracers.jfr.JFRTracer; +import oracle.jdbc.provider.observability.tracers.otel.OTelTracer; + +/** + * MBean that allows to configure the Oracle JDBC Observability Provider. + */ +public interface ObservabilityConfigurationMBean { + + /** + * Returns true if the provider is enabled, otherwise false. + * + * @return true if the provider is enabled, otherwise false. + */ + boolean getEnabled(); + + /** + * Enables/disables the provider. + * + * @param enabled true to enable the provider, otherwise false. + */ + void setEnabled(boolean enabled); + + /** + * Returns a comma separated list of enabled tracers. + * + * @return a comma separated list of enabled tracers. + */ + String getEnabledTracers(); + + /** + * Enables the tracers. + *

+ * This extension implements two tracers: + *

+ *
    + *
  • OTEL: which exports traces to Open Telemetry {@link OTelTracer}
  • + *
  • JFR: which exports traces to Java Flight recorder {@link JFRTracer}
  • + *
+ * + * @param tracers comma separated list of enabled tracers. + */ + void setEnabledTracers(String tracers); + + /** + * Returns true if sensitive data is enabled, otherwise false. + * + * @return true if sensitive data is enabled, otherwise false. + */ + boolean getSensitiveDataEnabled(); + + /** + * Enables/disables sensitive data. + * + * @param sensitiveDataEnabled true to enable sensitive data, otherwise false. + */ + void setSensitiveDataEnabled(boolean sensitiveDataEnabled); +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java new file mode 100644 index 00000000..9fd33725 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListener.java @@ -0,0 +1,275 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ +package oracle.jdbc.provider.observability; + +import java.lang.management.ManagementFactory; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.management.InstanceAlreadyExistsException; +import javax.management.MBeanRegistrationException; +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.NotCompliantMBeanException; +import javax.management.ObjectName; + +import oracle.jdbc.TraceEventListener; +import oracle.jdbc.provider.observability.ObservabilityConfiguration.ObservabilityConfigurationType; +import oracle.jdbc.provider.observability.tracers.ObservabilityTracer; +import oracle.jdbc.provider.observability.tracers.jfr.JFRTracer; +import oracle.jdbc.provider.observability.tracers.otel.OTelTracer; + +/** + *

+ * TraceEventListener implementation that receives notifications whenever events + * are generated in the driver and publishes these events different tracers + * depending on the configuration. + *

+ *

+ * These events include: + *

+ *
    + *
  • roundtrips to the database server
  • + *
  • AC begin and success
  • + *
  • VIP down event
  • + *
+ *

+ * This extension implements two tracers: + *

+ *
    + *
  • OTEL: which exports traces to Open Telemetry
  • + *
  • JFR: which exports traces to Java Flight recorder
  • + *
+ *

+ * The {@link ObservabilityConfiguration} class allows to configure which tracers + * are enabled and whether sensitive data should be exported or not. + *

+ *

+ * The {@link ObservabilityConfiguration} is a registered MBean the object name + * can be retrieved by calling {@link ObservabilityTraceEventListener#getMBeanObjectName()}. + * This MBean allows to configure the TraceEventListener by setting attributes. + * The following attributes are available: + *

+ *
    + *
  • EnabledTracers: comma separated list of tracers "OTEL,JFR" by + * default.
  • + *
  • SensitiveDataEnabled: enables/disables exporting sensiteve data + * (false by default)
  • + *
+ */ +public class ObservabilityTraceEventListener implements TraceEventListener { + /** + * MBean object name format + */ + private static final String MBEAN_OBJECT_NAME = "com.oracle.jdbc.provider.observability:type=ObservabilityConfiguration,uniqueIdentifier=%s"; + private static final String MBEAN_OBJECT_NAME_OTEL = "com.oracle.jdbc.extension.opentelemetry:type=OpenTelemetryTraceEventListener,uniqueIdentifier=%s"; + + /** + * Default unique identifier, if parameter not set. + */ + static final CharSequence DEFAULT_UNIQUE_IDENTIFIER = "default"; + + /** + * MBean server + */ + private static final MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + + /** + * Logger + */ + private static final Logger logger = Logger.getLogger( + ObservabilityTraceEventListener.class.getPackageName()); + + /** + * Configuration for this instance of Trace Event Listener + */ + private final ObservabilityConfiguration configuration; + + /** + * Static map linking the name of the listener to its instance. + */ + private static final Map INSTANCES + = new ConcurrentHashMap<>(); + + private ObjectName mBeanObjectName; + + /** + * Create a trace event listener identified by the given name. + * @param uniqueIdentifier the name of the trace event listener. + * @param configurationType configuration type for backward compatibility. + */ + private ObservabilityTraceEventListener(String uniqueIdentifier, + ObservabilityConfigurationType configurationType) { + // Create the configuration for this instance and register MBean + final String mBeanName = ObservabilityConfigurationType.OTEL.equals(configurationType) ? + String.format(MBEAN_OBJECT_NAME_OTEL, uniqueIdentifier) : + String.format(MBEAN_OBJECT_NAME, uniqueIdentifier); + this.configuration = new ObservabilityConfiguration(configurationType); + try { + mBeanObjectName = new ObjectName(mBeanName); + if (!server.isRegistered(mBeanObjectName)) { + server.registerMBean(configuration, mBeanObjectName); + logger.log(Level.FINEST, "MBean and tracers registered"); + } + } catch (InstanceAlreadyExistsException | MBeanRegistrationException | + NotCompliantMBeanException | MalformedObjectNameException e) { + logger.log(Level.WARNING, "Could not register MBean", e); + } + // Register known tracers + configuration.registerTracer(new OTelTracer(configuration)); + configuration.registerTracer(new JFRTracer(configuration)); + } + + @Override + @SuppressWarnings("unchecked") + public Object roundTrip(Sequence sequence, TraceContext traceContext, Object userContext) { + if (!configuration.getEnabled()) { return null;} + + // Cast the userContext to the map this listener uses, or create a new one + // if it is being used for the first time. This is the return value of the + // method, and will be send back by the driver on the next event. + Map currentUserContext = userContext == null ? + new HashMap<>() : (Map)userContext; + + // loop through all the enabled tracers + for (String tracerName : configuration.getEnabledTracersAsList()) { + ObservabilityTracer tracer = configuration.getTracer(tracerName); + if (tracer != null) { + // call the tracer's round trip event with the tracer's context and store + // the new user context returned by the tracer in the user context map + Object newUserContext = tracer.traceRoundTrip(sequence, traceContext, currentUserContext.get(tracerName)); + currentUserContext.put(tracerName, newUserContext); + } else { + // the listener does not fail if the tracer is unknow, it is possible to + // enable a tracer and register it later + logger.log(Level.WARNING, "Could not find registered tracer with name: " + tracer); + } + } + + // return the new user context + return currentUserContext; + } + + + @Override + @SuppressWarnings("unchecked") + public Object onExecutionEventReceived(JdbcExecutionEvent event, Object userContext, Object... params) { + if (!configuration.getEnabled()) { return null;} + + // Cast the userContext to the map this listener uses, or create a new one + // if it is being used for the first time. This is the return value of the + // method, and will be send back by the driver on the next event. + Map currentUserContext = userContext == null ? + new HashMap<>() : (Map)userContext; + + // loop through all the enabled tracers + for (String tracerName : configuration.getEnabledTracersAsList()) { + ObservabilityTracer tracer = configuration.getTracer(tracerName); + if (tracer != null) { + // call the tracer's execution event with the tracer's context and store + // the new user context returned by the tracer in the user context map + Object newUserContext = tracer.traceExecutionEvent(event, currentUserContext.get(tracerName), params); + currentUserContext.put(tracerName, newUserContext); + } else { + // the listener does not fail if the tracer is unknow, it is possible to + // enable a tracer and register it later + logger.log(Level.WARNING, "Could not find registered tracer with name: " + tracer); + } + } + + // return the new user context + return currentUserContext; + } + + @Override + public boolean isDesiredEvent(JdbcExecutionEvent event) { + // Accept all events + return true; + } + + + /** + * Returns the MBean object name assiciated with the configuration of the + * listener. + * + * @return the MBean object name. + */ + public ObjectName getMBeanObjectName() { + return mBeanObjectName; + } + + /** + * Returns the listener's configuration. + * + * @return the configuration instance associated to the listener. + */ + public ObservabilityConfiguration getObservabilityConfiguration() { + return configuration; + } + + + /** + * Returns the trace event listener identified by the given unique idetifier. + * + * @param uniqueIdentifier the unique identifier, if no unique identifier was + * provided as a connection property, the unique identifier is "default". + * @return the trace event listener identified by the given unique idetifier, + * or {@code null} if no trace event listener with that unique idetifier was + * found. + */ + public static ObservabilityTraceEventListener getTraceEventListener(String uniqueIdentifier) { + return INSTANCES.get(uniqueIdentifier); + } + + /** + * Gets or creates an instance of {@link ObservabilityTraceEventListener} + * associated to the name. + * @param uniqueIdentifier the name of the listener instance. + * @param configurationType configuration type for backward compatibility. + * + * @return an instance of {@link ObservabilityTraceEventListener}. + */ + static ObservabilityTraceEventListener getOrCreateInstance(String uniqueIdentifier, + ObservabilityConfigurationType configurationType) { + return INSTANCES.computeIfAbsent(uniqueIdentifier, n -> new ObservabilityTraceEventListener(n, configurationType)); + } + +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java new file mode 100644 index 00000000..26b726a7 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerProvider.java @@ -0,0 +1,108 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ +package oracle.jdbc.provider.observability; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import oracle.jdbc.TraceEventListener; +import oracle.jdbc.provider.observability.ObservabilityConfiguration.ObservabilityConfigurationType; +import oracle.jdbc.spi.TraceEventListenerProvider; + +/** + * Implementation of Oracle JDBC {@link TraceEventListenerProvider} for + * {@link ObservabilityTraceEventListener}. + */ +public class ObservabilityTraceEventListenerProvider implements TraceEventListenerProvider { + + /** + * Provider name + */ + private static final String PROVIDER_NAME = "observability-trace-event-listener-provider"; + + /** + * Name Parameter name, identifies the listener + */ + private static final String UNIQUE_IDENTIFIER_PARAMETER_NAME = "UNIQUE_IDENTIFIER"; + + + /** + * Unique identifier, identifies a {@link ObservabilityTraceEventListener}. + */ + protected static final Parameter uniqueIdentifierParameter = new Parameter() { + + @Override + public boolean isSensitive() { + return false; + } + + @Override + public String name() { + return UNIQUE_IDENTIFIER_PARAMETER_NAME; + } + + }; + + /** + * Constructs a new instance of ObservabilityTraceEventListenerProvider. This + * constructor will be called by the driver's service provider to create a new + * instance. + */ + public ObservabilityTraceEventListenerProvider() { } + + @Override + public TraceEventListener getTraceEventListener(Map map) { + String uniqueIdentifier = + map.getOrDefault( + uniqueIdentifierParameter, + (CharSequence)ObservabilityTraceEventListener.DEFAULT_UNIQUE_IDENTIFIER).toString(); + return ObservabilityTraceEventListener.getOrCreateInstance(uniqueIdentifier, ObservabilityConfigurationType.OBSERVABILITY); + } + + @Override + public String getName() { + return PROVIDER_NAME; + } + + @Override + public Collection getParameters() { + return Collections.singletonList(uniqueIdentifierParameter); + } + +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/OpenTelemetryTraceEventListenerProvider.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/OpenTelemetryTraceEventListenerProvider.java new file mode 100644 index 00000000..b815bcea --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/OpenTelemetryTraceEventListenerProvider.java @@ -0,0 +1,78 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ +package oracle.jdbc.provider.observability; + +import java.util.Map; + +import oracle.jdbc.TraceEventListener; +import oracle.jdbc.provider.observability.ObservabilityConfiguration.ObservabilityConfigurationType; + +/** + *

+ * This class implements the TraceEventListenerProvider interface exposed by the + * Oracle JDBC driver. It provides TraceEventListeners of type {@link + * oracle.jdbc.provider.observability.ObservabilityTraceEventListener}. + *

+ */ +public class OpenTelemetryTraceEventListenerProvider extends ObservabilityTraceEventListenerProvider { + + /** + * Name of the provider + */ + private static final String PROVIDER_NAME = "open-telemetry-trace-event-listener-provider"; + + /** + * Constructs a new instance of OpenTelemetryTraceEventListenerProvider. This + * constructor will be called by the driver's service provider to create a new + * instance. + */ + public OpenTelemetryTraceEventListenerProvider() { } + + @Override + public String getName() { + return PROVIDER_NAME; + } + + @Override + public TraceEventListener getTraceEventListener(Map map) { + String uniqueIdentifier = map.get(uniqueIdentifierParameter).toString(); + return ObservabilityTraceEventListener.getOrCreateInstance(uniqueIdentifier, + ObservabilityConfigurationType.OTEL); + } + +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java new file mode 100644 index 00000000..d0c4c208 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/ObservabilityTracer.java @@ -0,0 +1,100 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ +package oracle.jdbc.provider.observability.tracers; + +import java.util.EnumMap; +import java.util.Map; + +import oracle.jdbc.TraceEventListener.JdbcExecutionEvent; +import oracle.jdbc.TraceEventListener.Sequence; +import oracle.jdbc.TraceEventListener.TraceContext; +import oracle.jdbc.provider.observability.ObservabilityTraceEventListener; + +/** + * This interface must be implemented by all Observability tracers. + */ +public interface ObservabilityTracer { + + /** + * Map containing the number of parameters expected for each execution event + */ + Map EXECUTION_EVENTS_PARAMETERS + = new EnumMap(JdbcExecutionEvent.class) { + { + put(JdbcExecutionEvent.AC_REPLAY_STARTED, 3); + put(JdbcExecutionEvent.AC_REPLAY_SUCCESSFUL, 3); + put(JdbcExecutionEvent.VIP_RETRY, 8); + } + }; + + /** + * Returns the unique name of the tracer. + * @return the unique name of the tracer. + */ + String getName(); + + + /** + * Called by {@link ObservabilityTraceEventListener} when a round trip event + * is received. + * + * @param sequence BEFORE if before the round trip, AFTER if after the round + * trip + * @param traceContext Information about the round trip. Valid only during the + * call + * @param userContext Result of previous call on this Connection or null if no + * previous call or if observability was disabled since the previous call. + * @return a user context object that is passed to the next call on this + * Connection. May be null. + */ + Object traceRoundTrip(Sequence sequence, TraceContext traceContext, Object userContext); + + /** + * Called by {@link ObservabilityTraceEventListener} when an execution event + * is received. + * + * @param event the event. + * @param userContext the result of the previous call or null if no previous + * call has been made. + * @param params event specific parameters. + * @return a user context object that is passed to the next call for the same + * type of event. May be null. + */ + Object traceExecutionEvent(JdbcExecutionEvent event, Object userContext, Object... params); + +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFREventFactory.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFREventFactory.java new file mode 100644 index 00000000..f42c811a --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFREventFactory.java @@ -0,0 +1,1054 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ +package oracle.jdbc.provider.observability.tracers.jfr; + +import jdk.jfr.Event; +import jdk.jfr.Label; +import jdk.jfr.Name; + +import java.sql.SQLException; +import java.util.logging.Logger; + +import jdk.jfr.Category; +import oracle.jdbc.TraceEventListener.JdbcExecutionEvent; +import oracle.jdbc.TraceEventListener.TraceContext; +import oracle.jdbc.provider.observability.ObservabilityConfiguration; +import oracle.jdbc.provider.observability.ObservabilityTraceEventListener; +import oracle.jdbc.provider.observability.tracers.ObservabilityTracer; + +/** + * Factory class for creating JFR events depending on the database function. + */ +public class JFREventFactory { + + /** + * Logger + */ + private static final Logger logger = Logger.getLogger( + ObservabilityTraceEventListener.class.getPackageName()); + + /** + * This class only has a static method, no public constructor needed. + */ + private JFREventFactory() { } + + /** + * Creates an instance of {@link RoundTripEvent} for the given trace context. + * The type of round trip event depends on the database function. + * + * @param traceContext the trace context received by a TraceEventListener. + * @param configuration the configuration + * @return the {@link RoundTripEvent} for the database function. + */ + public static RoundTripEvent createJFRRoundTripEvent(TraceContext traceContext, ObservabilityConfiguration configuration) { + switch (traceContext.databaseFunction()) { + case ADVANCED_QUEUING_12C_EMON_DEQUEUE: + return new AdvancedQueuing12cEminDequeueEvent(traceContext, configuration); + case ADVANCED_QUEUING_ARRAY_ENQUEUE_DEQUEUE: + return new AdvancedQueuingArrayEnqueueDequeue(traceContext, configuration); + case ADVANCED_QUEUING_DEQUEUE_V8: + return new AdvancedQueuingDequeueV8(traceContext, configuration); + case ADVANCED_QUEUING_ENQUEUE: + return new AdvancedQueuingEnqueue(traceContext, configuration); + case ADVANCED_QUEUING_GET_PROPAGATION_STATUS: + return new AdvancedQueuingGetPropagationStatus(traceContext, configuration); + case ADVANCED_QUEUING_LISTEN: + return new AdvancedQueuingListen(traceContext, configuration); + case ADVANCED_QUEUING_SESSION_GET_RPC_1: + return new AdvancedQueuingSessionGetRPC1(traceContext, configuration); + case ADVANCED_QUEUING_SESSION_GET_RPC_2: + return new AdvancedQueuingSessionGetRPC2(traceContext, configuration); + case ADVANCED_QUEUING_SHARED_DEQUEUE: + return new AdvancedQueuingSharedDequeue(traceContext, configuration); + case ADVANCED_QUEUING_SHARED_ENQUEUE: + return new AdvancedQueuingSharedEnqueue(traceContext, configuration); + case APP_REPLAY: + return new AppReplay(traceContext, configuration); + case AUTH_CALL: + return new AuthCall(traceContext, configuration); + case AUTO_COMMIT_OFF: + return new AutoCommitOff(traceContext, configuration); + case AUTO_COMMIT_ON: + return new AutoCommitOn(traceContext, configuration); + case CANCEL_ALL: + return new CancelAll(traceContext, configuration); + case CANCEL_OPERATION: + return new CancelOperation(traceContext, configuration); + case CHUNCK_INFO: + return new ChunkInfo(traceContext, configuration); + case CLIENT_FEATURES: + return new ClientFeatures(traceContext, configuration); + case CLIENT_QUERY_CACHE_IDS: + return new ClientQueryCacheIds(traceContext, configuration); + case CLIENT_QUERY_CACHE_STATS_UPDATE: + return new ClientQueryCacheStatsUpdate(traceContext, configuration); + case CLOSE_ALL_CURSOR: + return new CloseAllCursor(traceContext, configuration); + case CLOSE_CURSOR: + return new CloseCursor(traceContext, configuration); + case COMMIT: + return new Commit(traceContext, configuration); + case DB12C_NOTIFICATION_RCV: + return new DB12cNotificationRCV(traceContext, configuration); + case DBNS_SAGAS: + return new DBNSSagas(traceContext, configuration); + case DESCRIBE_ANY_V8: + return new DescribeAnyV8(traceContext, configuration); + case DESCRIBE_ARRAY: + return new DescribeArray(traceContext, configuration); + case DESCRIBE_QUERY_CALL: + return new DescribeQueryCall(traceContext, configuration); + case DIRECT_PATH_LOAD_STREAM: + return new DirectPathLoadStream(traceContext, configuration); + case DIRECT_PATH_MISC_OP: + return new DirectPathMISCOp(traceContext, configuration); + case DIRECT_PATH_PREPARE: + return new DirectPathPrepare(traceContext, configuration); + case DISTRIBUTED_TRANS_MGR_RPC: + return new DistributedTransMGRRPC(traceContext, configuration); + case EXECUTE_QUERY: + return new ExecuteQuery(traceContext, configuration); + case EXTENSIBLE_SECURITY_SESSION_CREATE: + return new ExtensibleSecuritySessionCreate(traceContext, configuration); + case EXTENSIBLE_SECURITY_SESSION_PIGGYBACK: + return new ExtensibleSecuritySessionPiggyback(traceContext, configuration); + case EXTENSIBLE_SECURITY_SESSION_ROUNDTRIP: + return new ExtensibleSecuritySessionRoundtrip(traceContext, configuration); + case FAST_UPI_CALLS: + return new FastUPICalls(traceContext, configuration); + case FETCH_ROW: + return new FetchRow(traceContext, configuration); + case GET_VERSION: + return new GetVersion(traceContext, configuration); + case KERNEL_PROGRAMMATIC_NOTIFICATION: + return new KernelProgrammaticNotification(traceContext, configuration); + case KEY_VALUE: + return new KeyValue(traceContext, configuration); + case LOB_FILE_CALL: + return new LOBFileCall(traceContext, configuration); + case LOGOFF: + return new LogOff(traceContext, configuration); + case LOGON_CHALLENGE_RESPONSE_1: + return new LogonChallengeResponse1(traceContext, configuration); + case LOGON_CHALLENGE_RESPONSE_2: + return new LogonChallengeResponse2(traceContext, configuration); + case OEXFEN: + return new OEXFEN(traceContext, configuration); + case OPEN_CURSOR: + return new OpenCursor(traceContext, configuration); + case OSQL7: + return new OSQL7(traceContext, configuration); + case OSTART: + return new OStart(traceContext, configuration); + case OSTOP: + return new OStop(traceContext, configuration); + case PARAMETER_PUT_SPFILE: + return new ParameterPutSPFile(traceContext, configuration); + case PING: + return new Ping(traceContext, configuration); + case PIPELINE_END: + return new PipelineEnd(traceContext, configuration); + case PIPELINE_PIGGYBACK_BEGIN: + return new PipelinePiggybackBegin(traceContext, configuration); + case PIPELINE_PIGGYBACK_OP: + return new PipelinePiggybackOp(traceContext, configuration); + case ROLLBACK: + return new Rollback(traceContext, configuration); + case SESSION_KEY: + return new SessionKey(traceContext, configuration); + case SESSION_STATE_OPS: + return new SessionStateOps(traceContext, configuration); + case SESSION_STATE_TEMPLATE: + return new SessionStateTemplate(traceContext, configuration); + case SESSION_SWITCH_V8: + return new SessionSwitchV8(traceContext, configuration); + case TRACING_MESSAGE: + return new TracingMessage(traceContext, configuration); + case TRANSACTION_COMMIT: + return new TransactionCommit(traceContext, configuration); + case TRANSACTION_START: + return new TransactionStart(traceContext, configuration); + case TTC_DTY_ROUNDTRIP: + return new TTCDTYRoundtrip(traceContext, configuration); + case TTC_PRO_ROUNDTRIP: + return new TTCPRORoundtrip(traceContext, configuration); + case XS_ATTACH_SESSION: + return new XSAttachSession(traceContext, configuration); + case XS_CREATE_SESSION: + return new XSCreateSession(traceContext, configuration); + case XS_DESTROY_SESSION: + return new XSDestroySession(traceContext, configuration); + case XS_DETACH_SESSION: + return new XSDetachSession(traceContext, configuration); + case XS_NAMESPACE_OP: + return new XSNamespaceOp(traceContext, configuration); + case XS_NAMESPACE_OPS: + return new XSNamespaceOps(traceContext, configuration); + case XS_SET_SESSION_PARAMETER: + return new XSSetSessionParameter(traceContext, configuration); + case XS_STATE_SYNC_OP: + return new XSStateSyncOp(traceContext, configuration); + default: + logger.warning("Unknown round trip received: " + traceContext.databaseFunction()); + return new RoundTripEvent(traceContext, configuration); + } + } + + /** + * Creates an instance {@link ExecutionEvent} for the given {@link + * JdbcExecutionEvent}. + * + * @param event the event. + * @param params the parameters to populate the event properties. + * @return the execution event. + */ + public static Event createExecutionEvent(JdbcExecutionEvent event, Object... params) { + switch (event) { + case AC_REPLAY_STARTED: + return new ACReplayStarted(event, params); + case AC_REPLAY_SUCCESSFUL: + return new ACReplaySuccessful(event, params); + case VIP_RETRY: + return new VIPRetry(event, params); + default: + logger.warning("Unknow event received: " + event); + return new ExecutionEvent(event, params); + } + } + + // Round-trip events + + @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_12C_EMON_DEQUEUE") + @Label("AQ 12c emon dequeue") + @Category({"Oracle JDBC", "Round trips"}) + static class AdvancedQueuing12cEminDequeueEvent extends RoundTripEvent{ + public AdvancedQueuing12cEminDequeueEvent(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_ARRAY_ENQUEUE_DEQUEUE") + @Label("AQ Array Enqueue/Dequeue") + @Category({"Oracle JDBC", "Round trips"}) + static class AdvancedQueuingArrayEnqueueDequeue extends RoundTripEvent{ + public AdvancedQueuingArrayEnqueueDequeue(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_DEQUEUE_V8") + @Label("AQ Dequeue before 8.1") + @Category({"Oracle JDBC", "Round trips"}) + static class AdvancedQueuingDequeueV8 extends RoundTripEvent{ + public AdvancedQueuingDequeueV8(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_ENQUEUE") + @Label("AQ EnQueue") + @Category({"Oracle JDBC", "Round trips"}) + static class AdvancedQueuingEnqueue extends RoundTripEvent{ + public AdvancedQueuingEnqueue(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_GET_PROPAGATION_STATUS") + @Label("AQ get propagation status entries") + @Category({"Oracle JDBC", "Round trips"}) + static class AdvancedQueuingGetPropagationStatus extends RoundTripEvent{ + public AdvancedQueuingGetPropagationStatus(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_LISTEN") + @Label("AQ Listen") + @Category({"Oracle JDBC", "Round trips"}) + static class AdvancedQueuingListen extends RoundTripEvent{ + public AdvancedQueuingListen(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_SESSION_GET_RPC_1") + @Label("Session get RPC in server pool scenario") + @Category({"Oracle JDBC", "Round trips"}) + static class AdvancedQueuingSessionGetRPC1 extends RoundTripEvent{ + public AdvancedQueuingSessionGetRPC1(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_SESSION_GET_RPC_2") + @Label("Session get RPC in server pool scenario") + @Category({"Oracle JDBC", "Round trips"}) + static class AdvancedQueuingSessionGetRPC2 extends RoundTripEvent{ + public AdvancedQueuingSessionGetRPC2(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_SHARED_DEQUEUE") + @Label("AQ Sharded dequeue") + @Category({"Oracle JDBC", "Round trips"}) + static class AdvancedQueuingSharedDequeue extends RoundTripEvent{ + public AdvancedQueuingSharedDequeue(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.ADVANCED_QUEUING_SHARED_ENQUEUE") + @Label("AQ Sharded enqueue") + @Category({"Oracle JDBC", "Round trips"}) + static class AdvancedQueuingSharedEnqueue extends RoundTripEvent{ + public AdvancedQueuingSharedEnqueue(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.APP_REPLAY") + @Label("Application continuity REPLAY") + @Category({"Oracle JDBC", "Round trips"}) + static class AppReplay extends RoundTripEvent{ + public AppReplay(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.AUTH_CALL") + @Label("Generic authentication call") + @Category({"Oracle JDBC", "Round trips"}) + static class AuthCall extends RoundTripEvent{ + public AuthCall(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.AUTO_COMMIT_OFF") + @Label("Auto commit off") + @Category({"Oracle JDBC", "Round trips"}) + static class AutoCommitOff extends RoundTripEvent{ + public AutoCommitOff(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.AUTO_COMMIT_ON") + @Label("Auto commit on") + @Category({"Oracle JDBC", "Round trips"}) + static class AutoCommitOn extends RoundTripEvent{ + public AutoCommitOn(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.CANCEL_ALL") + @Label("Cancel All") + @Category({"Oracle JDBC", "Round trips"}) + static class CancelAll extends RoundTripEvent{ + public CancelAll(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.CANCEL_OPERATION") + @Label("Cancel the current operation") + @Category({"Oracle JDBC", "Round trips"}) + static class CancelOperation extends RoundTripEvent{ + public CancelOperation(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.CHUNCK_INFO") + @Label("Chunk info RPC") + @Category({"Oracle JDBC", "Round trips"}) + static class ChunkInfo extends RoundTripEvent{ + public ChunkInfo(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.CLIENT_FEATURES") + @Label("Client features") + @Category({"Oracle JDBC", "Round trips"}) + static class ClientFeatures extends RoundTripEvent{ + public ClientFeatures(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.CLIENT_QUERY_CACHE_IDS") + @Label("Client query cache IDs") + @Category({"Oracle JDBC", "Round trips"}) + static class ClientQueryCacheIds extends RoundTripEvent{ + public ClientQueryCacheIds(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.CLIENT_QUERY_CACHE_STATS_UPDATE") + @Label("Client query cache statistics update") + @Category({"Oracle JDBC", "Round trips"}) + static class ClientQueryCacheStatsUpdate extends RoundTripEvent{ + public ClientQueryCacheStatsUpdate(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.CLOSE_ALL_CURSOR") + @Label("Cursor close all") + @Category({"Oracle JDBC", "Round trips"}) + static class CloseAllCursor extends RoundTripEvent{ + public CloseAllCursor(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.CLOSE_CURSOR") + @Label("Close a cursor") + @Category({"Oracle JDBC", "Round trips"}) + static class CloseCursor extends RoundTripEvent{ + public CloseCursor(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.COMMIT") + @Label("Commit") + @Category({"Oracle JDBC", "Round trips"}) + static class Commit extends RoundTripEvent{ + public Commit(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.DB12C_NOTIFICATION_RCV") + @Label("12c notification receive") + @Category({"Oracle JDBC", "Round trips"}) + static class DB12cNotificationRCV extends RoundTripEvent{ + public DB12cNotificationRCV(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.DBNS_SAGAS") + @Label("DBMS Sagas") + @Category({"Oracle JDBC", "Round trips"}) + static class DBNSSagas extends RoundTripEvent{ + public DBNSSagas(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.DESCRIBE_ANY_V8") + @Label("V8 Describe Any") + @Category({"Oracle JDBC", "Round trips"}) + static class DescribeAnyV8 extends RoundTripEvent{ + public DescribeAnyV8(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.DESCRIBE_ARRAY") + @Label("Array describe") + @Category({"Oracle JDBC", "Round trips"}) + static class DescribeArray extends RoundTripEvent{ + public DescribeArray(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.DESCRIBE_QUERY_CALL") + @Label("New describe query call") + @Category({"Oracle JDBC", "Round trips"}) + static class DescribeQueryCall extends RoundTripEvent{ + public DescribeQueryCall(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.DIRECT_PATH_LOAD_STREAM") + @Label("Direct Path Load Stream") + @Category({"Oracle JDBC", "Round trips"}) + static class DirectPathLoadStream extends RoundTripEvent{ + public DirectPathLoadStream(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.DIRECT_PATH_MISC_OP") + @Label("Direct Path Misc Operations") + @Category({"Oracle JDBC", "Round trips"}) + static class DirectPathMISCOp extends RoundTripEvent{ + public DirectPathMISCOp(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.DIRECT_PATH_PREPARE") + @Label("Direct Path Prepare") + @Category({"Oracle JDBC", "Round trips"}) + static class DirectPathPrepare extends RoundTripEvent{ + public DirectPathPrepare(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.DISTRIBUTED_TRANS_MGR_RPC") + @Label("Distributed transaction manager RPC") + @Category({"Oracle JDBC", "Round trips"}) + static class DistributedTransMGRRPC extends RoundTripEvent{ + public DistributedTransMGRRPC(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.EXECUTE_QUERY") + @Label("Execute query") + @Category({"Oracle JDBC", "Round trips"}) + static class ExecuteQuery extends RoundTripEvent{ + public ExecuteQuery(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.EXTENSIBLE_SECURITY_SESSION_CREATE") + @Label("eXtensible Security Sessions Create Session") + @Category({"Oracle JDBC", "Round trips"}) + static class ExtensibleSecuritySessionCreate extends RoundTripEvent{ + public ExtensibleSecuritySessionCreate(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.EXTENSIBLE_SECURITY_SESSION_PIGGYBACK") + @Label("eXtensible Security Sessions Piggyback") + @Category({"Oracle JDBC", "Round trips"}) + static class ExtensibleSecuritySessionPiggyback extends RoundTripEvent{ + public ExtensibleSecuritySessionPiggyback(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.EXTENSIBLE_SECURITY_SESSION_ROUNDTRIP") + @Label("eXtensible Security Session Roundtrip") + @Category({"Oracle JDBC", "Round trips"}) + static class ExtensibleSecuritySessionRoundtrip extends RoundTripEvent{ + public ExtensibleSecuritySessionRoundtrip(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.FAST_UPI_CALLS") + @Label("Fast UPI calls to opial7") + @Category({"Oracle JDBC", "Round trips"}) + static class FastUPICalls extends RoundTripEvent{ + public FastUPICalls(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.FETCH_ROW") + @Label("Fetch a row") + @Category({"Oracle JDBC", "Round trips"}) + static class FetchRow extends RoundTripEvent{ + public FetchRow(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.GET_VERSION") + @Label("Get Oracle version-date string in new format") + @Category({"Oracle JDBC", "Round trips"}) + static class GetVersion extends RoundTripEvent{ + public GetVersion(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.KERNEL_PROGRAMMATIC_NOTIFICATION") + @Label("Kernel Programmatic Notification") + @Category({"Oracle JDBC", "Round trips"}) + static class KernelProgrammaticNotification extends RoundTripEvent{ + public KernelProgrammaticNotification(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.KEY_VALUE") + @Label("Client app context, configurationspace, attribute, values") + @Category({"Oracle JDBC", "Round trips"}) + static class KeyValue extends RoundTripEvent{ + public KeyValue(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.LOB_FILE_CALL") + @Label("LOB and FILE related calls") + @Category({"Oracle JDBC", "Round trips"}) + static class LOBFileCall extends RoundTripEvent{ + public LOBFileCall(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.LOGOFF") + @Label("Logoff of Oracle") + @Category({"Oracle JDBC", "Round trips"}) + static class LogOff extends RoundTripEvent{ + public LogOff(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.LOGON_CHALLENGE_RESPONSE_1") + @Label("First half of challenge-response logon") + @Category({"Oracle JDBC", "Round trips"}) + static class LogonChallengeResponse1 extends RoundTripEvent{ + public LogonChallengeResponse1(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.LOGON_CHALLENGE_RESPONSE_2") + @Label("Second half of challenge-response logon") + @Category({"Oracle JDBC", "Round trips"}) + static class LogonChallengeResponse2 extends RoundTripEvent{ + public LogonChallengeResponse2(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.OEXFEN") + @Label("OEXFEN") + @Category({"Oracle JDBC", "Round trips"}) + static class OEXFEN extends RoundTripEvent{ + public OEXFEN(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.OPEN_CURSOR") + @Label("Open a cursor") + @Category({"Oracle JDBC", "Round trips"}) + static class OpenCursor extends RoundTripEvent{ + public OpenCursor(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.OSQL7") + @Label("OSQL7") + @Category({"Oracle JDBC", "Round trips"}) + static class OSQL7 extends RoundTripEvent{ + public OSQL7(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.OSTART") + @Label("Starts Oracle") + @Category({"Oracle JDBC", "Round trips"}) + static class OStart extends RoundTripEvent{ + public OStart(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.OSTOP") + @Label("Stops Oracle") + @Category({"Oracle JDBC", "Round trips"}) + static class OStop extends RoundTripEvent{ + public OStop(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.PARAMETER_PUT_SPFILE") + @Label("Put parameter using spfile (for startup)") + @Category({"Oracle JDBC", "Round trips"}) + static class ParameterPutSPFile extends RoundTripEvent{ + public ParameterPutSPFile(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.PING") + @Label("Ping") + @Category({"Oracle JDBC", "Round trips"}) + static class Ping extends RoundTripEvent{ + public Ping(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.PIPELINE_END") + @Label("Pipeline End") + @Category({"Oracle JDBC", "Round trips"}) + static class PipelineEnd extends RoundTripEvent{ + public PipelineEnd(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.PIPELINE_PIGGYBACK_BEGIN") + @Label("Pipeline Begin Piggyback") + @Category({"Oracle JDBC", "Round trips"}) + static class PipelinePiggybackBegin extends RoundTripEvent{ + public PipelinePiggybackBegin(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.PIPELINE_PIGGYBACK_OP") + @Label("Pipeline Operation Piggyback") + @Category({"Oracle JDBC", "Round trips"}) + static class PipelinePiggybackOp extends RoundTripEvent{ + public PipelinePiggybackOp(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.ROLLBACK") + @Label("Rollback") + @Category({"Oracle JDBC", "Round trips"}) + static class Rollback extends RoundTripEvent{ + public Rollback(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.SESSION_KEY") + @Label("Get the session key") + @Category({"Oracle JDBC", "Round trips"}) + static class SessionKey extends RoundTripEvent{ + public SessionKey(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.SESSION_STATE_OPS") + @Label("Session state ops") + @Category({"Oracle JDBC", "Round trips"}) + static class SessionStateOps extends RoundTripEvent{ + public SessionStateOps(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.SESSION_STATE_TEMPLATE") + @Label("Session state template") + @Category({"Oracle JDBC", "Round trips"}) + static class SessionStateTemplate extends RoundTripEvent{ + public SessionStateTemplate(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.SESSION_SWITCH_V8") + @Label("V8 session switching piggyback") + @Category({"Oracle JDBC", "Round trips"}) + static class SessionSwitchV8 extends RoundTripEvent{ + public SessionSwitchV8(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.TRACING_MESSAGE") + @Label("End to end tracing message") + @Category({"Oracle JDBC", "Round trips"}) + static class TracingMessage extends RoundTripEvent{ + public TracingMessage(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.TRANSACTION_COMMIT") + @Label("Transaction commit, rollback, recover") + @Category({"Oracle JDBC", "Round trips"}) + static class TransactionCommit extends RoundTripEvent{ + public TransactionCommit(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.TRANSACTION_START") + @Label("Transaction start, attach, detach") + @Category({"Oracle JDBC", "Round trips"}) + static class TransactionStart extends RoundTripEvent{ + public TransactionStart(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.TTC_DTY_ROUNDTRIP") + @Label("Data type message exchange") + @Category({"Oracle JDBC", "Round trips"}) + static class TTCDTYRoundtrip extends RoundTripEvent{ + public TTCDTYRoundtrip(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.TTC_PRO_ROUNDTRIP") + @Label("Protocol negotiation message exchange") + @Category({"Oracle JDBC", "Round trips"}) + static class TTCPRORoundtrip extends RoundTripEvent{ + public TTCPRORoundtrip(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.XS_ATTACH_SESSION") + @Label("XS Attach Session") + @Category({"Oracle JDBC", "Round trips"}) + static class XSAttachSession extends RoundTripEvent{ + public XSAttachSession(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.XS_CREATE_SESSION") + @Label("XS Create Session") + @Category({"Oracle JDBC", "Round trips"}) + static class XSCreateSession extends RoundTripEvent{ + public XSCreateSession(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.XS_DESTROY_SESSION") + @Label("XS Destroy Session") + @Category({"Oracle JDBC", "Round trips"}) + static class XSDestroySession extends RoundTripEvent{ + public XSDestroySession(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.XS_DETACH_SESSION") + @Label("XS Detach Session") + @Category({"Oracle JDBC", "Round trips"}) + static class XSDetachSession extends RoundTripEvent{ + public XSDetachSession(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.XS_NAMESPACE_OP") + @Label("XS Namespace OP") + @Category({"Oracle JDBC", "Round trips"}) + static class XSNamespaceOp extends RoundTripEvent{ + public XSNamespaceOp(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.XS_NAMESPACE_OPS") + @Label("XS namespace OPs") + @Category({"Oracle JDBC", "Round trips"}) + static class XSNamespaceOps extends RoundTripEvent{ + public XSNamespaceOps(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.XS_SET_SESSION_PARAMETER") + @Label("XS Set Session Parameter") + @Category({"Oracle JDBC", "Round trips"}) + static class XSSetSessionParameter extends RoundTripEvent{ + public XSSetSessionParameter(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip.XS_STATE_SYNC_OP") + @Label("XS State Sync OP") + @Category({"Oracle JDBC", "Round trips"}) + static class XSStateSyncOp extends RoundTripEvent{ + public XSStateSyncOp(TraceContext traceContext, ObservabilityConfiguration configuration) { + super(traceContext, configuration); + } + } + + @Name("oracle.jdbc.provider.observability.RoundTrip") + @Label("Round trip") + @Category({"Oracle JDBC", "Round trips"}) + static class RoundTripEvent extends Event { + + public RoundTripEvent(TraceContext traceContext, ObservabilityConfiguration configuration) { + setValues(traceContext, configuration); + } + + public void setValues(TraceContext traceContext, ObservabilityConfiguration configuration) { + this.connectionID = traceContext.getConnectionId(); + this.databaseOperation = traceContext.databaseOperation(); + this.tenant = traceContext.tenant(); + this.sqlID = traceContext.getSqlId(); + if (configuration.getSensitiveDataEnabled()) { + this.originalSQLText = traceContext.originalSqlText(); + this.actualSQLText = traceContext.actualSqlText(); + this.databaseUser = traceContext.user(); + } + } + + @Label("Connection ID") + String connectionID; + + @Label("Database operation") + String databaseOperation; + + @Label("Database tenant") + String tenant; + + @Label("SQL ID") + String sqlID; + + @Label("Original SQL text") + String originalSQLText; + + @Label("Actual SQL text") + String actualSQLText; + + @Label("Database user") + String databaseUser; + + } + + // Execution Events + @Name("oracle.jdbc.provider.observability.ExecutionEvent.AC_REPLAY_STARTED") + @Label("AC replay started") + @Category({"Oracle JDBC", "Execution events"}) + static class ACReplayStarted extends ACReplay { + public ACReplayStarted(JdbcExecutionEvent event, Object... params) { + super(event, params); + } + } + + @Name("oracle.jdbc.provider.observability.ExecutionEvent.AC_REPLAY_SUCCESSFUL") + @Label("AC replay successful") + @Category({"Oracle JDBC", "Execution events"}) + static class ACReplaySuccessful extends ACReplay { + public ACReplaySuccessful(JdbcExecutionEvent event, Object... params) { + super(event, params); + } + } + + + @Name("oracle.jdbc.provider.observability.ExecutionEvent.AC_REPLAY") + @Label("AC replay") + @Category({"Oracle JDBC", "Execution events"}) + static class ACReplay extends ExecutionEvent { + public ACReplay(JdbcExecutionEvent event, Object... params) { + super(event, params); + if (ObservabilityTracer.EXECUTION_EVENTS_PARAMETERS.get(event) == params.length) { + this.errorCode = ((SQLException) params[1]).getErrorCode(); + this.sqlState = ((SQLException) params[1]).getSQLState(); + this.currentReplayRetryCount = params[2].toString(); + } + } + @Label("Error code") + public int errorCode; + + @Label("SQL state") + public String sqlState; + + @Label("Current replay retry count") + public String currentReplayRetryCount; + } + + @Name("oracle.jdbc.provider.observability.ExecutionEvent.VIP_RETRY") + @Label("VIP retry") + @Category({"Oracle JDBC", "Round trips"}) + static class VIPRetry extends ExecutionEvent { + public VIPRetry(JdbcExecutionEvent event, Object... params) { + super(event, params); + if (ObservabilityTracer.EXECUTION_EVENTS_PARAMETERS.get(event) == params.length) { + protocol = params[1].toString(); + host = params[2].toString(); + port = params[3].toString(); + serviceName = params[4].toString(); + sid = params[5].toString(); + connectionData = params[6].toString(); + vipAddress = params[7].toString(); + } + } + + @Label("The protocol") + public String protocol; + + @Label("The host") + public String host; + + @Label("The port") + public String port; + + @Label("The service name") + public String serviceName; + + @Label("The SID") + public String sid; + + @Label("The connection data") + public String connectionData; + + @Label("The VIP address") + public String vipAddress; + } + + @Name("oracle.jdbc.provider.observability.ExecutionEvent") + @Label("Execution event") + @Category({"Oracle JDBC", "Execution events"}) + static class ExecutionEvent extends Event { + public ExecutionEvent(JdbcExecutionEvent event, Object... params) { + if (ObservabilityTracer.EXECUTION_EVENTS_PARAMETERS.get(event) == params.length) { + if (params != null && params.length > 0) { + this.errorMessage = params[0].toString(); + } + } + } + + @Label("Error message") + String errorMessage; + } + +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFRTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFRTracer.java new file mode 100644 index 00000000..2c195506 --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/jfr/JFRTracer.java @@ -0,0 +1,112 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ +package oracle.jdbc.provider.observability.tracers.jfr; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import jdk.jfr.Event; +import oracle.jdbc.TraceEventListener.JdbcExecutionEvent; +import oracle.jdbc.TraceEventListener.Sequence; +import oracle.jdbc.TraceEventListener.TraceContext; +import oracle.jdbc.provider.observability.ObservabilityConfiguration; +import oracle.jdbc.provider.observability.tracers.ObservabilityTracer; +import oracle.jdbc.provider.observability.tracers.jfr.JFREventFactory.RoundTripEvent; + +/** + * {@link ObservabilityTracer} for tracing Java Flight Recorder events. + */ +public class JFRTracer implements ObservabilityTracer{ + + /** + * Configuraiton + */ + private final ObservabilityConfiguration configuration; + + /** + * Logger. + */ + private static Logger logger = Logger.getLogger(JFRTracer.class.getPackageName()); + + /** + * Creates a new instance. + * + * @param configuration the configuraiton. + */ + public JFRTracer(ObservabilityConfiguration configuration) { + this.configuration = configuration; + } + + @Override + public String getName() { + return "JFR"; + } + + @Override + public Object traceRoundTrip(Sequence sequence, TraceContext traceContext, Object userContext) { + if (sequence.equals(Sequence.BEFORE)) { + // Create the event and start measuring event duration + RoundTripEvent event = JFREventFactory.createJFRRoundTripEvent(traceContext, configuration); + event.begin(); + return event; + } else { + if (userContext != null) { + RoundTripEvent event = (RoundTripEvent) userContext; + // set event attributes + event.setValues(traceContext, configuration); + // stop the measuring event durating and commit + event.commit(); + } else { + logger.log(Level.WARNING, "Unknown or null user context received from the driver on " + + "database operation: " + traceContext.databaseOperation()); + } + return null; + } + } + + @Override + public Object traceExecutionEvent(JdbcExecutionEvent event, Object userContext, Object... params) { + // Create event and commit + Event executionEvent = JFREventFactory.createExecutionEvent(event, params); + executionEvent.begin(); + executionEvent.commit(); + //Return previous user context + return userContext; + } + +} diff --git a/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java new file mode 100644 index 00000000..f922004f --- /dev/null +++ b/ojdbc-provider-observability/src/main/java/oracle/jdbc/provider/observability/tracers/otel/OTelTracer.java @@ -0,0 +1,272 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ +package oracle.jdbc.provider.observability.tracers.otel; + +import java.sql.SQLException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Scope; +import oracle.jdbc.TraceEventListener; +import oracle.jdbc.TraceEventListener.JdbcExecutionEvent; +import oracle.jdbc.TraceEventListener.Sequence; +import oracle.jdbc.TraceEventListener.TraceContext; +import oracle.jdbc.provider.observability.ObservabilityConfiguration; +import oracle.jdbc.provider.observability.tracers.ObservabilityTracer; + +/** + * Open Telemetry tracer. Exports round trip event and execution events to + * Open Telemetry. + */ +public class OTelTracer implements ObservabilityTracer { + + /** + * Key used to send the current Open Telemetry Trace Context to the server + * using {@link TraceContext#setClientInfo(String, String)}. + */ + private static final String TRACE_KEY = "clientcontext.ora$opentelem$tracectx"; + + /** + * The trace context is sent to the server in two lines, this is the first + * line it contains the version and the span context. + */ + private static final String TRACE_FORMAT = "traceparent: %s-%s-%s-%s\r\n"; + + /** + * Trace context version. + */ + private static final String TRACE_VERSION = "00"; + + /** + * Format of the second line sent to the server containing the trace context. + * It contains the trace state. + */ + private static final String TRACE_STATE_FORMAT = "tracestate: %s\r\n"; + + /** + * Logger. + */ + private static Logger logger = Logger.getLogger(OTelTracer.class.getPackageName()); + + /** + * Configuraiton + */ + private final ObservabilityConfiguration configuration; + + + /** + * Constructor. This tracer always uses {@link GlobalOpenTelemetry} to get + * the Open Telemetry tracer. + * + * @param configuration the configuration. + */ + public OTelTracer(ObservabilityConfiguration configuration) { + this.configuration = configuration; + } + + @Override + public String getName() { + return "OTEL"; + } + + @Override + public Object traceRoundTrip(Sequence sequence, TraceContext traceContext, Object userContext) { + if (sequence == Sequence.BEFORE) { + // Create the Span before the round-trip. + final Span span = initAndGetSpan(traceContext, traceContext.databaseOperation()); + makeSpanCurrentAndSendContextToServer(traceContext, span); + // Return the Span instance to the driver. The driver holds this instance and + // supplies it as user context parameter on the next round-trip call. + return span; + } else { + // End the Span after the round-trip. + if (userContext != null) { + final Span span = (Span) userContext; + span.setStatus(traceContext.isCompletedExceptionally() ? StatusCode.ERROR : StatusCode.OK); + span.end(); + } else { + logger.log(Level.WARNING, "Unknown or null user context received from the driver on " + + "database operation: " + traceContext.databaseOperation()); + } + return null; + } + } + + @Override + public Object traceExecutionEvent(JdbcExecutionEvent event, Object userContext, Object... params) { + if (EXECUTION_EVENTS_PARAMETERS.get(event) == params.length) { + Tracer tracer = GlobalOpenTelemetry.get().getTracer(OTelTracer.class.getName()); + if (event == TraceEventListener.JdbcExecutionEvent.VIP_RETRY) { + SpanBuilder spanBuilder = tracer + .spanBuilder(event.getDescription()) + .setAttribute("Error message", params[0].toString()) + .setAttribute("VIP Address", params[7].toString()); + // Add sensitive information (URL and SQL) if it is enabled + if (configuration.getSensitiveDataEnabled()) { + logger.log(Level.FINEST, "Sensitive information on"); + spanBuilder.setAttribute("Protocol", params[1].toString()) + .setAttribute("Host", params[2].toString()) + .setAttribute("Port", params[3].toString()) + .setAttribute("Service name", params[4].toString()) + .setAttribute("SID", params[5].toString()) + .setAttribute("Connection data", params[6].toString()); + } + // start and end span. + spanBuilder.startSpan().end(); + } else if (event == TraceEventListener.JdbcExecutionEvent.AC_REPLAY_STARTED + || event == TraceEventListener.JdbcExecutionEvent.AC_REPLAY_SUCCESSFUL) { + SpanBuilder spanBuilder = tracer + .spanBuilder(event.getDescription()) + .setAttribute("Error Message", params[0].toString()) + .setAttribute("Error code", ((SQLException) params[1]).getErrorCode()) + .setAttribute("SQL state", ((SQLException) params[1]).getSQLState()) + .setAttribute("Current replay retry count", params[2].toString()); + spanBuilder.startSpan().end(); + } else { + logger.log(Level.WARNING, "Unknown event received : " + event.toString()); + } + } else { + // log wrong number of parameters returned for execution event + logger.log(Level.WARNING, "Wrong number of parameters received for event " + event.toString()); + } + // return the previous userContext + return userContext; + } + + /** + * Creates a Open Telemetry Span and sets it's attributes according to the + * trace context and the configuration. + * + * @param traceContext the trace context. + * @param spanName then span name. + * @return returns the Span. + */ + private Span initAndGetSpan(TraceContext traceContext, String spanName) { + /* + * If this is in the context of current span, the following becomes a nested or + * child span to the current span. I.e. the current span in context becomes + * parent to this child span. + */ + Tracer tracer = GlobalOpenTelemetry.get().getTracer(OTelTracer.class.getName()); + SpanBuilder spanBuilder = tracer + .spanBuilder(spanName) + .setAttribute("thread.id", Thread.currentThread().getId()) + .setAttribute("thread.name", Thread.currentThread().getName()) + .setAttribute("Connection ID", traceContext.getConnectionId()) + .setAttribute("Database Operation", traceContext.databaseOperation()) + .setAttribute("Database Tenant", traceContext.tenant()) + .setAttribute("SQL ID", traceContext.getSqlId()); + + // Add sensitive information (URL and SQL) if it is enabled + if (configuration.getSensitiveDataEnabled()) { + logger.log(Level.FINEST, "Sensitive information on"); + spanBuilder + .setAttribute("Database User", traceContext.user()) + .setAttribute("Original SQL Text", traceContext.originalSqlText()) + .setAttribute("Actual SQL Text", traceContext.actualSqlText()); + } + + // According to the semantic conventions the Span Kind should be CLIENT, + // used to be SERVER. + return spanBuilder.setSpanKind(SpanKind.CLIENT).startSpan(); + + } + + /** + * Sets the span as the current Open Telemetry Span and sends context information + * to the database server. + * + * @param traceContext the trace context + * @param span the currect spans + */ + private void makeSpanCurrentAndSendContextToServer(TraceContext traceContext, Span span) { + final String traceParent = initAndGetTraceParent(span); + final String traceState = initAndGetTraceState(span); + + try (Scope ignored = span.makeCurrent()) { + // Send the current context to the server + traceContext.setClientInfo(TRACE_KEY, traceParent + traceState); + } catch (Exception ex) { + logger.log(Level.WARNING, "An error occured while sending the current Open Telemetry context to the server. " + + ex.getMessage(), ex); + } + } + + /** + * Formats the current Open Telemetry context in a format that the server can + * understand. + * + * @param span the current Span + * @return the current Open Telemetry context formatted so that the server + * can understand. + */ + private String initAndGetTraceParent(Span span) { + final SpanContext spanContext = span.getSpanContext(); + final String traceId = spanContext.getTraceId(); + // parent-id is known as the span-id + final String parentId = spanContext.getSpanId(); + final String traceFlags = spanContext.getTraceFlags().toString(); + + return String.format(TRACE_FORMAT, TRACE_VERSION, traceId, parentId, traceFlags); + } + + /** + * Formats the current Open Telemetry Span state in a format that the server + * can understand. + * + * @param span the current Span + * @return the current Open Telemetry Span state formatted so that the server + * can understand. + */ + private String initAndGetTraceState(Span span) { + final TraceState traceState = span.getSpanContext().getTraceState(); + final StringBuilder stringBuilder = new StringBuilder(); + + traceState.forEach((k, v) -> stringBuilder.append(k).append("=").append(v)); + return String.format(TRACE_STATE_FORMAT, stringBuilder); + } + +} diff --git a/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.jdbc.spi.TraceEventListenerProvider b/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.jdbc.spi.TraceEventListenerProvider new file mode 100644 index 00000000..b1c86eef --- /dev/null +++ b/ojdbc-provider-observability/src/main/resources/META-INF/services/oracle.jdbc.spi.TraceEventListenerProvider @@ -0,0 +1,2 @@ +oracle.jdbc.provider.observability.ObservabilityTraceEventListenerProvider +oracle.jdbc.provider.observability.OpenTelemetryTraceEventListenerProvider \ No newline at end of file diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/BackwardCompatibilityTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/BackwardCompatibilityTest.java new file mode 100644 index 00000000..81e4e7f9 --- /dev/null +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/BackwardCompatibilityTest.java @@ -0,0 +1,119 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ +package oracle.jdbc.provider.observability; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.management.ManagementFactory; +import java.util.HashMap; +import java.util.Map; + +import javax.management.Attribute; +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import org.junit.jupiter.api.Test; + +import oracle.jdbc.spi.OracleResourceProvider.Parameter; +import oracle.jdbc.spi.TraceEventListenerProvider; + +public class BackwardCompatibilityTest { + private static final String INSTANCE_NAME = "test-instance"; + + @Test + public void testConfiguration() throws Exception { + + // System properties + System.setProperty("oracle.jdbc.provider.opentelemetry.enabled", "true"); + System.setProperty("oracle.jdbc.provider.opentelemetry.sensitive-enabled", "true"); + + TraceEventListenerProvider provider = new OpenTelemetryTraceEventListenerProvider(); + Map parameters = new HashMap<>(); + provider.getParameters().forEach(parameter -> { + parameters.put(parameter, (CharSequence)INSTANCE_NAME); + }); + ObservabilityTraceEventListener listener = (ObservabilityTraceEventListener)provider.getTraceEventListener(parameters); + ObservabilityConfiguration configuration = listener.getObservabilityConfiguration(); + + assertEquals(true, configuration.getEnabled()); + assertEquals("OTEL", configuration.getEnabledTracers()); + assertEquals(true, configuration.getSensitiveDataEnabled()); + + assertEquals(1, configuration.getEnabledTracersAsList().size()); + assertEquals("OTEL", configuration.getEnabledTracersAsList().get(0)); + + // MBean + ObjectName objectName = listener.getMBeanObjectName(); + MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + String enabled = server.getAttribute(objectName, "Enabled").toString(); + String enabledTracers = server.getAttribute(objectName, "EnabledTracers").toString(); + String sensitiveDataEnabled = server.getAttribute(objectName, "SensitiveDataEnabled").toString(); + + assertEquals(enabled, "true"); + assertEquals(enabledTracers, "OTEL"); + assertEquals(sensitiveDataEnabled, "true"); + + server.setAttribute(objectName, new Attribute("Enabled", false)); + server.setAttribute(objectName, new Attribute("SensitiveDataEnabled", false)); + + assertEquals(false, configuration.getEnabled()); + assertEquals(false, configuration.getSensitiveDataEnabled()); + + assertEquals("OTEL", configuration.getEnabledTracers()); + assertEquals(false, configuration.getSensitiveDataEnabled()); + + assertEquals(1, configuration.getEnabledTracersAsList().size()); + assertEquals("OTEL", configuration.getEnabledTracersAsList().get(0)); + + // Singleton + configuration.setEnabled(true); + configuration.setSensitiveDataEnabled(true); + + enabled = server.getAttribute(objectName, "Enabled").toString(); + enabledTracers = server.getAttribute(objectName, "EnabledTracers").toString(); + sensitiveDataEnabled = server.getAttribute(objectName, "SensitiveDataEnabled").toString(); + + assertEquals("true", enabled); + assertEquals("OTEL", enabledTracers); + assertEquals("true", sensitiveDataEnabled); + + assertEquals(1, configuration.getEnabledTracersAsList().size()); + assertEquals("OTEL", configuration.getEnabledTracersAsList().get(0)); + + } +} diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java new file mode 100644 index 00000000..df8e2ab6 --- /dev/null +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityConfigurationTest.java @@ -0,0 +1,203 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ +package oracle.jdbc.provider.observability; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.management.ManagementFactory; +import java.util.HashMap; +import java.util.Map; + +import javax.management.Attribute; +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import org.junit.jupiter.api.Test; + +import oracle.jdbc.spi.TraceEventListenerProvider; +import oracle.jdbc.spi.OracleResourceProvider.Parameter; + +public class ObservabilityConfigurationTest { + + private static final String INSTANCE_NAME = "configuration-single"; + private static final String INSTANCE_NAME_1 = "configuration-test-one"; + private static final String INSTANCE_NAME_2 = "configuration-test-two"; + + MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + + // Set system properties before starting + static { + System.setProperty("oracle.jdbc.provider.observability.enabledTracers", "JFR"); + System.setProperty("oracle.jdbc.provider.observability.sensitiveDataEnabled", "true"); + } + + @Test + public void testConfiguration() throws Exception { + + // Create a TraceEventListner named test-instance + TraceEventListenerProvider provider = new ObservabilityTraceEventListenerProvider(); + ObservabilityTraceEventListener listener = createTraceEventListener(provider, INSTANCE_NAME); + + // Get the configuration object + ObservabilityConfiguration configuration = listener.getObservabilityConfiguration(); + + // Verify that listener one is configured with values from system properties + verifyConfiguration(configuration, listener.getMBeanObjectName(), "JFR", 1, true); + + // Update configuration using MBean + server.setAttribute(listener.getMBeanObjectName(), new Attribute("EnabledTracers", "OTEL, JFR")); + server.setAttribute(listener.getMBeanObjectName(), new Attribute("SensitiveDataEnabled", false)); + + // check that the values have been updated using the instance of the configuration + verifyConfiguration(configuration, listener.getMBeanObjectName(), "OTEL,JFR", 2, false); + + // Update the configuration using the instance of the configuration + configuration.setEnabledTracers("OTEL"); + configuration.setSensitiveDataEnabled(true); + + // Check that the values returned by the MBean correspond to the values set using the instance + verifyConfiguration(configuration, listener.getMBeanObjectName(), "OTEL", 1, true); + + } + + @Test + public void testConfigurationWith2Instances() throws Exception { + + // Create a TraceEventListner named test-instance + TraceEventListenerProvider provider = new ObservabilityTraceEventListenerProvider(); + ObservabilityTraceEventListener listener1 = createTraceEventListener(provider, INSTANCE_NAME_1); + ObservabilityTraceEventListener listener2 = createTraceEventListener(provider, INSTANCE_NAME_2); + + // Verify that listener one is configured with values from system properties + verifyConfiguration(listener1.getObservabilityConfiguration(), + listener1.getMBeanObjectName(), "JFR", 1, true); + + // Verify that listener two is configured with values from system properties + verifyConfiguration(listener2.getObservabilityConfiguration(), + listener2.getMBeanObjectName(), "JFR", 1, true); + + // Update configuration one using MBean + server.setAttribute(listener1.getMBeanObjectName(), new Attribute("EnabledTracers", "OTEL,JFR")); + server.setAttribute(listener1.getMBeanObjectName(), new Attribute("SensitiveDataEnabled", false)); + + // Verify that listener one's configuration with the values set using the MBean + verifyConfiguration(listener1.getObservabilityConfiguration(), + listener1.getMBeanObjectName(), "OTEL,JFR", 2, false); + + // Verify that listener two's configuration did not change + verifyConfiguration(listener2.getObservabilityConfiguration(), + listener2.getMBeanObjectName(), "JFR", 1, true); + + // Update the configuration using the instance of the configuration + ObservabilityConfiguration configuration2 = listener2.getObservabilityConfiguration(); + configuration2.setEnabledTracers("OTEL"); + configuration2.setSensitiveDataEnabled(false); + + // Verify that listener one's configuration did not change + verifyConfiguration(listener1.getObservabilityConfiguration(), + listener1.getMBeanObjectName(), "OTEL,JFR", 2, false); + + // Verify that listener two's configuration has been updated + verifyConfiguration(listener2.getObservabilityConfiguration(), + listener2.getMBeanObjectName(), "OTEL", 1, false); + + } + + @Test + public void testDefaultUniqueIdentifier() throws Exception { + + // Create a TraceEventListner named test-instance + TraceEventListenerProvider provider = new ObservabilityTraceEventListenerProvider(); + ObservabilityTraceEventListener listener = (ObservabilityTraceEventListener)provider.getTraceEventListener(new HashMap<>()); + + // Get the configuration object + ObservabilityConfiguration configuration = listener.getObservabilityConfiguration(); + + // Verify that listener one is configured with values from system properties + verifyConfiguration(configuration, listener.getMBeanObjectName(), "JFR", 1, true); + + // Update configuration using MBean + server.setAttribute(listener.getMBeanObjectName(), new Attribute("EnabledTracers", "OTEL, JFR")); + server.setAttribute(listener.getMBeanObjectName(), new Attribute("SensitiveDataEnabled", false)); + + // check that the values have been updated using the instance of the configuration + verifyConfiguration(configuration, listener.getMBeanObjectName(), "OTEL,JFR", 2, false); + + // Update the configuration using the instance of the configuration + configuration.setEnabledTracers("OTEL"); + configuration.setSensitiveDataEnabled(true); + + // Check that the values returned by the MBean correspond to the values set using the instance + verifyConfiguration(configuration, listener.getMBeanObjectName(), "OTEL", 1, true); + + } + + private ObservabilityTraceEventListener createTraceEventListener(TraceEventListenerProvider provider, + String instanceName) { + Map parameters = new HashMap<>(); + provider.getParameters().forEach(parameter -> { + parameters.put(parameter, (CharSequence)instanceName); + }); + return (ObservabilityTraceEventListener)provider.getTraceEventListener(parameters); + } + + private void verifyConfiguration(ObservabilityConfiguration configuration, + ObjectName mBeanObjectName, + String expectedTracers, + int expectedTracerCount, + boolean expectedSensitiveDataEnabled) throws Exception{ + + String[] expectedTracersArray = expectedTracers.split(","); + // Verify that the configuration matches the configuration set using system properties + assertEquals(expectedTracers, configuration.getEnabledTracers()); + assertEquals(expectedSensitiveDataEnabled, configuration.getSensitiveDataEnabled()); + assertEquals(expectedTracerCount, configuration.getEnabledTracersAsList().size()); + assertEquals(expectedTracerCount, expectedTracersArray.length, "Wrong number of tracers."); + for (int i = 0; i < expectedTracerCount; i++) { + assertEquals(expectedTracersArray[i], configuration.getEnabledTracersAsList().get(i)); + } + + // Get configuration using MBean and check that it matches the configuration set using system properties + String enabledTracers = server.getAttribute(mBeanObjectName, "EnabledTracers").toString(); + String sensitiveDataEnabled = server.getAttribute(mBeanObjectName, "SensitiveDataEnabled").toString(); + assertEquals(enabledTracers, expectedTracers); + assertEquals(Boolean.parseBoolean(sensitiveDataEnabled), expectedSensitiveDataEnabled); + + } + +} diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTestProperties.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTestProperties.java new file mode 100644 index 00000000..7a765d5a --- /dev/null +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTestProperties.java @@ -0,0 +1,44 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ +package oracle.jdbc.provider.observability; + +public enum ObservabilityTestProperties { + OBSERVABILITY_URL, + OBSERVABILITY_USERNAME, + OBSERVABILITY_PASSWORD +} diff --git a/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java new file mode 100644 index 00000000..52432893 --- /dev/null +++ b/ojdbc-provider-observability/src/test/java/oracle/jdbc/provider/observability/ObservabilityTraceEventListenerTest.java @@ -0,0 +1,235 @@ +/* + ** Copyright (c) 2025 Oracle and/or its affiliates. + ** + ** The Universal Permissive License (UPL), Version 1.0 + ** + ** Subject to the condition set forth below, permission is hereby granted to any + ** person obtaining a copy of this software, associated documentation and/or data + ** (collectively the "Software"), free of charge and under any and all copyright + ** rights in the Software, and any and all patent rights owned or freely + ** licensable by each licensor hereunder covering either (i) the unmodified + ** Software as contributed to or provided by such licensor, or (ii) the Larger + ** Works (as defined below), to deal in both + ** + ** (a) the Software, and + ** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + ** one is included with the Software (each a "Larger Work" to which the Software + ** is contributed by such licensors), + ** + ** without restriction, including without limitation the rights to copy, create + ** derivative works of, display, perform, and distribute the Software and make, + ** use, sell, offer for sale, import, export, have made, and have sold the + ** Software and the Larger Work(s), and to sublicense the foregoing rights on + ** either these or other terms. + ** + ** This license is subject to the following condition: + ** The above copyright notice and either this complete permission notice or at + ** a minimum a reference to the UPL must be included in all copies or + ** substantial portions of the Software. + ** + ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + ** SOFTWARE. + */ +package oracle.jdbc.provider.observability; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.atLeastOnce; + +import java.nio.file.Path; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.Statement; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mockito; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.metrics.MeterBuilder; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.TracerProvider; +import jdk.jfr.Configuration; +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordingFile; +import oracle.jdbc.DatabaseFunction; +import oracle.jdbc.driver.OracleConnection; +import oracle.jdbc.provider.TestProperties; + +public class ObservabilityTraceEventListenerTest { + String url = TestProperties.getOrAbort(ObservabilityTestProperties.OBSERVABILITY_URL); + String userName = TestProperties.getOrAbort(ObservabilityTestProperties.OBSERVABILITY_USERNAME); + String password = TestProperties.getOrAbort(ObservabilityTestProperties.OBSERVABILITY_PASSWORD); + + // JFR + private static final String SESSION_KEY = "oracle.jdbc.provider.observability.RoundTrip.SESSION_KEY"; + private static final String AUTH_CALL = "oracle.jdbc.provider.observability.RoundTrip.AUTH_CALL"; + private static final String EXECUTE_QUERY = "oracle.jdbc.provider.observability.RoundTrip.EXECUTE_QUERY"; + private static final String LOGOFF = "oracle.jdbc.provider.observability.RoundTrip.LOGOFF"; + + // OTEL + private static final OpenTelemetry openTelemetry = Mockito.mock(OpenTelemetry.class); + private static final Span span = Mockito.mock(Span.class); + private static final SpanContext spanContext = Mockito.mock(SpanContext.class); + private static final TraceFlags traceFlags = Mockito.mock(TraceFlags.class); + private static final TraceState traceState = Mockito.mock(TraceState.class); + private static final SpanBuilder spanBuilder = Mockito.mock(SpanBuilder.class); + private static final TracerProvider tracerProvider = Mockito.mock(TracerProvider.class); + private static final Tracer tracer = Mockito.mock(Tracer.class); + private static final MeterBuilder meterBuilder = Mockito.mock(MeterBuilder.class); + + static { + GlobalOpenTelemetry.set(openTelemetry); + configureOTEL(); + } + + @ParameterizedTest(name = "JFRTraceTest - {arguments}") + @ValueSource(booleans = {true, false}) + public void JFRTraceTest(boolean sensitiveDataEnabled) throws Exception { + + System.setProperty("oracle.jdbc.provider.observability.enabledTracers", "JFR"); + System.setProperty("oracle.jdbc.provider.observability.sensitiveDataEnabled", String.valueOf(sensitiveDataEnabled)); + Configuration configuration = Configuration.getConfiguration("default"); + String connectionId = null; + try (Recording recording = new Recording(configuration)) { + recording.start(); + String jfrUrl = url + "?oracle.jdbc.provider.traceEventListener=observability-trace-event-listener-provider&oracle.jdbc.provider.traceEventListener.unique_identifier=test-jfr" + sensitiveDataEnabled; + try (Connection connection = DriverManager.getConnection(jfrUrl, userName, password); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT 'OK' FROM DUAL")) { + connectionId = ((OracleConnection)connection).getNetConnectionId(); + while (resultSet.next()) { + assertEquals("OK", resultSet.getString(1)); + } + } + recording.stop(); + recording.dump(Path.of("dump" + sensitiveDataEnabled + ".jfr")); + + try (RecordingFile recordingFile = new RecordingFile(Path.of("dump" + sensitiveDataEnabled + ".jfr"))) { + int countRoundTrips = 0; + while (recordingFile.hasMoreEvents()) { + RecordedEvent event = recordingFile.readEvent(); + if (event.getEventType().getCategoryNames().contains("Round trips")) { + countRoundTrips++; + switch (event.getEventType().getName()) { + case SESSION_KEY: + assertEquals(connectionId, event.getString("connectionID")); + assertNotNull(event.getString("databaseOperation")); + assertNull(event.getString("sqlID")); + assertNull(event.getString("originalSQLText")); + assertNull(event.getString("actualSQLText")); + assertEquals(sensitiveDataEnabled, event.getString("databaseUser") != null); + break; + case AUTH_CALL: + assertEquals(connectionId, event.getString("connectionID")); + assertNotNull(event.getString("databaseOperation")); + assertNull(event.getString("sqlID")); + assertNull(event.getString("originalSQLText")); + assertNull(event.getString("actualSQLText")); + assertEquals(sensitiveDataEnabled, event.getString("databaseUser") != null); + + break; + case EXECUTE_QUERY: + assertEquals(connectionId, event.getString("connectionID")); + assertNotNull(event.getString("databaseOperation")); + assertNotNull(event.getString("sqlID")); + assertEquals(sensitiveDataEnabled, event.getString("originalSQLText") != null); + assertEquals(sensitiveDataEnabled, event.getString("actualSQLText") != null); + assertEquals(sensitiveDataEnabled, event.getString("databaseUser") != null); + break; + case LOGOFF: + assertEquals(connectionId, event.getString("connectionID")); + assertNotNull(event.getString("databaseOperation")); + assertNull(event.getString("sqlID")); + assertNull(event.getString("originalSQLText")); + assertNull(event.getString("actualSQLText")); + assertEquals(sensitiveDataEnabled, event.getString("databaseUser") != null); + break; + default: + fail("Unexpected event"); + } + } + } + assertTrue(countRoundTrips > 0, "Application should have performed at least one round trip"); + } + } + + } + + @ParameterizedTest(name = "OTELTraceTest - {arguments}") + @ValueSource(booleans = {true, false}) + public void OTELTraceTest(boolean sensitiveDataEnabled) throws Exception { + Mockito.clearInvocations(tracer, spanBuilder); + System.setProperty("oracle.jdbc.provider.observability.enabledTracers", "OTEL"); + System.setProperty("oracle.jdbc.provider.observability.sensitiveDataEnabled", String.valueOf(sensitiveDataEnabled)); + String otelUrl = url + "?oracle.jdbc.provider.traceEventListener=observability-trace-event-listener-provider&oracle.jdbc.provider.traceEventListener.unique_identifier=test-otel-" + sensitiveDataEnabled ; + String connectionId = null; + try (Connection connection = DriverManager.getConnection(otelUrl, userName, password); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT 'OK' FROM DUAL")) { + connectionId = ((OracleConnection)connection).getNetConnectionId(); + while (resultSet.next()) { + assertEquals("OK", resultSet.getString(1)); + } + } + + Mockito.verify(tracer, atLeastOnce()).spanBuilder(DatabaseFunction.SESSION_KEY.getDescription()); + Mockito.verify(tracer, atLeastOnce()).spanBuilder(DatabaseFunction.AUTH_CALL.getDescription()); + Mockito.verify(tracer, atLeastOnce()).spanBuilder(DatabaseFunction.EXECUTE_QUERY.getDescription()); + Mockito.verify(tracer, atLeastOnce()).spanBuilder(DatabaseFunction.LOGOFF.getDescription()); + Mockito.verify(spanBuilder, atLeastOnce()).startSpan(); + Mockito.verify(spanBuilder, Mockito.atLeast(4)).setAttribute("thread.id", Thread.currentThread().getId()); + Mockito.verify(spanBuilder, Mockito.atLeast(4)).setAttribute("thread.name", Thread.currentThread().getName()); + Mockito.verify(spanBuilder, Mockito.atLeast(1)).setAttribute("Connection ID", connectionId); + Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Database Operation", DatabaseFunction.SESSION_KEY.getDescription()); + Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Database Operation", DatabaseFunction.AUTH_CALL.getDescription()); + Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Database Operation", DatabaseFunction.EXECUTE_QUERY.getDescription()); + Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Database Operation", DatabaseFunction.LOGOFF.getDescription()); + if (sensitiveDataEnabled) { + Mockito.verify(spanBuilder, Mockito.times(4)).setAttribute("Database User", userName); + Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Original SQL Text", "SELECT 'OK' FROM DUAL"); + Mockito.verify(spanBuilder, Mockito.times(1)).setAttribute("Actual SQL Text", "SELECT 'OK' FROM DUAL"); + } else { + Mockito.verify(spanBuilder, Mockito.times(0)).setAttribute("Database User", userName); + Mockito.verify(spanBuilder, Mockito.times(0)).setAttribute("Original SQL Text", "SELECT 'OK' FROM DUAL"); + Mockito.verify(spanBuilder, Mockito.times(0)).setAttribute("Actual SQL Text", "SELECT 'OK' FROM DUAL"); + } + Mockito.verify(span, atLeast(4)).end(); + + } + + private static void configureOTEL() { + Mockito.when(openTelemetry.getTracerProvider()).thenReturn(tracerProvider); + Mockito.when(tracerProvider.get(Mockito.anyString())).thenReturn(tracer); + Mockito.when(openTelemetry.meterBuilder(Mockito.anyString())).thenReturn(meterBuilder); + Mockito.when(spanContext.getTraceFlags()).thenReturn(traceFlags); + Mockito.when(spanContext.getTraceState()).thenReturn(traceState); + Mockito.when(span.getSpanContext()).thenReturn(spanContext); + Mockito.when(spanBuilder.setAttribute(Mockito.anyString(), Mockito.anyString())).thenReturn(spanBuilder); + Mockito.when(spanBuilder.setAttribute(Mockito.anyString(), Mockito.anyLong())).thenReturn(spanBuilder); + Mockito.when(spanBuilder.setAttribute(Mockito.anyString(), Mockito.anyBoolean())).thenReturn(spanBuilder); + Mockito.when(spanBuilder.setAttribute(Mockito.anyString(), Mockito.any())).thenReturn(spanBuilder); + Mockito.when(spanBuilder.setSpanKind(Mockito.any(SpanKind.class))).thenReturn(spanBuilder); + Mockito.when(spanBuilder.startSpan()).thenReturn(span); + Mockito.when(tracer.spanBuilder(Mockito.anyString())).thenReturn(spanBuilder); + } + +} diff --git a/ojdbc-provider-opentelemetry/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListener.java b/ojdbc-provider-opentelemetry/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListener.java index 00ac32c7..4c63944d 100644 --- a/ojdbc-provider-opentelemetry/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListener.java +++ b/ojdbc-provider-opentelemetry/src/main/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListener.java @@ -73,9 +73,9 @@ * * * The system properties - * {@value #OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_ENABLED} + * {@value #OPEN_TELEMETRY_TRACE_EVENT_LISTENER_ENABLED} * and - * {@value #OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED} + * {@value #OPEN_TELEMETRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED} * can be used * to enable/disable this listener and the use of sensitive data by this * listener. A MBean registered by the {@link @@ -88,12 +88,12 @@ public class OpenTelemetryTraceEventListener /** * Name of the property used to enable or disable this listener. */ - public static final String OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_ENABLED = "oracle.jdbc.provider.opentelemetry.enabled"; + public static final String OPEN_TELEMETRY_TRACE_EVENT_LISTENER_ENABLED = "oracle.jdbc.provider.opentelemetry.enabled"; /** * Name of the property used to enable or disable sensitive data for this * listener. */ - public static final String OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED = "oracle.jdbc.provider.opentelemetry.sensitive-enabled"; + public static final String OPEN_TELEMETRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED = "oracle.jdbc.provider.opentelemetry.sensitive-enabled"; private static final String TRACE_KEY = "clientcontext.ora$opentelem$tracectx"; @@ -107,7 +107,7 @@ public class OpenTelemetryTraceEventListener } }; - private static Logger logger = Logger.getLogger(OpenTelemetryTraceEventListener.class.getName()); + private static Logger logger = Logger.getLogger(OpenTelemetryTraceEventListener.class.getPackageName()); private Tracer tracer; @@ -132,8 +132,8 @@ private enum Configuration { private AtomicBoolean sensitiveDataEnabled; private Configuration(boolean enabled, boolean sensitiveDataEnabled) { - String enabledStr = System.getProperty(OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_ENABLED); - String sensitiveStr = System.getProperty(OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED); + String enabledStr = System.getProperty(OPEN_TELEMETRY_TRACE_EVENT_LISTENER_ENABLED); + String sensitiveStr = System.getProperty(OPEN_TELEMETRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED); this.enabled = new AtomicBoolean(enabledStr == null ? enabled : Boolean.parseBoolean(enabledStr)); this.sensitiveDataEnabled = new AtomicBoolean( sensitiveStr == null ? sensitiveDataEnabled : Boolean.parseBoolean(sensitiveStr)); diff --git a/ojdbc-provider-opentelemetry/src/test/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListenerTest.java b/ojdbc-provider-opentelemetry/src/test/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListenerTest.java index 8bac137e..3fd05dba 100644 --- a/ojdbc-provider-opentelemetry/src/test/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListenerTest.java +++ b/ojdbc-provider-opentelemetry/src/test/java/oracle/jdbc/provider/opentelemetry/OpenTelemetryTraceEventListenerTest.java @@ -97,8 +97,8 @@ public void setupMocks() throws Exception { @Test void testPropertiesDisabled() throws Exception { - System.setProperty(OpenTelemetryTraceEventListener.OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_ENABLED, "false"); - System.setProperty(OpenTelemetryTraceEventListener.OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED, "false"); + System.setProperty(OpenTelemetryTraceEventListener.OPEN_TELEMETRY_TRACE_EVENT_LISTENER_ENABLED, "false"); + System.setProperty(OpenTelemetryTraceEventListener.OPEN_TELEMETRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED, "false"); OpenTelemetryTraceEventListener traceEventListener = new OpenTelemetryTraceEventListener(tracer); Assertions.assertFalse(traceEventListener.isEnabled(), "Set to false using system property"); Assertions.assertFalse(traceEventListener.isSensitiveDataEnabled(), "Set to false using system property"); @@ -106,8 +106,8 @@ void testPropertiesDisabled() throws Exception { @Test void testPropertiesEnabled() throws Exception { - System.setProperty(OpenTelemetryTraceEventListener.OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_ENABLED, "true"); - System.setProperty(OpenTelemetryTraceEventListener.OPEN_TELEMENTRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED, "true"); + System.setProperty(OpenTelemetryTraceEventListener.OPEN_TELEMETRY_TRACE_EVENT_LISTENER_ENABLED, "true"); + System.setProperty(OpenTelemetryTraceEventListener.OPEN_TELEMETRY_TRACE_EVENT_LISTENER_SENSITIVE_ENABLED, "true"); OpenTelemetryTraceEventListener traceEventListener = new OpenTelemetryTraceEventListener(tracer); Assertions.assertTrue(traceEventListener.isEnabled(), "Set to false using system property"); Assertions.assertTrue(traceEventListener.isSensitiveDataEnabled(), "Set to false using system property"); diff --git a/ojdbc-provider-samples/pom.xml b/ojdbc-provider-samples/pom.xml index fe4ec2bc..a815c2ee 100644 --- a/ojdbc-provider-samples/pom.xml +++ b/ojdbc-provider-samples/pom.xml @@ -5,7 +5,7 @@ Oracle JDBC Provider Code Samples ojdbc-provider-samples - ${project.parent.version} + 1.0.3 jar @@ -23,17 +23,17 @@ com.oracle.database.jdbc ojdbc-provider-azure - ${project.parent.version} + 1.0.3 com.oracle.database.jdbc ojdbc-provider-oci - ${project.parent.version} + 1.0.3 com.oracle.database.jdbc ojdbc-provider-jackson-oson - ${project.parent.version} + 1.0.3 com.oracle.database.jdbc diff --git a/pom.xml b/pom.xml index 28583708..03ac4cba 100644 --- a/pom.xml +++ b/pom.xml @@ -53,6 +53,15 @@ 8 + + javac-release-jfr + + [9,) + + + 11 + + @@ -63,6 +72,7 @@ ojdbc-provider-opentelemetry ojdbc-provider-aws ojdbc-provider-jackson-oson + ojdbc-provider-observability @@ -77,6 +87,11 @@ ojdbc8 ${jdbc.version} + + com.oracle.database.jdbc + ojdbc11 + ${jdbc.version} + com.oracle.database.security oraclepki