Search code examples
javajdbcproxy-classesp6spy

ClassNotFoundException for P6Spy when trying to log sql requests


I'm getting this exception :

java.lang.ClassNotFoundException: com.p6spy.engine.spy.P6SpyDriver  

while trying to connect to my database through P6Spy Proxy Driver. This exception is pointing to this line of code :

Class.forName("com.p6spy.engine.spy.P6SpyDriver");  

I added the P6Spy.jar and spy.properties to the buildpath, here is the spy.properties configuration file :

 #################################################################
# P6Spy Options File                                            #
# See documentation for detailed instructions                   #
#################################################################

#################################################################
# MODULES                                                       #
#                                                               #
# Modules provide the P6Spy functionality.  If a module, such   #
# as module_log is commented out, that functionality will not   #
# be available.  If it is not commented out (if it is active),  #
# the functionality will be active.                             #
#                                                               #
# Values set in Modules cannot be reloaded using the            #
# reloadproperties variable.  Once they are loaded, they remain #
# in memory until the application is restarted.                 #
#                                                               #
#################################################################

#module.log=com.p6spy.engine.logging.P6LogFactory
#module.outage=com.p6spy.engine.outage.P6OutageFactory

#################################################################
# REALDRIVER(s)                                                 #
#                                                               #
# In your application server configuration file you replace the #
# "real driver" name with com.p6spy.engine.P6SpyDriver. This is #
# where you put the name of your real driver P6Spy can find and #
# register your real driver to do the database work.            #
#                                                               #
# If your application uses several drivers specify them in      #
# realdriver2, realdriver3.  See the documentation for more     #
# details.                                                      #
#                                                               #
# Values set in REALDRIVER(s) cannot be reloaded using the      #
# reloadproperties variable.  Once they are loaded, they remain #
# in memory until the application is restarted.                 #
#                                                               #
#################################################################

# oracle driver
realdriver=oracle.jdbc.driver.OracleDriver

# mysql Connector/J driver
# realdriver=com.mysql.jdbc.Driver

# informix driver
# realdriver=com.informix.jdbc.IfxDriver

# ibm db2 driver
# realdriver=COM.ibm.db2.jdbc.net.DB2Driver

# the mysql open source driver
#realdriver=org.gjt.mm.mysql.Driver

#specifies another driver to use
#realdriver2=
#specifies a third driver to use
#realdriver3=


#the DriverManager class sequentially tries every driver that is
#registered to find the right driver.  In some instances, it's possible to
#load up the realdriver before the p6spy driver, in which case your connections
#will not get wrapped as the realdriver will "steal" the connection before
#p6spy sees it.  Set the following property to "true" to cause p6spy to
#explicitily deregister the realdrivers
deregisterdrivers=false

################################################################
# P6LOG SPECIFIC PROPERTIES                                    #
################################################################
# no properties currently available

################################################################
# EXECUTION THRESHOLD PROPERTIES                               #
################################################################
# This feature applies to the standard logging of P6Spy.       # 
# While the standard logging logs out every statement          #
# regardless of its execution time, this feature puts a time   # 
# condition on that logging.  Only statements that have taken  # 
# longer than the time specified (in milliseconds) will be     #
# logged.  This way it is possible to see only statements that #
# have exceeded some high water mark.                          #
# This time is reloadable.                                     #
#
# executionthreshold=integer time (milliseconds)
#
#executionthreshold=

################################################################
# P6OUTAGE SPECIFIC PROPERTIES                                 #
################################################################
# Outage Detection
#
# This feature detects long-running statements that may be indicative of
# a database outage problem. If this feature is turned on, it will log any
# statement that surpasses the configurable time boundary during its execution.
# When this feature is enabled, no other statements are logged except the long
# running statements. The interval property is the boundary time set in seconds.
# For example, if this is set to 2, then any statement requiring at least 2 
# seconds will be logged. Note that the same statement will continue to be logged
# for as long as it executes. So if the interval is set to 2, and the query takes
# 11 seconds, it will be logged 5 times (at the 2, 4, 6, 8, 10 second intervals).
#
# outagedetection=true|false
# outagedetectioninterval=integer time (seconds)
#
outagedetection=false
#outagedetectioninterval=

################################################################
# COMMON PROPERTIES                                            #
################################################################

# filter what is logged
filter=false

# comma separated list of tables to include when filtering
include     = 
# comma separated list of tables to exclude when filtering
exclude     =

# sql expression to evaluate if using regex filtering
#sqlexpression = 


# turn on tracing
#autoflush   = true

# sets the date format using Java's SimpleDateFormat routine
dateformat=yyyy-MM-dd

#list of categories to explicitly include 
#includecategories=

#list of categories to exclude: error, info, batch, debug, statement,
#commit, rollback and result are valid values
excludecategories=


#allows you to use a regex engine or your own matching engine to determine 
#which statements to log
#
#stringmatcher=com.p6spy.engine.common.GnuRegexMatcher
#stringmatcher=com.p6spy.engine.common.JakartaRegexMatcher
#stringmatcher=

# prints a stack trace for every statement logged
stacktrace=false
# if stacktrace=true, specifies the stack trace to print
#stacktraceclass=

# determines if property file should be reloaded
#reloadproperties=false
# determines how often should be reloaded in seconds
#reloadpropertiesinterval=60

#if=true then url must be prefixed with p6spy:
useprefix=true

#specifies the appender to use for logging
#appender=com.p6spy.engine.logging.appender.Log4jLogger
#appender=com.p6spy.engine.logging.appender.StdoutLogger
appender=com.p6spy.engine.logging.appender.FileLogger

# name of logfile to use, note Windows users should make sure to use forward slashes in their pathname (e:/test/spy.log) (used for file logger only)
logfile     = D:/spy.log

# append to  the p6spy log file.  if this is set to false the
# log file is truncated every time.  (file logger only)
append=true

#The following are for log4j logging only
#log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender
#log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout
#log4j.appender.STDOUT.layout.ConversionPattern=p6spy - %m%n

#log4j.appender.CHAINSAW_CLIENT=org.apache.log4j.net.SocketAppender
#log4j.appender.CHAINSAW_CLIENT.RemoteHost=localhost
#log4j.appender.CHAINSAW_CLIENT.Port=4445
#log4j.appender.CHAINSAW_CLIENT.LocationInfo=true

#log4j.logger.p6spy=INFO,STDOUT


#################################################################
# DataSource replacement                                        #
#                                                               #
# Replace the real DataSource class in your application server  #
# configuration with the name com.p6spy.engine.spy.P6DataSource,#
# then add the JNDI name and class name of the real         #
# DataSource here                               #
#                                                               #
# Values set in this item cannot be reloaded using the          #
# reloadproperties variable.  Once it is loaded, it remains     #
# in memory until the application is restarted.                 #
#                                                               #
#################################################################
#realdatasource=/RealMySqlDS
#realdatasourceclass=com.mysql.jdbc.jdbc2.optional.MysqlDataSource

#################################################################
# DataSource properties                                         #
#                                                               #
# If you are using the DataSource support to intercept calls    # 
# to a DataSource that requires properties for proper setup,    #
# define those properties here. Use name value pairs, separate  #
# the name and value with a semicolon, and separate the         #
# pairs with commas.                                            #
#                                           #
# The example shown here is for mysql                           #
#                                                               #
#################################################################
#realdatasourceproperties=port;3306,serverName;ibmhost,databaseName;mydb


#################################################################
# JNDI DataSource lookup                                        #
#                                                               #
# If you are using the DataSource support outside of an app     #
# server, you will probably need to define the JNDI Context     #
# environment.                                                  #
#                                                               #
# If the P6Spy code will be executing inside an app server then #
# do not use these properties, and the DataSource lookup will   #
# use the naming context defined by the app server.             #
#                                                               #
# The two standard elements of the naming environment are   #
# jndicontextfactory and jndicontextproviderurl. If you need    #
# additional elements, use the jndicontextcustom property.      #
# You can define multiple properties in jndicontextcustom,      #
# in name value pairs. Separate the name and value with a       #
# semicolon, and separate the pairs with commas.                #
#                                                               #
# The example shown here is for a standalone program running on #
# a machine that is also running JBoss, so the JDNI context     #
# is configured for JBoss (3.0.4).                              #
#                                                               #
#################################################################
#jndicontextfactory=org.jnp.interfaces.NamingContextFactory
#jndicontextproviderurl=localhost:1099
#jndicontextcustom=java.naming.factory.url.pkgs;org.jboss.nameing:org.jnp.interfaces

#jndicontextfactory=com.ibm.websphere.naming.WsnInitialContextFactory
#jndicontextproviderurl=iiop://localhost:900

append=true    

and here is my connexion class :

public class ConnexionBD {
        private static final ConnexionBD INSTANCE= new ConnexionBD();
        private String host;
        private String database;
        private int port;
        private String login;
        private char[] password;
        private String privileges;
        private Connection connection;
        public static final String DATABASE_JDBC_DRIVER = "com.p6spy.engine.spy.P6SpyDriver"; 

    private ConnexionBD() {}

    public static ConnexionBD getInstance() {
        return INSTANCE;
    }


    //getters and setters...

    public Connection getConnection() throws ClassNotFoundException, SQLException {
        String DATABASE_URL=null;

        DATABASE_URL = "p6spy:jdbc:oracle:thin:@"+getHost()+":"+getPort()+":"+getDatabase();

        if (connection!=null) 
        { 
            return connection ; 
        } 
        else{
            Class.forName(DATABASE_JDBC_DRIVER);  

            connection = DriverManager.getConnection(DATABASE_URL,getLogin(),new String(getPassword())) ; 

            return connection ; 
        }
    }
    public void setConnection(Connection connection) {
        this.connection = connection;
    }


}

Could someone tell me what is going wrong here ? Any help geatly appreciated !


Solution

  • It looks like P6SpyDriver is not on your classpath at runtime. The statement Class.forName(DATABASE_JDBC_DRIVER); is trying to load P6SpyDriver using the same classloader that loaded ConnexionBD. For whatever reason, that classloader does not have access to the class.

    I would recommend logging the URLs registered with the classloader and its parents. This might give you some insight as to why this is happening. See the code below for an example of how you can log the classpath locations. Just add the methods to your ConnexionBD class and add logClassPath(this); on the line immediately above Class.forName(DATABASE_JDBC_DRIVER);.

      private void logClassPath(final Class clazz) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        ClassLoader cl = clazz.getClassLoader();
        pw.println("Processing classloader which loaded " + clazz.getName() +" classloader: "+cl.getClass().getName());
        if (cl instanceof URLClassLoader) {
          logUrls(pw, (URLClassLoader) cl);
        } else {
          pw.println("Unsupported classloader - " + cl.getClass().getName());
        }
        while (cl.getParent() != null) {
          cl = cl.getParent();
          pw.println("\n\nProcessing parent classloader" +" classloader: "+cl.getClass().getName());
          if (cl instanceof URLClassLoader) {
            logUrls(pw, (URLClassLoader) cl);
          } else {
            pw.println("Unsupported classloader - " + cl.getClass().getName());
          }
        }
        pw.flush();
        System.out.print(sw.toString());
    
      }
    
      private void logUrls(final PrintWriter pw, final URLClassLoader cl) {
        if( cl.getURLs().length == 0 ) {
          pw.println("No URLs registered for this classloader");
        }
        for (URL url : cl.getURLs()) {
          pw.println(url.toString());
        }
      }
    

    The code that I have provided will only work with classloaders which are instances of URLClassloader. It also assumes that you have hierarchal classloaders as well. This should be fine for a normal java application or a web app running in a lightweight container.

    If you are running in an OSGI based container then you will need to use whatever tools that the container provides for troubleshooting class loading.